کوډ ولټوئ

د ESP32-S3 RGB LED میټرکس پروژه ۳ - د ګرځنده تلیفون څخه متن

د ESP32-S3 RGB LED میټرکس پروژه ۳ - د ګرځنده تلیفون څخه متن

پروژه ۳ – د کنټرول میټرکس متن ستاسو له تلیفون څخه (HTTP متن)

په دې پروژه کې ESP32-S3 RGB LED میټرکس یوه کوچنۍ ویب پاڼه لري ترڅو تاسو کولی شئ د سکرول متن، رنګ، لوري او سرعت په مستقیم ډول له خپل تلیفون یا کمپیوټر څخه بدل کړئ. تاسو جلا اپلیکیشن ته اړتیا نلرئ – یوازې ویب براوزر. دا ماډل یو کوچنی وای-فای متن ښکارندوی جوړوي چې تاسو کولی شئ په ریښتیني وخت کې تازه کړئ.

د دې لړۍ ټول شپږ پروژې په یوه یوټیوب ویډیو کې تشریح او نندارې ته وړاندې شوي دي. همدا ویډیو په دې پاڼه کې ځای پر ځای شوې ده، نو تاسو کولی شئ په سمه توګه وګورئ چې ویب انٹرفیس څنګه ښکاري او څنګه متن په میټرکس کې سمدستي تازه کیږي. د دې پروژې بشپړ سرچینه کوډ د مقالې لاندې په اتوماتيک ډول بار شوی دی، او تاسو کولی شئ د ESP32-S3 RGB LED میټرکس ماډل د کوډ برخې لاندې لیست شوي اړوند پلورنځیو څخه واخلئ.

په دې مقاله کې موږ په دې تمرکز کوو چې د شبکې منطق څنګه کار کوي (کورنی وای-فای د لاسرسي نقطې په مقابل کې) او کوم تنظیمات تاسو کولی شئ په کوډ کې بدل کړئ ترڅو چلند ځانګړی کړئ.

د ESP32-S3 RGB LED میټرکس ماډل عمومي کتنه

هارډویر د دې لړۍ د نورو ټولو پروژو په څیر دی: د ESP32-S3 مایکروکنټرولر بورډ د جوړ شوي ۸×۸ RGB LED میټرکس او په شا اړخ کې د QMI8658C حرکت سینسر سره. د USB-C بندر د بریښنا او پروګرام کولو لپاره کارول کیږي، او د څنډو شاوخوا پنونه لاهم د نورو IO لپاره شتون لري.

  • ESP32-S3 – د وای-فای او بلوتوث وړ مایکروکنټرولر.
  • ۸×۸ RGB میټرکس – د متن او ګرافیک لپاره ۶۴ د پتې وړ RGB LEDs.
  • QMI8658C ایکسیلرومیټر – د ټیلټ او لوبو پروژو کې کارول کیږي.
  • USB بندر – بورډ ته بریښنا ورکوي او له Arduino IDE څخه سکیچونه پورته کوي.
  • ښکاره پنونه – که اړتیا وي اضافي سینسرونو یا فعالوونکو ته اجازه ورکوي.
  • بوټ/ری سیٹ تڼۍ – د فرم ویئر پورته کولو او بیا پیل لپاره.

د پروژې ۳ لپاره، تر ټولو مهمه ځانګړتیا د ESP32 وای-فای ده، کوم چې بورډ ته اجازه ورکوي چې د متن کنټرول پاڼې لپاره د یوې کوچنۍ ویب سرور په توګه عمل وکړي.

په ویډیو کې پوښل شوي پروژې (وختونه)

د دې لړۍ لپاره یوه ویډیو ټول شپږ پروژې پوښي. د چټک حوالې لپاره:

  • 00:00 – پیژندنه
  • 02:01 – د ESP32 بورډونو نصبول
  • 03:32 – د کتابتونونو نصبول
  • 05:32 – پروژه ۱: حرکت کوونکی نقطه
  • 11:11 – پروژه ۲: د متن سکرول
  • 12:59پروژه ۳: HTTP متن (دا پروژه)
  • 16:41 – پروژه ۴: ټیلټ نقطه
  • 18:55 – پروژه ۵: پورته تیر
  • 20:02 – پروژه ۶: هدف لوبه

