كود البحث

مشروع مصفوفة LED RGB ESP32-S3 3 - نص من الهاتف المحمول

مشروع مصفوفة LED RGB ESP32-S3 3 - نص من الهاتف المحمول

المشروع 3 - التحكم في مصفوفة النص من هاتفك (نص HTTP)

في هذا المشروع، تحتوي مصفوفة LED RGB ESP32-S3 على صفحة ويب صغيرة حتى تتمكن من تغيير النص المتحرك، واللون، والاتجاه، والسرعة مباشرة من هاتفك أو جهاز الكمبيوتر. لا تحتاج إلى تطبيق منفصل - فقط متصفح ويب. يجعل هذا الوحدة شاشة نصية صغيرة تعتمد على الواي فاي يمكنك تحديثها في الوقت الحقيقي.

جميع المشاريع الستة في هذه السلسلة مشروحة ومعروضة في فيديو واحد على يوتيوب. الفيديو نفسه مدمج في هذه الصفحة، لذا يمكنك رؤية كيفية ظهور واجهة الويب بالضبط وكيف تحدث تحديثات النصوص على المصفوفة بشكل فوري. يتم تحميل الشيفرة المصدرية الكاملة لهذا المشروع تلقائيًا أسفل المقال، ويمكنك شراء وحدة مصفوفة LED RGB ESP32-S3 من المتاجر التابعة المدرجة تحت قسم الشيفرة.

في هذه المقالة نركز على كيفية عمل منطق الشبكة (الواي فاي المنزلي مقابل نقطة الوصول) وما هي الإعدادات التي يمكنك تغييرها في الكود لتخصيص السلوك.

نظرة عامة على وحدة مصفوفة الصمامات الثنائية المتلألئة RGB ESP32-S3

الأجهزة هي نفسها كما في جميع المشاريع الأخرى في هذه السلسلة: لوحة متحكم دقيق ESP32-S3 مزودة بمصفوفة LED RGB 8×8 مدمجة ومستشعر حركة QMI8658C في الخلف. يتم استخدام منفذ USB-C للطاقة والبرمجة، والأطراف المحيطة بالحواف لا تزال متاحة لمدخلات ومخرجات أخرى.:contentReference[oaicite:0]{index=0}

  • ESP32-S3متكاملة دقيقة قادرة على الواي فاي والبلوتوث.
  • 8×8 مصفوفة RGB- 64 مصباح LED ملون قابل للعناوين للنصوص والرسوم.
  • QMI8658C مقياس التسارع- مستخدمة في مشاريع الميل واللعبة.
  • منفذ USBيعمل على تشغيل اللوحة وتحميل الرسومات من Arduino IDE.
  • دبابيس مكشوفة- السماح بإضافة مستشعرات أو محركات إضافية إذا لزم الأمر.
  • أزرار التمهيد/إعادة الضبط- لرفع البرنامج الثابت وإعادة التشغيل.

للمشروع 3، أهم ميزة هي شبكة الواي فاي في ESP32، التي تمكن اللوحة من العمل كخادم ويب صغير لصفحة التحكم النصية.:contentReference[oaicite:1]{index=1}

المشاريع التي تم تغطيتها في الفيديو (الطوابع الزمنية)

الفيديو الواحد لهذه السلسلة يغطي جميع المشاريع الستة. للرجوع السريع:

  • 00:00- مقدمة
  • 02:01- تثبيت لوحات ESP32
  • 03:32- تثبيت المكتبات
  • 05:32- المشروع 1: النقطة المتحركة
  • 11:11- المشروع 2: تمرير النص
  • ١٢:٥٩-المشروع 3: نص HTTP (هذا المشروع)
  • ١٦:٤١- المشروع 4: النقطة المائلة
  • ١٨:٥٥- المشروع 5: السهم للأعلى
  • ٢٠:٠٢- المشروع 6: لعبة الهدف

تشجع على مشاهدة قسم نص HTTP في الفيديو أثناء العمل مع هذه المقالة. يُظهر الفيديو كيف يتم توليد صفحة الويب بواسطة ESP32 وكيف يتم عكس تغيير النص، واللون، والسرعة فورياً على مصفوفة LED.:contentReference[oaicite:2]{index=2}

تثبيت لوحات ESP32 في بيئة تطوير Arduino

