شِفر (کود) جستجو

پروژه ماتریس LED RGB ESP32-S3 3 - متن از تلفن همراه

پروژه ماتریس LED RGB ESP32-S3 3 - متن از تلفن همراه

پروژه ۳ - متن کنترل ماتریس از تلفن شما (متن HTTP)

در این پروژه ماتریس LED RGB ESP32-S3 یک صفحه وب کوچک را میزبانی می‌کند تا بتوانید متن متحرک، رنگ، جهت و سرعت را مستقیماً از تلفن یا کامپیوتر خود تغییر دهید. نیازی به یک برنامه جداگانه نیست - فقط یک مرورگر وب کافی است. این ویژگی ماجیول را به یک نمایشگر متنی کوچک Wi-Fi تبدیل می‌کند که می‌توانید آن را به‌صورت آنی به‌روزرسانی کنید.

تمام شش پروژه در این سری در یک ویدیوی یوتیوب توضیح داده شده و به نمایش درآمده‌اند. همان ویدیو در این صفحه گنجانده شده است، بنابراین می‌توانید دقیقاً ببینید که رابط وب چگونه به نظر می‌رسد و چگونه متن به‌طور آنی در ماتریس به‌روزرسانی می‌شود. شِفر (کود) منبع کامل این پروژه به‌طور خودکار در زیر مقاله بارگذاری شده و می‌توانید ماجیول ماتریس LED RGB ESP32-S3 را از فروشگاه‌های وابسته که در بخش شِفر (کود) ذکر شده خریداری کنید.

در این مقاله ما بر روی چگونگی عملکرد منطق شبکه (وای‌فای خانگی در مقابل نقطه دسترسی) و تنظیماتی که می‌توانید در شِفر (کود) تغییر دهید تا رفتار را شخصی‌سازی کنید، تمرکز می‌کنیم.

مرور کلی ماجیول ماتریس LED RGB ESP32-S3

سخت‌افزار همانند دیگر پروژه‌های این سری است: یک برد میکروکنترلر ESP32-S3 با یک ماتریس LED RGB 8×8 داخلی و یک حساس(حس کننده) حرکت QMI8658C در پشت. درگاه USB-C برای تأمین انرژی و برنامه‌ریزی استفاده می‌شود و پایه‌های اطراف لبه‌ها هنوز برای ورودی/خروجی‌های دیگر در دسترس هستند.:contentReference[oaicite:0]{index=0}

  • ESP32-S3میکروکنترلر با قابلیت وای‌فای و بلوتوث.
  • ماتریس RGB هشت در هشت- ۶۴ دیود نوری RGB قابل آدرس‌دهی برای متن و گرافیک.
  • شتاب‌سنج QMI8658C- در پروژه‌های تیلت و بازی استفاده شده است.
  • پورت USBبرد را تغذیه می‌کند و طرح‌ها را از Arduino IDE بارگذاری می‌کند.
  • پایه‌های نمایاناگر لازم است، حسگرها یا عملگرهای اضافی را اجازه دهید.
  • کلیدهای راه‌اندازی/بازنشانی- برای بارگذاری نرم‌افزار سیستم عامل و راه‌اندازی مجدد.

برای پروژه ۳، مهم‌ترین ویژگی Wi-Fi ESP32 است که به بُرد اجازه می‌دهد به‌عنوان یک سرور وب کوچک برای صفحه کنترل متن عمل کند.:contentReference[oaicite:1]{index=1}

پروژه‌های پوشش داده شده در ویدئو (زمان‌های تعیین شده)

این ویدیوی تک برای این مجموعه تمام شش پروژه را پوشش می‌دهد. برای ارجاع سریع:

  • ۰۰:۰۰- مقدمه
  • ۰۲:۰۱- نصب بردهای ESP32
  • 03:32- نصب کتابخانه‌ها
  • 05:32- پروژه ۱: نقطه متحرک
  • ۱۱:۱۱- پروژه ۲: حرکت متن
  • ۱۲:۵۹-پروژه ۳: متن HTTP (این پروژه)
  • ۱۶:۴۱پروژه ۴: نقطه مایل
  • ۱۸:۵۵پروژه ۵: فلش به بالا
  • ۲۰:۰۲- پروژه 6: بازی هدف

شما تشویق می‌شوید که بخش متن HTTP را در ویدئو در حین کار با این مقاله تماشا کنید. ویدئو نشان می‌دهد که چگونه صفحه وب توسط ESP32 ایجاد می‌شود و چگونه تغییر متن، رنگ و سرعت بلافاصله بر روی ماتریس LED منعکس می‌شود.:contentReference[oaicite:2]{index=2}

