Search Code

Proiect 3 cu matrice LED RGB ESP32-S3 - Text de pe telefonul mobil

Proiect 3 cu matrice LED RGB ESP32-S3 - Text de pe telefonul mobil

Proiectul 3 – Controlează textul matricei de pe telefon (HTTP Text)

În acest proiect, matricea LED RGB ESP32-S3 găzduiește o mică pagină web, astfel încât să poți schimba textul derulant, culoarea, direcția și viteza direct de pe telefon sau computer. Nu ai nevoie de o aplicație separată – doar de un browser web. Astfel, modulul devine un mic afișaj text Wi-Fi pe care îl poți actualiza în timp real.

Toate cele șase proiecte din această serie sunt explicate și demonstrate într-un singur videoclip YouTube. Același videoclip este încorporat pe această pagină, astfel încât să poți vedea exact cum arată interfața web și cum se actualizează textul instantaneu pe matrice. Codul sursă complet pentru acest proiect este încărcat automat mai jos, după articol, iar modulul ESP32-S3 RGB LED Matrix poate fi achiziționat din magazinele afiliate listate sub secțiunea de cod.

În acest articol ne concentrăm pe modul în care funcționează logica de rețea (Wi-Fi de acasă vs. punct de acces) și ce setări poți modifica în cod pentru a personaliza comportamentul.

Prezentare generală a modulului ESP32-S3 RGB LED Matrix

Hardware-ul este același ca pentru toate celelalte proiecte din această serie: o placă cu microcontroler ESP32-S3 cu o matrice LED RGB integrată de 8×8 și un senzor de mișcare QMI8658C pe spate. Portul USB-C este folosit pentru alimentare și programare, iar pinii de pe margini rămân disponibili pentru alte IO.:contentReference[oaicite:0]{index=0}

  • ESP32-S3 – microcontroler cu capabilități Wi-Fi și Bluetooth.
  • Matrice RGB 8×8 – 64 de LED-uri RGB adresabile pentru text și grafică.
  • Accelerometru QMI8658C – folosit în proiectele de înclinare și jocuri.
  • Port USB – alimentează placa și încarcă schițele din Arduino IDE.
  • Pini expuși – permit conectarea de senzori sau actuatoare suplimentare, dacă este necesar.
  • Butoane Boot/Reset – pentru încărcarea firmware-ului și repornire.

Pentru Proiectul 3, cea mai importantă caracteristică este Wi-Fi-ul ESP32, care permite plăcii să acționeze ca un mic server web pentru pagina de control al textului.:contentReference[oaicite:1]{index=1}

Proiecte acoperite în videoclip (Marcaje de timp)

Videoclipul unic pentru această serie acoperă toate cele șase proiecte. Pentru referință rapidă:

  • 00:00 – Introducere
  • 02:01 – Instalarea plăcilor ESP32
  • 03:32 – Instalarea bibliotecilor
  • 05:32 – Proiectul 1: Punct în mișcare
  • 11:11 – Proiectul 2: Derulare text
  • 12:59Proiectul 3: HTTP Text (acest proiect)
  • 16:41 – Proiectul 4: Punct de înclinare
  • 18:55 – Proiectul 5: Săgeată în sus
  • 20:02 – Proiectul 6: Jocul țintă

Te încurajăm să urmărești secțiunea HTTP Text din videoclip în timp ce lucrezi cu acest articol. Videoclipul arată cum este generată pagina web de către ESP32 și cum schimbarea textului, culorii și vitezei se reflectă instantaneu pe matricea LED.:contentReference[oaicite:2]{index=2}

Instalarea plăcilor ESP32 în Arduino IDE

Dacă ai finalizat deja Proiectele 1 sau 2, configurarea plăcii este făcută și poți sări peste această secțiune. În caz contrar, urmează acești pași în Arduino IDE:

  1. Deschide File > Preferences și adaugă URL-ul plăcilor ESP32 la “Additional Boards Manager URLs”.
  2. Mergi la Tools > Board > Boards Manager…, caută ESP32 și instalează pachetul oficial ESP32.
  3. Selectează placa corectă ESP32-S3 RGB Matrix din Tools > Board.
  4. Conectează modulul prin USB și alege portul serial corect sub Tools > Port.

