Search Code

የESP32-S3 RGB LED ማትሪክስ ፕሮጀክት 3 - ከሞባይል ስልክ የሚመጣ ጽሑፍ

የESP32-S3 RGB LED ማትሪክስ ፕሮጀክት 3 - ከሞባይል ስልክ የሚመጣ ጽሑፍ

ፕሮጀክት 3 – ጽሑፍን ከስልክዎ ይቆጣጠሩ (ኤችቲቲፒ ጽሑፍ)

በዚህ ፕሮጀክት ውስጥ ESP32-S3 RGB LED ማትሪክስ ትንሽ የድር ገጽ ያስተናግዳል ስለዚህ የሚንቀሳቀሰውን ጽሑፍ፣ ቀለም፣ አቅጣጫ እና ፍጥነት በቀጥታ ከስልክዎ ወይም ከኮምፒውተርዎ መቀየር ይችላሉ። የተለየ መተግበሪያ አያስፈልግዎትም – የድር አሳሽ ብቻ ያስፈልጋል። ይህ ሞጁሉን በእውነተኛ ጊዜ ማዘመን የሚችሉት ትንሽ የWi-Fi ጽሑፍ ማሳያ ያደርገዋል።

በዚህ ተከታታይ ውስጥ ያሉት ስድስቱም ፕሮጀክቶች በአንድ የYouTube ቪዲዮ ተብራርተው ተቀርፀዋል። ተመሳሳዩ ቪዲዮ በዚህ ገጽ ላይ ተካቷል፣ ስለዚህ የድር በይነገጹ እንዴት እንደሚመስል እና ጽሑፍ በማትሪክስ ላይ ወዲያውኑ እንዴት እንደሚዘመን በትክክል ማየት ይችላሉ። ለዚህ ፕሮጀክት ሙሉ የምንጭ ኮድ ከጽሑፉ በታች በራስ-ሰር ተጭኗል፣ እና የESP32-S3 RGB LED ማትሪክስ ሞጁሉን በኮድ ክፍሉ ስር ከተዘረዘሩት የተባባሪ መደብሮች መግዛት ይችላሉ።

በዚህ ጽሑፍ ውስጥ የአውታረ መረብ አመክንዮ እንዴት እንደሚሰራ (የቤት Wi-Fi ከመዳረሻ ነጥብ ጋር) እና ባህሪውን ለማበጀት በኮዱ ውስጥ የትኞቹን ቅንብሮች መቀየር እንደሚችሉ ላይ እናተኩራለን።

የESP32-S3 RGB LED ማትሪክስ ሞጁል አጠቃላይ እይታ

ሃርድዌሩ በዚህ ተከታታይ ውስጥ ካሉት ሌሎች ፕሮጀክቶች ሁሉ ጋር ተመሳሳይ ነው፡ ከተሰራ ውስጡ 8×8 RGB LED ማትሪክስ እና በጀርባው ላይ QMI8658C የእንቅስቃሴ ዳሳሽ ያለው የESP32-S3 ማይክሮ መቆጣጠሪያ ሰሌዳ። የUSB-C ወደብ ለኃይል እና ለፕሮግራም አወጣጥ ጥቅም ላይ ይውላል፣ እና በጠርዙ ዙሪያ ያሉ ፒኖች ለሌላ IO አሁንም ይገኛሉ።:contentReference[oaicite:0]{index=0}

  • ESP32-S3 – Wi-Fi እና ብሉቱዝ አቅም ያለው ማይክሮ መቆጣጠሪያ።
  • 8×8 RGB ማትሪክስ – ለጽሑፍ እና ለግራፊክስ 64 አድራሻ ሊሰጣቸው የሚችሉ RGB LEDs።
  • 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:59ፕሮጀክት 3፡ ኤችቲቲፒ ጽሑፍ (ይህ ፕሮጀክት)
  • 16:41 – ፕሮጀክት 4፡ የማዘንበል ነጥብ
  • 18:55 – ፕሮጀክት 5፡ ወደ ላይ ቀስት
  • 20:02 – ፕሮጀክት 6፡ ዒላማ ጨዋታ

ከዚህ ጽሑፍ ጋር በመስራት ጊዜ በቪዲዮው ውስጥ ያለውን የኤችቲቲፒ ጽሑፍ ክፍል እንዲመለከቱ ይበረታታሉ። ቪዲዮው የድር ገጹ በESP32 እንዴት እንደሚፈጠር እና ጽሑፍን፣ ቀለምን እና ፍጥነትን መቀየር በLED ማትሪክስ ላይ ወዲያውኑ እንዴት እንደሚንጸባረቅ ያሳያል።:contentReference[oaicite:2]{index=2}

በArduino IDE ውስጥ የESP32 ሰሌዳዎችን መጫን