نصب بردهای ESP32 در Arduino IDE

اگر قبلاً پروژه‌های ۱ یا ۲ را کامل کرده‌اید، تنظیمات برد انجام شده و می‌توانید این بخش را بگذرانید. در غیر این صورت، مراحل زیر را در IDE آردوینو دنبال کنید:

  1. باز کنFile > Preferencesو آدرس تخته‌ های ESP32 را به "آدرس‌های اضافی مدیریت تخته" اضافه کنید.
  2. برو بهTools > Board > Boards Manager…جستجو برایESP32, و بسته نرم‌افزاری رسمی ESP32 را نصب کنید.
  3. برد RGB Matrix صحیح ESP32-S3 را انتخاب کنید ازTools > Board.
  4. ماجیول را از طریق USB متصل کرده و پورت سریال صحیح را انتخاب کنید زیرTools > Port.

بدون پشتیبانی مناسب از برد ESP32 و پورت صحیح، اسکیچ وب‌سرور بارگذاری نخواهد شد.

نصب NeoMatrix و کتابخانه‌های مورد نیاز

این پروژه از همان کتابخانه‌ها به عنوان پروژه اسکرول متن قبلی استفاده می‌کند:

  • Adafruit NeoMatrix
  • Adafruit NeoPixel
  • Adafruit GFX Library

از طریق مدیر کتابخانه نصب کنید:

  1. باز کنیدSketch > Include Library > Manage Libraries….
  2. جستجو برایAdafruit NeoMatrixو کلیک کنیدنصب کنید.
  3. پذیرش نصب وابستگی‌ها (Adafruit GFXوAdafruit NeoPixel).

پس از نصب، باید نمونه‌ طرح‌های NeoMatrix و NeoPixel را در زیر مشاهده کنید.File > Examples.

دو حالت Wi-Fi در پروژه ۳

مفهوم مهم‌ترین در این پروژه این است که ESP32 می‌تواند دردو حالت مختلف:

  1. حالت ایستگاه (STA)- ESP32 به شبکه Wi-Fi خانگی موجود شما متصل می‌شود.
  2. حالت نقطه دسترسی (AP)ESP32 در صورت عدم دسترسی به شبکه Wi-Fi خانگی، شبکه Wi-Fi مخصوص به خود را ایجاد می‌کند.

هر دو حالت از یک رابط وب مشابه استفاده می‌کنند: یک صفحه HTML که از خود ESP32 ارائه می‌شود، جایی که می‌توانید متن، رنگ، جهت و سرعت اسکرول را تغییر دهید.:contentReference[oaicite:3]{index=3}

حالت ۱ - اتصال به وای‌فای خانگی (حالت ایستگاه)

در حالت ایستگاه، ESP32 به شبکه Wi-Fi خانه یا دفتر شما متصل می‌شود. این حالت زمانی که روتر شما در دسترس است، حالت ترجیحی است زیرا:

  • تلفن و کامپیوتر شما در حال حاضر به یک شبکه Wi-Fi مشابه متصل هستند.
  • شما می‌توانید مرورگر خود را به آدرس IP ESP32 متصل کنید و متن را از هر دستگاهی در آن شبکه کنترل کنید.

در بخش تنظیمات طرح، SSID و رمز عبور Wi-Fi خود را ارائه می‌دهید:


// Home Wi-Fi credentials (Station mode)
const char* WIFI_SSID = "YourHomeWiFi";
const char* WIFI_PASS = "YourHomePassword";

پس از روشن شدن برد، سعی می‌کند به هم متصل شودWIFI_SSIDاگر موفق باشد، شِفر (کود) آدرس IP تخصیص داده شده را بر روی نمایشگر مسلسل چاپ می‌کند، به عنوان مثال:


Connected to WiFi
IP address: 192.168.1.16

برای کنترل متن:

  1. اطمینان حاصل کنید که تلفن یا رایانه شما به همان شبکه Wi-Fi متصل است (برای مثال،YourHomeWiFi).
  2. یک مرورگر باز کنید و آدرس IP چاپ شده را وارد کنید، مانندhttp://192.168.1.16/. :contentReference[oaicite:4]{index=4}
  3. صفحه کنترل ظاهر خواهد شد و به شما این امکان را می‌دهد که متن را تایپ کنید، رنگ را انتخاب کنید، جهت را مشخص کنید و سرعت اسکرول را تنظیم کنید.

حالت ۲ - نقطه دسترسی مستقل (حالت AP)

