CharGraphs added

This commit is contained in:
XPS\Micro 2026-02-10 19:51:57 +01:00
parent 679cfab9e8
commit 520bcf7d7f
10 changed files with 227 additions and 67 deletions

134
include/WordFinder.h Normal file
View File

@ -0,0 +1,134 @@
/**
* CharGraph - Unified Word Finder
*
* Central component for finding words in patterns
* Handles both PROGMEM patterns (for validation) and regular strings (for display)
* Shared between CharGraphTimeLogic and rgbPanel
*/
#ifndef WORD_FINDER_H
#define WORD_FINDER_H
#include <Arduino.h>
// ============================================================================
// PROGMEM WORD SEARCH (for validation)
// ============================================================================
/**
* Find word in PROGMEM pattern starting from position
* Both pattern and word are in PROGMEM
*
* @param pattern_progmem Pattern string in PROGMEM
* @param word_progmem Word pointer in PROGMEM
* @param searchStart Start position for search (default 0)
* @return Position of word, or -1 if not found
*/
inline int16_t findWordProgmem(const char* pattern_progmem, const char* word_progmem, uint16_t searchStart = 0) {
if (!pattern_progmem || !word_progmem) return -1;
// Load word from PROGMEM into buffer (max 11 chars: DREIVIERTEL)
char wordBuf[12]; // 11 chars + null terminator
strcpy_P(wordBuf, word_progmem);
uint8_t wordLen = strlen(wordBuf);
// Search through pattern_progmem byte by byte
uint16_t pos = searchStart;
while (true) {
char patByte = pgm_read_byte(pattern_progmem + pos);
if (patByte == '\0') break; // End of pattern
// Check if word matches at this position
bool match = true;
for (uint8_t i = 0; i < wordLen; i++) {
char p = pgm_read_byte(pattern_progmem + pos + i);
if (p != wordBuf[i]) {
match = false;
break;
}
}
if (match) return pos;
pos++;
}
return -1;
}
// ============================================================================
// STRING WORD SEARCH (for display)
// ============================================================================
/**
* Find word in regular string with occurrence support
*
* @param text String to search in
* @param word Word to find
* @param occurrence Which occurrence to find (0 = first, 1 = second, etc.)
* @param searchBackward Search backward from end
* @return Position of word, or -1 if not found
*/
inline int findWordString(const char* text, const char* word, int occurrence = 0, bool searchBackward = false) {
if (!text || !word) return -1;
const int wordLen = strlen(word);
const int textLen = strlen(text);
if (searchBackward) {
// Backward search: from end to beginning
int foundCount = 0;
for (int pos = textLen - wordLen; pos >= 0; --pos) {
bool match = true;
for (int j = 0; j < wordLen; ++j) {
unsigned char c1 = text[pos + j];
unsigned char c2 = word[j];
// Lowercase comparison (case-insensitive)
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) {
return pos;
}
foundCount++;
}
}
} else {
// Forward search: from beginning to end
int foundCount = 0;
for (int pos = 0; pos <= textLen - wordLen; ++pos) {
bool match = true;
for (int j = 0; j < wordLen; ++j) {
unsigned char c1 = text[pos + j];
unsigned char c2 = word[j];
// Lowercase comparison (case-insensitive)
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) {
return pos;
}
foundCount++;
}
}
}
return -1;
}
#endif // WORD_FINDER_H

View File