Fără suportul adecvat pentru plăcile ESP32 și portul corect, schița serverului web nu se va încărca.

Instalarea NeoMatrix și a bibliotecilor necesare

Acest proiect folosește aceleași biblioteci ca proiectul anterior de derulare a textului:

  • Adafruit NeoMatrix
  • Adafruit NeoPixel
  • Adafruit GFX Library

Instalează prin Library Manager:

  1. Deschide Sketch > Include Library > Manage Libraries….
  2. Caută Adafruit NeoMatrix și dă clic pe Install.
  3. Acceptă instalarea dependențelor (Adafruit GFX și Adafruit NeoPixel).

Odată instalate, ar trebui să vezi schițele de exemplu NeoMatrix și NeoPixel sub File > Examples.

Două moduri Wi-Fi în Proiectul 3

Cel mai important concept din acest proiect este că ESP32 poate funcționa în două moduri diferite:

  1. Modul Station (STA) – ESP32 se conectează la rețeaua Wi-Fi existentă de acasă.
  2. Modul Access Point (AP) – ESP32 își creează propria rețea Wi-Fi dacă Wi-Fi-ul de acasă nu este disponibil.

Ambele moduri folosesc aceeași interfață web: o pagină HTML servită de ESP32 însuși, unde poți schimba textul, culoarea, direcția de derulare și viteza.:contentReference[oaicite:3]{index=3}

Modul 1 – Conectare la Wi-Fi de acasă (Modul Station)

În modul Station, ESP32 se alătură rețelei Wi-Fi de acasă sau de la birou. Acesta este modul preferat ori de câte ori routerul este disponibil, deoarece:

  • Telefonul și computerul tău sunt deja conectate la aceeași rețea Wi-Fi.
  • Poți introduce adresa IP a ESP32 în browser și controla textul de pe orice dispozitiv din acea rețea.

În secțiunea de setări a schiței, furnizezi SSID-ul și parola Wi-Fi:

// Credențiale Wi-Fi de acasă (modul Stație) const char* WIFI_SSID = "YourHomeWiFi"; const char* WIFI_PASS = "YourHomePassword";

După pornirea plăcii, aceasta încearcă să se conecteze la WIFI_SSID. Dacă reușește, codul afișează adresa IP atribuită în Monitorul Serial, de exemplu:


Connected to WiFi
IP address: 192.168.1.16

Pentru a controla textul:

  1. Asigurați-vă că telefonul sau PC-ul este conectat la aceeași rețea Wi-Fi (de exemplu, YourHomeWiFi).
  2. Deschideți un browser și introduceți adresa IP afișată, cum ar fi http://192.168.1.16/.
  3. Va apărea pagina de control, permițându-vă să tastați text, să alegeți culoarea, să selectați direcția și să ajustați viteza de derulare.

Modul 2 – Punct de Acces Independent (Modul AP)

Dacă ESP32 nu se poate conecta la Wi-Fi-ul de acasă (parolă greșită, rețea indisponibilă sau folosiți modulul în exterior), schița comută automat în modul Punct de Acces. În modul AP, ESP32 devine el însuși un hotspot Wi-Fi cu propriul nume de rețea și parolă.

În acest proiect, setările AP sunt fixate astfel:


// Credențiale Punct de Acces (AP) (mod de rezervă)
const char* AP_SSID = "ESP32";
const char* AP_PASS = "password";

Când modul Stație eșuează, modulul comută în modul AP și începe să transmită o rețea Wi-Fi numită ESP32. Pentru a controla matricea:

  1. Pe telefon sau computer, deschideți setările Wi-Fi și conectați-vă la rețeaua ESP32.
  2. Introduceți parola password (așa cum este definită în cod).
  3. După conectare, deschideți un browser și accesați http://192.168.4.1/ (IP-ul implicit pentru modul AP al ESP32).
  4. Aceeași pagină de control apare, permițându-vă să schimbați textul, culoarea, viteza și direcția.