اگر ESP32 نتواند به Wi-Fi خانگی شما متصل شود (رمز عبور اشتباه، شبکه در دسترس نیست، یا شما ماجیول را در خارج از خانه استفاده می‌کنید)، شِفر (کود) به طور خودکار به حالت Access Point بازمی‌گردد. در حالت AP، خود ESP32 به یک نقطه داغ Wi-Fi با نام شبکه و رمز عبور خود تبدیل می‌شود.

در این پروژه، تنظیمات AP به صورت ثابت تعیین شده‌اند:


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

وقتی حالت ایستگاه ناموفق است، ماجیول به حالت AP تغییر حالت می‌دهد و شروع به پخش یک شبکه Wi-Fi به نام می‌کند.ESP32برای کنترل ماتریس:

  1. در تلفن یا کامپیوتر خود، تنظیمات Wi-Fi را باز کرده و به شبکه متصل شوید.ESP32.
  2. رمز عبور را وارد کنیدگذرواژه(طبق تعریفی که در شِفر (کود) آمده است).
  3. پس از اتصال، یک مرورگر را باز کنید و بهhttp://192.168.4.1/(آدرس IP پیش فرض برای حالت AP ESP32).
  4. همان صفحه کنترل ظاهر می‌شود و به شما این امکان را می‌دهد که متن، رنگ، سرعت و جهت را تغییر دهید.

این رفتار پشتیبان باعث می‌شود پروژه در هر جایی مفید باشد: در خانه، در آزمایشگاه یا در یک محیط نمایش که هیچ روتر در دسترس نیست.

پروژه ۳ - تنظیمات اصلی در شِفر (کود)

نقشه کامل متن HTTP در زیر این مقاله توسط وب‌سایت بارگذاری شده است. در اینجا تنها گزینه‌های پیکربندی مهمی را که احتمالاً ویرایش خواهید کرد، مستند می‌کنیم.

تنظیمات Wi-Fi و نقطه دسترسی

در بالای طرح، بخش پیکربندی Wi-Fi را خواهید یافت. تنها مشخصات ایستگاه (Wi-Fi خانگی) را تغییر دهید؛ تنظیمات AP معمولاً به عنوان پیش‌فرض نگه‌داشته می‌شوند:


// ---------- Wi-Fi SETTINGS ----------

// Home Wi-Fi (Station mode)
const char* WIFI_SSID = "YourHomeWiFi";      // put your router SSID here
const char* WIFI_PASS = "YourHomePassword";  // put your router password here

// Fallback Access Point (AP mode)
const char* AP_SSID = "ESP32";               // fixed AP name
const char* AP_PASS = "password";            // fixed AP password

رفتار:

  • اگرWIFI_SSIDوWIFI_PASSدرست هستند و شبکه موجود است → ESP32 به عنوان یک دستگاه Wi-Fi عادی (حالت ایستگاه) متصل می‌شود.
  • اگر اتصال بعد از یک زمان انتظار ناموفق باشد → ESP32 نقطه دسترسی خود را با استفاده از آغاز می‌کندAP_SSIDوAP_PASS.

پایه ماتریس، اندازه و روشنایی

این تنظیمات مشابه پروژه‌های قبلی هستند:


// Matrix configuration
const int MATRIX_PIN    = 14;   // RGB matrix data pin
const int MATRIX_WIDTH  = 8;
const int MATRIX_HEIGHT = 8;

// Overall display brightness (0–255)
uint8_t matrixBrightness = 40;  // adjust for your environment

نگه‌داریدMATRIX_PINدر14برای این تخته. شما می‌توانید افزایش دهیدmatrixBrightnessاگر به نور بیشتری نیاز دارید، اما مقدارهای پایین‌تر برای تماشای نزدیک‌تر راحت‌تر هستند.

تنظیمات متنی پیش‌فرض و تنظیمات پیمایش

هنگامی که تابلو شروع به کار می‌کند، یک پیام اولیه نمایش داده می‌شود تا زمانی که شما صفحه وب را باز کنید و متن جدیدی تایپ کنید. شما می‌توانید متن پیش‌فرض را در تنظیمات تغییر دهید:


// Default message shown at startup
String currentText = "Robojax";   // overwrite from web UI later

بقیه رفتار اسکرول توسط مجموعه‌ای از متغیرها کنترل می‌شود که توسط رابط وب به‌روزرسانی می‌شوند:


// Scroll delay in milliseconds (lower = faster)
int scrollDelayMs = 50;

// Scroll direction: 0=left, 1=right, 2=up, 3=down
int scrollDirection = 0;   // default: scroll left

