Search Code

Aikin Matrix LED RGB ESP32-S3 Project 3 - Rubutu daga wayar hannu

Aikin Matrix LED RGB ESP32-S3 Project 3 - Rubutu daga wayar hannu

Aikin 3 – Sarrafa Rubutun Matrix daga Wayarka (Rubutun HTTP)

A cikin wannan aikin, ESP32-S3 RGB LED Matrix yana ɗaukar ƙaramin shafin yanar gizo domin ka iya canza rubutun da ke gudana, launi, alkibla, da sauri kai tsaye daga wayarka ko kwamfutarka. Ba ka buƙatar wani app na daban – kawai burauza na yanar gizo. Wannan yana sa na'urar ta zama ƙaramin nuni na rubutu ta Wi-Fi da za ka iya sabuntawa a lokaci guda.

Dukkan ayyuka shida a cikin wannan jerin an bayyana su kuma an nuna su a cikin bidiyo ɗaya na YouTube. Wannan bidiyon an saka shi a wannan shafin, domin ka ga daidai yadda mu'amalar yanar gizo take kama da yadda rubutu ke sabuntawa nan take akan matrix. Cikakken lambar tushe na wannan aikin ana loda shi ta atomatik a ƙasan labarin, kuma za ka iya siyan na'urar ESP32-S3 RGB LED Matrix daga shagunan haɗin gwiwa da aka jera a ƙarƙashin sashin lambar.

A cikin wannan labarin mun mayar da hankali kan yadda dabaru na hanyar sadarwa ke aiki (Wi-Fi na gida vs wurin shiga) da waɗanne saituna za ka iya canzawa a cikin lambar don daidaita halayen.

Bayani Kan Na'urar ESP32-S3 RGB LED Matrix

Kayan aikin suna iri ɗaya da na sauran ayyukan a cikin wannan jerin: kwamfutar ESP32-S3 mai haɗe-haɗen matrix na RGB LED 8×8 da na'urar motsi QMI8658C a baya. Ana amfani da tashar USB-C don wuta da shirye-shirye, kuma fil ɗin da ke kewaye har yanzu suna nan don sauran IO.:contentReference[oaicite:0]{index=0}

  • ESP32-S3 – kwamfuta mai iya Wi-Fi da Bluetooth.
  • Matrix na RGB 8×8 – LED 64 masu adireshi na RGB don rubutu da zane.
  • QMI8658C accelerometer – ana amfani da shi a ayyukan karkata da wasa.
  • Tashar USB – tana ba da wuta ga kwamfutar kuma tana loda sketches daga Arduino IDE.
  • Fil ɗin da aka fallasa – suna ba da damar ƙarin na'urori masu auna ko masu motsawa idan an buƙata.
  • Maɓallan Boot/Reset – don loda firmware da sake farawa.

Don Aikin 3, mafi mahimmancin fasalin shine Wi-Fi na ESP32, wanda ke ba kwamfutar damar zama ƙaramin sabar yanar gizo don shafin sarrafa rubutu.:contentReference[oaicite:1]{index=1}

Ayyukan Da Aka Rufe a Bidiyon (Lokuttan)

Bidiyo ɗaya na wannan jerin ya rufe dukkan ayyuka shida. Don saurin dubawa:

  • 00:00 – Gabatarwa
  • 02:01 – Shigar da kwamfutocin ESP32
  • 03:32 – Shigar da ɗakunan karatu
  • 05:32 – Aiki 1: Digo Mai Motsi
  • 11:11 – Aiki 2: Gudun Rubutu
  • 12:59Aiki 3: Rubutun HTTP (wannan aikin)
  • 16:41 – Aiki 4: Digo Mai Karkata
  • 18:55 – Aiki 5: Kibiya Zuwa Sama
  • 20:02 – Aiki 6: Wasan Bursa