Acest comportament de rezervă face proiectul util oriunde: acasă, în laborator sau într-un mediu demonstrativ unde nu este disponibil un router.

Proiectul 3 – Setări Principale în Cod

Schița completă HTTP Text este încărcată mai jos de acest articol de către site. Aici documentăm doar cele mai importante opțiuni de configurare pe care probabil le veți edita.

Setări Wi-Fi și Punct de Acces

În partea de sus a schiței veți găsi secțiunea de configurare Wi-Fi. Schimbați doar credențialele pentru Stație (Wi-Fi de acasă); setările AP sunt păstrate în mod normal ca implicite:


// ---------- SETĂRI WI-FI ----------

// Wi-Fi de acasă (modul Stație)
const char* WIFI_SSID = "YourHomeWiFi";      // puneți SSID-ul routerului aici
const char* WIFI_PASS = "YourHomePassword";  // puneți parola routerului aici

// Punct de Acces de rezervă (modul AP)
const char* AP_SSID = "ESP32";               // nume AP fix
const char* AP_PASS = "password";            // parolă AP fixă

Comportament:

  • Dacă WIFI_SSID și WIFI_PASS sunt corecte și rețeaua este disponibilă → ESP32 se conectează ca un dispozitiv Wi-Fi normal (modul Stație).
  • Dacă conexiunea eșuează după un timeout → ESP32 pornește propriul AP folosind AP_SSID și AP_PASS.

Pinul Matricei, Dimensiune și Luminozitate

Aceste setări sunt aceleași ca în proiectele anterioare:


// Configurarea matricei
const int MATRIX_PIN    = 14;   // pinul de date al matricei RGB
const int MATRIX_WIDTH  = 8;
const int MATRIX_HEIGHT = 8;

// Luminozitatea generală a afișajului (0–255)
uint8_t matrixBrightness = 40;  // ajustați pentru mediul dvs.

Păstrați MATRIX_PIN la 14 pentru această placă. Puteți crește matrixBrightness dacă aveți nevoie de mai multă lumină, dar valorile mai mici sunt mai confortabile pentru vizionarea de aproape.

Text Implicit și Setări de Derulare

Când placa pornește, afișează un mesaj inițial până când deschideți pagina web și tastați unul nou. Puteți schimba textul implicit în configurare:


// Mesaj implicit afișat la pornire
String currentText = "Robojax";   // suprascris ulterior din interfața web

Restul comportamentului de derulare este controlat de un set de variabile care sunt actualizate de interfața web:


// Întârziere de derulare în milisecunde (mai mic = mai rapid)
int scrollDelayMs = 50;

// Direcția de derulare: 0=stânga, 1=dreapta, 2=sus, 3=jos
int scrollDirection = 0;   // implicit: derulare la stânga

Pagina web trimite valori noi pe baza selecțiilor de glisor și butoane. Din partea Arduino, trebuie doar să știți că:

  • Scăderea scrollDelayMs face textul să se miște mai repede.
  • Creșterea scrollDelayMs îl face să se miște mai lent.
  • Schimbarea scrollDirection comută între modurile de derulare stânga, dreapta, sus sau jos.

Culoarea Textului (Controlată din Pagina Web)

Culoarea textului este controlată de trei valori 0–255 (roșu, verde, albastru). Acestea sunt actualizate de fiecare dată când alegeți o nouă culoare pe pagina web:


// Culoarea curentă a textului (R, G, B)
uint8_t textRed   = 255;
uint8_t textGreen = 255;
uint8_t textBlue  = 255;

Când alegeți o culoare în browser și faceți clic pe Apply, ESP32 parsează valorile RGB și actualizează aceste trei variabile; textul își schimbă imediat culoarea pe matrice. În videoclip, acest comportament este demonstrat cu multiple schimbări de culoare, inclusiv exemple roșu, verde și albastru.

Rezumat