صفحه وب بر اساس انتخاب‌های اسلایدر و دکمه مقادیر جدیدی ارسال می‌کند. از سمت آردوینو، فقط باید بدانید که:

  • کاهشیscrollDelayMsمتن را سریع‌تر حرکت می‌دهد.
  • افزایشscrollDelayMsاین باعث می‌شود که حرکت آن کندتر شود.
  • تغییر دادنscrollDirectionبین حالت‌های پیمایش چپ، راست، بالا یا پایین جابه‌جا می‌شود.

رنگ متن (کنترل شده از صفحه وب)

رنگ متن توسط سه مقدار ۰-۲۵۵ (قرمز، سبز، آبی) کنترل می‌شود. این مقادیر هر بار که رنگ جدیدی را در وب‌سایت انتخاب کنید، به‌روز می‌شوند:


// Current text color (R, G, B)
uint8_t textRed   = 255;
uint8_t textGreen = 255;
uint8_t textBlue  = 255;

زمانی که شما یک رنگ را در مرورگر انتخاب کرده و روی "اعمال" کلیک می‌کنید، ESP32 مقادیر RGB را تجزیه کرده و این سه متغیر را به‌روز می‌کند؛ متن بلافاصله رنگش در ماتریس تغییر می‌کند. در ویدیو، این رفتار با تغییر رنگ‌های مختلف، از جمله مثال‌های قرمز، سبز و آبی نشان داده می‌شود.:contentReference[oaicite:5]{index=5}

خلاصه

پروژه ۳ ماتریس LED RGB ESP32-S3 شما را به یک نمایشگر متنی کاملاً بی‌سیم تبدیل می‌کند که می‌توانید با استفاده از هر دستگاهی که دارای مرورگر وب است آن را کنترل کنید. طرح به گونه‌ای طراحی شده است که انعطاف‌پذیر باشد:

  • ابتدا سعی می‌کند به شبکه Wi-Fi خانگی شما متصل شود با استفاده از SSID و رمز عبوری که تنظیم کرده‌اید.
  • اگر این کار نکرد، به‌طور خودکار به یک نقطه دسترسی با نام تبدیل می‌شودESP32و رمز عبورpassword.
  • در هر دو حالت، باز کردن آدرس IP صحیح در یک مرورگر همان صفحه کنترل برای متن، رنگ، جهت و سرعت را نمایش می‌دهد.

شِفر (کود) کامل HTTP Text در زیر این مقاله موجود است (به طور خودکار در وب‌سایت بارگذاری می‌شود). برای مشاهده یک راهنمای دقیق مرحله به مرحله و یک نمایش زنده از نحوه به‌روزرسانی متن در زمان واقعی، حتماً بخش پروژه 3 از ویدیو را تماشا کنید. اگر می‌خواهید پروژه را خودتان بسازید، می‌توانید ماجیول ماتریس LED RGB ESP32-S3 را نیز با استفاده از واصل‌های وابسته‌ای که در زیر شِفر (کود) ذکر شده‌اند، خریداری کنید.

