#include #include #include #include #include #include #include // --- WIFI & API CONFIG --- const char* ssid = "SSID"; const char* password = "YOUR_PASSWORD"; const char* apiKey = "Weather_API_Key"; const char* city = "Locale"; // --- HARDWARE CONFIG --- #define HARDWARE_TYPE MD_MAX72XX::FC16_HW #define MAX_DEVICES 4 #define DATA_PIN 13 #define CS_PIN 14 #define CLK_PIN 15 MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CS_PIN, CLK_PIN, MAX_DEVICES); WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "pool.ntp.org", 0, 60000); WebServer server(80); // --- STATE VARIABLES --- bool displayPower = true; bool weatherMode = false; bool autoNightEnabled = true; bool manualNightOverride = false; bool isDST = true; int dayIntensity = 8; int nightIntensity = 0; int nightStartHour = 22; int nightEndHour = 7; String weatherText = "LOADING..."; unsigned long lastWeatherUpdate = 0; unsigned long lastClockUpdate = 0; unsigned long lastWeatherScroll = 0; const unsigned long weatherInterval = 600000; int prevDigits[4] = {-1, -1, -1, -1}; bool prevIsColon = false; // --- BITMAP DATA --- byte digits[10][8] = { {0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00}, {0x08, 0x18, 0x28, 0x08, 0x08, 0x08, 0x3E, 0x00}, {0x3C, 0x42, 0x02, 0x1C, 0x20, 0x40, 0x7E, 0x00}, {0x3C, 0x42, 0x02, 0x1C, 0x02, 0x42, 0x3C, 0x00}, {0x22, 0x42, 0x42, 0x7E, 0x02, 0x02, 0x02, 0x00}, {0x7E, 0x40, 0x40, 0x7C, 0x02, 0x42, 0x3C, 0x00}, {0x3C, 0x40, 0x40, 0x7C, 0x42, 0x42, 0x3C, 0x00}, {0x7E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00}, {0x3C, 0x42, 0x42, 0x3C, 0x42, 0x42, 0x3C, 0x00}, {0x3C, 0x42, 0x42, 0x3E, 0x02, 0x02, 0x3C, 0x00} }; byte colon[8] = {0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00}; // --- WEB INTERFACE --- const char index_html[] PROGMEM = R"rawliteral( Matrix Control