إذا كنت قد أكملت بالفعل المشروعين 1 أو 2، فقد تم إعداد اللوحة ويمكنك تخطي هذا القسم. خلاف ذلك، اتبع هذه الخطوات في بيئة تطوير أردوينو:

  1. فتحFile > Preferencesوإضافة عنوان URL الخاص بلوحات ESP32 إلى "عناوين مدير اللوحات الإضافية".
  2. اذهب إلىTools > Board > Boards Manager…ابحث عنESP32، وتثبيت حزمة ESP32 الرسمية.
  3. اختر اللوحة الصحيحة لمصفوفة RGB ESP32-S3 منTools > Board.
  4. قم بتوصيل الوحدة عبر USB واختر منفذ السيريال الصحيح تحتTools > Port.

بدون دعم اللوحة المناسب لـ ESP32 والم port الصحيح، لن يتم رفع برنامج خادم الويب.

تثبيت 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.

وضعان لشبكة الواي فاي في المشروع 3

أهم مفهوم في هذا المشروع هو أن ESP32 يمكن أن يعمل فينمطان مختلفان:

  1. وضع المحطة (STA)يتصل ESP32 بشبكة الواي فاي المنزلية الموجودة لديك.
  2. وضع نقطة الوصول (AP)إن ESP32 ينشئ شبكته الخاصة للواي فاي إذا لم يكن الواي فاي المنزلي متاحًا.

تستخدم كلا الوضعين نفس واجهة الويب: صفحة HTML يتم تقديمها من جهاز ESP32 نفسه، حيث يمكنك تغيير النص، اللون، اتجاه التمرير، والسرعة.:contentReference[oaicite:3]{index=3}

الوضع 1 - الاتصال بشبكة الواي فاي المنزلية (وضع المحطة)

في وضع المحطة، ينضم ESP32 إلى شبكة واي فاي المنزلية أو مكتبك. هذا هو الوضع المفضل كلما كان جهاز التوجيه الخاص بك متاحًا لأنه:

  • لقد تم توصيل هاتفك وجهاز الكمبيوتر الخاص بك بالفعل بنفس شبكة الواي فاي.
  • يمكنك توجيه متصفحك إلى عنوان IP الخاص بـ ESP32 والتحكم في النص من أي جهاز على تلك الشبكة.

في قسم الإعدادات من الرسم التخطيطي، تقدم اسم شبكة الواي فاي (SSID) وكلمة المرور الخاصة بك:


// 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. تأكد من أن هاتفك أو الكمبيوتر متصل بنفس شبكة الواي فاي (على سبيل المثال،YourHomeWiFi).
  2. افتح متصفحاً وأدخل عنوان IP المطبوعة، مثلhttp://192.168.1.16/. :contentReference[oaicite:4]{index=4}
  3. ستظهر صفحة التحكم، مما يتيح لك كتابة النص، واختيار اللون، وتحديد الاتجاه، وضبط سرعة التمرير.

الوضع 2 - نقطة وصول مستقلة (وضع نقطة الوصول)

إذا لم يتمكن جهاز ESP32 من الاتصال بشبكة Wi-Fi المنزلية الخاصة بك (كلمة مرور خاطئة، الشبكة غير متاحة، أو أنك تستخدم الوحدة في الخارج)، فإن البرنامج يقوم بالتبديل تلقائيًا إلى وضع نقطة الوصول. في وضع AP، يصبح جهاز ESP32 نفسه نقطة ساخنة لشبكة Wi-Fi باسمه وكلمة المرور الخاصة به.

في هذا المشروع، تم تثبيت إعدادات نقطة الوصول على النحو التالي:


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

عندما يفشل وضع المحطة، يتحول الوحدة إلى وضع نقطة الوصول ويبدأ في بث شبكة واي فاي تسمىESP32للتحكم في المصفوفة:

  1. على هاتفك أو الكمبيوتر الخاص بك، افتح إعدادات الواي فاي واتصل بالشبكةESP32.
  2. أدخل كلمة المروركلمة المرور(كما هو معرف في القانون).
  3. بمجرد الاتصال، افتح متصفحًا واذهب إلىhttp://192.168.4.1/(عنوان بروتوكول الإنترنت الافتراضي لوضع نقطة الوصول ESP32).
  4. تظهر نفس صفحة التحكم، مما يتيح لك تغيير النص واللون والسرعة والاتجاه.

يُتيح هذا السلوك البديل للبرنامج أن يكون مفيدًا في أي مكان: في المنزل، أو في المختبر، أو في بيئة العرض حيث لا يوجد جهاز توجيه متاح.