Ana ƙarfafa ka ka kalli sashin Rubutun HTTP a cikin bidiyon yayin aiki da wannan labarin. Bidiyon yana nuna yadda ESP32 ke samar da shafin yanar gizo da kuma yadda canza rubutu, launi, da sauri ke bayyana nan take akan matrix na LED.:contentReference[oaicite:2]{index=2}

Shigar da Kwamfutocin ESP32 a Arduino IDE

Idan ka riga ka kammala Ayyuka 1 ko 2, saitin kwamfutar ya ƙare kuma za ka iya tsallake wannan sashin. In ba haka ba, bi waɗannan matakan a cikin Arduino IDE:

  1. Buɗe File > Preferences kuma ƙara URL ɗin kwamfutocin ESP32 zuwa “Additional Boards Manager URLs”.
  2. Je zuwa Tools > Board > Boards Manager…, bincika ESP32, kuma shigar da fakitin ESP32 na hukuma.
  3. Zaɓi kwamfutar ESP32-S3 RGB Matrix daidai daga Tools > Board.
  4. Haɗa na'urar ta USB kuma zaɓi tashar serial daidai a ƙarƙashin Tools > Port.

Idan babu goyon bayan kwamfutar ESP32 da tashar da ta dace, sketch ɗin sabar yanar gizo ba zai loda ba.

Shigar da NeoMatrix da Bukatattun Ɗakunan Karatu

Wannan aikin yana amfani da ƙananan ɗakunan karatu iri ɗaya da na aikin gudun rubutu na baya:

  • Adafruit NeoMatrix
  • Adafruit NeoPixel
  • Adafruit GFX Library

Shigar ta hanyar Library Manager:

  1. Buɗe Sketch > Include Library > Manage Libraries….
  2. Bincika Adafruit NeoMatrix kuma danna Install.
  3. Karɓi shigar da abubuwan dogaro (Adafruit GFX da Adafruit NeoPixel).

Da zarar an shigar, ya kamata ka ga misalan sketches na NeoMatrix da NeoPixel a ƙarƙashin File > Examples.

Hanyoyin Wi-Fi Biyu a Aiki 3

Mafi mahimmancin ra'ayi a cikin wannan aikin shine ESP32 na iya aiki a hanyoyi biyu daban-daban:

  1. Yanayin Station (STA) – ESP32 yana haɗawa da cibiyar sadarwar Wi-Fi na gidanka da ke akwai.
  2. Yanayin Access Point (AP) – ESP32 yana ƙirƙirar cibiyar sadarwar Wi-Fi nasa idan babu Wi-Fi na gida.

Duk hanyoyin biyu suna amfani da mu'amalar yanar gizo iri ɗaya: shafin HTML da ESP32 ke bayarwa, inda za ka iya canza rubutu, launi, alkiblar gudu, da sauri.:contentReference[oaicite:3]{index=3}

Hanya 1 – Haɗa da Wi-Fi na Gida (Yanayin Station)

A yanayin Station, ESP32 yana shiga cibiyar sadarwar Wi-Fi na gida ko ofis. Wannan ita ce hanyar da aka fi so a duk lokacin da router ɗinka yana nan saboda:

  • Wayarka da kwamfutarka sun riga sun haɗa da wannan cibiyar sadarwar Wi-Fi.
  • Za ka iya nuna burauzarka zuwa adireshin IP na ESP32 kuma ka sarrafa rubutun daga kowace na'ura a wannan cibiyar sadarwar.

A sashin saitunan na sketch, za ka ba da SSID da kalmar sirri na Wi-Fi ɗinka:


// Bayan gida Wi-Fi bayanan shiga (Station mode)
const char* WIFI_SSID = "YourHomeWiFi";
const char* WIFI_PASS = "YourHomePassword";

Bayan allo ya tashi, yana ƙoƙarin haɗawa da WIFI_SSID. Idan ya yi nasara, lambar tana buga adireshin IP da aka ba shi zuwa Serial Monitor, misali:


An haɗa da WiFi
Adireshin IP: 192.168.1.16