Matrix Control


)rawliteral"; // --- FUNCTION PROTOTYPES --- void updateClock(); void updateWeather(); void scrollText(const char *pMsg); void displayDigit(int dev, int digit); void displayColon(int dev); void setup() { Serial.begin(115200); mx.begin(); mx.control(MD_MAX72XX::INTENSITY, dayIntensity); mx.clear(); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); } timeClient.begin(); server.on("/", []() { server.send(200, "text/html", index_html); }); server.on("/state", []() { String json = "{"; json += "\"power\":" + String(displayPower ? "true" : "false") + ","; json += "\"weather\":" + String(weatherMode ? "true" : "false") + ","; json += "\"dst\":" + String(isDST ? "true" : "false") + ","; json += "\"night\":" + String(manualNightOverride ? "true" : "false") + ","; json += "\"brightness\":" + String(dayIntensity); json += "}"; server.send(200, "application/json", json); }); server.on("/power", []() { displayPower = !displayPower; mx.control(MD_MAX72XX::SHUTDOWN, !displayPower); if(displayPower) { for(int i=0; i<4; i++) prevDigits[i] = -1; prevIsColon = false; } server.send(200, "text/plain", displayPower ? "ON" : "OFF"); }); server.on("/brightness", []() { if (server.hasArg("value")) { dayIntensity = server.arg("value").toInt(); if (displayPower && !manualNightOverride) mx.control(MD_MAX72XX::INTENSITY, dayIntensity); } server.send(200, "text/plain", "OK"); }); server.on("/toggleWeather", []() { weatherMode = !weatherMode; server.send(200, "text/plain", weatherMode ? "ON" : "OFF"); }); server.on("/dst", []() { isDST = !isDST; for(int i=0; i<4; i++) prevDigits[i] = -1; prevIsColon = false; server.send(200, "text/plain", isDST ? "ON" : "OFF"); }); server.on("/manual-night", []() { manualNightOverride = !manualNightOverride; server.send(200, "text/plain", manualNightOverride ? "ON" : "OFF"); }); server.on("/message", []() { if (server.hasArg("text") && displayPower) { scrollText(server.arg("text").c_str()); mx.clear(); for(int i=0; i<4; i++) prevDigits[i] = -1; prevIsColon = false; lastClockUpdate = 0; } server.send(200, "text/plain", "Sent"); }); server.begin(); updateWeather(); } void loop() { server.handleClient(); if (!displayPower) return; if (millis() - lastWeatherUpdate > weatherInterval) { updateWeather(); } if (weatherMode && (millis() - lastWeatherScroll > 60000)) { lastWeatherScroll = millis(); scrollText(weatherText.c_str()); mx.clear(); for(int i=0; i<4; i++) prevDigits[i] = -1; prevIsColon = false; lastClockUpdate = 0; } if (millis() - lastClockUpdate >= 1000) { lastClockUpdate = millis(); updateClock(); } } void updateWeather() { if (WiFi.status() == WL_CONNECTED) { HTTPClient http; String url = "http://api.openweathermap.org/data/2.5/weather?q=" + String(city) + "&appid=" + String(apiKey) + "&units=metric"; http.begin(url); int httpCode = http.GET(); if (httpCode == 200) { String payload = http.getString(); StaticJsonDocument<512> doc; deserializeJson(doc, payload); float temp = doc["main"]["temp"]; const char* desc = doc["weather"][0]["description"]; weatherText = String(temp, 1) + "C - " + String(desc); weatherText.toUpperCase(); } http.end(); } lastWeatherUpdate = millis(); } void updateClock() { timeClient.update(); int currentOffset = -25200 + (isDST ? 3600 : 0); timeClient.setTimeOffset(currentOffset); int h24 = timeClient.getHours(); int mins = timeClient.getMinutes(); bool shouldBeDim = manualNightOverride || (autoNightEnabled && (h24 >= nightStartHour || h24 < nightEndHour)); mx.control(MD_MAX72XX::INTENSITY, shouldBeDim ? nightIntensity : dayIntensity); int h12 = h24; if (h12 == 0) h12 = 12; else if (h12 > 12) h12 -= 12; int currentDigits[4]; bool isColon = false; if (h12 < 10) { currentDigits[0] = h12; currentDigits[1] = -1; isColon = true; } else { currentDigits[0] = h12 / 10; currentDigits[1] = h12 % 10; } currentDigits[2] = mins / 10; currentDigits[3] = mins % 10; for (int i = 0; i < 4; i++) { int dev = MAX_DEVICES - 1 - i; if (i == 1 && isColon) { if (!prevIsColon) { displayColon(dev); prevIsColon = true; } } else if (currentDigits[i] != prevDigits[i]) { displayDigit(dev, currentDigits[i]); prevDigits[i] = currentDigits[i]; } } } void scrollText(const char *pMsg) { uint8_t charBuf[8]; mx.clear(); while (*pMsg != '\0') { uint8_t charLen = mx.getChar(*pMsg++, sizeof(charBuf), charBuf); for (uint8_t i = 0; i < charLen; i++) { mx.transform(MD_MAX72XX::TSL); mx.setColumn(0, charBuf[i]); delay(35); // Reverted to 35ms server.handleClient(); } mx.transform(MD_MAX72XX::TSL); mx.setColumn(0, 0); delay(35); } for (uint8_t i = 0; i < MAX_DEVICES * 8; i++) { mx.transform(MD_MAX72XX::TSL); mx.setColumn(0, 0); delay(35); } } void displayDigit(int dev, int digit) { if (digit >= 0 && digit <= 9) { for (int row = 0; row < 8; row++) mx.setRow(dev, row, digits[digit][row]); } else { for (int row = 0; row < 8; row++) mx.setRow(dev, row, 0); } } void displayColon(int dev) { for (int row = 0; row < 8; row++) mx.setRow(dev, row, colon[row]); }