المشروع 3 - الإعدادات الرئيسية في الكود

تم تحميل مخطط HTTP Text الكامل أسفل هذه المقالة بواسطة الموقع. هنا نوثق فقط أهم خيارات التكوين التي من المحتمل أن تقوم بتحريرها.

إعدادات الواي فاي ونقطة الوصول

في أعلى الرسم التخطيطي ستجد قسم إعدادات الواي فاي. قم بتغيير بيانات اعتماد المحطة (واي فاي المنزل) فقط؛ عادةً ما يتم الاحتفاظ بإعدادات نقطة الوصول كإعدادات افتراضية:


// ---------- 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 كجهاز واي فاي عادي (وضع المحطة).
  • إذا فشل الاتصال بعد انتهاء المهلة → يبدأ 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

ترسل الصفحة الإلكترونية قيمًا جديدة بناءً على خيارات الشريط المتحرك والأزرار. من جانب Arduino، تحتاج فقط إلى معرفة ما يلي:

  • تخفيضscrollDelayMsيجعل النص يتحرك بشكل أسرع.
  • يزيدscrollDelayMsيجعلها تتحرك ببطء أكبر.
  • تغييرscrollDirectionيتبدل بين أوضاع التمرير لليسار أو اليمين أو إلى الأعلى أو إلى الأسفل.

لون النص (يتم التحكم فيه من صفحة الويب)

يتم التحكم في لون النص بواسطة ثلاث قيم تتراوح بين 0-255 (أحمر، أخضر، أزرق). يتم تحديثها كلما اخترت لونًا جديدًا على صفحة الويب:


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

عند اختيار لون في المتصفح والنقر على تطبيق، يقوم ESP32 بتحليل قيم RGB وتحديث هذه المتغيرات الثلاثة؛ يتغير لون النص على المصفوفة على الفور. في الفيديو، يتم توضيح هذا السلوك مع تغييرات لونية متعددة، بما في ذلك أمثلة باللون الأحمر والأخضر والأزرق.:contentReference

ملخص

يقوم المشروع 3 بتحويل مصفوفة مصابيح LED RGB ESP32-S3 الخاصة بك إلى شاشة نصية لاسلكية بالكامل يمكنك التحكم فيها باستخدام أي جهاز يحتوي على متصفح ويب. تم تصميم البرنامج ليكون قويًا:

  • تحاول أولاً الاتصال بشبكة الواي فاي المنزلية الخاصة بك باستخدام SSID وكلمة المرور التي قمت بتكوينها.
  • إذا فشل ذلك، فإنه يصبح تلقائيًا نقطة وصول بالاسمESP32وكلمة المرورpassword.
  • في كلا الوضعين، فتح عنوان IP الصحيح في متصفح يعرض نفس صفحة التحكم للنص واللون والاتجاه والسرعة.

يتوفر كود نص HTTP الكامل أدناه هذه المقالة (يتم تحميله تلقائيًا على الموقع). للحصول على شرح تفصيلي خطوة بخطوة وعرض حي يوضح كيف يتم تحديث النص في الوقت الفعلي، تأكد من مشاهدة قسم المشروع 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++
/*
 * مشروع 4: نص تمرير HTTP - مصفوفة LED RGB ESP32-S3 (Waveshare)
 * 
 * - يتصل بشبكة WiFi المنزلية الخاصة بك (وضع المحطة، مع إمكانية الانتقال إلى الوضع AP).
 * - يقدم صفحة ويب حيث يمكنك ضبط:
 * نص
 * لون
 * تشغيل/إيقاف عرض
 * اتجاه التمرير (يسار / يمين / لأعلى / لأسفل)
 * تأخير التمرير (السرعة)
 * - يدعم مصفوفة LED RGB بحجم 8×8 باستخدام 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>

 // ======================= إعدادات الواي فاي =========================
 // واي فاي المنزل (قم بتغيير هذه إلى بيانات اعتماد الموجه الخاص بك)
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 بكسل لكل حرف
  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; // 8 → يدخل من الأسفل
      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(" مللي ثانية");
  }

  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; // ارجع للخلف
      }
    }
  }
}

 // ======================= إعداد الواي فاي =============================

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; // 10 ثواني

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

الأشياء التي قد تحتاجها

الموارد والمراجع

ملفات📁

Fritzing File