#include #include #include #include #if __has_include("arduino_secrets.h") #include "arduino_secrets.h" #define HAS_WIFI_SECRETS 1 #else #define HAS_WIFI_SECRETS 0 #endif constexpr char AP_SSID[] = "Arduino-Matrix"; constexpr char AP_PASSWORD[] = "matrix123"; constexpr uint8_t MAX_MESSAGE_LENGTH = 64; constexpr uint16_t DEFAULT_SCROLL_SPEED_MS = 75; constexpr uint16_t REPEAT_PAUSE_MS = 900; // Font_5x7 uses six horizontal pixels per character including spacing. The // extra frames let the text enter from and leave through the matrix edges. constexpr size_t ANIMATION_FRAMES = MAX_MESSAGE_LENGTH * 6 + 24; TEXT_ANIMATION_DEFINE(scrollAnimation, ANIMATION_FRAMES) ArduinoLEDMatrix matrix; WiFiServer server(80); String message = "Arduino message board er klar"; uint16_t scrollSpeedMs = DEFAULT_SCROLL_SPEED_MS; bool accessPointMode = false; bool waitingToRestart = false; unsigned long restartAt = 0; volatile bool animationComplete = false; void onAnimationComplete() { // This callback runs in an interrupt, so it must remain very short. animationComplete = true; } String ipToString(const IPAddress &ip) { return String(ip[0]) + "." + String(ip[1]) + "." + String(ip[2]) + "." + String(ip[3]); } String urlDecode(const String &encoded) { String decoded; decoded.reserve(encoded.length()); for (unsigned int i = 0; i < encoded.length(); ++i) { const char current = encoded[i]; if (current == '+') { decoded += ' '; } else if (current == '%' && i + 2 < encoded.length()) { const char hex[] = {encoded[i + 1], encoded[i + 2], '\0'}; char *end = nullptr; const long value = strtol(hex, &end, 16); if (end == hex + 2) { decoded += static_cast(value); i += 2; } else { decoded += current; } } else { decoded += current; } } return decoded; } String toMatrixText(const String &input) { String output; output.reserve(MAX_MESSAGE_LENGTH); for (unsigned int i = 0; i < input.length() && output.length() < MAX_MESSAGE_LENGTH; ++i) { const uint8_t current = static_cast(input[i]); if (current == 0xC3 && i + 1 < input.length()) { const uint8_t next = static_cast(input[++i]); const char *replacement = nullptr; switch (next) { case 0xA6: replacement = "ae"; break; // ae case 0xB8: replacement = "oe"; break; // oe case 0xA5: replacement = "aa"; break; // aa case 0x86: replacement = "AE"; break; case 0x98: replacement = "OE"; break; case 0x85: replacement = "AA"; break; } if (replacement != nullptr) { for (uint8_t j = 0; replacement[j] != '\0' && output.length() < MAX_MESSAGE_LENGTH; ++j) { output += replacement[j]; } } else { output += '?'; } } else if (current >= 32 && current <= 126) { output += static_cast(current); } else { output += '?'; } } output.trim(); return output; } String htmlEscape(String value) { value.replace("&", "&"); value.replace("<", "<"); value.replace(">", ">"); value.replace("\"", """); value.replace("'", "'"); return value; } String queryValue(const String &query, const String &name) { const String prefix = name + "="; int start = 0; while (start < static_cast(query.length())) { int end = query.indexOf('&', start); if (end < 0) { end = query.length(); } const String part = query.substring(start, end); if (part.startsWith(prefix)) { return urlDecode(part.substring(prefix.length())); } start = end + 1; } return ""; } void startAnimation() { matrix.clear(); animationComplete = false; waitingToRestart = false; matrix.beginDraw(); matrix.stroke(0xFFFFFFFF); matrix.textFont(Font_5x7); matrix.textScrollSpeed(scrollSpeedMs); matrix.beginText(12, 1, 0xFFFFFF); matrix.print(message); matrix.endTextAnimation(SCROLL_LEFT, scrollAnimation); matrix.loadTextAnimationSequence(scrollAnimation); matrix.play(); } void sendRedirect(WiFiClient &client) { client.println(F("HTTP/1.1 303 See Other")); client.println(F("Location: /")); client.println(F("Connection: close")); client.println(); } void sendPage(WiFiClient &client) { const String escapedMessage = htmlEscape(message); const String ip = ipToString(WiFi.localIP()); client.println(F("HTTP/1.1 200 OK")); client.println(F("Content-Type: text/html; charset=utf-8")); client.println(F("Cache-Control: no-store")); client.println(F("Connection: close")); client.println(); client.println(F("")); client.println(F("")); client.println(F("Arduino Matrix")); client.println(F("