Don sarrafa rubutu:

  1. Tabbatar cewa wayarka ko PC ɗinka an haɗa su da hanyar sadarwar Wi-Fi ɗaya (misali, YourHomeWiFi).
  2. Bude burauza kuma shigar da adireshin IP da aka buga, kamar http://192.168.1.16/.
  3. Shafin sarrafawa zai bayyana, yana ba ka damar rubuta rubutu, zaɓi launi, zaɓi alkibla, da daidaita saurin gudu.

Yanayin 2 – Matsayin Access Point Mai Zaman Kansa (AP Mode)

Idan ESP32 ba zai iya haɗawa da Wi-Fi na gida ba (kalmar sirri ba daidai ba, hanyar sadarwa ba ta samuwa, ko kana amfani da na'urar a waje), sketch ɗin yana komawa ta atomatik zuwa yanayin Access Point. A yanayin AP, ESP32 ɗin kansa ya zama hotspot na Wi-Fi tare da sunan hanyar sadarwa da kalmar sirri.

A cikin wannan aikin, saitunan AP an kafa su kamar haka:


// Bayanan Access Point (AP) (yanayin fallback)
const char* AP_SSID = "ESP32";
const char* AP_PASS = "password";

Lokacin da yanayin Station ya gaza, na'urar tana canzawa zuwa yanayin AP kuma tana fara watsa hanyar sadarwar Wi-Fi mai suna ESP32. Don sarrafa matrix ɗin:

  1. A kan wayarka ko kwamfutarka, bude saitunan Wi-Fi kuma haɗa da hanyar sadarwar ESP32.
  2. Shigar da kalmar sirri password (kamar yadda aka ayyana a cikin lambar).
  3. Da zarar an haɗa, bude burauza kuma je zuwa http://192.168.4.1/ (adireshin IP na asali don yanayin AP na ESP32).
  4. Shafin sarrafawa ɗaya yana bayyana, yana ba ka damar canza rubutu, launi, sauri, da alkibla.

Wannan halin fallback yana sa aikin ya zama mai amfani a ko'ina: a gida, a dakin gwaje-gwaje, ko a yanayin nunin inda babu router.

Aiki na 3 – Manyan Saituna a cikin Lambar

Cikakken sketch ɗin HTTP Text an ɗora shi a ƙasan wannan labarin ta gidan yanar gizon. Anan muna rubuta mafi mahimmancin zaɓuɓɓukan saituna waɗanda kake iya gyara.

Saitunan Wi-Fi da Access Point

A saman sketch ɗin za ka sami sashin saitunan Wi-Fi. Kawai canza bayanan Station (Wi-Fi na gida); saitunan AP galibi ana kiyaye su a matsayin na asali:


// ---------- SAITUNAN Wi-Fi ----------

// Wi-Fi na gida (Station mode)
const char* WIFI_SSID = "YourHomeWiFi";      // sanya SSID na router ɗinka anan
const char* WIFI_PASS = "YourHomePassword";  // sanya kalmar sirri ta router ɗinka anan

// Fallback Access Point (AP mode)
const char* AP_SSID = "ESP32";               // sunan AP na tsaye
const char* AP_PASS = "password";            // kalmar sirri ta AP na tsaye

Hali:

  • Idan WIFI_SSID da WIFI_PASS sun yi daidai kuma hanyar sadarwa tana samuwa → ESP32 yana haɗawa a matsayin na'urar Wi-Fi ta al'ada (Station mode).
  • Idan haɗin ya gaza bayan lokaci → ESP32 yana fara AP ɗin kansa ta amfani da AP_SSID da AP_PASS.

Pin na Matrix, Girma, da Haskewa

Waɗannan saitunan suna ɗaya da na ayyukan da suka gabata:


// Saitunan Matrix
const int MATRIX_PIN    = 14;   // pin ɗin bayanan RGB matrix
const int MATRIX_WIDTH  = 8;
const int MATRIX_HEIGHT = 8;

// Haskewar nuni gabaɗaya (0–255)
uint8_t matrixBrightness = 40;  // daidaita don muhallinka

Kiyaye MATRIX_PIN a 14 don wannan allo. Kana iya ƙara matrixBrightness idan kana buƙatar ƙarin haske, amma ƙananan ƙima sun fi dacewa don kallo na kusa.

Rubutu na Asali da Saitunan Gudu

Lokacin da allo ya fara, yana nuna saƙon farko har sai ka bude shafin yanar gizo kuma ka rubuta sabo. Kana iya canza rubutun na asali a cikin saitunan:


// Saƙon na asali da aka nuna a lokacin farawa
String currentText = "Robojax";   // za a sake rubuta daga web UI daga baya

Sauran halin gudu ana sarrafa shi ta hanyar saitin masu canji waɗanda keɓaɓɓen yanar gizo ke sabunta su:


// Jinkirin gudu a milliseconds (ƙasa = sauri)
int scrollDelayMs = 50;

// Alkiblar gudu: 0=hagu, 1=dam, 2=sama, 3=ƙasa
int scrollDirection = 0;   // na asali: gudu zuwa hagu

Shafin yanar gizo yana aika sabbin ƙima bisa zaɓin slider da maɓallin. Daga gefen Arduino, kawai kana buƙatar sanin cewa:

  • Rage scrollDelayMs yana sa rubutu ya motsa da sauri.
  • Ƙara scrollDelayMs yana sa shi motsa a hankali.
  • Canza scrollDirection yana canzawa tsakanin yanayin gudu na hagu, dama, sama, ko ƙasa.

Launin Rubutu (Ana Sarrafa shi daga Shafin Yanar Gizo)

Launin rubutu ana sarrafa shi ta ƙimar 0–255 guda uku (ja, kore, shuɗi). Ana sabunta su duk lokacin da ka zaɓi sabon launi a shafin yanar gizo:


// Launin rubutu na yanzu (R, G, B)
uint8_t textRed   = 255;
uint8_t textGreen = 255;
uint8_t textBlue  = 255;

Lokacin da ka zaɓi launi a cikin burauza kuma ka danna Apply, ESP32 yana nazarin ƙimar RGB kuma yana sabunta waɗannan masu canji guda uku; rubutu nan take yana canza launi a matrix ɗin. A cikin bidiyon, an nuna wannan hali tare da canje-canjen launi da yawa, gami da misalan ja, kore, da shuɗi.

Taƙaitawa

Aiki na 3 yana juya ESP32-S3 RGB LED Matrix ɗinka zuwa nunin rubutu mara waya wanda za ka iya sarrafa shi ta amfani da kowace na'ura mai burauza. Sketch ɗin an tsara shi don zama mai ƙarfi:

  • Da farko yana ƙoƙarin haɗawa da Wi-Fi na gidanka ta amfani da SSID da kalmar sirri da ka saita.
  • Idan hakan ya gaza, yana zama wurin shiga ta atomatik da suna ESP32 da kalmar sirri password.
  • A cikin hanyoyin biyu, buɗe adireshin IP da ya dace a cikin browser yana nuna wannan shafin sarrafawa don rubutu, launi, alkibla, da sauri.

Cikakken lambar HTTP Text yana samuwa a ƙasan wannan labarin (ana loda shi ta atomatik akan gidan yanar gizon). Don cikakken bayani mataki-mataki da nunin kai tsaye na yadda rubutu ke sabuntawa a lokaci guda, tabbatar ka kalli sashin Project 3 na bidiyon. Idan kana son gina aikin da kanka, za ka iya siyan na'urar ESP32-S3 RGB LED Matrix ta amfani da hanyoyin haɗin affiliate da aka jera a ƙasan lambar.

Hotuna

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
Harshe: 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();
  }
}

Abin da zaka iya bukata

Albarkatun da kuma shafukan da za a duba

Fayiloli📁

Fayil Fritzing