تصاویر

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++
/*
 * پروژه ۴: اسکرول متن HTTP - ماتریس LED RGB ESP32-S3 (Waveshare)
 * 
 * - به شبکه WiFi خانگی شما متصل می‌شود (حالت ایستگاهی، با پشتیبانی از AP).
 * - صفحه وبی را ارائه می‌دهد که در آن می‌توانید تنظیم کنید:
 * متن
 * رنگ
 * نمایش روشن/خاموش
 * جهت اسکرول (چپ / راست / بالا / پایین)
 * تاخیر اسکرول (سرعت)
 * - از ماتریس LED RGB ۸×۸ با استفاده از Adafruit_NeoMatrix پشتیبانی می‌کند.
 * 
 * ▶️ آموزش ویدئویی:
 * https://youtu.be/JKLuYrRcLMI
 * 
 * 📄 منابع و صفحه شِفر (کود):
 * 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
 // رمز عبور WiFi خانگی (این را به مشخصات روتر خود تغییر دهید)
const char* WIFI_SSID = "Biseem";
const char* WIFI_PASS = "wan9&Jang~";

 // نقطه دسترسی پشتیبان
const char* AP_SSID = "ESP32";
const char* AP_PASS = "password";

 // ======================= تنظیمات ماتریس =======================

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

 // 0، 1، 2 یا 3 - اگر جهت متن نادرست است، با USB بالا تنظیم کنید
#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
);

 // ======================= وضعیت اسکرول ==========================

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

String   scrollText  = "Robojax";
uint16_t textColor   = 0xFFFF; // سفید پیش‌فرض
bool     displayOn   = true;
ScrollDir currentDir = DIR_LEFT;

 // 1 قدم هر این تعداد میلی ثانیه (کمتر = سریعتر)
unsigned long scrollInterval = 80;
unsigned long lastScrollTime = 0;

 // موقعیت افقی اسکرول
int scrollX = MATRIX_WIDTH;
int scrollY = 0; // ردیف بالایی

 // وضعیت برای اسکرول عمودی به ازای هر حرف
int vertCharIndex = 0; // کدام خصیصه از رشته
int vertY         = MATRIX_HEIGHT; // موقعیت عمودی آن خصیصه

 // ======================= سرور وب ============================

WebServer server(80);

 // صفحه HTML

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';
      });
  }

 // فعال پیش‌فرض را تنظیم کنید
  setDir('left');
</script>
</body>
</html>
)rawliteral";

 // ======================= کمک کنندگان ===============================

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) {
 // "#RRGGBB" یا "RRGGBB" را انتظار داشته باشید
  String c = hex;
  if (c.startsWith("#")) c.remove(0, 1);
  if (c.length() != 6) {
 // سفید پیش‌فرض
    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; // فونت پیش‌فرض ~6 px برای هر خصیصه
  int textHeight = 8; // فونت 5x7 در 8 جا می‌گیرد

  switch (currentDir) {
    case DIR_LEFT:
 // از درست خارج از لبه راست شروع کنید
      scrollX = MATRIX_WIDTH;
      scrollY = 0;
      break;

    case DIR_RIGHT:
 // از درست خارج لبه چپ شروع کنید
      scrollX = -textWidth;
      scrollY = 0;
      break;

    case DIR_UP:
 // اسکرول عمودی به ازای هر حرف، که با اولین خصیصه زیر ماتریس شروع می‌شود
      vertCharIndex = 0;
      vertY = MATRIX_HEIGHT; // ۸ → از پایین وارد می‌شود
      break;

    case DIR_DOWN:
 // لقطه عمودی بر اساس هر حرف، با شروع از اولین خصیصه بالای ماتریس
      vertCharIndex = 0;
      vertY = -textHeight; // -8 → از بالا وارد می‌شود
      break;
  }
}

 // ======================= هندلرهای HTTP =========================

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();

 // گیره ایمنی ساده
    if (val < 10)   val = 10; // بسیار سریع
    if (val > 1000) val = 1000; // حداکثر 1 ثانیه

    scrollInterval = (unsigned long)val;
 // انتظار پیمایش تنظیم شده به 
 // Serial.print(scrollInterval);
 // Serial.println(" ms");
  }

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

 // ======================= منطق اسکرول ==========================

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);

 // -------- اسکرول افقی (چپ / راست) --------
  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 { // راست
      scrollX++;
      if (scrollX > MATRIX_WIDTH) {
        scrollX = -textWidth;
      }
    }
    return;
  }

 // -------- اسکرول عمودی (بالا / پایین) - به ازای هر حرف --------

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

 // شخصیت کنونی
  char c = scrollText[vertCharIndex];

  int charWidth  = 6;
  int baseX = (MATRIX_WIDTH - charWidth) / 2; // به صورت افقی وسط قرار دهید

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

  if (currentDir == DIR_UP) {
 // شخصیت را به سمت بالا حرکت دهید
    vertY--;
    if (vertY < -textHeight) {
 // این شخصیت به طور کامل گذرانده شده است، به مرحله بعدی بروید
      vertY = MATRIX_HEIGHT;
      vertCharIndex++;
      if (vertCharIndex >= scrollText.length()) {
        vertCharIndex = 0; // به نخستین نویسه برگردید
      }
    }
  } else if (currentDir == DIR_DOWN) {
 // خصیصه را به سمت پایین حرکت دهید
    vertY++;
    if (vertY > MATRIX_HEIGHT) {
 // این شخصیت به طور کامل گذرانده شده است، به مرحله بعدی بروید
      vertY = -textHeight;
      vertCharIndex++;
      if (vertCharIndex >= scrollText.length()) {
        vertCharIndex = 0; // برگشت به حلقه
      }
    }
  }
}

 // ======================= راه‌اندازی WIFI =============================

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; // ۱۰ ثانیه

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

 // ======================= تنظیمات و حلقه ==========================

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

 // ماتریس راه‌اندازی
  matrix.begin();
  matrix.setRotation(MATRIX_ROTATION);
  matrix.setBrightness(BRIGHTNESS);
  matrix.fillScreen(0);
  matrix.show();

 // وای‌فای
  connectWiFi();

 // مسیرهای وب سرور
  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();
  }
}

مواردی که ممکن است به آن‌ها نیاز داشته باشید

منابع و مراجع

فایل‌ها📁

فایل فریزینگ