Proiectul 3 transformă matricea LED RGB ESP32-S3 într-un afișaj de text complet wireless pe care îl puteți controla folosind orice dispozitiv cu un browser web. Schița este concepută pentru a fi robustă:

  • Încearcă mai întâi să se conecteze la rețeaua Wi-Fi de acasă folosind SSID-ul și parola pe care le-ai configurat.
  • Dacă această încercare eșuează, devine automat un punct de acces cu numele ESP32 și parola password.
  • În ambele moduri, deschiderea adresei IP corecte într-un browser afișează aceeași pagină de control pentru text, culoare, direcție și viteză.

Codul HTTP Text complet este disponibil mai jos în acest articol (încărcat automat pe site). Pentru o prezentare detaliată pas cu pas și o demonstrație live a modului în care textul se actualizează în timp real, asigură-te că urmărești secțiunea Proiectul 3 din videoclip. Dacă dorești să construiești proiectul singur, poți achiziționa și modulul ESP32-S3 RGB LED Matrix folosind linkurile afiliate listate sub cod.

Imagini

ESP32 S3 Matrix
ESP32 S3 Matrix
ESP32 S3 Matrix  pin out
ESP32 S3 Matrix pin out
ESP32-S3_RGB_8x8_matrix-3
ESP32-S3_RGB_8x8_matrix-3
ESP32 S3 Matrix displaying rainbow heart
ESP32 S3 Matrix displaying rainbow heart
ESP32-S3_RGB_8x8_matrix1
ESP32-S3_RGB_8x8_matrix1
ESP32-S3_RGB_8x8_matrix-2
ESP32-S3_RGB_8x8_matrix-2
ESP32-S3 RGB Matrix- Mobile Phone Text
ESP32-S3 RGB Matrix- Mobile Phone Text
801-ESP32-S3 RGB LED Matrix Project 3 - Text from mobile phone
Limba: C++
/*
  Project 4: HTTP Text Scroll – ESP32-S3 RGB LED Matrix (Waveshare)

  - Connects to your home WiFi (station mode, with AP fallback).
  - Serves a web page where you can set:
      * Text
      * Color
      * Display ON/OFF
      * Scroll direction (Left / Right / Up / Down)
      * Scroll delay (speed)
  - Supports 8×8 RGB LED matrix using Adafruit_NeoMatrix.

  ▶️ Video Tutorial:
  https://youtu.be/JKLuYrRcLMI

  📄 Resources & Code Page:
  https://robojax.com/your-resources-page-here

  HTTP_Text_Scroll
*/

#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>

#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>

// ======================= WIFI SETTINGS =========================
// Home WiFi (change these to your router credentials)
const char* WIFI_SSID = "Biseem";
const char* WIFI_PASS = "wan9&Jang~";

// Fallback Access Point
const char* AP_SSID = "ESP32";
const char* AP_PASS = "password";

// ======================= MATRIX SETTINGS =======================

#define MATRIX_PIN    14
#define MATRIX_WIDTH  8
#define MATRIX_HEIGHT 8

// 0, 1, 2, or 3 – adjust if text orientation is wrong with USB up
#define MATRIX_ROTATION 0
#define BRIGHTNESS 15

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(
  MATRIX_WIDTH, MATRIX_HEIGHT, MATRIX_PIN,
  NEO_MATRIX_TOP + NEO_MATRIX_LEFT +
  NEO_MATRIX_ROWS + NEO_MATRIX_PROGRESSIVE,
  NEO_RGB + NEO_KHZ800
);

// ======================= SCROLL STATE ==========================

enum ScrollDir {
  DIR_LEFT = 0,
  DIR_RIGHT,
  DIR_UP,
  DIR_DOWN
};

String   scrollText  = "Robojax";
uint16_t textColor   = 0xFFFF;  // default white
bool     displayOn   = true;
ScrollDir currentDir = DIR_LEFT;

// 1 step every this many ms (lower = faster)
unsigned long scrollInterval = 80;
unsigned long lastScrollTime = 0;

// Horizontal scroll position
int scrollX = MATRIX_WIDTH;
int scrollY = 0;              // top row

// State for vertical per-letter scroll
int vertCharIndex = 0;        // which character of the string
int vertY         = MATRIX_HEIGHT;    // vertical position of that character

// ======================= WEB SERVER ============================