تاسو هڅول کیږئ چې د دې مقالې سره کار کولو پرمهال په ویډیو کې د HTTP متن برخه وګورئ. ویډیو ښیې چې ویب پاڼه څنګه د ESP32 لخوا جوړیږي او څنګه د متن، رنګ او سرعت بدلول په LED میټرکس کې سمدستي ښکاري.

په Arduino IDE کې د ESP32 بورډونو نصبول

که تاسو دمخه پروژې ۱ یا ۲ بشپړې کړې وي، د بورډ تنظیم ترسره شوی دی او تاسو کولی شئ دا برخه پریږدئ. که نه، په Arduino IDE کې دا ګامونه تعقیب کړئ:

  1. File > Preferences خلاص کړئ او د ESP32 بورډونو URL د "Additional Boards Manager URLs" ته اضافه کړئ.
  2. Tools > Board > Boards Manager… ته لاړ شئ، د ESP32 لټون وکړئ، او رسمي ESP32 بسته نصب کړئ.
  3. د Tools > Board څخه سم ESP32-S3 RGB میټرکس بورډ غوره کړئ.
  4. ماډل د USB له لارې وصل کړئ او د Tools > Port لاندې سم سیریل بندر غوره کړئ.

د سم ESP32 بورډ ملاتړ او سم بندر پرته، د ویب سرور سکیچ به پورته نشي.

د NeoMatrix او اړینو کتابتونونو نصبول

دا پروژه د تیرې متن سکرول پروژې په څیر ورته کتابتونونه کاروي:

  • Adafruit NeoMatrix
  • Adafruit NeoPixel
  • Adafruit GFX Library

د کتابتون مدیر له لارې نصب کړئ:

  1. Sketch > Include Library > Manage Libraries… خلاص کړئ.
  2. د Adafruit NeoMatrix لټون وکړئ او Install کلیک وکړئ.
  3. د انحصارونو نصبول ومنئ (Adafruit GFX او Adafruit NeoPixel).

یوځل چې نصب شي، تاسو باید د File > Examples لاندې د NeoMatrix او NeoPixel مثال سکیچونه وګورئ.

په پروژه ۳ کې دوه وای-فای حالتونه

په دې پروژه کې تر ټولو مهم مفهوم دا دی چې ESP32 کولی شي په دوه مختلفو حالتونو کې کار وکړي:

  1. سټیشن (STA) حالت – ESP32 ستاسو موجوده کورني وای-فای شبکې سره نښلوي.
  2. د لاسرسي نقطه (AP) حالت – ESP32 خپله وای-فای شبکه جوړوي که کورنی وای-فای شتون ونلري.

دواړه حالتونه ورته ویب انٹرفیس کاروي: یوه HTML پاڼه چې د ESP32 څخه پخپله خدمت کیږي، چیرې چې تاسو کولی شئ متن، رنګ، د سکرول لوري او سرعت بدل کړئ.

حالت ۱ – کورني وای-فای سره نښلول (سټیشن حالت)

په سټیشن حالت کې، ESP32 ستاسو کورني یا دفتر وای-فای شبکې سره یوځای کیږي. دا غوره حالت دی هرکله چې ستاسو روټر شتون ولري ځکه:

  • ستاسو تلیفون او کمپیوټر دمخه ورته وای-فای شبکې سره وصل دي.
  • تاسو کولی شئ خپل براوزر د ESP32 IP پتې ته وښایئ او د متن کنټرول له هرې وسیلې څخه په هغه شبکه کې ترسره کړئ.

د سکیچ په تنظیماتو برخه کې، تاسو خپل وای-فای SSID او پاسورډ چمتو کوئ:

// د کور وای فای سندونه (د سټیشن حالت) const char* WIFI_SSID = "YourHomeWiFi"; const char* WIFI_PASS = "YourHomePassword";

له هغې وروسته چې بورډ پیل شي، دا هڅه کوي چې له WIFI_SSID سره وصل شي. که بریالی شي، کوډ ټاکل شوی IP پته په سیریل مانیټور کې چاپوي، د بېلګې په توګه:


Connected to WiFi
IP address: 192.168.1.16

د متن کنټرول لپاره:

  1. ډاډ ترلاسه کړئ چې ستاسو تلیفون یا کمپیوټر له همدغه وای فای شبکې سره وصل دی (د بېلګې په توګه، YourHomeWiFi).
  2. یو براوزر خلاص کړئ او چاپ شوې IP پته دننه کړئ، لکه http://192.168.1.16/.
  3. د کنټرول پاڼه به ښکاره شي، چې تاسو ته اجازه درکوي متن ولیکئ، رنګ غوره کړئ، لوري وټاکئ، او د سکرول سرعت تنظیم کړئ.