@ -3,10 +3,30 @@
//Hier Umlaute wie Ö und Ü bereits durch o und u wegen UTF8 ersetzen!!!! //Hier Umlaute wie Ö und Ü bereits durch o und u wegen UTF8 ersetzen!!!!
const char DEFAULT_CHARSOAP[] = "ESHISTRFASTKURZVIERTELFuNFZWANZIGAZEHNJNACHRVORWHALBKLBZWEINSECHSEELFVIERACHTFuNFSIEBENITDREIZWoLFZZEHNEUNLUHR"; //119 const char DEFAULT_CHARSOAP[] PROGMEM = "ESPISTGZEHNFuNFVIERTELVORNACHBALDHALBGZWEINSWCDLZIAXMTVZEHNEUNACHTELFuNFZWoLFSECHSIEBENBGDREIVIERZECRPFACWLUHR";//DLZIAX
//"ESYISTHZEHNFuNFVIERTELVORNACHBALDHALBUZWEINSDG6MSMARTINZEHNEUNACHTELFuNFZWoLFSECHSIEBENBXDREIVIERLUWRNFACWFUHR";//DG6MS
//"ESYISTMZEHNFuNFVIERTELVORNACHBALDHALBDZWEINSNZUXHAUSEMZZEHNEUNACHTELFuNFZWoLFSECHSIEBENHXDREIVIERKNOTPAUSECUHR";//Böcker
//"ESQISTFZEHNFuNFVIERTELVORNACHBALDHALBFYZWEITJEGARTNERVDELFuNFZWoLFZEHNEUNACHTXDREINSVIERSECHSIEBENCRWDSIMONUHR";//GARTNERSIMON
//"ESQISTOZEHNFuNFVIERTLEVORNACHBALDHALBPJZWEIOVQGARTNERCAELFuNFZWoLFZEHNEUNACHTYDREINSVIERSECHSIEBENLRWDTZEITUHR";//GÄRTNER
//"ESHISTQBALDVIERTELFuNFKURZEHNUVORVNACHTAHALBACHTZEHNEUNLIMONCIELLOXVIERIYZWEIELFZWoLFuNFDREINSIEBENSECHSRWDUHR";//LIMONCIELLO
//"ESHISTQBALDVIERTELFuNFKURZEHNUVORVNACHTAHALBACHTZEHNEUNLIMONCIELLOXVIERIYZWEIELFZWoLFuNFDREINSIEBENSECHSRWDUHR";//Limonciello
//"ESJISTSBALDVIERTELFuNFKURZEHNIVORINACHZOHALBGRDNEUSSOBHACHTZEHNEUNHVIERTBZWEIELFZWoLFuNFDREINSIEBENSECHSWCFUHR";//NEUSS
//"ESHISTGFuNFZEHNVIERTELVORFASTNACHHALBALKZWEIFCAROLAZEITDREINSIEBENELFZWoLFuNFZEHNEUNACHTOSECHSVIERGFURWDUWEUHR";//Carola
//"ESMISTKBALDFuNFASTZEHNVIERTELMVORKNACHPHALBDHANNESKBRAUZWoLFuNFHWADREIELFVIERSECHSIEBENARZWEINSJXLUZEHNEUNACHT";//Uwe Kleinhans
//"ESOISTNZEHNFuNFVIERTELVORNACHBALDHALBEZWEINSONEAFAMILYAZEHNEUNACHTELFuNFZWoLFSECHSIEBENKFREIVIERTLYAMPAUSEIUHR"; //Breunig
//"ESXISTXFASTBALDKURZEHNFuNFVIERTELVOROBNACHGEBGZPAULHALBDREINSZWEIJELFVIERFuNFZEHNEUNACHTOSECHSIEBENZWoLFRDWUHR";// LASKAI
//"ESHISTCKURZSIVOTXZEHNMFuNFVIERTELVORNACHBALDHALBXEINSMEZEHNEUNZWEIELFuNFZWoLFSECHSIEBENXMDREIVIERVILRACHTSYUHR";//Harald Mück
//"ESKISTHZEHNFuNFVIERTELVORNACHBALDHALBPZWEINSAXISIHTHEMZWATERMELONSELFuNFZWoLFSECHSIEBENTXDREIVIERECZEHNEUNACHT"; //ISI THE WATERMELON
//"ESHISTPZEHNFuNFVIERTELVORNACHBALDHALBWZWEINSELFuNFZWoLFZEHNEUNACHTDREIVIERJIEJASMIN&LEVIRSECHSIEBENGPAUSERDUHR";//JASMIN&LEVI
//"ESGISTKZEHNFuNFVIERTELVORNACHBALDHALBDZWEINSDREIVIERWLZZEHNEUNACHTPSECHSIEBENELFuNFZWoLFMAIKETOCVXIUHRU&FABIAN";//Maike&Fabian
//"ESHISTPZEHNFuNFVIERTELVORNACHBALDHALBWZWEINSELFuNFZWoLFZEHNEUNACHTDREIVIERJIEJASMINKLEVIRSECHSIEBENGPAUSERDUHR";//JASMIN&LEVI
//"ESHISTJZEHNFuNFVIERTELNACHFASTVORYHALBPBALDSQXBMICHLFPYDREINSIEBENELFZWoLFuNFPSECHSZVIERACHTZEHNEUNZWEIRWDUHRK";//Rouven Öttinger 12BM02
//"ESOISTBZEHNFuNFVIERTELVORNACHBALDHALBCZWEINSULINAKLUCADIDREIVIEREFZEHNEUNACHTELFuNFZWoLFSECHSIEBENBPAUSEMBYUHR"; //Spiller
//"ESHISTRFASTKURZVIERTELFuNFZWANZIGAZEHNJNACHRVORWHALBKLBZWEINSECHSEELFVIERACHTFuNFSIEBENITDREIZWoLFZZEHNEUNLUHR";
//"ESHISTRFASTKURZVIERTELFuNFZWANZIGAZEHNJNACHRVORWHALBKLBZWEINSECHSEELFVIERACHTFuNFSIEBENITDREIZWoLFZZEHNEUNLUHR"; //119
//"ESXISTEFuNFVIERTELZEHNKURZWXPNACHSVORTHALBTVEINSXJZWEIUDREIVIERYPHFuNFSECHSWYSIEBENACHTUNEUNZEHNELFZWoLFXUHRJT"; //"ESXISTEFuNFVIERTELZEHNKURZWXPNACHSVORTHALBTVEINSXJZWEIUDREIVIERYPHFuNFSECHSWYSIEBENACHTUNEUNZEHNELFZWoLFXUHRJT";
//115"ESHISTCKURZSIVORXZEHNMFuNFVIERTELVORNACHBALDHALBXEINSMEZEHNEUNZWEIELFuNFZWoLFSECHSIEBENXMDREIVIERVILRACHTSYUHR"; //115"ESHISTCKURZSIVORXZEHNMFuNFVIERTELVORNACHBALDHALBXEINSMEZEHNEUNZWEIELFuNFZWoLFSECHSIEBENXMDREIVIERVILRACHTSYUHR";
char testPattern[]="ES-IST-WIR-HABEN-BALD-FAST-KURZ-FuNF-ZEHN-DREIVIERTEL-VIERTEL-ZWANZIG-VOR-NACH-EIN-EINS-ZWEI-DREI-VIER-FuNF-SECHS-SIEBEN-ACHT-NEUN-ZEHN-ELF-ZWoLF-UHR-NACHT-PAUSE-ALARM-ZEIT-RWD-KKS-KARLKUEBEL-MINT\0"; char testPattern[]="ES-IST-WIR-HABEN-BALD-FAST-KURZ-FuNF-ZEHN-DREIVIERTEL-VIERTEL-ZWANZIG-VOR-NACH-EIN-EINS-ZWEI-DREI-VIER-FuNF-SECHS-SIEBEN-ACHT-NEUN-ZEHN-ELF-ZWoLF-UHR-NACHT-PAUSE-ALARM-ZEIT-RWD-KKS-KARLKUEBEL-MINT-UWE\0";
/* /*
"ES_IST_ZEHNFuNFVIERTELVORNACHBALDHALB_ZWEINS___________ZEHNEUNACHTELFuNFZWoLFSECHSIEBEN__DREIVIER____PAUSE_UHR" "ES_IST_ZEHNFuNFVIERTELVORNACHBALDHALB_ZWEINS___________ZEHNEUNACHTELFuNFZWoLFSECHSIEBEN__DREIVIER____PAUSE_UHR"

View File

@ -25,7 +25,6 @@
#define AP_SSID_LENGHT 13 #define AP_SSID_LENGHT 13
#define AP_PASSWORD_LENGTH 17 #define AP_PASSWORD_LENGTH 17
#define MAXWORDS 3 // Anzahl Spezialwörter
// ════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════
// EEPROM ADRESSEN // EEPROM ADRESSEN
@ -104,6 +103,6 @@
#define MINUTE_LEDS_MAGIC 0xEE #define MINUTE_LEDS_MAGIC 0xEE
#define RUNNING_FLAG_MAGIC 0x42 // Magic Byte #define RUNNING_FLAG_MAGIC 0x42 // Magic Byte
#define MAGIC_BYTE_INIT 0xAA #define MAGIC_BYTE_INIT 0x55
#endif #endif

View File

@ -17,8 +17,8 @@ int8_t isConfigured = 0xFF;
const unsigned long AP_TIMEOUT = 300000; const unsigned long AP_TIMEOUT = 300000;
bool powerLossDetected = false; //Flag für Stromausfall bool powerLossDetected = false; //Flag für Stromausfall
char AP_SSID[AP_SSID_LENGHT] = "CharGrap101\0"; char AP_SSID[AP_SSID_LENGHT] = "CharGraph\0";
char AP_PASSWORD[AP_PASSWORD_LENGTH] = "MeinPasswort101\0"; char AP_PASSWORD[AP_PASSWORD_LENGTH] = "CharGraph\0";
uint16_t bootCounter = 0; uint16_t bootCounter = 0;

View File

@ -129,17 +129,17 @@ static uint8_t rule_15_19(const RuleContext& ctx, const char** outWords) {
// :20-:24 - ZWANZIG NACH or ZEHN VOR HALB (with fallback support) // :20-:24 - ZWANZIG NACH or ZEHN VOR HALB (with fallback support)
static uint8_t rule_20_24(const RuleContext& ctx, const char** outWords) { static uint8_t rule_20_24(const RuleContext& ctx, const char** outWords) {
if (ctx.fallbackLevel == 0 && ctx.hasZwanzig) { if (ctx.fallbackLevel == 0 && ctx.hasZwanzig) {
// Primary: ZWANZIG NACH [h] // Primary: ZWANZIG NACH [h] - uses current hour
outWords[0] = ZWANZIG; outWords[0] = ZWANZIG;
outWords[1] = NACH; outWords[1] = NACH;
outWords[2] = ctx.hourWord; outWords[2] = ctx.hourWord;
return 3; return 3;
} }
// Fallback: ZEHN VOR HALB [h+1] // Fallback: ZEHN VOR HALB [h+1] - must use NEXT hour
outWords[0] = ZEHN; outWords[0] = ZEHN;
outWords[1] = VOR; outWords[1] = VOR;
outWords[2] = HALB; outWords[2] = HALB;
outWords[3] = ctx.hourWord; outWords[3] = getHourWord((ctx.h12 + 1) % 12);
return 4; return 4;
} }

View File

@ -88,9 +88,11 @@ uint8_t getWordsForTime(
hasUhrAtEnd = afterUhrEmpty; hasUhrAtEnd = afterUhrEmpty;
} }
// ========== STEP 4: Calculate display hour (advance at mm >= 20) ========== // ========== STEP 4: Calculate display hour (advance at mm >= 25) ==========
// :20-:24 ZWANZIG NACH uses current hour
// :25-:59 other rules use next hour
uint8_t h12 = hour % 12; uint8_t h12 = hour % 12;
if (minute >= 20) { if (minute >= 25) {
h12 = (h12 + 1) % 12; h12 = (h12 + 1) % 12;
} }

View File

@ -10,15 +10,15 @@ CRGB specialColor = CRGB::Green;
CRGB leds[NUM_LEDS]; CRGB leds[NUM_LEDS];
uint8_t brightness = 80; uint8_t brightness = 80;
char charsoap[COLS * ROWS * 2]; //Char * 2 to fit all in, cause UTF (maybe ÄÖÜ) char charsoap[(COLS * ROWS * 2) + 1] = {'\0'}; //Char * 2 to fit all in, cause UTF (maybe ÄÖÜ)
bool customCharsoap = false; bool customCharsoap = false;
// ── Konfiguration für Spezialanzeige ── // ── Konfiguration für Spezialanzeige ──
// MAXWORDS ist in defines.inc definiert // MAXWORDS ist in defines.inc definiert
// Platzhalter - werden dynamisch aus EEPROM oder Defaults geladen (siehe loadSpecialWords() in main.cpp) // 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! char SPECIAL_WORD[MAXWORDS][13] = {"DLZIAX\0", //max 11 Zeichen, \0 wird automatisch angefügt!
"", "FACW\0",
"" }; //Text der ein- bzw. ausgeblendet werden soll "\0" }; //Text der ein- bzw. ausgeblendet werden soll
const uint16_t SPECIAL_HOLD_MS = 5000; // Anzeigedauer des Spezialworts const uint16_t SPECIAL_HOLD_MS = 5000; // Anzeigedauer des Spezialworts
const int hourpattern[][2] = { const int hourpattern[][2] = {
@ -87,7 +87,7 @@ void fadeInCurrentFrame(uint8_t targetBrightness, uint8_t steps = MAX_STEPS, uin
// Zeigt ein Wort (falls vorhanden) mit sanftem Fade-In, hält es und blendet es wieder aus // 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 // OPTIMIERT: Schnellere Animation für bessere WiFi-Performance
bool showSpecialWordSequence(const char words[][12], CRGB color, uint8_t steps = 20, uint16_t stepDelayMs = 15) bool showSpecialWordSequence(const char words[][SPECIAL_WORD_LENGTH], CRGB color, uint8_t steps = 20, uint16_t stepDelayMs = 15)
{ {
int pos = findWord(words[0], 0); int pos = findWord(words[0], 0);
@ -404,24 +404,27 @@ int findWord(const char* word, int occurrence, bool searchBackward)
uint8_t testWords() uint8_t testWords()
{ {
//return 0; #if (defined(TEST_ALLTHETIME) && TEST_ALLTHETIME == false)
return 0;
#endif
DEBUG_PRINT("╔════════════════════════╗\n"); DEBUG_PRINT("╔════════════════════════╗\n");
DEBUG_PRINT("║ Teste Wörter ... ║\n"); DEBUG_PRINT("║ Teste Wörter ... ║\n");
DEBUG_PRINT("╚════════════════════════╝\n") DEBUG_PRINT("╚════════════════════════╝\n")
DEBUG_PRINTLN(DEFAULT_CHARSOAP); DEBUG_PRINTLN(charsoap);
if(strlen(DEFAULT_CHARSOAP) != (COLS * (ROWS-1))) if(strlen(charsoap) != (COLS * (ROWS-1)))
{ {
//return -1; //return -1;
DEBUG_PRINT("\nLänge stimmt nicht: "); DEBUG_PRINT("\nLänge stimmt nicht: ");
DEBUG_PRINTLN(strlen(DEFAULT_CHARSOAP)); DEBUG_PRINTLN(strlen(charsoap));
} }
else else
{ {
DEBUG_PRINT("\nLänge stimmt: "); DEBUG_PRINT("\nLänge stimmt: ");
DEBUG_PRINTLN(strlen(DEFAULT_CHARSOAP)); DEBUG_PRINTLN(strlen(charsoap));
} }
uint8_t currentHour = 12; uint8_t currentHour = 0;
uint8_t currentMinute = 29; uint8_t currentMinute = 0;
//CharGraphTimeWords result; //CharGraphTimeWords result;
while(true) while(true)
{ {
@ -429,10 +432,10 @@ uint8_t testWords()
currentMinute++; currentMinute++;
if(currentMinute == 60) if(currentMinute == 60)
{ {
currentHour = 1; currentHour += 1;
currentMinute = 0; currentMinute = 0;
} }
if(currentHour == 1 && currentMinute == 31) if(currentHour == 23 && currentMinute == 59)
{ {
DEBUG_PRINT ("\n╔════════════════════════╗\n"); DEBUG_PRINT ("\n╔════════════════════════╗\n");
DEBUG_PRINT ( "║ Teste Wörter fertig. ║\n"); DEBUG_PRINT ( "║ Teste Wörter fertig. ║\n");
@ -449,9 +452,6 @@ uint8_t testWords()
DEBUG_PRINT(currentMinute); DEBUG_PRINT(currentMinute);
DEBUG_PRINT("' -> "); DEBUG_PRINT("' -> ");
//delay(1000); //delay(1000);
//int8_t resultval = getCharGraphWords(DEFAULT_CHARSOAP, currentHour, currentMinute, result);
//if (resultval == 0)
{ {
// ===== Display current time ===== // ===== Display current time =====
displayTime(currentHour, currentMinute); displayTime(currentHour, currentMinute);
@ -461,13 +461,13 @@ uint8_t testWords()
// DEBUG_PRINT("ERROR: "); DEBUG_PRINT(resultval); DEBUG_PRINTLN(" Pattern validation failed!"); // DEBUG_PRINT("ERROR: "); DEBUG_PRINT(resultval); DEBUG_PRINTLN(" Pattern validation failed!");
// delay(1000); // delay(1000);
//} //}
delay(500); delay(50);
} }
} }
void checkPattern() void checkPattern()
{ {
//testWords(); testWords();
FastLED.clear(); FastLED.clear();
uint8_t lengthPattern = strlen(testPattern); uint8_t lengthPattern = strlen(testPattern);

View File

@ -11,6 +11,8 @@
#define COLS 11 #define COLS 11
#define ROWS 11 #define ROWS 11
#define MAXWORDS 3 //Anzahl Spezialwörter
#define SPECIAL_WORD_LENGTH (COLS+2)
extern CRGB normalColor; extern CRGB normalColor;
extern CRGB specialColor; extern CRGB specialColor;
extern CRGB leds[NUM_LEDS]; extern CRGB leds[NUM_LEDS];
@ -20,9 +22,9 @@
extern int MINUTE_LEDS[4]; extern int MINUTE_LEDS[4];
extern char testPattern[]; extern char testPattern[];
extern bool customCharsoap; extern bool customCharsoap;
extern char charsoap[COLS * ROWS * 2]; extern char charsoap[(COLS * ROWS * 2)+1];
extern const char DEFAULT_CHARSOAP[]; extern const char DEFAULT_CHARSOAP[];
extern char SPECIAL_WORD[3][12]; // MAXWORDS = 3 extern char SPECIAL_WORD[MAXWORDS][SPECIAL_WORD_LENGTH]; // MAXWORDS = 3
extern void showLEDs(); extern void showLEDs();
extern int bridgeLED(int pos); extern int bridgeLED(int pos);

View File

@ -43,6 +43,7 @@ build_flags =
-DUSE_RTC=false -DUSE_RTC=false
-DTEST_RGB_ONLY=false -DTEST_RGB_ONLY=false
-DTEST_RGB=false -DTEST_RGB=false
-DTEST_ALLTHETIME=false
-DPOWERLOSSDETECT=false -DPOWERLOSSDETECT=false
-DCHARGRAPH_DEBUG -DCHARGRAPH_DEBUG
@ -63,7 +64,7 @@ platform = ${env.platform}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
upload_speed = 115200 ; Langsam für Clone-Stabilität upload_speed = 115200 ; Langsam für Clone-Stabilität
upload_port = COM13 ;upload_port = COM13
; Flash-Einstellungen (sicher für alle Clones) ; Flash-Einstellungen (sicher für alle Clones)
board_build.flash_mode = dio ; Statt qio - kompatibler! board_build.flash_mode = dio ; Statt qio - kompatibler!
@ -100,7 +101,7 @@ monitor_speed = ${common.monitor_speed}
;upload_speed = 921600 ;upload_speed = 921600
upload_speed = 115200 upload_speed = 115200
;upload_port = COM13 ; Automatische Erkennung oder manuell anpassen ;upload_port = COM13 ; Automatische Erkennung oder manuell anpassen
upload_port = COM5 ;upload_port = COM5
board_build.flash_mode = dio board_build.flash_mode = dio
# CPU-Frequenz auf 160MHz erhöht # CPU-Frequenz auf 160MHz erhöht

View File

@ -15,37 +15,42 @@ const byte DNS_PORT = 53;
// ════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════
// CHARSOAP / ZEICHENSATZ // CHARSOAP / ZEICHENSATZ
// ════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════
void initCharsoap() { void initCharsoap(char *_charsoap) {
// Konvertiert UTF-8 Umlaute in charsoap zu ASCII in-place
// charsoap muss bereits gefüllt sein (von strcpy_P)
char temp[COLS * ROWS * 2]; // Temporärer Buffer für Konvertierung
int writePos = 0; int writePos = 0;
int readPos = 0; int readPos = 0;
int sourceLen = strlen(DEFAULT_CHARSOAP); int sourceLen = strlen(_charsoap);
while (readPos < sourceLen && writePos < (ROWS * COLS)) { while (readPos < sourceLen && writePos < (ROWS * COLS)) {
unsigned char c = DEFAULT_CHARSOAP[readPos]; unsigned char c = _charsoap[readPos];
// UTF-8 Umlaute erkennen und zu lowercase konvertieren // UTF-8 Umlaute erkennen und zu lowercase konvertieren
if (c == 0xC3 && readPos + 1 < sourceLen) { if (c == 0xC3 && readPos + 1 < sourceLen) {
unsigned char next = DEFAULT_CHARSOAP[readPos + 1]; unsigned char next = _charsoap[readPos + 1];
switch(next) { switch(next) {
case 0x84: charsoap[writePos++] = 'a'; break; // Ä → a case 0x84: temp[writePos++] = 'a'; break; // Ä → a
case 0x96: charsoap[writePos++] = 'o'; break; // Ö → o case 0x96: temp[writePos++] = 'o'; break; // Ö → o
case 0x9C: charsoap[writePos++] = 'u'; break; // Ü → u case 0x9C: temp[writePos++] = 'u'; break; // Ü → u
default: default:
charsoap[writePos++] = c; temp[writePos++] = c;
if (writePos < (ROWS * COLS)) charsoap[writePos++] = next; if (writePos < (ROWS * COLS)) temp[writePos++] = next;
break; break;
} }
readPos += 2; readPos += 2;
} else { } else {
charsoap[writePos++] = c; temp[writePos++] = c;
readPos++; readPos++;
} }
} }
charsoap[writePos] = '\0'; temp[writePos] = '\0';
strcpy(_charsoap, temp); // Konvertiertes Ergebnis zurück nach charsoap
DEBUG_PRINTLN("Default charsoap initialisiert (Umlaute → lowercase)"); DEBUG_PRINTLN("charsoap initialisiert (Umlaute → lowercase)");
DEBUG_PRINTLN(charsoap); DEBUG_PRINTLN(_charsoap);
} }
void loadCharsoap() { void loadCharsoap() {
@ -119,24 +124,20 @@ void loadCharsoap() {
} }
} else { } else {
DEBUG_PRINTF("❌ Falsche Länge: %d\n", writePos); DEBUG_PRINTF("❌ Falsche Länge: %d\n", writePos);
strcpy(charsoap, DEFAULT_CHARSOAP); strcpy_P(charsoap, (const char*)DEFAULT_CHARSOAP);
initCharsoap(); // UTF-8 zu ASCII konvertieren! initCharsoap(charsoap); // UTF-8 zu ASCII konvertieren!
customCharsoap = false; customCharsoap = false;
DEBUG_PRINTLN("→ Verwende Default"); DEBUG_PRINTLN("→ Verwende Default");
} }
} else { } else {
strcpy(charsoap, DEFAULT_CHARSOAP); strcpy_P(charsoap, (const char*)DEFAULT_CHARSOAP);
initCharsoap(); // UTF-8 zu ASCII konvertieren! initCharsoap(charsoap); // UTF-8 zu ASCII konvertieren!
DEBUG_PRINTLN("✓ Standard charsoap"); DEBUG_PRINTLN("✓ Standard charsoap");
} }
} }
void saveCharsoap(const char* newCharsoap) { void saveCharsoap(const char* newCharsoap)
if (strlen(newCharsoap) != (ROWS * COLS)) { {
DEBUG_PRINTF("❌ Länge: %d\n", strlen(newCharsoap));
return;
}
char cleaned[uint8_t (ROWS * COLS)+1]; char cleaned[uint8_t (ROWS * COLS)+1];
uint16_t writePos = 0; uint16_t writePos = 0;
uint16_t readPos = 0; uint16_t readPos = 0;
@ -184,8 +185,8 @@ void saveCharsoap(const char* newCharsoap) {
} }
void resetCharsoap() { void resetCharsoap() {
strcpy(charsoap, DEFAULT_CHARSOAP); strcpy_P(charsoap, (const char*)DEFAULT_CHARSOAP);
initCharsoap(); // UTF-8 zu ASCII konvertieren! initCharsoap(charsoap); // UTF-8 zu ASCII konvertieren!
customCharsoap = false; customCharsoap = false;
EEPROM.write(ADDR_CHARSOAP_SET, 0); EEPROM.write(ADDR_CHARSOAP_SET, 0);
EEPROM.commit(); EEPROM.commit();
@ -220,8 +221,8 @@ void loadSpecialWords() {
// Erste Initialisierung: Default-Werte setzen // Erste Initialisierung: Default-Werte setzen
DEBUG_PRINTLN("Setze Standard SPECIAL_WORD..."); DEBUG_PRINTLN("Setze Standard SPECIAL_WORD...");
const char DEFAULT_SPECIAL_WORD[MAXWORDS][12] = { const char DEFAULT_SPECIAL_WORD[MAXWORDS][12] = {
"RWD", "DLZIAX",
"", "FACW",
"" ""
}; };
@ -439,8 +440,8 @@ void loadConfig() {
DEBUG_PRINTLN("╚════════════════════════════════════════════════════╝"); DEBUG_PRINTLN("╚════════════════════════════════════════════════════╝");
// DEFAULT_CHARSOAP vorbereiten (UTF-8 → ASCII) // DEFAULT_CHARSOAP vorbereiten (UTF-8 → ASCII)
strcpy(charsoap, DEFAULT_CHARSOAP); strcpy_P(charsoap, (const char*)DEFAULT_CHARSOAP);
initCharsoap(); initCharsoap(charsoap);
DEBUG_PRINTLN("→ Speichere DEFAULT_CHARSOAP ins EEPROM..."); DEBUG_PRINTLN("→ Speichere DEFAULT_CHARSOAP ins EEPROM...");
@ -827,7 +828,7 @@ void displayTime(int hours, int minutes)
yield(); yield();
CharGraphTimeWords result; CharGraphTimeWords result;
int8_t resultval = getCharGraphWords(DEFAULT_CHARSOAP, testPattern, hours, minutes, result); int8_t resultval = getCharGraphWords(charsoap, testPattern, hours, minutes, result);
if (resultval == 0) if (resultval == 0)
{ {
@ -1361,6 +1362,7 @@ void handleSave() {
server.send(400, "text/plain", "Fehler"); server.send(400, "text/plain", "Fehler");
} }
} }
// ════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════
// LED TEST HANDLER // LED TEST HANDLER
// ════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════
@ -2395,7 +2397,7 @@ void setup()
Serial.begin(115200); Serial.begin(115200);
while (!Serial) { } while (!Serial) { }
Serial.setDebugOutput(true); Serial.setDebugOutput(true);
delay(500); delay(5000);
Serial.setDebugOutput(false); Serial.setDebugOutput(false);
Serial.printf("Flash Chip ID: %08X\n", ESP.getFlashChipId()); Serial.printf("Flash Chip ID: %08X\n", ESP.getFlashChipId());
@ -2450,7 +2452,7 @@ void setup()
EEPROM.begin(EEPROM_SIZE); EEPROM.begin(EEPROM_SIZE);
// Zuerst Default charsoap initialisieren // Zuerst Default charsoap initialisieren
initCharsoap(); initCharsoap(charsoap);
loadCharsoap(); loadCharsoap();
loadSpecialWords(); loadSpecialWords();