WebServer server(80);

// ======================= HTML PAGE ============================

const char MAIN_page[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ESP32 Text Scroll</title>
<style>
  body {
    font-family: Arial, sans-serif;
    background: #111;
    color: #eee;
    text-align: center;
    margin: 0;
    padding: 10px;
  }
  .container {
    max-width: 360px;
    width: 100%%;
    margin: 0 auto;
    background: #222;
    padding: 12px;
    border-radius: 10px;
    box-sizing: border-box;
  }
  input[type="text"] {
    width: 100%%;
    padding: 8px;
    box-sizing: border-box;
    border-radius: 5px;
    border: 1px solid #444;
    background: #111;
    color: #eee;
    margin-bottom: 10px;
  }
  .row {
    margin: 10px 0;
  }
  .label {
    display: block;
    margin-bottom: 5px;
    text-align: left;
  }
  .toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
  }
  .arrow-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: repeat(3, 50px);
    gap: 8px;
    justify-content: center;
    margin-top: 10px;
  }
  .arrow-btn {
    background: #333;
    border: 1px solid #555;
    color: #eee;
    font-size: 18px;
    border-radius: 10px;
    cursor: pointer;
    width: 100%%;
    height: 100%%;
  }
  .arrow-btn.active {
    background: #0a84ff;
  }
  .arrow-btn:disabled {
    opacity: 0.5;
    cursor: default;
  }
  button#applyBtn {
    margin-top: 15px;
    padding: 10px 20px;
    border-radius: 8px;
    border: none;
    background: #0a84ff;
    color: #fff;
    font-size: 16px;
    cursor: pointer;
  }
  button#applyBtn:active {
    transform: scale(0.97);
  }
</style>
</head>
<body>
  <div class="container">
    <h2>Robojax ESP32-S3 Matrix Text Scroll</h2>

    <div class="row">
      <span class="label">Text to scroll:</span>
      <input type="text" id="text" value="Hello" />
    </div>

    <div class="row">
      <span class="label">Color:</span>
      <input type="color" id="color" value="#ffffff" />
    </div>

    <div class="row">
      <span class="label">Scroll Delay (fast → slow):</span>
      <input
        type="range"
        id="speed"
        min="20"
        max="300"
        value="80"
        oninput="document.getElementById('speedVal').innerText = this.value + ' ms';"
      />
      <div id="speedVal" style="text-align:right;font-size:12px;margin-top:4px;">
        80 ms
      </div>
    </div>

    <div class="row">
      <label class="toggle">
        <input type="checkbox" id="power" checked />
        <span>Display ON</span>
      </label>
    </div>

    <div class="row">
      <span class="label">Scroll Direction:</span>
      <div class="arrow-grid">
        <div></div>
        <button class="arrow-btn" id="btnUp" onclick="setDir('up')">&#9650;</button>
        <div></div>

        <button class="arrow-btn" id="btnLeft" onclick="setDir('left')">&#9664;</button>
        <div></div>
        <button class="arrow-btn" id="btnRight" onclick="setDir('right')">&#9654;</button>

        <div></div>
        <button class="arrow-btn" id="btnDown" onclick="setDir('down')">&#9660;</button>
        <div></div>
      </div>
    </div>

    <button id="applyBtn" onclick="sendUpdate()">Apply</button>
    <p id="status"></p>
  </div>

<script>
  let currentDir = 'left';

  function setDir(dir) {
    currentDir = dir;
    document.querySelectorAll('.arrow-btn').forEach(b => b.classList.remove('active'));
    if (dir === 'up')    document.getElementById('btnUp').classList.add('active');
    if (dir === 'down')  document.getElementById('btnDown').classList.add('active');
    if (dir === 'left')  document.getElementById('btnLeft').classList.add('active');
    if (dir === 'right') document.getElementById('btnRight').classList.add('active');
    sendUpdate();
  }

  function sendUpdate() {
    const text  = document.getElementById('text').value;
    const color = document.getElementById('color').value;
    const power = document.getElementById('power').checked ? '1' : '0';
    const speed = document.getElementById('speed').value;

    const url = `/update?text=${encodeURIComponent(text)}&color=${encodeURIComponent(color)}&power=${power}&dir=${currentDir}&speed=${speed}`;

    fetch(url)
      .then(r => r.text())
      .then(t => {
        document.getElementById('status').innerText = t;
      })
      .catch(err => {
        document.getElementById('status').innerText = 'Error sending update';
      });
  }

  // Set default active
  setDir('left');