ፕሮጀክት 1 ወይም 2 አስቀድመው ካጠናቀቁ፣ የሰሌዳ ማዋቀሩ ተጠናቋል እና ይህን ክፍል መዝለል ይችላሉ። አለበለዚያ፣ በArduino IDE ውስጥ እነዚህን ደረጃዎች ይከተሉ፡

  1. ፋይል > ምርጫዎች ን ይክፈቱ እና የESP32 ሰሌዳዎችን URL ወደ “ተጨማሪ የሰሌዳ አስተዳዳሪ URLs” ያክሉ።
  2. ወደ መሳሪያዎች > ሰሌዳ > የሰሌዳ አስተዳዳሪ… ይሂዱ፣ ESP32 ን ይፈልጉ እና ኦፊሴላዊውን የESP32 ጥቅል ይጫኑ።
  3. ትክክለኛውን የESP32-S3 RGB ማትሪክስ ሰሌዳ ከመሳሪያዎች > ሰሌዳ ይምረጡ።
  4. ሞጁሉን በUSB ያገናኙ እና በመሳሪያዎች > ወደብ ስር ትክክለኛውን ተከታታይ ወደብ ይምረጡ።

ትክክለኛ የESP32 ሰሌዳ ድጋፍ እና ትክክለኛ ወደብ ከሌለ፣ የድር አገልጋይ ስኬቹ አይሰቀልም።

NeoMatrix እና አስፈላጊ ቤተ-መጻሕፍትን መጫን

ይህ ፕሮጀክት ከቀዳሚው የጽሑፍ ማንሸራተት ፕሮጀክት ጋር ተመሳሳይ ቤተ-መጻሕፍትን ይጠቀማል፡

  • Adafruit NeoMatrix
  • Adafruit NeoPixel
  • Adafruit GFX Library

በቤተ-መጻሕፍት አስተዳዳሪ በኩል ይጫኑ፡

  1. ስኬች > ቤተ-መጻሕፍትን አካትት > ቤተ-መጻሕፍትን ያስተዳድሩ… ን ይክፈቱ።
  2. Adafruit NeoMatrix ን ይፈልጉ እና ጫን ን ጠቅ ያድርጉ።
  3. የጥገኞችን መጫን ይቀበሉ (Adafruit GFX እና Adafruit NeoPixel)።

አንዴ ከተጫኑ በኋላ፣ በፋይል > ምሳሌዎች ስር የNeoMatrix እና NeoPixel ምሳሌ ስኬቾችን ማየት ይችላሉ።

በፕሮጀክት 3 ውስጥ ሁለት የWi-Fi ሁነታዎች

በዚህ ፕሮጀክት ውስጥ በጣም አስፈላጊው ጽንሰ-ሐሳብ ESP32 በሁለት የተለያዩ ሁነታዎች መስራት ይችላል የሚለው ነው፡

  1. ስቴሽን (STA) ሁነታ – ESP32 ከቤትዎ ካለው የWi-Fi አውታረ መረብ ጋር ይገናኛል።
  2. የመዳረሻ ነጥብ (AP) ሁነታ – የቤት Wi-Fi ከሌለ ESP32 የራሱን የWi-Fi አውታረ መረብ ይፈጥራል።

ሁለቱም ሁነታዎች ተመሳሳይ የድር በይነገጽ ይጠቀማሉ፡ ከESP32 ራሱ የሚቀርብ የኤችቲኤምኤል ገጽ፣ ጽሑፉን፣ ቀለሙን፣ የማንሸራተት አቅጣጫውን እና ፍጥነቱን መቀየር የሚችሉበት።:contentReference[oaicite:3]{index=3}

ሁነታ 1 – ከቤት Wi-Fi ጋር መገናኘት (ስቴሽን ሁነታ)

በስቴሽን ሁነታ፣ ESP32 ከቤትዎ ወይም ከቢሮዎ የWi-Fi አውታረ መረብ ጋር ይቀላቀላል። ራውተርዎ በሚገኝበት ጊዜ ይህ ተመራጭ ሁነታ ነው ምክንያቱም፡

  • ስልክዎ እና ኮምፒውተርዎ ቀድሞውኑ ከተመሳሳይ የWi-Fi አውታረ መረብ ጋር የተገናኙ ናቸው።
  • አሳሽዎን ወደ ESP32 የIP አድራሻ እየጠቆሙ በዚያ አውታረ መረብ ላይ ካሉ ማንኛውም መሣሪያ ጽሑፉን መቆጣጠር ይችላሉ።

በስኬቹ ቅንብሮች ክፍል ውስጥ፣ የWi-Fi SSID እና የይለፍ ቃልዎን ያቀርባሉ፡

