commit 8bc7a09830d5fb358217f63321c3c65018a8faab Author: Hairless Date: Fri Jul 10 13:56:58 2026 +0000 Upload files to "/" diff --git a/gemini-code-1783691801580.cpp b/gemini-code-1783691801580.cpp new file mode 100644 index 0000000..3490693 --- /dev/null +++ b/gemini-code-1783691801580.cpp @@ -0,0 +1,293 @@ +#include +#include +#include +#include +#include +#include +#include + +// --- WIFI & API CONFIG --- +const char* ssid = "Ocelot"; +const char* password = "YOUR_PASSWORD"; +const char* apiKey = "d0128438a6bf2336df0e3e1faefcb556"; +const char* city = "Leduc,CA"; + +// --- 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]); +} \ No newline at end of file