LED Matrix Message Board

")); client.println(F("

Skriv en besked, og send den direkte til Arduinoens 12×8 LED-matrix.

")); client.println(F("
")); client.print(F("")); client.println(F("
HurtigRolig
")); client.print(F("")); client.println(F("
")); client.print(F("

Netværk: ")); client.print(accessPointMode ? AP_SSID : WiFi.SSID()); client.print(F("
Adresse: http://")); client.print(ip); client.println(F("

")); } void handleClient() { WiFiClient client = server.available(); if (!client) { return; } client.setTimeout(750); String requestLine = client.readStringUntil('\n'); requestLine.trim(); while (client.connected()) { String headerLine = client.readStringUntil('\n'); headerLine.trim(); if (headerLine.length() == 0) { break; } } if (requestLine.startsWith("GET /set?")) { const int targetEnd = requestLine.indexOf(' ', 4); const int queryStart = requestLine.indexOf('?'); if (targetEnd > queryStart) { const String query = requestLine.substring(queryStart + 1, targetEnd); const String requestedMessage = toMatrixText(queryValue(query, "message")); const long requestedSpeed = queryValue(query, "speed").toInt(); if (requestedMessage.length() > 0) { message = requestedMessage; } if (requestedSpeed >= 35 && requestedSpeed <= 180) { scrollSpeedMs = requestedSpeed; } startAnimation(); } sendRedirect(client); } else { sendPage(client); } delay(1); client.stop(); } bool connectToConfiguredWiFi() { #if HAS_WIFI_SECRETS Serial.print(F("Forbinder til ")); Serial.println(SECRET_SSID); WiFi.begin(SECRET_SSID, SECRET_PASS); const unsigned long deadline = millis() + 15000; while (WiFi.status() != WL_CONNECTED && static_cast(millis() - deadline) < 0) { delay(250); Serial.print('.'); } Serial.println(); return WiFi.status() == WL_CONNECTED; #else return false; #endif } bool startAccessPoint() { accessPointMode = true; Serial.print(F("Opretter access point: ")); Serial.println(AP_SSID); return WiFi.beginAP(AP_SSID, AP_PASSWORD) == WL_AP_LISTENING; } void setup() { Serial.begin(115200); const unsigned long serialDeadline = millis() + 2500; while (!Serial && static_cast(millis() - serialDeadline) < 0) { } matrix.begin(); matrix.setCallback(onAnimationComplete); startAnimation(); if (WiFi.status() == WL_NO_MODULE) { Serial.println(F("Wi-Fi-modulet svarer ikke.")); while (true) { delay(1000); } } if (!connectToConfiguredWiFi()) { WiFi.disconnect(); delay(500); if (!startAccessPoint()) { Serial.println(F("Kunne ikke starte access point.")); while (true) { delay(1000); } } } server.begin(); Serial.println(F("Message board er klar.")); Serial.print(F("Aabn http://")); Serial.println(ipToString(WiFi.localIP())); if (accessPointMode) { Serial.print(F("Wi-Fi-kode: ")); Serial.println(AP_PASSWORD); } } void loop() { handleClient(); if (animationComplete) { noInterrupts(); animationComplete = false; interrupts(); waitingToRestart = true; restartAt = millis() + REPEAT_PAUSE_MS; } if (waitingToRestart && static_cast(millis() - restartAt) >= 0) { waitingToRestart = false; matrix.play(); } }