#include #include #include // ════════════════════════════════════════════════════════════════ // Optimiertes FastLED.show() mit Interrupt-Schutz // ════════════════════════════════════════════════════════════════ CRGB normalColor = CRGB::White; CRGB specialColor = CRGB::Green; CRGB leds[NUM_LEDS]; uint8_t brightness = 80; char charsoap[COLS * ROWS * 2]; //Char * 2 to fit all in, cause UTF (maybe ÄÖÜ) bool customCharsoap = false; // ── Konfiguration für Spezialanzeige ── // MAXWORDS ist in defines.inc definiert // Platzhalter - werden dynamisch aus EEPROM oder Defaults geladen (siehe loadSpecialWords() in main.cpp) char SPECIAL_WORD[MAXWORDS][12] = {"", //max 11 Zeichen, \0 wird automatisch angefügt! "", "" }; //Text der ein- bzw. ausgeblendet werden soll const uint16_t SPECIAL_HOLD_MS = 5000; // Anzeigedauer des Spezialworts const int hourpattern[][2] = { {46, 7}, {102, 5} }; void showLEDs() { noInterrupts(); FastLED.show(); interrupts(); } //eleminate bridged LEDS int bridgeLED(int pos) { #ifndef BRIDGE_LEDS_POS77 return pos; #else //if(pos==77) // return NUM_LEDS; //else return (pos >= 77 ? pos-1:pos); #endif } // Sanftes Ausblenden des aktuellen Frames (komplette Matrix) // OPTIMIERT: Weniger Schritte und kürzeres Delay für bessere WiFi-Performance #define MAX_STEPS 200 void fadeOutAll(uint8_t steps = MAX_STEPS, uint16_t stepDelayMs = 15) { if(steps > MAX_STEPS) steps = MAX_STEPS; for (uint8_t i = 0; i < MAX_STEPS; i++) { fadeToBlackBy(leds, NUM_LEDS, MAX_STEPS / steps); showLEDs(); yield(); delay(stepDelayMs); } FastLED.clear(); showLEDs(); } // Sanftes Einblenden mittels globaler Helligkeit // OPTIMIERT: Weniger Schritte und kürzeres Delay für bessere WiFi-Performance void fadeInCurrentFrame(uint8_t targetBrightness, uint8_t steps = MAX_STEPS, uint16_t stepDelayMs = 15) { if(steps > MAX_STEPS) steps = MAX_STEPS; uint8_t saved = targetBrightness; FastLED.setBrightness(0); showLEDs(); // Schrittweite so wählen, dass exakt targetBrightness erreicht wird for (uint8_t s = 1; s <= steps; s++) { uint8_t b = (uint16_t)s * saved / steps; FastLED.setBrightness(b); showLEDs(); yield(); delay(stepDelayMs); } FastLED.setBrightness(saved); showLEDs(); } // Zeigt ein Wort (falls vorhanden) mit sanftem Fade-In, hält es und blendet es wieder aus // OPTIMIERT: Schnellere Animation für bessere WiFi-Performance bool showSpecialWordSequence(const char words[][12], CRGB color, uint8_t steps = 20, uint16_t stepDelayMs = 15) { int pos = findWord(words[0], 0); if (pos < 0) { // Wort nicht gefunden return false; } // Volle Matrix zunächst aus FastLED.clear(); for(uint i = 0; i < MAXWORDS; i++ ) { if(findWord(words[i], 0)) { // Wort setzen setWord(words[i], color, 0); } } // Helligkeit schonend einblenden uint8_t savedBrightness = FastLED.getBrightness(); // optional; wenn nicht verfügbar, nimm die globale 'brightness' if (savedBrightness == 0) savedBrightness = 80; // Fallback fadeInCurrentFrame(savedBrightness, steps, stepDelayMs); // Für die gewünschte Dauer halten unsigned long t0 = millis(); while (millis() - t0 < SPECIAL_HOLD_MS) { // Optional leichte "Herzschlag"-Animation vermeiden → einfach halten yield(); delay(10); } // Ausblenden fadeOutAll(steps, stepDelayMs); return true; } // Orchestriert: bisherigen Text ausblenden → Spezialwort zeigen → neue Zeit rendern (mit sanftem Einblenden) void showSpecialWordThenTime(int hours, int minutes) { // Aktuelle Helligkeit speichern (BEVOR wir sie auf 0 setzen!) uint8_t savedBrightness = FastLED.getBrightness(); if (savedBrightness == 0) savedBrightness = map(brightness, 0, 80, 0, 204); // Fallback auf globale Variable (gemappt) // 1) Bisherigen Frame ausblenden fadeOutAll(); // 2) Spezialwort-Sequenz (falls im Layout vorhanden), Farbe: specialColor showSpecialWordSequence(SPECIAL_WORD, specialColor); // 3) Bisherigen Frame ausblenden fadeOutAll(); // 4) Neue Zeit zeichnen //FastLED.clear(); FastLED.setBrightness(0); // Helligkeit vor dem internen showLEDs der displayTime auf 0 setzen displayTime(hours, minutes); // 5) Neue Zeit sanft einblenden (falls displayTime viel setzt, ist der Effekt angenehm) fadeInCurrentFrame(savedBrightness); } // ════════════════════════════════════════════════════════════════ // FUNKTIONEN: RGB-TEST // ════════════════════════════════════════════════════════════════ void rgbTest() { DEBUG_PRINTLN("\n╔════════════════════════════════╗"); DEBUG_PRINTLN("║ RGB LED TEST ROUTINE ║"); DEBUG_PRINTLN("╚════════════════════════════════╝\n"); // Test 1: Erste LED DEBUG_PRINTLN("Test 1: Erste LED (Rot)"); FastLED.clear(); leds[bridgeLED(0)] = CRGB::Red; showLEDs(); delay(1000); // Test 2: Letzte LED DEBUG_PRINTLN("Test 2: Letzte LED (Blau)"); FastLED.clear(); leds[bridgeLED(NUM_LEDS - 1)] = CRGB::Blue; showLEDs(); delay(1000); // Test 3: Alle Rot DEBUG_PRINTLN("Test 3: Alle LEDs Rot"); fill_solid(leds, NUM_LEDS, CRGB::Red); showLEDs(); delay(1000); // Test 4: Alle Grün DEBUG_PRINTLN("Test 4: Alle LEDs Grün"); fill_solid(leds, NUM_LEDS, CRGB::Green); showLEDs(); delay(1000); // Test 5: Alle Blau DEBUG_PRINTLN("Test 5: Alle LEDs Blau"); fill_solid(leds, NUM_LEDS, CRGB::Blue); showLEDs(); delay(1000); // Test 6: Alle Weiß DEBUG_PRINTLN("Test 6: Alle LEDs Weiß"); fill_solid(leds, NUM_LEDS, CRGB::White); showLEDs(); delay(1000); // Test 7: Lauflicht DEBUG_PRINTLN("Test 7: Lauflicht"); FastLED.clear(); for (int i = 0; i < NUM_LEDS; i++) { leds[bridgeLED(i)] = CRGB::Green; showLEDs(); delay(100); leds[bridgeLED(i)] = CRGB::Black; } // Test 8: Regenbogen DEBUG_PRINTLN("Test 8: Regenbogen"); for (int hue = 0; hue < 256; hue += 4) { for (int i = 0; i < NUM_LEDS; i++) { leds[bridgeLED(i)] = CHSV((hue + i * 2) % 256, 255, 255); } showLEDs(); delay(20); } // Test 9: Matrix Zeilen DEBUG_PRINTLN("Test 9: Matrix Zeilen"); for (int row = 0; row < ROWS; row++) { FastLED.clear(); for (int col = 0; col < COLS; col++) { int index; if (row % 2 == 0) { index = row * COLS + col; } else { index = row * COLS + (COLS - 1 - col); } leds[bridgeLED(index)] = CRGB::Blue; } showLEDs(); delay(300); } // Test 10: Matrix Spalten DEBUG_PRINTLN("Test 10: Matrix Spalten"); for (int col = 0; col < COLS; col++) { FastLED.clear(); for (int row = 0; row < ROWS; row++) { int index; if (row % 2 == 0) { index = row * COLS + col; } else { index = row * COLS + (COLS - 1 - col); } leds[bridgeLED(index)] = CRGB::Orange; } showLEDs(); delay(300); } //Test 11: Minuten-LEDs DEBUG_PRINTLN("Test 11: Minuten-LEDs (4 Eck-LEDs)"); // Alle 4 nacheinander CRGB minuteColors[] = {CRGB::Red, CRGB::Green, CRGB::Blue, CRGB::Yellow}; for (int i = 0; i < 4; i++) { FastLED.clear(); leds[bridgeLED(MINUTE_LEDS[i])] = minuteColors[i]; DEBUG_PRINTF(" → Minuten-LED %d (Index %d)\n", i+1, MINUTE_LEDS[i]); showLEDs(); delay(500); } FastLED.clear(); showLEDs(); DEBUG_PRINTLN("\n✓ RGB Test abgeschlossen!\n"); } void getLedsFromPosition(int startPos, int length, int* ledArray) { for (int i = 0; i < length; i++) { int pos = startPos + i; int row = pos / COLS; int col = pos % COLS; if (row % 2 == 0) { ledArray[i] = row * COLS + col; } else { ledArray[i] = row * COLS + (COLS - 1 - col); } } } int setWord(const char* word, CRGB color, int occurrence, bool searchBackward) { int pos = findWord(word, occurrence, searchBackward); if (pos == -1) { return -1; } int length = strlen(word); int ledIndices[length]; getLedsFromPosition(pos, length, ledIndices); for (int i = 0; i < length; i++) { leds[bridgeLED(ledIndices[i])] = color; } return pos; } // ════════════════════════════════════════════════════════════════ // FUNKTIONEN: LED-ANSTEUERUNG // ════════════════════════════════════════════════════════════════ int findWord(const char* word, int occurrence, bool searchBackward) { const int wordLen = strlen(word); const int soapLen = strlen(charsoap); //DEBUG_PRINTF("Search Word '%s' %s", word, searchBackward ? "(backward) " : ""); if (searchBackward) { // Rückwärtssuche: vom Ende zum Anfang int foundCount = 0; for (int pos = soapLen - wordLen; pos >= 0; --pos) { bool match = true; for (int j = 0; j < wordLen; ++j) { unsigned char c1 = charsoap[pos + j]; unsigned char c2 = word[j]; //lowercase => FuNF => funf if (c1 >= 'A' && c1 <= 'Z') c1 += ('a' - 'A'); if (c2 >= 'A' && c2 <= 'Z') c2 += ('a' - 'A'); if (c1 != c2) { match = false; break; } } if (match) { if (foundCount == occurrence) { //DEBUG_PRINTF(" found on pos %d (%d) ✓\n", pos, occurrence); return pos; } foundCount++; } } //DEBUG_PRINTF(" not found (%d) ❌\n", occurrence); return -1; } else { // Vorwärtssuche: vom Anfang zum Ende (original) int start = 0; for (int i = 0; i <= occurrence; ++i) { int found = -1; for (int pos = start; pos <= soapLen - wordLen; ++pos) { bool match = true; for (int j = 0; j < wordLen; ++j) { unsigned char c1 = charsoap[pos + j]; unsigned char c2 = word[j]; //lowercase => FuNF => funf if (c1 >= 'A' && c1 <= 'Z') c1 += ('a' - 'A'); if (c2 >= 'A' && c2 <= 'Z') c2 += ('a' - 'A'); if (c1 != c2) { match = false; break; } } if (match) { found = pos; break; } } if (found == -1) { //DEBUG_PRINTF(" not found (%d) ❌\n", occurrence); return -1; // dieses i-te Vorkommen existiert nicht } if (i == occurrence) { //DEBUG_PRINTF(" found on pos %d (%d) ✓\n", found, occurrence); return found; } start = found + 1; // ab nächster Position weiter suchen } } return -1; } uint8_t testWords() { //return 0; DEBUG_PRINT("╔════════════════════════╗\n"); DEBUG_PRINT("║ Teste Wörter ... ║\n"); DEBUG_PRINT("╚════════════════════════╝\n") DEBUG_PRINTLN(DEFAULT_CHARSOAP); if(strlen(DEFAULT_CHARSOAP) != (COLS * (ROWS-1))) { //return -1; DEBUG_PRINT("\nLänge stimmt nicht: "); DEBUG_PRINTLN(strlen(DEFAULT_CHARSOAP)); } else { DEBUG_PRINT("\nLänge stimmt: "); DEBUG_PRINTLN(strlen(DEFAULT_CHARSOAP)); } uint8_t currentHour = 12; uint8_t currentMinute = 29; //CharGraphTimeWords result; while(true) { yield(); currentMinute++; if(currentMinute == 60) { currentHour = 1; currentMinute = 0; } if(currentHour == 1 && currentMinute == 31) { DEBUG_PRINT ("\n╔════════════════════════╗\n"); DEBUG_PRINT ( "║ Teste Wörter fertig. ║\n"); DEBUG_PRINTLN( "╚════════════════════════╝\n") delay(1500); return 0; } DEBUG_PRINT("Zeit: '"); if (currentHour < 10) DEBUG_PRINT("0"); DEBUG_PRINT(currentHour); DEBUG_PRINT(":"); if (currentMinute < 10) DEBUG_PRINT("0"); DEBUG_PRINT(currentMinute); DEBUG_PRINT("' -> "); //delay(1000); //int8_t resultval = getCharGraphWords(DEFAULT_CHARSOAP, currentHour, currentMinute, result); //if (resultval == 0) { // ===== Display current time ===== displayTime(currentHour, currentMinute); } //else //{ // DEBUG_PRINT("ERROR: "); DEBUG_PRINT(resultval); DEBUG_PRINTLN(" Pattern validation failed!"); // delay(1000); //} delay(500); } } void checkPattern() { //testWords(); FastLED.clear(); uint8_t lengthPattern = strlen(testPattern); for(uint8_t n = 0; n < lengthPattern && testPattern[n] != '\0' ;n++) { char pattern[12]; uint8_t patternPos = 0; while(testPattern[n] != '-' && testPattern[n] != '\0' ) { pattern[patternPos++] = testPattern[n++]; } pattern[patternPos]='\0'; int firstPos = findWord(pattern, 0); int secondPos = findWord(pattern, 1); if(firstPos >= 0) { setWord(pattern, normalColor,0); } if(secondPos >= 0) { setWord(pattern, normalColor,1); } showLEDs(); //delay(500); yield(); FastLED.clear(); } }