حالت 2 – خپلواک لاسرسي ټکی (AP حالت)

که ESP32 ستاسو کورني وای فای سره وصل نشي (غلط پاسورډ، شبکه شتون نلري، یا تاسو ماډل بهر کاروئ)، سکیچ په اتوماتيک ډول د لاسرسي ټکي حالت ته ځي. په AP حالت کې، ESP32 پخپله د وای فای هوټ سپاټ کیږي چې خپل شبکه نوم او پاسورډ لري.

په دې پروژه کې، د AP تنظیمات په دې ډول ټاکل شوي دي:


// د لاسرسي ټکي (AP) سندونه (د بیک اپ حالت)
const char* AP_SSID = "ESP32";
const char* AP_PASS = "password";

کله چې د سټیشن حالت ناکام شي، ماډل د AP حالت ته ځي او د وای فای شبکې خپرول پیل کوي چې ESP32 نومیږي. د میټرکس کنټرول لپاره:

  1. په خپل تلیفون یا کمپیوټر کې، د وای فای تنظیمات خلاص کړئ او له ESP32 شبکې سره وصل شئ.
  2. پاسورډ password دننه کړئ (لکه څنګه چې په کوډ کې ټاکل شوی).
  3. یوځل چې وصل شئ، یو براوزر خلاص کړئ او http://192.168.4.1/ ته لاړ شئ (د ESP32 AP حالت لپاره ډیفالټ IP).
  4. همدغه کنټرول پاڼه ښکاره کیږي، چې تاسو ته اجازه درکوي متن، رنګ، سرعت، او لوري بدل کړئ.

دا بیک اپ چلند پروژه په هر ځای کې ګټوره کوي: په کور کې، په لابراتوار کې، یا په ډیمو چاپیریال کې چې هیڅ روټر شتون نلري.

پروژه 3 – په کوډ کې اصلي تنظیمات

د HTTP متن بشپړ سکیچ د دې مقالې لاندې د ویب پاڼې لخوا بار شوی دی. دلته موږ یوازې هغه مهم ترتیب اختیارونه مستند کوو چې تاسو یې احتمالا سم کوئ.

وای فای او د لاسرسي ټکي تنظیمات

د سکیچ په پورتنۍ برخه کې به تاسو د وای فای ترتیب برخه ومومئ. یوازې د سټیشن (کورني وای فای) سندونه بدل کړئ؛ د AP تنظیمات معمولا د ډیفالټ په توګه ساتل کیږي:


// ---------- د وای فای تنظیمات ----------

// کورنی وای فای (د سټیشن حالت)
const char* WIFI_SSID = "YourHomeWiFi";      // دلته خپل روټر SSID واچوئ
const char* WIFI_PASS = "YourHomePassword";  // دلته خپل روټر پاسورډ واچوئ

// د بیک اپ لاسرسي ټکی (AP حالت)
const char* AP_SSID = "ESP32";               // ټاکل شوی AP نوم
const char* AP_PASS = "password";            // ټاکل شوی AP پاسورډ

چلند:

  • که WIFI_SSID او WIFI_PASS سم وي او شبکه شتون ولري → ESP32 د عادي وای فای وسیلې په توګه وصل کیږي (د سټیشن حالت).
  • که اړیکه د وخت پای ته رسیدو وروسته ناکامه شي → ESP32 خپل AP د AP_SSID او AP_PASS په کارولو سره پیل کوي.

د میټرکس پن، اندازه، او روښانتیا

دا تنظیمات د تیرو پروژو په څیر دي:


// د میټرکس ترتیب
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";   // وروسته د ویب UI څخه بیا لیکل کیږي

د سکرول پاتې چلند د متغیرونو یوې سیټ لخوا کنټرول کیږي چې د ویب انٹرفیس لخوا تازه کیږي:


// د سکرول ځنډ په ملی ثانیو کې (ټیټ = ګړندی)
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 او پاسورډ په کارولو سره وصل شي چې تاسو تنظیم کړی دی.
  • که چیرې دا ناکامه شي، دا په اوتومات ډول د 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 فایل