Кодты іздеу

ESP32-S3 RGB LED матрица жобасы 3 - Мобильді телефоннан мәтін

ESP32-S3 RGB LED матрица жобасы 3 - Мобильді телефоннан мәтін

Жоба 3 – Матрица мәтінін телефоннан басқару (HTTP мәтіні)

Бұл жобада ESP32-S3 RGB LED матрицасы шағын веб-парақшаны орналастырады, сондықтан сіз айналмалы мәтінді, түсті, бағытты және жылдамдықты телефоннан немесе компьютерден тікелей өзгерте аласыз. Бөлек қосымша қажет емес – тек веб-браузер жеткілікті. Бұл модульді нақты уақытта жаңартуға болатын шағын Wi-Fi мәтін дисплейіне айналдырады.

Осы сериядағы барлық алты жоба бір YouTube бейнеде түсіндіріліп, көрсетілген. Сол бейне осы парақшаға ендірілген, сондықтан веб-интерфейстің қалай көрінетінін және мәтіннің матрицада лезде қалай жаңаратынын дәл көре аласыз. Бұл жобаның толық бастапқы коды мақаланың астында автоматты түрде жүктеледі, ал ESP32-S3 RGB LED Matrix модулін код бөлімінің астында көрсетілген серіктес дүкендерден сатып ала аласыз.

Бұл мақалада біз желілік логиканың қалай жұмыс істейтініне (үй Wi-Fi vs қолжетімділік нүктесі) және мінез-құлықты теңшеу үшін кодта қандай параметрлерді өзгертуге болатынына назар аударамыз.

ESP32-S3 RGB LED Matrix модуліне шолу

Аппараттық қамтамасыз ету осы сериядағы барлық басқа жобалармен бірдей: кірістірілген 8×8 RGB LED матрицасы және артқы жағында QMI8658C қозғалыс сенсоры бар ESP32-S3 микроконтроллер тақтасы. USB-C порты қуат пен бағдарламалау үшін қолданылады, ал шеттеріндегі түйреуіштер басқа IO үшін қолжетімді.:contentReference[oaicite:0]{index=0}

  • ESP32-S3 – Wi-Fi және Bluetooth қолдайтын микроконтроллер.
  • 8×8 RGB матрица – мәтін мен графика үшін 64 адрестік RGB LED.
  • QMI8658C акселерометр – еңкейту және ойын жобаларында қолданылады.
  • USB порты – тақтаны қуаттандырады және Arduino IDE-ден эскиздерді жүктейді.
  • Ашық түйреуіштер – қажет болса қосымша сенсорлар немесе жетектерді қосуға мүмкіндік береді.
  • Boot/Reset түймелері – микробағдарламаны жүктеу және қайта іске қосу үшін.

3-жоба үшін ең маңызды мүмкіндік – ESP32 Wi-Fi, ол тақтаға мәтінді басқару парақшасы үшін шағын веб-сервер ретінде әрекет етуге мүмкіндік береді.:contentReference[oaicite:1]{index=1}

Бейнеде қамтылған жобалар (уақыт белгілері)

Осы серияға арналған бір бейне барлық алты жобаны қамтиды. Жылдам анықтама үшін:

  • 00:00 – Кіріспе
  • 02:01 – ESP32 тақталарын орнату
  • 03:32 – Кітапханаларды орнату
  • 05:32 – 1-жоба: Қозғалатын нүкте
  • 11:11 – 2-жоба: Мәтінді айналдыру
  • 12:593-жоба: HTTP мәтіні (осы жоба)
  • 16:41 – 4-жоба: Еңкейту нүктесі
  • 18:55 – 5-жоба: Жоғары көрсеткі
  • 20:02 – 6-жоба: Нысана ойыны

Осы мақаламен жұмыс істегенде бейнедегі HTTP мәтіні бөлімін көруге кеңес береміз. Бейне ESP32 арқылы веб-парақшаның қалай жасалатынын және мәтінді, түсті және жылдамдықты өзгерту LED матрицасында лезде қалай көрсетілетінін көрсетеді.:contentReference[oaicite:2]{index=2}

Arduino IDE-де ESP32 тақталарын орнату

Егер сіз 1 немесе 2-жобаларды аяқтаған болсаңыз, тақтаны орнату дайын және бұл бөлімді өткізіп жіберуге болады. Әйтпесе, Arduino IDE-де мына қадамдарды орындаңыз:

  1. File > Preferences ашып, ESP32 тақталарының URL мекенжайын “Additional Boards Manager URLs” қосыңыз.
  2. Tools > Board > Boards Manager… бөліміне өтіп, ESP32 іздеп, ресми ESP32 пакетін орнатыңыз.
  3. Tools > Board бөлімінен дұрыс ESP32-S3 RGB Matrix тақтасын таңдаңыз.
  4. Модульді USB арқылы қосып, Tools > Port бөлімінен дұрыс сериялық портты таңдаңыз.

Тиісті ESP32 тақта қолдауы мен дұрыс порт болмаса, веб-сервер эскизі жүктелмейді.

NeoMatrix және қажетті кітапханаларды орнату