</script>
</body>
</html>
)rawliteral";

// ======================= HELPERS ===============================

bool isHexChar(char c) {
  return (c >= '0' && c <= '9') ||
         (c >= 'a' && c <= 'f') ||
         (c >= 'A' && c <= 'F');
}

String urlDecode(const String &src) {
  String result;
  result.reserve(src.length());
  for (size_t i = 0; i < src.length(); i++) {
    char c = src[i];
    if (c == '+') {
      result += ' ';
    } else if (c == '%' && i + 2 < src.length()) {
      char h1 = src[i + 1];
      char h2 = src[i + 2];
      if (isHexChar(h1) && isHexChar(h2)) {
        char hex[3] = {h1, h2, 0};
        int val = (int)strtol(hex, nullptr, 16);
        result += (char)val;
        i += 2;
      } else {
        result += c;
      }
    } else {
      result += c;
    }
  }
  return result;
}

uint16_t colorFromHex(const String &hex) {
  // Expect "#RRGGBB" or "RRGGBB"
  String c = hex;
  if (c.startsWith("#")) c.remove(0, 1);
  if (c.length() != 6) {
    // default white
    return matrix.Color(255, 255, 255);
  }
  long value = strtol(c.c_str(), NULL, 16);
  uint8_t r = (value >> 16) & 0xFF;
  uint8_t g = (value >> 8) & 0xFF;
  uint8_t b = (value) & 0xFF;
  return matrix.Color(r, g, b);
}

void resetScrollPosition() {
  int textWidth  = scrollText.length() * 6;  // default font ~6 px per char
  int textHeight = 8;                        // 5x7 font fits in 8

  switch (currentDir) {
    case DIR_LEFT:
      // Start from just outside the right edge
      scrollX = MATRIX_WIDTH;
      scrollY = 0;
      break;

    case DIR_RIGHT:
      // Start from just outside the left edge
      scrollX = -textWidth;
      scrollY = 0;
      break;

    case DIR_UP:
      // Per-letter vertical scroll, starting with first character below matrix
      vertCharIndex = 0;
      vertY = MATRIX_HEIGHT;   // 8 → enters from bottom
      break;

    case DIR_DOWN:
      // Per-letter vertical scroll, starting with first character above matrix
      vertCharIndex = 0;
      vertY = -textHeight;     // -8 → enters from top
      break;
  }
}

// ======================= HTTP HANDLERS =========================

void handleRoot() {
  server.send_P(200, "text/html", MAIN_page);
}

void handleUpdate() {
  if (server.hasArg("text")) {
    String txt = urlDecode(server.arg("text"));
    if (txt.length() == 0) {
      scrollText = " ";
    } else {
      scrollText = txt;
    }
  }

  if (server.hasArg("color")) {
    String hex = server.arg("color");
    textColor = colorFromHex(hex);
  }

  if (server.hasArg("power")) {
    String p = server.arg("power");
    displayOn = (p == "1");
  }

  if (server.hasArg("dir")) {
    String d = server.arg("dir");
    if (d == "left")      currentDir = DIR_LEFT;
    else if (d == "right") currentDir = DIR_RIGHT;
    else if (d == "up")    currentDir = DIR_UP;
    else if (d == "down")  currentDir = DIR_DOWN;
  }

  if (server.hasArg("speed")) {
    String s = server.arg("speed");
    int val = s.toInt();

    // simple safety clamp
    if (val < 10)   val = 10;    // very fast
    if (val > 1000) val = 1000;  // 1 second max

    scrollInterval = (unsigned long)val;
    // Serial.print("Scroll interval set to ");
    // Serial.print(scrollInterval);
    // Serial.println(" ms");
  }

  resetScrollPosition();
  server.send(200, "text/plain", "Updated");
}