// የቤት ዋይ-ፋይ ማረጋገጫዎች (Station ሁነታ) 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. ስልክዎ ወይም ኮምፒውተርዎ ከተመሳሳይ ዋይ-ፋይ አውታረ መረብ ጋር መገናኘቱን ያረጋግጡ (ለምሳሌ፣ YourHomeWiFi)።
  2. አሳሽ ይክፈቱ እና የታተመውን IP አድራሻ ያስገቡ፣ ለምሳሌ http://192.168.1.16/
  3. የመቆጣጠሪያ ገጹ ይታያል፣ ጽሑፍ ለመተየብ፣ ቀለም ለመምረጥ፣ አቅጣጫ ለመምረጥ እና የማሸብለል ፍጥነትን ለማስተካከል ያስችልዎታል።

ሁነታ 2 – ገለልተኛ መዳረሻ ነጥብ (AP ሁነታ)

ESP32 ከቤትዎ ዋይ-ፋይ ጋር መገናኘት ካልቻለ (የተሳሳተ የይለፍ ቃል፣ አውታረ መረብ ካልተገኘ፣ ወይም ሞጁሉን ከቤት ውጭ እየተጠቀሙ ከሆነ)፣ ስኬቹ በራስ-ሰር ወደ መዳረሻ ነጥብ ሁነታ ይመለሳል። በAP ሁነታ፣ ESP32 ራሱ የራሱ የአውታረ መረብ ስም እና የይለፍ ቃል ያለው የዋይ-ፋይ መገናኛ ነጥብ ይሆናል።

በዚህ ፕሮጀክት ውስጥ፣ የAP ቅንብሮች እንደሚከተለው ተስተካክለዋል፦


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

የStation ሁነታ ሲወድቅ፣ ሞጁሉ ወደ AP ሁነታ ይቀየራል እና ESP32 የሚባል የዋይ-ፋይ አውታረ መረብ ማሰራጨት ይጀምራል። ማትሪክሱን ለመቆጣጠር፦

  1. በስልክዎ ወይም በኮምፒውተርዎ ላይ፣ የዋይ-ፋይ ቅንብሮችን ይክፈቱ እና ከESP32 አውታረ መረብ ጋር ይገናኙ።
  2. የይለፍ ቃሉን password ያስገቡ (በኮዱ ውስጥ እንደተገለጸው)።
  3. ከተገናኙ በኋላ፣ አሳሽ ይክፈቱ እና ወደ http://192.168.4.1/ ይሂዱ (ለESP32 AP ሁነታ ነባሪ IP)።
  4. ተመሳሳይ የመቆጣጠሪያ ገጽ ይታያል፣ ጽሑፍን፣ ቀለምን፣ ፍጥነትን እና አቅጣጫን ለመቀየር ያስችልዎታል።

ይህ የመጠባበቂያ ባህሪ ፕሮጀክቱን በማንኛውም ቦታ ጠቃሚ ያደርገዋል፦ በቤት፣ በላብራቶሪ፣ ወይም ራውተር በሌለበት የማሳያ አካባቢ።

ፕሮጀክት 3 – በኮዱ ውስጥ ዋና ቅንብሮች

ሙሉው የHTTP ጽሑፍ ስኬች ከዚህ ጽሑፍ በታች በድረ-ገጹ ተጭኗል። እዚህ ሊያርትዑት የሚችሉትን በጣም አስፈላጊ የውቅር አማራጮችን ብቻ እንመዘግባለን።

የዋይ-ፋይ እና የመዳረሻ ነጥብ ቅንብሮች

በስኬቹ አናት ላይ የዋይ-ፋይ ውቅር ክፍል ያገኛሉ። የStation (የቤት ዋይ-ፋይ) ማረጋገጫዎችን ብቻ ይቀይሩ፤ የ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 እንደ መደበኛ የዋይ-ፋይ መሣሪያ ይገናኛል (Station ሁነታ)።
  • ግንኙነቱ ከተወሰነ ጊዜ በኋላ ከተሳካ → ESP32 የራሱን AP በ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 ን መቀየር በግራ፣ በቀኝ፣ ወደ ላይ፣ ወይም ወደ ታች በማሸብለል ሁነታዎች መካከል ይቀይራል።

የጽሑፍ ቀለም (ከድረ-ገጹ ቁጥጥር ይደረግበታል)

የጽሑፉ ቀለም በሦስት 0–255 እሴቶች (ቀይ፣ አረንጓዴ፣ ሰማያዊ) ቁጥጥር ይደረግበታል። በድረ-ገጹ ላይ አዲስ ቀለም በሚመርጡ ቁጥር ይዘመናሉ፦


// Current text color (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 Matrix ሞጁሉን መግዛት ይችላሉ።

ምስሎች

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

እንደሚያስፈልግዎት ይችላል

ምንጮች እና ምንጮች

ፋይሎች📁

ፍሪትዚንግ ፋይል