Бұл жоба алдыңғы мәтінді айналдыру жобасымен бірдей кітапханаларды пайдаланады:

  • Adafruit NeoMatrix
  • Adafruit NeoPixel
  • Adafruit GFX Library

Library Manager арқылы орнатыңыз:

  1. Sketch > Include Library > Manage Libraries… ашыңыз.
  2. Adafruit NeoMatrix іздеп, Install түймесін басыңыз.
  3. Тәуелділіктерді орнатуды қабылдаңыз (Adafruit GFX және Adafruit NeoPixel).

Орнатылғаннан кейін File > Examples бөлімінде NeoMatrix және NeoPixel мысал эскиздерін көруіңіз керек.

3-жобадағы екі Wi-Fi режимі

Бұл жобадағы ең маңызды тұжырымдама – ESP32 екі түрлі режимде жұмыс істей алады:

  1. Станция (STA) режимі – ESP32 сіздің бар үй Wi-Fi желіңізге қосылады.
  2. Қолжетімділік нүктесі (AP) режимі – үй Wi-Fi қолжетімді болмаса, ESP32 өз Wi-Fi желісін жасайды.

Екі режим де бірдей веб-интерфейсті пайдаланады: ESP32-дің өзінен берілетін HTML парақшасы, онда мәтінді, түсті, айналдыру бағытын және жылдамдықты өзгертуге болады.:contentReference[oaicite:3]{index=3}

1-режим – Үй Wi-Fi желісіне қосылу (Станция режимі)

Станция режимінде ESP32 үй немесе кеңсе Wi-Fi желісіне қосылады. Маршрутизатор қолжетімді болғанда бұл қалаулы режим, себебі:

  • Сіздің телефон мен компьютер сол Wi-Fi желісіне қосылған.
  • Браузерді ESP32 IP мекенжайына бағыттап, сол желідегі кез келген құрылғыдан мәтінді басқара аласыз.

Эскиздің параметрлер бөлімінде Wi-Fi SSID және құпия сөзді бересіз:

// Үй Wi-Fi тіркелгі деректері (Станция режимі) const char* WIFI_SSID = "YourHomeWiFi"; const char* WIFI_PASS = "YourHomePassword";

Плата жүктелгеннен кейін, ол WIFI_SSID желісіне қосылуға тырысады. Сәтті болса, код тағайындалған IP мекенжайын Serial Monitor-ге басып шығарады, мысалы:


Connected to WiFi
IP address: 192.168.1.16

Мәтінді басқару үшін:

  1. Телефоныңыз немесе компьютеріңіз бірдей Wi-Fi желісіне қосылғанын тексеріңіз (мысалы, YourHomeWiFi).
  2. Браузерді ашып, басып шығарылған IP мекенжайын енгізіңіз, мысалы http://192.168.1.16/.
  3. Басқару беті пайда болады, онда мәтін теріп, түс таңдап, бағытты таңдап, айналдыру жылдамдығын реттей аласыз.

Режим 2 – Тәуелсіз қолжетімділік нүктесі (AP режимі)

ESP32 үй Wi-Fi желісіне қосыла алмаса (қате құпия сөз, желі қолжетімсіз немесе модульді сыртта қолдансаңыз), скетч автоматты түрде Access Point режиміне ауысады. AP режимінде ESP32 өзі Wi-Fi хотспотына айналады, өз желі атауы мен құпия сөзімен.

Бұл жобада AP параметрлері тұрақты:


// Access Point (AP) тіркелгі деректері (қосалқы режим)
const char* AP_SSID = "ESP32";
const char* AP_PASS = "password";

Станция режимі сәтсіз болғанда, модуль AP режиміне ауысып, ESP32 деп аталатын Wi-Fi желісін тарата бастайды. Матрицаны басқару үшін:

  1. Телефоныңызда немесе компьютеріңізде Wi-Fi параметрлерін ашып, ESP32 желісіне қосылыңыз.
  2. password құпия сөзін енгізіңіз (кодта анықталғандай).
  3. Қосылғаннан кейін браузерді ашып, http://192.168.4.1/ мекенжайына өтіңіз (ESP32 AP режимінің әдепкі IP-і).
  4. Бірдей басқару беті пайда болады, мәтін, түс, жылдамдық және бағытты өзгерте аласыз.

Бұл қосалқы мінез-құлық жобаны кез келген жерде пайдалы етеді: үйде, зертханада немесе маршрутизатор жоқ демо ортада.

Жоба 3 – Кодтағы негізгі параметрлер

Толық HTTP Text скетчі осы мақаланың астында веб-сайт арқылы жүктеледі. Мұнда біз тек өзгертуіңіз ықтимал маңызды конфигурация опцияларын құжаттаймыз.

Wi-Fi және Access Point параметрлері

Скетчтің жоғарғы жағында Wi-Fi конфигурация бөлімін табасыз. Тек Станция (үй Wi-Fi) тіркелгі деректерін өзгертіңіз; AP параметрлері әдетте әдепкі күйінде қалады:


// ---------- Wi-Fi ПАРАМЕТРЛЕРІ ----------