// ======================= SCROLL LOGIC ==========================

void drawScroll() {
  if (!displayOn) {
    matrix.fillScreen(0);
    matrix.show();
    return;
  }

  int textWidth  = scrollText.length() * 6;
  int textHeight = 8;

  matrix.fillScreen(0);
  matrix.setTextSize(1);
  matrix.setTextWrap(false);
  matrix.setTextColor(textColor);

  // -------- HORIZONTAL SCROLL (LEFT / RIGHT) --------
  if (currentDir == DIR_LEFT || currentDir == DIR_RIGHT) {
    matrix.setCursor(scrollX, 0);
    matrix.print(scrollText);
    matrix.show();

    if (currentDir == DIR_LEFT) {
      scrollX--;
      if (scrollX < -textWidth) {
        scrollX = MATRIX_WIDTH;
      }
    } else { // DIR_RIGHT
      scrollX++;
      if (scrollX > MATRIX_WIDTH) {
        scrollX = -textWidth;
      }
    }
    return;
  }

  // -------- VERTICAL SCROLL (UP / DOWN) – PER LETTER --------

  if (scrollText.length() == 0) {
    matrix.show();
    return;
  }

  // Current character
  char c = scrollText[vertCharIndex];

  int charWidth  = 6;
  int baseX = (MATRIX_WIDTH - charWidth) / 2;  // center horizontally

  matrix.setCursor(baseX, vertY);
  matrix.print(c);
  matrix.show();

  if (currentDir == DIR_UP) {
    // Move character upwards
    vertY--;
    if (vertY < -textHeight) {
      // This character is fully passed, go to next
      vertY = MATRIX_HEIGHT;
      vertCharIndex++;
      if (vertCharIndex >= scrollText.length()) {
        vertCharIndex = 0; // loop back to first character
      }
    }
  } else if (currentDir == DIR_DOWN) {
    // Move character downwards
    vertY++;
    if (vertY > MATRIX_HEIGHT) {
      // This character is fully passed, go to next
      vertY = -textHeight;
      vertCharIndex++;
      if (vertCharIndex >= scrollText.length()) {
        vertCharIndex = 0; // loop back
      }
    }
  }
}

// ======================= WIFI INIT =============================

void startAPFallback() {
  Serial.println("Starting AP fallback...");
  WiFi.mode(WIFI_AP);
  bool apOk = WiFi.softAP(AP_SSID, AP_PASS);
  if (apOk) {
    Serial.print("AP started. SSID: ");
    Serial.print(AP_SSID);
    Serial.print("  IP: ");
    Serial.println(WiFi.softAPIP());
  } else {
    Serial.println("Failed to start AP.");
  }
}

void connectWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  Serial.print("Connecting to WiFi ");
  Serial.print(WIFI_SSID);

  unsigned long startAttempt = millis();
  const unsigned long timeout = 10000; // 10s

  while (WiFi.status() != WL_CONNECTED && (millis() - startAttempt) < timeout) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();

  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("Connected. IP address: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("WiFi connect failed, starting AP fallback.");
    startAPFallback();
  }
}

// ======================= SETUP & LOOP ==========================

void setup() {
  Serial.begin(115200);
  delay(500);

  // Matrix init
  matrix.begin();
  matrix.setRotation(MATRIX_ROTATION);
  matrix.setBrightness(BRIGHTNESS);
  matrix.fillScreen(0);
  matrix.show();

  // WiFi
  connectWiFi();

  // Web server routes
  server.on("/", handleRoot);
  server.on("/update", handleUpdate);
  server.onNotFound([]() {
    server.send(404, "text/plain", "Not found");
  });
  server.begin();
  Serial.println("HTTP server started.");

  resetScrollPosition();
}

void loop() {
  server.handleClient();

  unsigned long now = millis();
  if (now - lastScrollTime >= scrollInterval) {
    lastScrollTime = now;
    drawScroll();
  }
}

Lucruri de care ai putea avea nevoie

Resurse și referințe

Fișiere📁

Fișier Fritzing