CharGraph-FW/lib/rgbPanel/rgbPanel.cpp
2026-01-25 20:44:07 +01:00

500 lines
14 KiB
C++

#include <Arduino.h>
#include <rgbPanel.h>
#include <charPattern.inc>
// ════════════════════════════════════════════════════════════════
// 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();
}
}