// Үй Wi-Fi (Станция режимі)
const char* WIFI_SSID = "YourHomeWiFi";      // маршрутизатор SSID-ін осында қойыңыз
const char* WIFI_PASS = "YourHomePassword";  // маршрутизатор құпия сөзін осында қойыңыз

// Қосалқы Access Point (AP режимі)
const char* AP_SSID = "ESP32";               // тұрақты AP атауы
const char* AP_PASS = "password";            // тұрақты AP құпия сөзі

Мінез-құлық:

  • Егер WIFI_SSID және WIFI_PASS дұрыс болса және желі қолжетімді болса → ESP32 қалыпты Wi-Fi құрылғысы ретінде қосылады (Станция режимі).
  • Егер қосылу уақыт аралығынан кейін сәтсіз болса → ESP32 AP_SSID және AP_PASS арқылы өз AP-ін бастайды.

Матрица пині, өлшемі және жарықтығы

Бұл параметрлер алдыңғы жобалармен бірдей:


// Матрица конфигурациясы
const int MATRIX_PIN    = 14;   // RGB матрица деректер пині
const int MATRIX_WIDTH  = 8;
const int MATRIX_HEIGHT = 8;

// Жалпы дисплей жарықтығы (0–255)
uint8_t matrixBrightness = 40;  // ортаңызға қарай реттеңіз

MATRIX_PIN мәнін осы плата үшін 14 деп сақтаңыз. Көбірек жарық қажет болса, matrixBrightness мәнін арттыра аласыз, бірақ төмен мәндер жақыннан қарауға ыңғайлы.

Әдепкі мәтін және айналдыру параметрлері

Плата іске қосылғанда, веб-бетті ашып жаңа мәтін тергенше, ол бастапқы хабарламаны көрсетеді. Әдепкі мәтінді конфигурацияда өзгерте аласыз:


// Іске қосу кезінде көрсетілетін әдепкі хабарлама
String currentText = "Robojax";   // кейін веб-интерфейстен қайта жазылады

Қалған айналдыру мінез-құлқы веб-интерфейс арқылы жаңартылатын айнымалылар жиынтығымен басқарылады:


// Айналдыру кідірісі миллисекундпен (төмен = жылдам)
int scrollDelayMs = 50;

// Айналдыру бағыты: 0=солға, 1=оңға, 2=жоғары, 3=төмен
int scrollDirection = 0;   // әдепкі: солға айналдыру

Веб-бет слайдер мен батырма таңдауларына негізделген жаңа мәндерді жібереді. Arduino жағынан тек мынаны білу керек:

  • scrollDelayMs мәнін азайту мәтінді жылдам қозғалтады.
  • scrollDelayMs мәнін арттыру оны баяу қозғалтады.
  • scrollDirection өзгерту солға, оңға, жоғары немесе төмен айналдыру режимдері арасында ауыстырады.

Мәтін түсі (Веб-беттен басқарылады)

Мәтін түсі үш 0–255 мәнімен (қызыл, жасыл, көк) басқарылады. Олар веб-бетте жаңа түс таңдаған сайын жаңартылады:


// Ағымдағы мәтін түсі (R, G, B)
uint8_t textRed   = 255;
uint8_t textGreen = 255;
uint8_t textBlue  = 255;

Браузерде түс таңдап, Apply түймесін басқанда, ESP32 RGB мәндерін талдап, осы үш айнымалыны жаңартады; мәтін матрицада бірден түсін өзгертеді. Бейнеде бұл мінез-құлық бірнеше түс өзгерістерімен көрсетілген, соның ішінде қызыл, жасыл және көк мысалдар.

Қорытынды

Жоба 3 ESP32-S3 RGB LED матрицаңызды веб-браузері бар кез келген құрылғыдан басқара алатын толық сымсыз мәтін дисплейіне айналдырады. Скетч сенімді болу үшін жасалған:

  • Ол алдымен сіз конфигурациялаған SSID және құпия сөзді пайдаланып үй Wi-Fi желісіне қосылуға тырысады.
  • Егер бұл сәтсіз болса, ол автоматты түрде ESP32 атауымен және password құпия сөзімен қолжетімділік нүктесіне айналады.
  • Екі режимде де браузерде дұрыс IP мекенжайын ашу мәтін, түс, бағыт және жылдамдық үшін бірдей басқару бетін көрсетеді.

Толық HTTP мәтін коды осы мақаланың төменінде қолжетімді (веб-сайтта автоматты түрде жүктеледі). Қадамдық толық түсіндірме және мәтіннің нақты уақытта қалай жаңартылатынын тірі демонстрациясы үшін бейненің 3-жоба бөлігін көруді ұмытпаңыз. Егер жобаны өзіңіз құрғыңыз келсе, кодтың астында көрсетілген серіктестік сілтемелер арқылы ESP32-S3 RGB LED матрица модулін де сатып ала аласыз.

Суреттер

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
Тіл: 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();
  }
}

Сізге қажет болуы мүмкін нәрселер

Ресурстар мен сілтемелер

Файлдар📁

Fritzing файлы