From 602e7f46f32b9f4ca4ddfc0649a72ed7c665b463 Mon Sep 17 00:00:00 2001 From: "XPS\\Micro" Date: Sun, 25 Jan 2026 20:44:07 +0100 Subject: [PATCH] Initial Setup --- .claude/settings.local.json | 7 + .gitignore | 66 + doc/.gitkeep | 1 + html/index.html | 2624 ++++++++++++++++ html_to_header.py | 64 + include/charPattern.inc | 35 + include/common.inc | 24 + include/defines.inc | 109 + include/extern.inc | 9 + include/html.h | 1332 +++++++++ include/vars.inc | 92 + include/version.inc | 33 + .../.claude/settings.local.json | 8 + lib/CharGraphTimeLogic/blackboxtest/README.md | 186 ++ .../blackboxtest/blackboxtest.ino | 285 ++ lib/CharGraphTimeLogic/blackboxtest/config.h | 48 + .../examples/BasicExample.ino | 117 + .../examples/FullExample.ino | 199 ++ .../examples/MinuteRangeTest.ino | 195 ++ lib/CharGraphTimeLogic/library.properties | 10 + .../src/CharGraphTimeLogic.cpp | 167 ++ .../src/CharGraphTimeLogic.h | 114 + lib/CharGraphTimeLogic/src/Constants.cpp | 151 + lib/CharGraphTimeLogic/src/Constants.h | 139 + lib/CharGraphTimeLogic/src/LEDCalculator.cpp | 370 +++ lib/CharGraphTimeLogic/src/LEDCalculator.h | 86 + lib/CharGraphTimeLogic/src/MinuteRules.cpp | 495 +++ lib/CharGraphTimeLogic/src/MinuteRules.h | 33 + lib/CharGraphTimeLogic/src/Types.h | 67 + lib/CharGraphTimeLogic/src/Validator.cpp | 337 +++ lib/CharGraphTimeLogic/src/Validator.h | 88 + lib/CharGraphTimeLogic/src/WordMatcher.cpp | 213 ++ lib/CharGraphTimeLogic/src/WordMatcher.h | 40 + lib/PowerOnDetector/PowerOnDetector.cpp | 132 + lib/PowerOnDetector/PowerOnDetector.h | 18 + lib/PowerOnDetector/library.json | 8 + lib/info/info.cpp | 47 + lib/info/info.h | 6 + lib/rgbPanel/rgbPanel.cpp | 499 ++++ lib/rgbPanel/rgbPanel.h | 38 + platformio.ini | 120 + src/main.cpp | 2655 +++++++++++++++++ 42 files changed, 11267 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 .gitignore create mode 100644 doc/.gitkeep create mode 100644 html/index.html create mode 100644 html_to_header.py create mode 100644 include/charPattern.inc create mode 100644 include/common.inc create mode 100644 include/defines.inc create mode 100644 include/extern.inc create mode 100644 include/html.h create mode 100644 include/vars.inc create mode 100644 include/version.inc create mode 100644 lib/CharGraphTimeLogic/.claude/settings.local.json create mode 100644 lib/CharGraphTimeLogic/blackboxtest/README.md create mode 100644 lib/CharGraphTimeLogic/blackboxtest/blackboxtest.ino create mode 100644 lib/CharGraphTimeLogic/blackboxtest/config.h create mode 100644 lib/CharGraphTimeLogic/examples/BasicExample.ino create mode 100644 lib/CharGraphTimeLogic/examples/FullExample.ino create mode 100644 lib/CharGraphTimeLogic/examples/MinuteRangeTest.ino create mode 100644 lib/CharGraphTimeLogic/library.properties create mode 100644 lib/CharGraphTimeLogic/src/CharGraphTimeLogic.cpp create mode 100644 lib/CharGraphTimeLogic/src/CharGraphTimeLogic.h create mode 100644 lib/CharGraphTimeLogic/src/Constants.cpp create mode 100644 lib/CharGraphTimeLogic/src/Constants.h create mode 100644 lib/CharGraphTimeLogic/src/LEDCalculator.cpp create mode 100644 lib/CharGraphTimeLogic/src/LEDCalculator.h create mode 100644 lib/CharGraphTimeLogic/src/MinuteRules.cpp create mode 100644 lib/CharGraphTimeLogic/src/MinuteRules.h create mode 100644 lib/CharGraphTimeLogic/src/Types.h create mode 100644 lib/CharGraphTimeLogic/src/Validator.cpp create mode 100644 lib/CharGraphTimeLogic/src/Validator.h create mode 100644 lib/CharGraphTimeLogic/src/WordMatcher.cpp create mode 100644 lib/CharGraphTimeLogic/src/WordMatcher.h create mode 100644 lib/PowerOnDetector/PowerOnDetector.cpp create mode 100644 lib/PowerOnDetector/PowerOnDetector.h create mode 100644 lib/PowerOnDetector/library.json create mode 100644 lib/info/info.cpp create mode 100644 lib/info/info.h create mode 100644 lib/rgbPanel/rgbPanel.cpp create mode 100644 lib/rgbPanel/rgbPanel.h create mode 100644 platformio.ini create mode 100644 src/main.cpp diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..26bd418 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(python html_to_header.py:*)" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01fd046 --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch + +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch + +# Build-Artefakte und kompilierte Dateien +*.elf +*.bin +*.hex +*.map +*.o +*.a + +*.zip + +# Eagle Backup-Dateien +*.s#? +*.b#? +*.l#? + +# PlatformIO +.pio/ +.pioenvs/ +.piolibdeps/ +firmware/build/ + +# KiCad temporaere Dateien +*.000 +*.bak +*.bck +*-bak +*-cache.lib +*.kicad_pcb-bak +*.kicad_sch-bak +fp-info-cache +*-save.pro +*-save.kicad_pcb + +# Generierte Fertigungsdateien (optional, je nach Workflow) +hardware/gerber/*.gbr +hardware/gerber/*.drl + +# CAD temporaere Dateien +*.stl.autosave +*.step.autosave + +# Halte .gitkeep Dateien +!.gitkeep + +# IDE-Einstellungen +.vscode/ +.idea/ +*.code-workspace + +# Betriebssystem-Dateien +.DS_Store +Thumbs.db +desktop.ini +CLAUDE.md \ No newline at end of file diff --git a/doc/.gitkeep b/doc/.gitkeep new file mode 100644 index 0000000..41f68a5 --- /dev/null +++ b/doc/.gitkeep @@ -0,0 +1 @@ +# Dieser Ordner ist Teil der Projektstruktur diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..cf1d69e --- /dev/null +++ b/html/index.html @@ -0,0 +1,2624 @@ + + + + + + CharGraph Konfiguration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

CharGraph Einstellungen

+ +
+ +

+ ℹ️ Projekt-Information + +

+ + +

+ πŸ•’ Zeit einstellen + +

+ + + + +

+ πŸ“Š Helligkeitsanpassung + β–Ό +

+ +

+ πŸ“‘ WiFi & Zeit-Server Konfiguration + +

+ + +

+ πŸ”„ Firmware Update (OTA) + +

+ + +

+ 🎨 Farbeinstellungen + +

+ + +

+ πŸ”€ Wortmatrix + +

+ + +

+ ✨ Spezialwârter + +

+ + +

+ ⏰ Minuten-LEDs + +

+ + +

+ πŸ”¬ LED Test & Diagnose + +

+ + +

+ 🎯 Pattern-Test (Kritische Zeiten) + +

+ + +
+ + + + \ No newline at end of file diff --git a/html_to_header.py b/html_to_header.py new file mode 100644 index 0000000..5fe0973 --- /dev/null +++ b/html_to_header.py @@ -0,0 +1,64 @@ +import os +import gzip + +html_path = "html/index.html" +header_path = "include/html.h" + +# HTML-Datei lesen +with open(html_path, 'r', encoding='utf-8') as f: + html_content = f.read() + +# HTML-Inhalt mit GZIP komprimieren +html_bytes = html_content.encode('utf-8') +compressed = gzip.compress(html_bytes, compresslevel=9) + +# Original- und komprimierte Grâße ausgeben +original_size = len(html_bytes) +compressed_size = len(compressed) +ratio = (1 - compressed_size / original_size) * 100 + +print(f"Original-Grâße: {original_size:6d} Bytes") +print(f"Komprimierte Grâße: {compressed_size:6d} Bytes") +print(f"Kompression: {ratio:5.1f}%") + +# Header-Datei erstellen mit komprimierten Daten +header_content = f"""// Auto-generiert von html_to_header.py +// Original-Grâße: {original_size} Bytes +// Komprimierte Grâße: {compressed_size} Bytes +// Kompression: {ratio:.1f}% + +#ifndef HTML_H +#define HTML_H + +#include + +// GZIP-komprimierte HTML-Seite +const uint8_t HTML_PAGE_GZIP[] PROGMEM = {{ +""" + +# Komprimierte Bytes als Hex-Array schreiben +for i, byte in enumerate(compressed): + if i % 16 == 0: + header_content += " " + header_content += f"0x{byte:02x}" + if i < len(compressed) - 1: + header_content += "," + if (i + 1) % 16 == 0: + header_content += "\n" + else: + header_content += " " + +header_content += f""" +}}; + +const size_t HTML_PAGE_GZIP_LEN = {compressed_size}; + +#endif // HTML_H +""" + +# Header-Datei schreiben +os.makedirs(os.path.dirname(header_path), exist_ok=True) +with open(header_path, 'w', encoding='utf-8') as f: + f.write(header_content) + +print(f"\nHeader-Datei erstellt: {header_path}") diff --git a/include/charPattern.inc b/include/charPattern.inc new file mode 100644 index 0000000..d6e8201 --- /dev/null +++ b/include/charPattern.inc @@ -0,0 +1,35 @@ +#ifndef CHARPATTERN_H_ +#define CHARPATTERN_H_ + +//Hier Umlaute wie Γ– und Ü bereits durch o und u wegen UTF8 ersetzen!!!! + +const char DEFAULT_CHARSOAP[] = "ESxISTxFASTBALDKURZEHNFuNFZEITVORDREIVIERTELNACHRWDHALBxSECHSVIERxELFZWoLFuNFZEHNEUNACHTDREINSIEBENxZWEIxxUHRx"; +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"; + +/* + "ES_IST_ZEHNFuNFVIERTELVORNACHBALDHALB_ZWEINS___________ZEHNEUNACHTELFuNFZWoLFSECHSIEBEN__DREIVIER____PAUSE_UHR" + "ES_IST_ZEHNFuNFVIERTEL_KURZ_FAST_VORNACHBALDHALB_SECHS__ZWEIVIER__ZEHNEUNACHTDREINSIEBENZWoLFELFuNF___ZEIT_UHR" + "ES_IST_FASTFuNFVIERTELZEHNBALDVORNACH___HALB_VIER______ELFZWoLFuNF_SECHSIEBENZEHNEUNACHT_ZWEINSDREI________UHR" + "ES_IST_BALDKURZ_FAST___FuNFZEHN__VIERTEL_VORNACH__HALB__SECHSIEBENZEHNEUNACHT_ZWEINSDREIVIERELFuNF__ZWoLF__UHR" + "ES_IST_BALDKURZ_FAST___FuNFZEHN__VIERTEL_VORNACH__HALB__SECHSIEBENZEHNEUNACHT_ZWEINSDREIELFuNFVIER_ZWoLFRWDUHR" + "ES_IST_FASTBALDKURZEHNFuNFVIERTELVORNACH___________HALBDREINSZWEI_ELFVIERFuNFZEHNEUNACHT_SECHSIEBENZWoLFRWDUHR" + "ES_IST_FASTBALDKURZEHNFuNFVIERTELVORNACHZEIT_______HALBDREINSZWEI_ELFVIERFuNFZEHNEUNACHT_SECHSIEBENZWoLFRWDUHR" + "ES_IST_FASTBALDKURZEHNFuNFZEITVORDREIVIERTELNACHRWDHALB_SECHSVIER_ELFZWoLFuNFZEHNEUNACHTDREINSIEBEN_ZWEI__UHR_" + "ES_IST_FASTFuNFKURZEHNVIERTEL_VORNACH_HALB______________ZWEIDREINSZEHNEUNACHTELFVIERFuNF_SECHSIEBENZWoLFRWDUHR" + "ES_IST_KURZBALDFuNFASTZEHNVIERTELDREI_NACH___VOR__HALB_SIEBENSECHSFuNFVIERELF_DREIZWEINSZEHNEUNACHT_ZWoLF_UHR_" + "ES_IST_FASTFuNFKURZEHNDREIVIERTELVORNACHBALDHALBVIERTELSECHS_VIER_ELFuNFZWoLFZEHNEUNACHTDREINSIEBEN_ZWEI___UHR" + "ES_IST_FASTFuNFKURZEHNVORNACHBALDDREIVIERTELHALB_______SECHS_VIER_ELFuNFZWoLFZEHNEUNACHTDREINSIEBEN_ZWEI___UHR" + "ES_IST_FASTZEHNZWANZIGDREIVIERTELFuNFBALDVOR_NACH__HALBSECHS_VIER_ELFuNFZWoLFZEHNEUNACHTDREINSIEBEN_ZWEI___UHR" + "ES_IST_BALDFuNFZWANZIGDREIVIERTELZEHNKURZEITFASTNACHVOR_HALB_VIER_ZWEINSIEBENELFZWoLFuNFZEHNEUNACHT_SECHS_DREI" + "ES_IST_FAST_FuNFZEHN__BALDKURZEITVIERTEL_VOR_NACH_HALB_DREINSIEBENELFZWoLFuNFZEHNEUNACHT_VIERSECHS_RWDZWEI_UHR" + "WIR_HABEN__BALDZWANZIGKURZEHNFuNFDREIVIERTELFASTNACHVOR_HALB_FuNF_DREINSIEBENZEHNEUNACHTELFVIERZWEI_SECHSZWoLF" + "WIR_HABEN__BALD_FuNF__DREIVIERTELZEHNZWANZIGNACH_VOR____HALB_ZWEI_ZEHNEUNACHTDREINSIEBENZWoLFuNFELF_SECHS_VIER" + "_WIR_HABEN_KURZBALD____FuNFZEHN___VOR_NACH__HALB_ZWEINSVIERSECHS__ELFuNFZWoLFZEHNEUNACHTDREINSIEBENPAUSE___UHR" + "_WIR_HABEN_KURZBALD____FuNFZEHN__VIERTEL_VORNACH__HALB_DREINSIEBENZWoLFuNFELFVIERSECHS__ZEHNEUNACHT_ZWEI__UHR_" + "WIR_HABEN__BALDKURZEIT_FuNFZEHN__VIERTEL_VOR_NACH_HALB_DREINSIEBENELFZWoLFuNFZEHNEUNACHT_VIERSECHS_RWDZWEI_UHR" + "ES_IST_FuNFZEHNZWANZIGDREIVIERTELVOR____NACHHALB_ELFuNFEINS___ZWEIDREI___VIERSECHS__ACHTSIEBENZWoLFZEHNEUN_UHR" + "ESKISTAFuNFZEHNZWANZIGDREIVIERTELVORFUNKNACHHALBAELFuNFEINSXAMZWEIDREIPMJVIERSECHSNLACHTSIEBENZWoLFZEHNEUNKUHR" +*/ + +int MINUTE_LEDS[4] = {112, 114, 116, 118}; +#endif diff --git a/include/common.inc b/include/common.inc new file mode 100644 index 0000000..7f46604 --- /dev/null +++ b/include/common.inc @@ -0,0 +1,24 @@ +#ifndef _COMMON_INC_ + #define _COMMON_INC_ + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include // NTP support + + #include // FΓΌr rst_info + + #include + #include + #include + #include + #include + #include + #include + #include +#endif diff --git a/include/defines.inc b/include/defines.inc new file mode 100644 index 0000000..a694812 --- /dev/null +++ b/include/defines.inc @@ -0,0 +1,109 @@ +#ifndef _DEFINES_INC_ + #define _DEFINES_INC_ + +#ifdef BRIDGE_LEDS_POS77 + #warning "BRIDGE_LEDS_POS77 is definded, so RGB-LED on pos 77 would bridged" +#endif + +#if defined(DEBUG_SOURCE) && (DEBUG_SOURCE == true) + #warning "DEBUG_MODE is set to true, so Serial is active" + #define DEBUG_PRINT(x) {Serial.print(x);Serial.flush(); yield();} + #define DEBUG_PRINTLN(x) {Serial.println(x);Serial.flush(); yield();} + #define DEBUG_PRINTF(...) {Serial.printf(__VA_ARGS__);Serial.flush(); yield();} +#else + #define DEBUG_PRINT(x) + #define DEBUG_PRINTLN(x) + #define DEBUG_PRINTF(...) +#endif + +#define I2C_SDA D2 +#define I2C_SCL D1 + +// APA102/SK9822 auf D7 (DATA), D5 (CLK) +#define DATA_PIN2 D7 +#define CLK_PIN2 D5 + +#define AP_SSID_LENGHT 13 +#define AP_PASSWORD_LENGTH 17 +#define MAXWORDS 3 // Anzahl SpezialwΓΆrter + +// ════════════════════════════════════════════════════════════════ +// EEPROM ADRESSEN +// ════════════════════════════════════════════════════════════════ +#define EEPROM_SIZE 512 +#define ADDR_BRIGHTNESS 0 +#define ADDR_COLOR_R 1 +#define ADDR_COLOR_G 2 +#define ADDR_COLOR_B 3 +#define ADDR_SPECIAL_R 4 +#define ADDR_SPECIAL_G 5 +#define ADDR_SPECIAL_B 6 +#define ADDR_CONFIGURED 7 +#define ADDR_TIMESTAMP 8 +#define ADDR_SSID (ADDR_TIMESTAMP+sizeof(savedTime)) +#define ADDR_PASSWORD (ADDR_SSID + AP_SSID_LENGHT) +#define ADDR_WIFI_SET (ADDR_PASSWORD + AP_PASSWORD_LENGTH) +#define ADDR_LAST_SYNC 40 +#define ADDR_DRIFT_RATE 44 +#define ADDR_SYNC_COUNT 48 +#define ADDR_CHARSOAP 50 +#define ADDR_CHARSOAP_SET 161 +#define ADDR_BOOT_COUNTER 163 // Boot-Counter +#define ADDR_CLEAN_SHUTDOWN 164 // Clean-Shutdown-Flag +#define ADDR_RUNNING_FLAG 165 // "System lΓ€uft" Flag + +// ════════════════════════════════════════════════════════════════ +// OTA UPDATE ADRESSEN (346 bytes frei: 166-511) +// ════════════════════════════════════════════════════════════════ +#define ADDR_OTA_VERSION 166 // Firmware-Version (32 bytes) +#define ADDR_OTA_BUILD_DATE 198 // Build-Datum/Zeit (20 bytes) +#define ADDR_OTA_FLAGS 218 // OTA-Status-Flags (1 byte) + +#define OTA_FLAG_UPDATE_SUCCESS 0x01 +#define OTA_FLAG_UPDATE_FAILED 0x02 + +// ════════════════════════════════════════════════════════════════ +// WIFI STATION & NTP ADRESSEN (293 bytes frei: 219-511) +// ════════════════════════════════════════════════════════════════ +#define ADDR_STA_SSID 219 // Station SSID (32 bytes) +#define ADDR_STA_PASSWORD 251 // Station Password (64 bytes) +#define ADDR_STA_ENABLED 315 // Station enabled flag (1 byte) +#define ADDR_STA_DHCP 316 // DHCP enabled (1 byte) +#define ADDR_STA_IP 317 // Static IP (4 bytes) +#define ADDR_STA_GATEWAY 321 // Gateway (4 bytes) +#define ADDR_STA_SUBNET 325 // Subnet mask (4 bytes) +#define ADDR_STA_DNS 329 // DNS server (4 bytes) +#define ADDR_NTP_SERVER 333 // NTP server hostname (32 bytes) +#define ADDR_NTP_LAST_SYNC 365 // Last NTP sync timestamp (4 bytes) +#define ADDR_NTP_ENABLED 369 // NTP enabled flag (1 byte) + +// ════════════════════════════════════════════════════════════════ +// AUTO-BRIGHTNESS ADRESSEN (140 bytes frei: 377-511) +// ════════════════════════════════════════════════════════════════ +#define ADDR_AUTO_BRIGHTNESS_ENABLED 370 // Auto-Brightness aktiviert (1 byte) +#define ADDR_AUTO_BRIGHTNESS_MIN_ADC 371 // Min ADC-Wert (2 bytes uint16_t) +#define ADDR_AUTO_BRIGHTNESS_MAX_ADC 373 // Max ADC-Wert (2 bytes uint16_t) +#define ADDR_AUTO_BRIGHTNESS_MIN 375 // Min Helligkeit (1 byte uint8_t) +#define ADDR_AUTO_BRIGHTNESS_MAX 376 // Max Helligkeit (1 byte uint8_t) + +// ════════════════════════════════════════════════════════════════ +// SPECIAL WORD & MINUTE LEDS ADRESSEN (93 bytes frei: 377-511) +// ════════════════════════════════════════════════════════════════ +#define ADDR_SPECIAL_WORD_1 377 // Erstes Spezialwort (12 bytes) +#define ADDR_SPECIAL_WORD_2 389 // Zweites Spezialwort (12 bytes) +#define ADDR_SPECIAL_WORD_3 401 // Drittes Spezialwort (12 bytes) +#define ADDR_MINUTE_LEDS 413 // 4 Minuten-LEDs Positionen (4 bytes) +#define ADDR_SPECIAL_WORD_SET 417 // Flag: Custom Special Words (1 byte) +#define ADDR_MINUTE_LEDS_SET 418 // Flag: Custom Minute LEDs (1 byte) +#define ADDR_SPECIAL_WORD_INTERVAL 419 // Anzeigeintervall in Minuten (1 byte) + +#define STA_ENABLED_MAGIC 0xAA +#define NTP_ENABLED_MAGIC 0xBB +#define AUTO_BRIGHTNESS_MAGIC 0xCC +#define SPECIAL_WORD_MAGIC 0xDD +#define MINUTE_LEDS_MAGIC 0xEE + +#define RUNNING_FLAG_MAGIC 0x42 // Magic Byte +#define MAGIC_BYTE_INIT 0xA5 + +#endif \ No newline at end of file diff --git a/include/extern.inc b/include/extern.inc new file mode 100644 index 0000000..f275c50 --- /dev/null +++ b/include/extern.inc @@ -0,0 +1,9 @@ +#ifndef _EXTERN_INC_ + #define _EXTERN_INC_ + + extern unsigned long savedTime; + extern void showLEDs(); + extern uint16_t bootCounter; + //extern CRGB leds[]; + //extern RTC_DS1307 rtc; +#endif diff --git a/include/html.h b/include/html.h new file mode 100644 index 0000000..5b77349 --- /dev/null +++ b/include/html.h @@ -0,0 +1,1332 @@ +// Auto-generiert von html_to_header.py +// Original-Grâße: 120778 Bytes +// Komprimierte Grâße: 21032 Bytes +// Kompression: 82.6% + +#ifndef HTML_H +#define HTML_H + +#include + +// GZIP-komprimierte HTML-Seite +const uint8_t HTML_PAGE_GZIP[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x50, 0x70, 0x76, 0x69, 0x02, 0xff, 0xed, 0xbd, 0x6b, 0x73, 0x1b, 0x49, + 0x92, 0x20, 0xf8, 0x5d, 0xbf, 0x22, 0xc4, 0xea, 0x2a, 0x00, 0x5d, 0x00, 0x08, 0x80, 0x0f, 0x51, + 0xa4, 0x48, 0x19, 0xc5, 0x87, 0xc4, 0x16, 0x45, 0x71, 0x09, 0x4a, 0xda, 0xad, 0xc7, 0x74, 0x27, + 0x80, 0x00, 0x90, 0xc5, 0x44, 0x26, 0x2e, 0x33, 0x41, 0x8a, 0xac, 0xd6, 0xd9, 0x7c, 0xe8, 0x1e, + 0x5b, 0xdb, 0x1d, 0xeb, 0xde, 0xeb, 0xee, 0xe9, 0x31, 0x6b, 0xab, 0xb1, 0xea, 0x39, 0xab, 0xb5, + 0x3d, 0xb3, 0xbb, 0xb3, 0x9d, 0xb1, 0x35, 0x2b, 0xdb, 0xb3, 0xab, 0xbd, 0x2f, 0xfc, 0x27, 0xfa, + 0x03, 0xdb, 0x3f, 0xe1, 0xdc, 0x23, 0x22, 0x33, 0x23, 0x33, 0x23, 0x32, 0x13, 0x20, 0xa4, 0x52, + 0xa9, 0x8b, 0x7a, 0x10, 0xc8, 0x8c, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0xf7, 0xf0, 0xf0, 0xb8, + 0x77, 0x7b, 0xf7, 0xe9, 0xce, 0xe9, 0xbf, 0x3b, 0xde, 0x23, 0x43, 0x7f, 0x64, 0x6d, 0xdd, 0xba, + 0x17, 0xfc, 0xa2, 0x46, 0x6f, 0xeb, 0x16, 0x81, 0x9f, 0x7b, 0x23, 0xea, 0x1b, 0xa4, 0x3b, 0x34, + 0x5c, 0x8f, 0xfa, 0x9b, 0x0b, 0xcf, 0x4e, 0xf7, 0x6b, 0x6b, 0x0b, 0xf2, 0x2b, 0xdb, 0x18, 0xd1, + 0xcd, 0x85, 0x73, 0x93, 0x5e, 0x8c, 0x1d, 0xd7, 0x5f, 0x20, 0x5d, 0xc7, 0xf6, 0xa9, 0x0d, 0x45, + 0x2f, 0xcc, 0x9e, 0x3f, 0xdc, 0xec, 0xd1, 0x73, 0xb3, 0x4b, 0x6b, 0xec, 0x4b, 0x95, 0x98, 0xb6, + 0xe9, 0x9b, 0x86, 0x55, 0xf3, 0xba, 0x86, 0x45, 0x37, 0x9b, 0xf5, 0x46, 0x00, 0xca, 0x37, 0x7d, + 0x8b, 0x6e, 0xed, 0x40, 0x33, 0x0f, 0x5d, 0x63, 0x3c, 0x24, 0x8f, 0x1d, 0xbb, 0x6f, 0x0e, 0x26, + 0xae, 0xe1, 0x9b, 0x8e, 0x7d, 0x6f, 0x91, 0xbf, 0xe6, 0x45, 0x3d, 0xff, 0x32, 0xf8, 0x8c, 0x3f, + 0xeb, 0xae, 0xe3, 0xf8, 0xe4, 0xcb, 0xf0, 0x3b, 0xfe, 0xd4, 0x6a, 0x9d, 0x41, 0x6d, 0xe0, 0x1a, + 0x3d, 0x13, 0x30, 0xa9, 0x79, 0xbe, 0xe1, 0xfa, 0xeb, 0xe4, 0x83, 0xd5, 0xd5, 0x3b, 0x94, 0x1a, + 0x1b, 0x19, 0x25, 0xa9, 0xdd, 0x83, 0x72, 0x77, 0x56, 0x97, 0x3b, 0x46, 0x2b, 0x59, 0x0e, 0xfb, + 0x65, 0x98, 0x36, 0x75, 0xa1, 0xc6, 0x3a, 0xb9, 0x18, 0x9a, 0x3e, 0x4d, 0x16, 0xf1, 0xe9, 0x4b, + 0xbf, 0x36, 0x76, 0xcd, 0x91, 0xe1, 0x5e, 0x02, 0x9c, 0xa5, 0xa5, 0x25, 0x65, 0x09, 0x8f, 0x02, + 0xac, 0x1e, 0x2f, 0xb3, 0xb2, 0xb2, 0xa2, 0x2c, 0xe3, 0x53, 0x17, 0x08, 0xc5, 0x8a, 0xac, 0xae, + 0xae, 0xa6, 0x70, 0x76, 0xdc, 0x1e, 0x20, 0xd2, 0x75, 0x2c, 0xc7, 0x85, 0x12, 0xbd, 0x5e, 0x2f, + 0x59, 0x62, 0xe8, 0x9c, 0x0b, 0x4c, 0x3f, 0xe8, 0xaf, 0xe0, 0x9f, 0x64, 0x01, 0xd3, 0xee, 0x3b, + 0xfc, 0x3d, 0x5d, 0xa3, 0x46, 0x3f, 0xd5, 0x84, 0xd1, 0xed, 0x22, 0x45, 0x82, 0x26, 0xd4, 0xb4, + 0x33, 0xed, 0xf1, 0xc4, 0xd7, 0xd3, 0xc3, 0x1b, 0x1a, 0x3d, 0xe7, 0x62, 0x9d, 0xb8, 0x83, 0x8e, + 0x51, 0x6e, 0x54, 0xd9, 0x9f, 0xfa, 0x52, 0x25, 0x2a, 0xf6, 0xea, 0x56, 0xf8, 0xb1, 0xe3, 0xf4, + 0x2e, 0xeb, 0x40, 0x94, 0xb3, 0xda, 0xc8, 0xe9, 0xd1, 0x42, 0xe3, 0xd9, 0x34, 0x9a, 0x46, 0x8b, + 0xe6, 0x8f, 0x67, 0x73, 0xb5, 0xd5, 0x5c, 0xa2, 0xd9, 0xe3, 0xf9, 0x41, 0xa3, 0xbf, 0xb4, 0xbc, + 0xda, 0xc8, 0x19, 0x51, 0xda, 0xc0, 0x3f, 0xb9, 0x83, 0xda, 0x69, 0xe0, 0x9f, 0xbc, 0x71, 0xbd, + 0x7b, 0xf7, 0x6e, 0xce, 0xb8, 0x2e, 0x2f, 0x2f, 0x67, 0x8c, 0x6b, 0xd3, 0x58, 0xee, 0xdd, 0x31, + 0xf4, 0xe3, 0xda, 0x34, 0x96, 0x8c, 0x15, 0xe3, 0x86, 0xe3, 0xaa, 0x21, 0xb2, 0x72, 0x64, 0x57, + 0xf5, 0x23, 0x9b, 0x18, 0xcf, 0x3e, 0xd0, 0xbe, 0xd6, 0x37, 0x46, 0xa6, 0x05, 0x84, 0xd8, 0x76, + 0x41, 0x22, 0x54, 0x89, 0x67, 0xd8, 0x1e, 0x50, 0xd1, 0x35, 0xfb, 0xf1, 0xc6, 0x46, 0xc6, 0x4b, + 0x2e, 0x3d, 0xd6, 0xc9, 0x4a, 0xa3, 0x31, 0x7e, 0x99, 0x7c, 0xeb, 0x0e, 0x4c, 0x1b, 0x5f, 0x8d, + 0x5f, 0x12, 0x63, 0xe2, 0x3b, 0xf1, 0xd7, 0x63, 0xa3, 0xd7, 0x33, 0x6d, 0xe8, 0x47, 0x2b, 0x55, + 0xb3, 0x63, 0x74, 0xcf, 0x06, 0xae, 0x33, 0x41, 0x16, 0xb1, 0x80, 0x0f, 0x0c, 0x37, 0xe4, 0x9b, + 0x72, 0x73, 0x69, 0xa5, 0x47, 0x07, 0x55, 0x72, 0x6e, 0xb8, 0x65, 0x05, 0xef, 0x55, 0x48, 0xe3, + 0x43, 0xd5, 0x4b, 0x60, 0xb7, 0x0a, 0x69, 0x36, 0x1a, 0x1f, 0x56, 0xe2, 0x4d, 0xf9, 0x2e, 0xf4, + 0xcd, 0x44, 0x49, 0xb6, 0x2e, 0x35, 0x4b, 0x60, 0x2e, 0x78, 0x84, 0x1a, 0x1e, 0x95, 0xc9, 0x16, + 0x7c, 0xaa, 0x87, 0xfc, 0x99, 0xa0, 0x9d, 0x8c, 0x37, 0x47, 0x41, 0xe6, 0xe4, 0x8a, 0xa6, 0xff, + 0x4b, 0xe9, 0xfe, 0x73, 0x5e, 0x43, 0xdc, 0x27, 0xde, 0x3a, 0x69, 0xae, 0xa4, 0x0b, 0xbc, 0x0c, + 0xc7, 0xb9, 0x41, 0xd6, 0x80, 0xbe, 0x48, 0x44, 0xd1, 0x26, 0x7f, 0x91, 0x6c, 0xcd, 0x09, 0x7a, + 0xe9, 0x52, 0x0b, 0x24, 0xf7, 0x39, 0xbd, 0x39, 0x1d, 0x86, 0xcd, 0x44, 0xff, 0x05, 0xe7, 0x72, + 0x34, 0xe4, 0xf9, 0x99, 0x24, 0x3a, 0xbe, 0x32, 0x2c, 0x73, 0x00, 0x8d, 0x21, 0xcb, 0x53, 0x57, + 0xc5, 0x39, 0x30, 0xe5, 0x7c, 0xdf, 0x19, 0xa9, 0x08, 0x24, 0x63, 0xcb, 0x1a, 0xcd, 0x41, 0xb4, + 0x95, 0x8b, 0x68, 0x28, 0x23, 0x12, 0xa8, 0xb2, 0xe9, 0xe0, 0x99, 0x57, 0x14, 0x86, 0x61, 0x4d, + 0xcd, 0xe1, 0x35, 0xdf, 0x19, 0x03, 0x17, 0xaf, 0x68, 0xde, 0x06, 0xbd, 0x50, 0x8d, 0x22, 0x1b, + 0xe6, 0xa0, 0x40, 0x0b, 0x46, 0xd0, 0x73, 0x2c, 0xb3, 0x27, 0xf0, 0x92, 0xc5, 0x81, 0x9a, 0x77, + 0xc2, 0xaa, 0x29, 0xd0, 0xdd, 0x89, 0xeb, 0x61, 0x0f, 0xc7, 0x8e, 0x99, 0x26, 0x6f, 0xcf, 0xf4, + 0xc6, 0x96, 0x01, 0xd3, 0xbb, 0x6f, 0xd1, 0x44, 0xbd, 0x2f, 0x26, 0x9e, 0x6f, 0xf6, 0x2f, 0x6b, + 0x42, 0x53, 0x58, 0x27, 0xde, 0xd8, 0x00, 0x15, 0xa1, 0x43, 0xfd, 0x0b, 0x4a, 0xed, 0x78, 0x59, + 0x36, 0x80, 0x35, 0x58, 0x5b, 0x46, 0x9e, 0x7a, 0x18, 0x27, 0x20, 0x32, 0x80, 0xb0, 0x16, 0xed, + 0x02, 0x20, 0xdb, 0xb1, 0xe9, 0x0d, 0x07, 0x71, 0x9d, 0x49, 0xd8, 0xdc, 0x39, 0x17, 0xc8, 0x61, + 0x0d, 0xcd, 0x2c, 0xda, 0x07, 0x74, 0x9a, 0x29, 0x9e, 0x0a, 0xde, 0xbb, 0xe6, 0x60, 0xa8, 0x2c, + 0x20, 0xc6, 0x93, 0xd7, 0xaf, 0x69, 0xdf, 0x8b, 0xfa, 0x8a, 0x02, 0x89, 0x69, 0x1d, 0x63, 0x27, + 0x49, 0xba, 0xf8, 0x43, 0x3a, 0xa2, 0xc0, 0x54, 0x83, 0x81, 0x95, 0x5c, 0x6c, 0xa3, 0x59, 0x6c, + 0x74, 0x80, 0x55, 0x26, 0xc9, 0x75, 0x9d, 0x71, 0x62, 0x9a, 0xd1, 0x82, 0x2e, 0xa5, 0x5e, 0x08, + 0xe1, 0xbd, 0x9c, 0x42, 0x75, 0x48, 0x79, 0x95, 0xe5, 0x4c, 0xd9, 0xcc, 0x46, 0x70, 0x6c, 0xb8, + 0x30, 0xf6, 0xaa, 0x9e, 0xaa, 0x06, 0x7d, 0x56, 0xc6, 0xcc, 0x65, 0xb6, 0x14, 0xe7, 0xaa, 0x0a, + 0xc9, 0x2c, 0xc7, 0x3e, 0xf7, 0x1d, 0x77, 0xa4, 0x62, 0xbb, 0x98, 0x80, 0x6e, 0xe4, 0x0e, 0x93, + 0x92, 0x33, 0xc3, 0x06, 0x60, 0x16, 0xa1, 0x4e, 0x5d, 0x6e, 0xd6, 0x9b, 0x2b, 0x15, 0xe2, 0x3a, + 0xbe, 0xe1, 0xd3, 0x72, 0xab, 0x01, 0xcb, 0x58, 0x25, 0x9f, 0x01, 0xbc, 0xf3, 0x41, 0x02, 0xee, + 0x0c, 0x83, 0xd6, 0x37, 0x2d, 0xab, 0x80, 0x5c, 0xf6, 0x7c, 0xd7, 0x39, 0xa3, 0x85, 0x0b, 0x06, + 0x6b, 0x7f, 0x4b, 0xf9, 0x12, 0x17, 0xee, 0xae, 0x01, 0xfc, 0xc8, 0x98, 0x45, 0x5b, 0xe4, 0x0b, + 0x07, 0x75, 0x04, 0x45, 0x19, 0x79, 0xac, 0x10, 0xff, 0x68, 0x98, 0xaa, 0xa2, 0x7e, 0xce, 0x2a, + 0x0d, 0x22, 0x1d, 0x6b, 0xab, 0x67, 0x92, 0x24, 0xd7, 0x5b, 0x99, 0xcb, 0x4b, 0x26, 0x9b, 0x68, + 0x5b, 0x03, 0x15, 0xc1, 0xb2, 0x8c, 0xb1, 0x47, 0x7b, 0x7a, 0xa6, 0x10, 0x8c, 0x50, 0xbb, 0xab, + 0xe7, 0x84, 0x00, 0xa8, 0x60, 0xea, 0x04, 0x2c, 0x54, 0xbf, 0x82, 0x31, 0x07, 0xd5, 0x26, 0xad, + 0x81, 0x21, 0x53, 0xf6, 0x2d, 0x54, 0x12, 0x86, 0x66, 0xaf, 0x97, 0x94, 0xdf, 0x72, 0x2f, 0x23, + 0x50, 0x32, 0x99, 0x1d, 0x10, 0xfe, 0xa6, 0x7f, 0xa9, 0x9b, 0x20, 0xe2, 0x35, 0x34, 0x5e, 0x04, + 0x79, 0x2d, 0x49, 0xe4, 0x6e, 0x34, 0x34, 0x2d, 0xa8, 0xa7, 0x20, 0xd2, 0xb1, 0x86, 0xc2, 0x68, + 0x9c, 0x02, 0x19, 0x5b, 0x7a, 0xe3, 0x6b, 0x73, 0x04, 0xc0, 0x32, 0x3a, 0xd4, 0x4a, 0x54, 0x0d, + 0x65, 0x50, 0xc7, 0x72, 0xba, 0x67, 0x99, 0x2b, 0x7a, 0x4a, 0x1f, 0x60, 0x4c, 0x75, 0x21, 0xba, + 0xd2, 0x71, 0xac, 0x04, 0x47, 0x17, 0xd7, 0x3a, 0xa6, 0x5c, 0x1a, 0x99, 0x4d, 0xf0, 0xa9, 0x7f, + 0x39, 0x06, 0x6b, 0x9f, 0x15, 0x5f, 0xf8, 0x5c, 0x2d, 0x33, 0x50, 0x01, 0x56, 0xcb, 0x8c, 0x55, + 0xcd, 0x6a, 0x95, 0x56, 0x4b, 0x64, 0x43, 0xa8, 0x52, 0x7c, 0x81, 0xcb, 0x14, 0xfe, 0xea, 0xae, + 0x00, 0x11, 0x06, 0x74, 0xfa, 0xae, 0xa4, 0x9a, 0x85, 0x21, 0xe9, 0x9c, 0x99, 0xa0, 0x71, 0x8e, + 0xc7, 0x60, 0x4a, 0x18, 0x76, 0x97, 0xaa, 0x56, 0xa6, 0xec, 0xb7, 0x69, 0x35, 0xa3, 0x38, 0x1d, + 0x52, 0xcb, 0xae, 0x33, 0xf1, 0x51, 0xf4, 0x25, 0xdb, 0xc9, 0xa4, 0xc2, 0xfa, 0x7a, 0xd0, 0x0d, + 0x0f, 0xc6, 0x02, 0xa0, 0xfb, 0xc3, 0xc9, 0xa8, 0x93, 0x34, 0xc6, 0xe7, 0xd0, 0xd3, 0x40, 0xa8, + 0xaf, 0xe8, 0x96, 0x97, 0xf4, 0x9b, 0x34, 0x71, 0x32, 0x74, 0xd7, 0x4c, 0x0d, 0x20, 0x49, 0x39, + 0x79, 0x8c, 0xd5, 0xe4, 0xe9, 0x81, 0x0c, 0xf5, 0x4d, 0x58, 0x2f, 0x61, 0xba, 0x1a, 0xd6, 0xc2, + 0xe7, 0xd5, 0xd8, 0x5b, 0x1b, 0x68, 0x44, 0xdd, 0xe4, 0x53, 0x9c, 0x7b, 0xc9, 0x67, 0x63, 0xc3, + 0xf3, 0x2e, 0xa0, 0x75, 0x7c, 0xce, 0x75, 0xd6, 0xa2, 0x7c, 0x17, 0xea, 0x09, 0xcd, 0xd6, 0x5b, + 0x98, 0x43, 0xb2, 0x41, 0xb2, 0xaa, 0xb4, 0x0b, 0xcd, 0x2b, 0x86, 0x4d, 0x68, 0x5d, 0xe4, 0x0e, + 0x56, 0xe0, 0x52, 0xa8, 0x6c, 0xcc, 0x66, 0xcd, 0xe5, 0x98, 0x8e, 0xd5, 0xa4, 0x08, 0xab, 0x12, + 0xb9, 0xff, 0xd9, 0xa2, 0x0d, 0xdb, 0x05, 0x05, 0xd3, 0x28, 0x3a, 0x18, 0x31, 0xf7, 0x45, 0x69, + 0xc7, 0x99, 0xb8, 0x26, 0x28, 0x66, 0x47, 0xf4, 0xa2, 0x54, 0x25, 0x23, 0xc7, 0x76, 0x98, 0x3d, + 0x33, 0x05, 0x45, 0xa3, 0xc1, 0x7d, 0x1b, 0x02, 0xd2, 0xa5, 0x1c, 0x8f, 0xf4, 0xac, 0xb4, 0xa8, + 0xef, 0xa3, 0x35, 0x85, 0x0b, 0x22, 0x73, 0x9a, 0x24, 0xab, 0xa2, 0x40, 0x89, 0xb4, 0x81, 0xfa, + 0xca, 0x1b, 0x1e, 0xf4, 0x22, 0x9c, 0xf6, 0x46, 0x19, 0x23, 0xe1, 0x94, 0xd4, 0xf0, 0x49, 0x96, + 0x27, 0xa9, 0xc1, 0xfc, 0x48, 0x92, 0x0d, 0x43, 0x5a, 0xcb, 0xe3, 0x97, 0x55, 0xe6, 0xdb, 0x63, + 0x42, 0xae, 0x52, 0x8d, 0xc1, 0x4a, 0x52, 0x5b, 0x86, 0x75, 0xf7, 0x26, 0xc0, 0x32, 0x07, 0x24, + 0xea, 0x41, 0xa0, 0xae, 0xae, 0xa0, 0xbb, 0x47, 0xa3, 0xd1, 0x20, 0x55, 0xd6, 0x6d, 0xc7, 0x2f, + 0x47, 0xa4, 0xa9, 0xcc, 0x95, 0x36, 0xdc, 0xcd, 0x3a, 0x3f, 0xf2, 0x14, 0x83, 0xa7, 0x70, 0x61, + 0x4f, 0x47, 0x97, 0x3a, 0x5b, 0x45, 0x6b, 0xe7, 0x86, 0x35, 0x49, 0x5a, 0x02, 0x79, 0xce, 0x28, + 0xd9, 0x52, 0x58, 0xbe, 0x89, 0xc6, 0x97, 0xb1, 0x26, 0xca, 0x8e, 0xa4, 0xb8, 0xa0, 0x91, 0xcd, + 0x42, 0x5c, 0xe5, 0x9c, 0x31, 0xce, 0xa7, 0x2a, 0x7c, 0xa3, 0x9e, 0x2f, 0xbe, 0xe9, 0x34, 0xd8, + 0x19, 0xac, 0xe8, 0x4c, 0x9f, 0x95, 0x8c, 0x8b, 0xb6, 0xf5, 0x2c, 0x81, 0x59, 0xd4, 0x55, 0x93, + 0x27, 0x26, 0x67, 0x72, 0xda, 0x4a, 0xe4, 0x4b, 0xe8, 0x57, 0x3d, 0xd3, 0xc1, 0x55, 0x3f, 0xd6, + 0x29, 0x45, 0x09, 0xb5, 0x8d, 0xa1, 0x75, 0x17, 0x05, 0x7a, 0x94, 0xd6, 0x4c, 0x6f, 0x15, 0x18, + 0x68, 0x6e, 0xa2, 0x24, 0x70, 0x53, 0x99, 0x2d, 0x81, 0xb3, 0xbd, 0x91, 0xc1, 0x9f, 0x36, 0x98, + 0x4b, 0x86, 0x35, 0xb5, 0x4a, 0x5e, 0x1f, 0x19, 0xf6, 0xc4, 0xb0, 0x6a, 0x88, 0x96, 0x8e, 0xd5, + 0xd2, 0x0b, 0x56, 0x8c, 0xa5, 0x57, 0xf4, 0xcb, 0xea, 0xca, 0x3b, 0xc6, 0x25, 0x48, 0x67, 0x14, + 0x9b, 0x5e, 0xf1, 0x59, 0x85, 0x4f, 0x6a, 0x3d, 0xd3, 0xe5, 0xf6, 0x2e, 0xb3, 0xd8, 0x26, 0xa3, + 0x84, 0xb1, 0x3d, 0x30, 0x32, 0xa6, 0x76, 0x67, 0x02, 0xd3, 0xcd, 0x9e, 0x5e, 0xdf, 0x5c, 0x9b, + 0x65, 0xe3, 0x44, 0xec, 0x2b, 0xb1, 0x7d, 0x12, 0xb1, 0x9f, 0xaa, 0xda, 0x18, 0x11, 0xa2, 0x4b, + 0x25, 0x79, 0xb5, 0xce, 0xbd, 0x69, 0x74, 0xd7, 0xa9, 0x8d, 0xe7, 0x2c, 0xb3, 0x41, 0x2f, 0x3f, + 0xb3, 0xdc, 0x39, 0x2d, 0x4f, 0x3f, 0x18, 0x79, 0x1e, 0x3d, 0xf6, 0xd1, 0x02, 0xdb, 0xe3, 0xdf, + 0x95, 0x6b, 0xa0, 0x8b, 0x55, 0xb2, 0xf6, 0x67, 0x60, 0xd5, 0x60, 0xb6, 0x41, 0x7c, 0x4b, 0xae, + 0x55, 0xd1, 0xb7, 0x5e, 0x0f, 0x1d, 0x03, 0x6a, 0xa6, 0xc8, 0xd8, 0x4d, 0xc3, 0xee, 0x2b, 0x84, + 0x8e, 0x4c, 0xfa, 0xe5, 0x2c, 0xb6, 0x89, 0xef, 0x7d, 0x4a, 0x33, 0x03, 0x77, 0x2f, 0x73, 0x7d, + 0xef, 0x62, 0x8b, 0xb3, 0x52, 0x7c, 0xab, 0x2f, 0x87, 0x65, 0xb2, 0xb7, 0x58, 0x44, 0x6d, 0xee, + 0x92, 0x5f, 0x2e, 0xbe, 0x7f, 0x32, 0x93, 0x68, 0x60, 0x04, 0x18, 0x2e, 0xa9, 0x97, 0x01, 0x86, + 0x60, 0xe3, 0xe6, 0x06, 0x54, 0x01, 0xcd, 0x97, 0xaf, 0x0f, 0x42, 0x1e, 0x15, 0x97, 0x52, 0xd3, + 0x6c, 0xed, 0x14, 0x56, 0x03, 0x10, 0x13, 0x60, 0xf6, 0xa4, 0x73, 0x15, 0x1a, 0x8f, 0x79, 0x05, + 0xf3, 0xf4, 0x2c, 0x15, 0x48, 0xf4, 0xf1, 0xda, 0x83, 0xd9, 0xbd, 0x73, 0x2b, 0x85, 0xa7, 0x40, + 0xb2, 0x75, 0x95, 0x96, 0x28, 0x6b, 0x81, 0x29, 0xdb, 0xab, 0xa8, 0x9a, 0x97, 0x21, 0xe2, 0x22, + 0x1c, 0x3e, 0xe8, 0xb9, 0x66, 0xdf, 0x3f, 0x48, 0xcf, 0xb6, 0xac, 0x35, 0x73, 0x8a, 0x59, 0x94, + 0xae, 0xad, 0x59, 0xc5, 0x25, 0xb2, 0x30, 0x94, 0xf2, 0xe8, 0x32, 0xb5, 0x48, 0xcf, 0x72, 0xa7, + 0xa6, 0x1a, 0xf7, 0x7c, 0xc3, 0x9f, 0x78, 0x19, 0xad, 0x2f, 0x67, 0x8e, 0x4a, 0x2c, 0xc2, 0xe3, + 0x86, 0xf3, 0x8f, 0x23, 0x34, 0x70, 0x9c, 0x5e, 0x86, 0x3c, 0xfc, 0xa0, 0xbb, 0x46, 0x57, 0xbb, + 0x77, 0x0b, 0xc9, 0xab, 0x0f, 0x96, 0xbb, 0x46, 0x7f, 0xa5, 0x91, 0xd1, 0xd8, 0x85, 0xe1, 0xda, + 0x66, 0x6a, 0x36, 0xc4, 0xda, 0xeb, 0xf7, 0xfb, 0x77, 0xbb, 0xcb, 0xc5, 0xda, 0xeb, 0xf7, 0xbb, + 0xcd, 0xc6, 0x9d, 0x8c, 0xf6, 0x3a, 0x46, 0x2f, 0xbb, 0xad, 0x6e, 0xaf, 0xd7, 0x2a, 0xd8, 0xd6, + 0xf2, 0xf2, 0xd2, 0xd2, 0xaa, 0xba, 0x2d, 0xea, 0x1b, 0xa6, 0xe5, 0x65, 0x48, 0xd4, 0x66, 0xe6, + 0x2c, 0x5e, 0x9a, 0x79, 0xc8, 0x83, 0xcd, 0x5e, 0x4d, 0x23, 0xa2, 0x2f, 0xfc, 0x6d, 0x71, 0xf7, + 0x4e, 0x06, 0x23, 0x4d, 0xe3, 0xda, 0x08, 0xe9, 0xd2, 0x33, 0xcf, 0x35, 0x6a, 0x3e, 0xda, 0xba, + 0x0d, 0x4d, 0x6c, 0x0a, 0xc0, 0x87, 0x85, 0x86, 0x62, 0xd8, 0xdf, 0x1b, 0x5d, 0x1d, 0xb2, 0xad, + 0x55, 0x8e, 0x47, 0x7a, 0x6d, 0x08, 0x76, 0x2f, 0xd7, 0x3e, 0x2c, 0xbc, 0xe3, 0x9c, 0xe3, 0x4d, + 0x9f, 0xc1, 0x0f, 0x17, 0x5b, 0xff, 0x8b, 0x8f, 0x0b, 0x86, 0x5a, 0x0e, 0x30, 0x06, 0xb2, 0x66, + 0x76, 0xf5, 0x66, 0xb7, 0x69, 0x33, 0x5f, 0x9c, 0x62, 0x85, 0x0a, 0x4c, 0xc2, 0x35, 0xad, 0x49, + 0xb8, 0x96, 0x1d, 0x54, 0x90, 0x7a, 0x7d, 0x8e, 0xec, 0xdd, 0x05, 0xe3, 0x4c, 0x2c, 0xac, 0x23, + 0xb3, 0xd7, 0xb3, 0x8a, 0x20, 0x5f, 0xf7, 0xc0, 0x14, 0xb4, 0xd4, 0x63, 0x93, 0x5e, 0xe0, 0x42, + 0xfc, 0x5a, 0xd9, 0xf8, 0xad, 0x6a, 0x98, 0x21, 0x70, 0xae, 0x8b, 0xdd, 0x51, 0x15, 0xf9, 0x82, + 0xb6, 0x97, 0xb5, 0x6d, 0x2f, 0xab, 0x77, 0xb5, 0xd3, 0x66, 0x48, 0xb0, 0x8d, 0x9d, 0x0e, 0xe3, + 0xd3, 0xee, 0x5b, 0x73, 0x5c, 0xef, 0x2d, 0x8a, 0x00, 0xd6, 0x7b, 0x8b, 0x3c, 0xca, 0xf6, 0x1e, + 0xfa, 0xd3, 0x44, 0x6c, 0xeb, 0xed, 0x5a, 0x8d, 0xb4, 0x9f, 0x3f, 0x24, 0xbb, 0xb4, 0xcf, 0x42, + 0x65, 0x1d, 0xdb, 0x23, 0xb5, 0x5a, 0x10, 0xf8, 0x7a, 0x3e, 0x20, 0xac, 0xee, 0xe6, 0x42, 0x7c, + 0x35, 0x5d, 0x88, 0xa2, 0x61, 0xef, 0xf5, 0x68, 0xdf, 0xdb, 0x8a, 0xe1, 0xc3, 0x80, 0x46, 0x91, + 0xb5, 0x07, 0x48, 0x96, 0x00, 0x66, 0x58, 0xc6, 0xbb, 0x1c, 0xc1, 0xda, 0x49, 0xcc, 0xde, 0xe6, + 0x02, 0xd2, 0xad, 0x16, 0x0e, 0xe4, 0x02, 0xc1, 0x39, 0xfe, 0xc0, 0x79, 0xb9, 0xb9, 0xd0, 0x40, + 0x33, 0x63, 0x19, 0xfe, 0x2e, 0x6c, 0xa5, 0x9c, 0x68, 0xf7, 0xd0, 0x30, 0xe5, 0xf4, 0xdd, 0x5c, + 0x80, 0x12, 0x82, 0xa2, 0xfc, 0x33, 0xd2, 0x70, 0x73, 0x81, 0x85, 0x12, 0x36, 0x8d, 0x05, 0xe2, + 0x02, 0xac, 0xd6, 0xc2, 0xa2, 0x02, 0xc8, 0x20, 0x28, 0x0a, 0xeb, 0x8c, 0xa2, 0x91, 0xa8, 0x21, + 0x06, 0x81, 0x5c, 0xb2, 0xff, 0x45, 0xa3, 0x4b, 0x51, 0x9b, 0x4b, 0x2a, 0xe0, 0xb1, 0xca, 0xab, + 0x37, 0xa9, 0xdc, 0x5c, 0xbe, 0x51, 0xed, 0xb5, 0x9b, 0xd4, 0x6e, 0xdd, 0xa8, 0xdb, 0xbc, 0xf2, + 0xd2, 0xf2, 0x4d, 0x88, 0x36, 0x6b, 0xed, 0x66, 0xe3, 0x66, 0xd5, 0x97, 0x6f, 0x54, 0x7d, 0x49, + 0x54, 0xbf, 0x3b, 0x63, 0xf5, 0xb5, 0x69, 0xab, 0xdf, 0x5b, 0x1c, 0x64, 0x31, 0x78, 0x0b, 0x06, + 0xb2, 0xd0, 0x60, 0xad, 0xde, 0x64, 0xac, 0x56, 0x6f, 0x34, 0x54, 0xab, 0xb3, 0x77, 0x16, 0x84, + 0x1c, 0x13, 0x28, 0x5b, 0xb7, 0xd2, 0xa2, 0xa8, 0x3d, 0xb1, 0xb9, 0x10, 0x2a, 0xaf, 0x11, 0xd7, + 0xb8, 0xf4, 0x2a, 0xf9, 0xd2, 0xc8, 0x9b, 0xd8, 0x09, 0x39, 0xd4, 0x5a, 0x86, 0xbf, 0x2a, 0x39, + 0xd4, 0x35, 0xdd, 0xae, 0x45, 0x49, 0x17, 0xbb, 0x01, 0x14, 0xec, 0x5e, 0xf2, 0xdf, 0x2e, 0x48, + 0x22, 0x25, 0xe6, 0xb8, 0x8c, 0x92, 0x97, 0x4d, 0x5e, 0xea, 0xb2, 0xc9, 0xc8, 0xfe, 0xb2, 0x25, + 0xbe, 0xb6, 0x8a, 0xd7, 0x6a, 0xc4, 0xab, 0xb5, 0x5a, 0xd9, 0xf5, 0x96, 0x79, 0xb5, 0xa6, 0x68, + 0x6d, 0x95, 0xd7, 0x6a, 0xe6, 0xd4, 0x62, 0xb2, 0x43, 0xaa, 0x86, 0xad, 0x16, 0xa9, 0xb7, 0x52, + 0x5f, 0x15, 0x0d, 0xf2, 0x4f, 0x58, 0xf7, 0x4e, 0xbd, 0xb1, 0xc2, 0x6b, 0xb3, 0x4f, 0xd9, 0xed, + 0xae, 0xd6, 0xef, 0xae, 0x88, 0xa6, 0xf9, 0x47, 0xd6, 0xd7, 0xb5, 0xfa, 0x52, 0x80, 0x38, 0xfb, + 0x58, 0x10, 0x07, 0x51, 0x2f, 0x8e, 0x04, 0x87, 0x5b, 0x14, 0x0b, 0x5e, 0x2f, 0x81, 0x04, 0x6b, + 0x61, 0xb1, 0x38, 0x23, 0x3e, 0x71, 0x1c, 0xbb, 0xe0, 0x72, 0x38, 0x82, 0xa2, 0x45, 0x39, 0x70, + 0x6c, 0xf8, 0x43, 0x02, 0x15, 0x9f, 0xb4, 0x9a, 0xa4, 0xd9, 0xaa, 0xdf, 0xb9, 0xbb, 0x7d, 0x97, + 0xdc, 0x85, 0x0a, 0x4d, 0xfc, 0xd3, 0xac, 0xc3, 0xd3, 0x25, 0x72, 0x07, 0xfe, 0x34, 0xd8, 0x9f, + 0xa0, 0xd0, 0xd5, 0x34, 0x98, 0xef, 0x5d, 0x52, 0x31, 0x85, 0xce, 0x4d, 0xcf, 0xec, 0x58, 0xb4, + 0xc0, 0x1c, 0xa2, 0x97, 0xb4, 0x26, 0x4a, 0x4f, 0xdf, 0x13, 0xc0, 0xd1, 0x5b, 0xaa, 0xdd, 0x01, + 0x4d, 0x9c, 0xfd, 0x47, 0xf8, 0x7f, 0x35, 0xe8, 0x48, 0x8d, 0x7d, 0xc0, 0xe7, 0xec, 0xbf, 0x2b, + 0xe5, 0x10, 0xea, 0x27, 0xe5, 0xd2, 0x6c, 0xdd, 0xe6, 0xa1, 0x66, 0x05, 0x7b, 0xcd, 0x0b, 0xbf, + 0xcb, 0x9d, 0x56, 0xca, 0x86, 0xe5, 0xf8, 0x1c, 0x87, 0xdf, 0x5a, 0x52, 0x45, 0x4f, 0x22, 0xf5, + 0x0f, 0xde, 0x9e, 0x0f, 0x04, 0x15, 0xef, 0xa1, 0x91, 0xd7, 0xb5, 0x40, 0x41, 0xc6, 0xd8, 0x2d, + 0x71, 0x5a, 0x40, 0xd6, 0x1a, 0xc5, 0xe6, 0x80, 0x28, 0x22, 0xc7, 0x86, 0x2e, 0x10, 0xc7, 0xee, + 0x5a, 0x66, 0xf7, 0x0c, 0x1e, 0xb3, 0x07, 0xa7, 0xf8, 0xb2, 0x5c, 0x59, 0x20, 0xec, 0x4c, 0xd6, + 0xe6, 0xc2, 0xae, 0xe1, 0x9e, 0xc1, 0x54, 0xea, 0x51, 0x32, 0x19, 0x79, 0xa0, 0x36, 0x5a, 0x60, + 0xda, 0x2d, 0x24, 0x47, 0x05, 0x34, 0x57, 0x1c, 0x12, 0x0e, 0x38, 0x53, 0xa4, 0xe7, 0x6a, 0xb8, + 0x21, 0xd0, 0x89, 0x47, 0xc9, 0xd0, 0xa5, 0x7d, 0x58, 0x4d, 0x43, 0x88, 0x69, 0x0a, 0x9d, 0x0f, + 0xf2, 0x71, 0x99, 0x66, 0x76, 0x27, 0x5b, 0x65, 0x75, 0xb3, 0x9b, 0xbd, 0xb7, 0xc8, 0xc9, 0x2b, + 0x3d, 0x19, 0x36, 0xb7, 0x18, 0x22, 0xc1, 0x98, 0xc4, 0xcc, 0xa6, 0x85, 0xad, 0x54, 0x2b, 0x91, + 0x3a, 0xbe, 0xb8, 0xc5, 0xa1, 0x47, 0x0a, 0xfd, 0x9e, 0x69, 0x7b, 0x3e, 0xb5, 0xac, 0x89, 0x3d, + 0xa0, 0x36, 0x18, 0x15, 0x4d, 0x69, 0xea, 0xb0, 0x81, 0xc7, 0xde, 0x7a, 0x63, 0xda, 0x35, 0x0d, + 0xeb, 0x05, 0xd8, 0x47, 0xde, 0x2e, 0x27, 0x6d, 0x48, 0x6a, 0x85, 0xd3, 0x34, 0xe5, 0x72, 0xcb, + 0x8e, 0x2b, 0x0c, 0x5d, 0x06, 0x6c, 0x63, 0xa0, 0xc1, 0x0f, 0x8a, 0x34, 0x36, 0xe2, 0xbe, 0xb9, + 0x95, 0x46, 0x03, 0xc6, 0x11, 0x58, 0xd4, 0x3c, 0x97, 0x31, 0x1c, 0xb6, 0x92, 0x1c, 0xd6, 0xe6, + 0x7b, 0x5c, 0xe5, 0x52, 0x10, 0xdc, 0x89, 0x4e, 0xf1, 0x52, 0x25, 0xc5, 0x54, 0x63, 0xc3, 0xde, + 0x7a, 0xfd, 0xab, 0xff, 0xe7, 0x7f, 0xfe, 0xf7, 0xdf, 0x92, 0x63, 0xd7, 0xf9, 0x82, 0x9e, 0xf9, + 0x35, 0xf4, 0x68, 0xba, 0x23, 0x71, 0x68, 0x90, 0x15, 0x48, 0xd7, 0x09, 0x88, 0x9e, 0x08, 0xdd, + 0x0d, 0x23, 0x47, 0x17, 0xb6, 0x5e, 0xff, 0xf1, 0xbb, 0x64, 0x6d, 0xa0, 0x6b, 0x6b, 0x4b, 0x41, + 0x56, 0x09, 0xc3, 0x85, 0x24, 0xe0, 0x20, 0xa0, 0x56, 0x82, 0x1c, 0xc7, 0x06, 0xa1, 0x88, 0x31, + 0xb8, 0xd9, 0xa6, 0x5a, 0x7c, 0x23, 0x2d, 0xb1, 0x15, 0x92, 0x3a, 0xe9, 0xc3, 0x1e, 0xaa, 0xbc, + 0xef, 0x2a, 0x7e, 0x1f, 0x2e, 0x05, 0x28, 0xc6, 0x37, 0x21, 0x12, 0x4d, 0x6a, 0x8f, 0x99, 0xb0, + 0xed, 0xa8, 0xd6, 0xca, 0x4a, 0x35, 0xf8, 0xc7, 0x4e, 0x00, 0xa6, 0x0e, 0x97, 0x30, 0xa4, 0x74, + 0x1a, 0xb1, 0x76, 0xa6, 0x10, 0xe6, 0x60, 0x08, 0x19, 0x19, 0x74, 0x6b, 0x1f, 0x9d, 0x34, 0x1d, + 0xe6, 0x29, 0xb0, 0xa9, 0xe7, 0x95, 0x1b, 0x15, 0x62, 0xda, 0xe8, 0xc0, 0x28, 0x37, 0x2b, 0x1b, + 0x85, 0x26, 0x96, 0x12, 0x85, 0x70, 0xb6, 0xd5, 0x04, 0xa7, 0x29, 0x54, 0xe0, 0xe1, 0x92, 0x6a, + 0x35, 0x89, 0x53, 0x2f, 0x98, 0x21, 0x1b, 0xc9, 0x50, 0xaa, 0x55, 0x7d, 0xe7, 0xd9, 0x16, 0xc5, + 0x96, 0x68, 0x97, 0x00, 0x95, 0xc9, 0x63, 0xc3, 0xb5, 0xc8, 0xe3, 0xeb, 0xef, 0x70, 0x87, 0xbe, + 0xdd, 0x1d, 0x4e, 0x80, 0x7b, 0x4d, 0x9b, 0x3c, 0xa0, 0xb6, 0x07, 0x00, 0x47, 0xe8, 0x60, 0x60, + 0x55, 0xee, 0x75, 0x5c, 0x0d, 0x4c, 0x43, 0xd0, 0x60, 0xe8, 0xfb, 0x63, 0x6f, 0x7d, 0x71, 0xf1, + 0xe2, 0xe2, 0xa2, 0x7e, 0x06, 0x40, 0xcf, 0x26, 0x14, 0x60, 0x7a, 0x0c, 0x64, 0xbd, 0x07, 0xb2, + 0xdf, 0x07, 0xac, 0xf1, 0xd8, 0xef, 0xcf, 0x3b, 0x96, 0x61, 0x9f, 0x85, 0x84, 0x0e, 0x4e, 0x0f, + 0xf6, 0xfb, 0xbd, 0x3b, 0x30, 0xab, 0xf9, 0xb6, 0x4b, 0x0f, 0xe4, 0x01, 0x3f, 0xaf, 0x1b, 0x0a, + 0xed, 0xbf, 0x7c, 0xfd, 0xdb, 0xff, 0x93, 0x68, 0x80, 0xdf, 0x5b, 0x34, 0xf4, 0x18, 0xfe, 0xe5, + 0xeb, 0xdf, 0xff, 0x9a, 0xfc, 0x0c, 0x43, 0x02, 0x5c, 0x60, 0xe2, 0xd6, 0xaa, 0x82, 0xdc, 0xe3, + 0xa4, 0xcc, 0x65, 0x72, 0xa5, 0xc8, 0xfc, 0x42, 0xef, 0xc2, 0x52, 0xb7, 0xb7, 0x91, 0xd8, 0xec, + 0x50, 0x6d, 0x70, 0x28, 0xa7, 0x49, 0x9e, 0xc7, 0x5b, 0x39, 0x8d, 0x96, 0xb3, 0xa7, 0xd1, 0x07, + 0xbd, 0xbb, 0x77, 0xee, 0x34, 0x56, 0x19, 0xcd, 0x7e, 0xf7, 0x2f, 0x64, 0xdf, 0xb4, 0x0d, 0xfb, + 0xca, 0xa4, 0x2e, 0x08, 0x75, 0xe0, 0xac, 0xe5, 0x22, 0x9c, 0xb5, 0x32, 0x1d, 0x63, 0x85, 0x4d, + 0x00, 0x4f, 0x4d, 0xdc, 0xee, 0x90, 0xb4, 0xc7, 0xd4, 0x06, 0x3d, 0x89, 0x80, 0x84, 0x84, 0x5f, + 0x37, 0x66, 0x9e, 0x45, 0xf6, 0xc9, 0x82, 0x67, 0xf6, 0x62, 0xdf, 0xa1, 0x48, 0x31, 0x98, 0x87, + 0xd4, 0xb4, 0x17, 0x53, 0x6c, 0xb5, 0x15, 0x30, 0xf9, 0xfe, 0xf5, 0xb7, 0x51, 0xb9, 0x90, 0xd5, + 0xcf, 0x18, 0xa7, 0x73, 0xc0, 0x84, 0xd6, 0x9f, 0xd7, 0x23, 0x0e, 0x07, 0x1e, 0x9a, 0x33, 0x6b, + 0xf0, 0xc3, 0xd2, 0x73, 0x67, 0x0d, 0x71, 0xce, 0x76, 0x26, 0xd6, 0x08, 0xeb, 0xfe, 0xe5, 0xeb, + 0xff, 0xed, 0x3f, 0x07, 0x4b, 0x9d, 0x4f, 0x8d, 0xd1, 0xcd, 0x38, 0x63, 0xad, 0xa0, 0xc8, 0x31, + 0x7b, 0x14, 0x74, 0x3b, 0xbb, 0x47, 0x76, 0x91, 0x4d, 0xfa, 0xd7, 0xdf, 0x0d, 0x91, 0x2b, 0xd7, + 0xf3, 0xc5, 0xcc, 0xeb, 0xbf, 0xfd, 0xdf, 0x49, 0xdb, 0x1c, 0x81, 0x28, 0xc0, 0xe3, 0xfc, 0x40, + 0xbb, 0xcc, 0x92, 0xc7, 0x43, 0xd3, 0x32, 0xc7, 0x28, 0xc5, 0xae, 0x32, 0xcb, 0x9d, 0xf0, 0x43, + 0xb1, 0x2f, 0x4c, 0x0a, 0xbc, 0xd3, 0x2b, 0x3c, 0xfa, 0xdf, 0xef, 0xf0, 0x8b, 0xbd, 0xb7, 0x99, + 0x86, 0x3f, 0xac, 0xfb, 0x97, 0xaf, 0xff, 0xfe, 0x3f, 0x91, 0x17, 0xb4, 0x43, 0x3e, 0x22, 0x3b, + 0x0e, 0x0a, 0xd0, 0x37, 0x3a, 0xfa, 0xd0, 0x90, 0x47, 0x61, 0x6d, 0x27, 0x18, 0xa6, 0x24, 0x58, + 0x81, 0x7a, 0xd1, 0xb0, 0xa7, 0x05, 0x41, 0xb8, 0x96, 0xce, 0xb4, 0x96, 0x84, 0x5c, 0x9e, 0x09, + 0x26, 0x73, 0xd5, 0x08, 0x50, 0x7f, 0x68, 0xfa, 0xe4, 0x84, 0xb2, 0x73, 0x8c, 0x8e, 0x7b, 0x99, + 0x85, 0xf2, 0x00, 0x3a, 0x68, 0x28, 0xa5, 0x57, 0xb8, 0xe2, 0x17, 0x47, 0xbc, 0x00, 0xb0, 0xa9, + 0x04, 0x56, 0x31, 0x96, 0xa5, 0x4b, 0xfd, 0x56, 0xbf, 0xd8, 0x62, 0xa6, 0xe3, 0xce, 0x56, 0xf3, + 0xee, 0x6a, 0x7f, 0x69, 0x36, 0xee, 0x0c, 0xeb, 0xfe, 0xe5, 0xeb, 0xdf, 0xfc, 0x3e, 0xe0, 0x93, + 0x0e, 0xf5, 0x29, 0xcc, 0xe6, 0x81, 0x7f, 0x63, 0x26, 0x4d, 0xed, 0xbe, 0xe7, 0x70, 0x2d, 0xa8, + 0x43, 0xd7, 0xdf, 0x59, 0xd4, 0x35, 0x6d, 0x1b, 0x96, 0x32, 0x94, 0x5a, 0xc1, 0x93, 0x82, 0x02, + 0x6b, 0xdb, 0xb6, 0x0d, 0xf2, 0x04, 0x6a, 0x40, 0x95, 0xcc, 0x82, 0x20, 0xa8, 0x10, 0xf6, 0xc8, + 0xec, 0xf9, 0x99, 0xe5, 0x76, 0x2c, 0xc3, 0x35, 0x70, 0xda, 0xe6, 0xc0, 0xdb, 0x35, 0xce, 0x61, + 0x2c, 0xf6, 0x4d, 0xe0, 0x9a, 0x9c, 0x92, 0x7b, 0xa3, 0x11, 0x00, 0x34, 0x06, 0x76, 0x4e, 0xb9, + 0x7d, 0x6a, 0x99, 0x2f, 0x01, 0xcf, 0xee, 0x59, 0x4e, 0xc1, 0x87, 0x2e, 0x26, 0x72, 0x79, 0xe4, + 0xf4, 0xfb, 0x23, 0xc3, 0xb6, 0x33, 0x8b, 0x3e, 0xa2, 0xb6, 0x7b, 0x09, 0xe2, 0xbc, 0x3b, 0xcc, + 0x2c, 0x76, 0xd0, 0x33, 0xc8, 0x89, 0xd9, 0x1d, 0xfa, 0x39, 0x2d, 0xff, 0xcc, 0xb1, 0x0d, 0x8f, + 0x3c, 0xb6, 0x60, 0xb9, 0xcf, 0x2c, 0xf7, 0x98, 0x11, 0x11, 0xc8, 0xed, 0x5e, 0x7f, 0xdb, 0xcb, + 0x81, 0x79, 0x48, 0xc1, 0x30, 0x38, 0xa2, 0x93, 0xdc, 0xbe, 0x3c, 0x31, 0x0d, 0xf2, 0xc0, 0x35, + 0x26, 0xd9, 0xa5, 0x8e, 0x1c, 0x63, 0x48, 0x3e, 0x31, 0x47, 0x23, 0xea, 0xe6, 0x42, 0x7c, 0x6a, + 0x99, 0xe7, 0x00, 0xf4, 0xb1, 0x7b, 0xfd, 0xdd, 0x20, 0x07, 0xcb, 0x63, 0x63, 0x62, 0x21, 0xc9, + 0x73, 0x61, 0xfe, 0x9b, 0x09, 0xf0, 0x32, 0xe7, 0x34, 0x3f, 0x9b, 0xd3, 0x4e, 0x1c, 0xcf, 0x20, + 0x87, 0x18, 0x83, 0x9e, 0xbf, 0x24, 0x03, 0x43, 0xba, 0x79, 0xfc, 0x73, 0x2a, 0x68, 0x0e, 0x0d, + 0x5f, 0xe5, 0x0b, 0xad, 0x37, 0x3b, 0xaf, 0x1f, 0x50, 0x3c, 0xc7, 0xea, 0x01, 0x3f, 0xb1, 0xf5, + 0x68, 0x7f, 0x62, 0x9f, 0x19, 0x60, 0xe7, 0x53, 0x50, 0x49, 0xac, 0x49, 0xc7, 0x83, 0xee, 0x98, + 0xf6, 0x10, 0x95, 0xc6, 0xfd, 0xed, 0x9d, 0x17, 0x4c, 0x49, 0x2c, 0x38, 0xdf, 0x77, 0x9f, 0x2c, + 0x1f, 0x3c, 0xc9, 0x2e, 0x71, 0x78, 0xb7, 0xbd, 0xbd, 0x9b, 0x53, 0x64, 0xf9, 0x93, 0xed, 0xa7, + 0x39, 0x45, 0xee, 0x3c, 0x7b, 0x9c, 0xd7, 0x50, 0xeb, 0x60, 0xfb, 0xdf, 0x66, 0x17, 0xf9, 0xd9, + 0xda, 0xe1, 0x4e, 0x76, 0x89, 0x07, 0xad, 0xfd, 0x7f, 0xa3, 0x2c, 0x91, 0xa7, 0x17, 0xcf, 0xe2, + 0x99, 0xc1, 0x50, 0x36, 0x8d, 0x67, 0xe6, 0x2f, 0x5f, 0xff, 0xc3, 0xef, 0xc8, 0x27, 0xa0, 0x41, + 0x10, 0x2a, 0x1c, 0x54, 0xf4, 0xed, 0xfa, 0x64, 0x10, 0xb7, 0x42, 0x3e, 0x99, 0x38, 0x88, 0xc0, + 0x07, 0x1a, 0xc5, 0xc7, 0x27, 0xbb, 0xc7, 0xc2, 0xf6, 0x89, 0x1c, 0xb6, 0xcf, 0xda, 0xb5, 0xfd, + 0xf1, 0x29, 0x6b, 0x92, 0x67, 0xc4, 0x42, 0x00, 0xe8, 0x16, 0x5d, 0x20, 0x2c, 0xa4, 0x8d, 0xbd, + 0x5f, 0x50, 0x0d, 0x1b, 0xd0, 0x78, 0x88, 0x93, 0x36, 0x74, 0xb0, 0x8a, 0x8a, 0xe8, 0x63, 0xed, + 0x99, 0x9e, 0xd1, 0xb1, 0x68, 0x2f, 0x81, 0x01, 0x0f, 0xce, 0xef, 0x3b, 0xae, 0xd4, 0x6a, 0xe0, + 0x8e, 0x35, 0x29, 0xa7, 0xfa, 0x85, 0xe9, 0xf6, 0x08, 0x5a, 0x4e, 0x2e, 0x39, 0x3a, 0x3d, 0x26, + 0xc6, 0xc4, 0x83, 0x99, 0x33, 0x22, 0x07, 0xe8, 0xdf, 0xb3, 0x29, 0x1b, 0x95, 0x01, 0x65, 0xe3, + 0xe2, 0x2b, 0xe6, 0xde, 0xf6, 0x04, 0xb4, 0x5a, 0x30, 0xe6, 0x61, 0x35, 0x92, 0x80, 0x94, 0x83, + 0xda, 0x95, 0x34, 0x77, 0xb1, 0x61, 0x14, 0x74, 0x68, 0xb3, 0x38, 0x3a, 0x74, 0xd5, 0x2f, 0x24, + 0xd4, 0x07, 0xae, 0x7b, 0x70, 0x67, 0x13, 0x18, 0xf9, 0xff, 0xac, 0xe4, 0x88, 0x45, 0xd6, 0xbf, + 0x0c, 0xce, 0xbc, 0xd1, 0x38, 0x61, 0x5c, 0x73, 0xe6, 0x40, 0x61, 0x01, 0x60, 0x9c, 0x21, 0x2e, + 0x9c, 0xbd, 0x29, 0x47, 0x4c, 0x3f, 0x50, 0x61, 0xb3, 0x5b, 0x32, 0x6d, 0xcf, 0x41, 0x14, 0xf7, + 0x4c, 0xea, 0xc1, 0xc8, 0x3c, 0xa4, 0xee, 0xf5, 0x37, 0xfe, 0x1b, 0xee, 0x3c, 0x3f, 0x66, 0x91, + 0xd9, 0x7d, 0x5e, 0x64, 0x61, 0x6e, 0x1d, 0x97, 0x9a, 0xdc, 0x7a, 0x02, 0x9f, 0x81, 0xe3, 0x38, + 0xf3, 0x75, 0x50, 0x20, 0xa8, 0x7b, 0x1b, 0x7c, 0x55, 0xf6, 0x5a, 0x3a, 0x2a, 0x92, 0xec, 0xd4, + 0x01, 0xf6, 0xda, 0x53, 0xf9, 0x59, 0x45, 0xdd, 0xe8, 0x34, 0xbf, 0x4a, 0xdb, 0xe5, 0xc8, 0x30, + 0xe7, 0xd3, 0x2e, 0x70, 0xf0, 0x88, 0x29, 0x90, 0xcf, 0x86, 0xee, 0x15, 0x4c, 0xa8, 0xf5, 0x14, + 0xae, 0x4a, 0x5a, 0x27, 0x8e, 0x0a, 0x4b, 0xf8, 0x01, 0x44, 0xca, 0xa9, 0x50, 0x40, 0x0a, 0x67, + 0x12, 0x80, 0x79, 0x9b, 0x13, 0x50, 0x86, 0x4b, 0x5b, 0xb9, 0x7e, 0xd2, 0xe9, 0xf6, 0x15, 0xf8, + 0x24, 0x4e, 0xfb, 0x35, 0xb5, 0xb4, 0x95, 0x43, 0xc6, 0x55, 0xd4, 0x4d, 0x16, 0xed, 0x38, 0x2f, + 0x73, 0x56, 0x7d, 0x14, 0x64, 0xe1, 0x32, 0xae, 0x2e, 0x99, 0x04, 0xca, 0x98, 0x98, 0x93, 0x9d, + 0x7a, 0x5c, 0x34, 0x6e, 0xd5, 0x6a, 0xeb, 0xec, 0xaf, 0xc2, 0x11, 0xa4, 0xf1, 0x0f, 0xcd, 0x88, + 0xef, 0x01, 0xd0, 0x56, 0xcc, 0x63, 0x5a, 0xbb, 0x19, 0xf2, 0x1d, 0xd7, 0xb9, 0xf0, 0xa8, 0x3b, + 0x6b, 0x07, 0x14, 0x8f, 0xd2, 0xe3, 0x86, 0xed, 0x84, 0x81, 0xe0, 0x39, 0x23, 0x26, 0x45, 0x67, + 0x6b, 0x88, 0xb0, 0xdd, 0xb9, 0xa0, 0xa0, 0xf2, 0xa3, 0x6f, 0x48, 0x5a, 0x0d, 0x58, 0xbd, 0xe7, + 0xbc, 0x5a, 0x43, 0x48, 0x7b, 0xd2, 0xa6, 0x67, 0x13, 0xf4, 0x35, 0xce, 0x32, 0x14, 0x72, 0xa4, + 0xf6, 0x42, 0xd4, 0x04, 0xe7, 0xd6, 0x70, 0x7f, 0x69, 0x6a, 0x5a, 0x04, 0xd0, 0x79, 0x54, 0xac, + 0x86, 0x18, 0x5c, 0x34, 0x1c, 0x52, 0xff, 0x0a, 0x15, 0xd1, 0xf6, 0xa5, 0xdd, 0x95, 0xbb, 0x0a, + 0x10, 0x7c, 0x7c, 0x86, 0xe3, 0x25, 0x7a, 0x9a, 0xd5, 0x9f, 0x10, 0xf5, 0x13, 0x10, 0x0c, 0x27, + 0xce, 0x05, 0x5b, 0x10, 0xff, 0x03, 0xd9, 0x65, 0xfd, 0xc3, 0x67, 0x29, 0x32, 0xe2, 0xc3, 0x29, + 0x80, 0x7b, 0x80, 0xcb, 0x8e, 0x33, 0xb1, 0x7d, 0x01, 0xfc, 0x0f, 0x68, 0xdd, 0x5e, 0x19, 0x43, + 0x8b, 0x61, 0xee, 0xc9, 0xe0, 0xc3, 0xa2, 0xc5, 0xc0, 0x6f, 0xbd, 0xfe, 0xed, 0xbf, 0x82, 0xa5, + 0x31, 0xe9, 0x33, 0xc1, 0x28, 0xc1, 0x99, 0x8c, 0x7d, 0xc1, 0xb0, 0x7a, 0x20, 0x79, 0x6a, 0xa7, + 0xd8, 0x8b, 0x0e, 0x55, 0x4f, 0xcf, 0x38, 0x67, 0x72, 0x13, 0xf5, 0x21, 0x95, 0x03, 0x44, 0xf8, + 0xc7, 0x52, 0x0e, 0xb9, 0x86, 0x50, 0x32, 0x40, 0x0d, 0xdd, 0x3e, 0xf3, 0x71, 0xd9, 0xa1, 0x81, + 0x2c, 0x27, 0xde, 0x18, 0x59, 0x15, 0x54, 0x99, 0x68, 0x6f, 0x36, 0x81, 0x10, 0x0f, 0x38, 0x00, + 0x26, 0x25, 0xb2, 0x3e, 0x19, 0x0b, 0x38, 0x60, 0x45, 0x5e, 0xff, 0xc3, 0x7f, 0xfa, 0x41, 0xff, + 0x4d, 0xf7, 0xe8, 0xd1, 0xde, 0xe1, 0xe1, 0xc1, 0xc3, 0xc7, 0x7b, 0x07, 0xa7, 0xed, 0xed, 0xa3, + 0xe3, 0xed, 0x76, 0xfb, 0xd9, 0xd1, 0xc3, 0xf7, 0xbd, 0xdf, 0x05, 0x4c, 0x1d, 0xd4, 0xdf, 0xa2, + 0x8d, 0xc5, 0x94, 0xd1, 0xc3, 0xe6, 0xee, 0x23, 0x60, 0x32, 0x73, 0x70, 0x06, 0x1c, 0xe6, 0x19, + 0x36, 0xc6, 0x23, 0x83, 0x30, 0x2c, 0x6a, 0xec, 0xcc, 0x62, 0xe2, 0xc4, 0x71, 0x9a, 0xce, 0xd8, + 0x29, 0xe4, 0x54, 0xcc, 0xf1, 0x18, 0x26, 0x9d, 0x8e, 0xc9, 0x94, 0x48, 0x0d, 0x8d, 0x27, 0x52, + 0x25, 0x5e, 0xc5, 0x42, 0x29, 0x36, 0xf4, 0x71, 0x45, 0x92, 0x1c, 0xb7, 0x68, 0xd5, 0x44, 0xc4, + 0x95, 0x6d, 0x1b, 0xd0, 0x24, 0x29, 0x58, 0xfd, 0x8e, 0xef, 0x5c, 0x60, 0xc6, 0x1a, 0x58, 0x17, + 0x40, 0x65, 0x9b, 0xd8, 0xdc, 0x51, 0x30, 0x22, 0x20, 0x37, 0x7d, 0xa0, 0x01, 0x79, 0x30, 0xe9, + 0x0e, 0xe1, 0x5d, 0x07, 0xf7, 0xb7, 0x26, 0x7d, 0xf6, 0xea, 0xd8, 0xb0, 0x41, 0x45, 0x65, 0x36, + 0xd0, 0x84, 0xba, 0x7e, 0x3d, 0x85, 0xd2, 0x0b, 0x6a, 0xdb, 0x84, 0x7a, 0xc4, 0x1c, 0x91, 0x13, + 0x03, 0x94, 0xc1, 0xde, 0xc4, 0x3e, 0xb3, 0x00, 0xae, 0xe9, 0xf9, 0x55, 0x72, 0x81, 0x3b, 0x5a, + 0x08, 0x0c, 0x94, 0x78, 0x50, 0xe0, 0xe5, 0x06, 0x2e, 0xa8, 0x6d, 0x0e, 0xa0, 0xdc, 0x10, 0x35, + 0x5d, 0x8b, 0x4e, 0xd0, 0x0d, 0x66, 0xd7, 0xd1, 0x26, 0xc7, 0x6e, 0x8c, 0x8c, 0x97, 0x26, 0x68, + 0x63, 0xb1, 0xfe, 0x00, 0x44, 0x66, 0xa0, 0xb5, 0x99, 0x50, 0x1a, 0x22, 0xff, 0x0c, 0xdc, 0xeb, + 0xef, 0xf8, 0x76, 0x1c, 0xa0, 0xbb, 0xd6, 0xf8, 0x90, 0x74, 0xe8, 0xc0, 0xa5, 0xf6, 0x95, 0x5f, + 0x57, 0x1a, 0xf7, 0x8f, 0x0d, 0xcb, 0xec, 0x60, 0x1a, 0x12, 0xf4, 0x4a, 0x42, 0x2b, 0x88, 0xd2, + 0x13, 0xd3, 0x5e, 0x7c, 0x62, 0xbc, 0xac, 0xbd, 0x80, 0xde, 0x51, 0x02, 0x8c, 0xe0, 0x8d, 0x5d, + 0x0a, 0x0d, 0xd8, 0xbd, 0x34, 0x8c, 0x59, 0xb6, 0x4f, 0x73, 0xb6, 0x41, 0xd5, 0xcc, 0xb1, 0x1e, + 0x06, 0x87, 0x4c, 0xc7, 0x17, 0x30, 0xbf, 0xfe, 0x29, 0xea, 0x25, 0xcc, 0x2b, 0xdc, 0xf2, 0x5e, + 0x66, 0x2e, 0x41, 0x36, 0xc2, 0x71, 0x77, 0x8f, 0xda, 0x01, 0x22, 0x0a, 0x34, 0xeb, 0x64, 0x97, + 0x0d, 0x25, 0x79, 0x36, 0x02, 0x2b, 0x04, 0x61, 0x0d, 0x91, 0x73, 0x02, 0x17, 0x45, 0x86, 0xd7, + 0x88, 0xf9, 0x17, 0xd1, 0xaf, 0x89, 0xc3, 0x15, 0x44, 0x3b, 0x11, 0x07, 0x77, 0x2a, 0x8d, 0x4e, + 0x0f, 0x0d, 0x46, 0x5b, 0x5b, 0xef, 0x85, 0x81, 0xc3, 0xd0, 0x0a, 0x95, 0x1f, 0xd2, 0x31, 0x3d, + 0xf2, 0x04, 0xe6, 0xed, 0x05, 0xee, 0xc1, 0x1a, 0xb0, 0x44, 0x41, 0xef, 0x3c, 0xb6, 0x21, 0x8b, + 0xfc, 0xad, 0x85, 0xb3, 0x0b, 0x9c, 0x01, 0x0d, 0x49, 0x94, 0xf9, 0xc3, 0xff, 0x88, 0x66, 0x4a, + 0x87, 0x02, 0xbb, 0xc2, 0xc8, 0x9b, 0xa3, 0xc9, 0xa8, 0xb6, 0x4f, 0x2d, 0x3d, 0x9c, 0xc3, 0xbd, + 0x5d, 0x2f, 0x64, 0x4f, 0x32, 0x02, 0x36, 0x1c, 0x61, 0x35, 0x03, 0x59, 0x3c, 0xe2, 0xcd, 0x90, + 0x9a, 0x5a, 0x72, 0xb6, 0xea, 0xac, 0xf8, 0x8d, 0xa8, 0x89, 0x2e, 0xa2, 0x18, 0x35, 0x4f, 0x0d, + 0xf4, 0x83, 0xdb, 0x96, 0x31, 0x1a, 0x53, 0x3c, 0x80, 0x72, 0xc1, 0xf6, 0xa6, 0xbf, 0x0f, 0xda, + 0x7e, 0x97, 0xa4, 0x2d, 0xce, 0xdf, 0x19, 0x68, 0x2b, 0xa6, 0xfd, 0x74, 0xb4, 0x5d, 0xaa, 0x93, + 0x7d, 0xa0, 0x8d, 0xd1, 0xf1, 0x40, 0xfb, 0x18, 0x21, 0x6d, 0xcb, 0xdc, 0xdc, 0x37, 0xac, 0x4a, + 0x3e, 0x6d, 0x61, 0x72, 0x98, 0xb8, 0xef, 0x40, 0x07, 0xd8, 0x2e, 0xa3, 0xeb, 0x27, 0xa0, 0xfe, + 0x51, 0x1b, 0xcd, 0x6f, 0x10, 0x55, 0x05, 0x08, 0xcb, 0x3a, 0x02, 0x2a, 0xd3, 0x00, 0xba, 0xe1, + 0x39, 0x60, 0x3d, 0xfb, 0x4c, 0xb4, 0x5c, 0xff, 0x0a, 0x6a, 0xb1, 0x59, 0x58, 0x36, 0x70, 0xe8, + 0x23, 0xca, 0x57, 0xb4, 0xa0, 0xf6, 0x78, 0xa3, 0x7d, 0xa0, 0x1b, 0x20, 0x22, 0xe4, 0xe7, 0xc5, + 0xf5, 0x37, 0x60, 0x33, 0x81, 0xc8, 0xc6, 0x67, 0x0f, 0xa8, 0xe1, 0x02, 0x8d, 0x7d, 0x84, 0x6b, + 0x33, 0xbe, 0x60, 0x32, 0xde, 0xc3, 0x39, 0xce, 0xbc, 0x05, 0xb9, 0xf4, 0x5a, 0xae, 0x63, 0x18, + 0x43, 0xa0, 0xda, 0xe5, 0xd1, 0x07, 0xd4, 0x42, 0xf3, 0x1c, 0x65, 0x26, 0xeb, 0xd3, 0x0e, 0x3a, + 0x7c, 0xf0, 0x2c, 0xd9, 0x82, 0xe4, 0xa0, 0x91, 0xc5, 0xb4, 0x07, 0x84, 0xa4, 0x18, 0x66, 0xb7, + 0x30, 0x05, 0xff, 0x68, 0xb4, 0x02, 0x59, 0x03, 0xd5, 0xa1, 0xa9, 0xdd, 0x06, 0x9c, 0xce, 0x97, + 0xf1, 0xef, 0xf9, 0x1c, 0xc3, 0x8c, 0x06, 0x78, 0x1e, 0x0d, 0x6c, 0xd3, 0x0c, 0x57, 0x86, 0x24, + 0xf3, 0x93, 0x39, 0x69, 0x14, 0x27, 0x6d, 0x53, 0x9b, 0x8e, 0xaa, 0xbd, 0x74, 0xd5, 0x4e, 0xa4, + 0x22, 0xe4, 0x50, 0x6b, 0x5a, 0x07, 0xb6, 0x45, 0x77, 0xe2, 0x62, 0x52, 0x9f, 0xed, 0xdd, 0x9d, + 0x63, 0xea, 0x62, 0xa5, 0xd4, 0x4e, 0x6c, 0xb8, 0x17, 0x59, 0x0b, 0xed, 0x8f, 0x0c, 0x98, 0xe9, + 0x6e, 0xc6, 0x22, 0x1d, 0x59, 0xde, 0xfd, 0x85, 0xad, 0x0f, 0x75, 0x80, 0xf4, 0x96, 0xcd, 0xad, + 0x02, 0x01, 0x74, 0x5c, 0x35, 0x8a, 0x37, 0x05, 0xc2, 0xe6, 0xbf, 0x84, 0x42, 0xd4, 0xb3, 0x70, + 0xd0, 0x6a, 0xf2, 0x8a, 0xc7, 0x5d, 0x31, 0x37, 0x62, 0x07, 0xb1, 0x30, 0x90, 0x32, 0x4c, 0x33, + 0xae, 0xa7, 0x70, 0xe6, 0xa8, 0x14, 0x63, 0x89, 0xf8, 0xd9, 0x4e, 0x65, 0xa6, 0x1f, 0x29, 0x09, + 0x89, 0x6e, 0x44, 0xe3, 0xae, 0x49, 0x4c, 0x3c, 0x18, 0xfa, 0x65, 0x1f, 0x84, 0xca, 0x2c, 0x60, + 0x0a, 0x23, 0xbd, 0x80, 0x6b, 0xd2, 0xe6, 0x42, 0x63, 0x01, 0xe5, 0x27, 0x1e, 0x4c, 0x69, 0x2d, + 0x85, 0x7e, 0xca, 0x56, 0xa3, 0xb1, 0xa0, 0x4d, 0xe6, 0xc4, 0x9c, 0x95, 0xac, 0x21, 0x54, 0x87, + 0xfd, 0x67, 0x1e, 0x75, 0xb7, 0x7b, 0xec, 0xfc, 0xa9, 0x3d, 0x28, 0x57, 0x36, 0x10, 0x86, 0x89, + 0xae, 0x3a, 0x68, 0x03, 0x5a, 0xc2, 0x27, 0x93, 0xb1, 0xf8, 0x7e, 0xcc, 0x8f, 0xb5, 0xe2, 0x33, + 0x0f, 0x24, 0xd3, 0xa1, 0x79, 0x4e, 0xa3, 0x47, 0x99, 0x2d, 0x06, 0x1c, 0x25, 0x32, 0x13, 0x84, + 0xa7, 0x0a, 0x97, 0x8a, 0x52, 0x43, 0x64, 0x12, 0xd4, 0x92, 0x83, 0xfb, 0x51, 0x92, 0x34, 0x69, + 0x48, 0x24, 0x59, 0x20, 0x2e, 0x35, 0x7a, 0x8e, 0x6d, 0x5d, 0x16, 0x40, 0x54, 0x1c, 0x51, 0xbc, + 0xc3, 0x78, 0x31, 0x9c, 0xca, 0x52, 0xa8, 0x80, 0x1c, 0xc8, 0xc9, 0x6e, 0x99, 0x50, 0x9d, 0x93, + 0x55, 0x4d, 0x66, 0xa5, 0x24, 0x58, 0x28, 0x30, 0x1b, 0xe5, 0xbc, 0xc3, 0x4a, 0xc0, 0xf2, 0x94, + 0x51, 0x48, 0xa4, 0x8c, 0x09, 0xab, 0x74, 0x2e, 0x20, 0x95, 0xdb, 0xd4, 0x67, 0x3c, 0xb0, 0xa0, + 0x44, 0x02, 0x88, 0x5d, 0x8b, 0x3d, 0x08, 0x09, 0xb5, 0x2a, 0x6b, 0xb5, 0x8d, 0x78, 0x9f, 0x59, + 0x00, 0x64, 0x3c, 0x8b, 0x4d, 0x2a, 0xbb, 0x4c, 0x2a, 0xdf, 0x43, 0xb8, 0x89, 0x13, 0xf8, 0x2b, + 0xd8, 0x36, 0x2d, 0xe8, 0x2f, 0x96, 0x17, 0xa8, 0x74, 0xc0, 0x92, 0xfe, 0x55, 0x2a, 0xc0, 0x3e, + 0x6e, 0x88, 0xfe, 0xe1, 0x7f, 0xa8, 0xfb, 0x9e, 0x0a, 0x43, 0x9f, 0x56, 0x94, 0x4d, 0x2b, 0x6c, + 0xb8, 0xa6, 0xc4, 0x85, 0x0d, 0xcc, 0x1f, 0xeb, 0x9d, 0x95, 0x35, 0xc6, 0xcb, 0x1c, 0x59, 0xb3, + 0xd6, 0x9a, 0x93, 0xac, 0x31, 0x5e, 0xfe, 0x00, 0x64, 0x0d, 0x23, 0x47, 0x9e, 0xac, 0x59, 0xfb, + 0x51, 0xd6, 0xcc, 0x28, 0x6b, 0x90, 0x07, 0xde, 0x6d, 0x59, 0x23, 0x26, 0x6e, 0x11, 0x59, 0xf3, + 0xdd, 0x1b, 0x92, 0x35, 0x6a, 0xb5, 0x69, 0x49, 0xa9, 0x36, 0xfd, 0xee, 0x9f, 0x65, 0x1d, 0xbb, + 0xf6, 0x00, 0xc3, 0x87, 0xbb, 0xc3, 0xf4, 0xce, 0x95, 0xde, 0xdd, 0xb5, 0xd6, 0x5f, 0xa1, 0x77, + 0x73, 0xc3, 0x37, 0x73, 0xdc, 0x5d, 0xfa, 0xc0, 0xbb, 0x02, 0xc1, 0x2e, 0xd9, 0xfe, 0xaf, 0x43, + 0x3a, 0x10, 0x8e, 0x9d, 0x3e, 0x65, 0xde, 0x27, 0xf8, 0xc4, 0xdc, 0x4b, 0x68, 0xb8, 0x30, 0x1b, + 0x2d, 0x52, 0xe8, 0x3a, 0x57, 0x17, 0x75, 0x49, 0xdc, 0x46, 0x66, 0x28, 0x74, 0xc6, 0x12, 0x8e, + 0xa8, 0x5b, 0xea, 0x88, 0x36, 0x33, 0x5c, 0x66, 0x40, 0x00, 0x70, 0x2f, 0x5b, 0x80, 0xd7, 0x13, + 0xe1, 0x1b, 0x90, 0xcd, 0xd7, 0x10, 0xbd, 0xc0, 0x8e, 0xf4, 0xb3, 0x61, 0x0b, 0xb6, 0x4a, 0xc3, + 0x4e, 0xbb, 0xc4, 0xa6, 0x81, 0xcd, 0xfd, 0x5b, 0x3d, 0xe3, 0xea, 0x82, 0x59, 0x6c, 0x76, 0x60, + 0x5b, 0xf2, 0x73, 0x34, 0x84, 0x4d, 0x80, 0x31, 0x0c, 0x24, 0x14, 0x9b, 0xff, 0x1a, 0x17, 0x90, + 0x65, 0xdb, 0x66, 0x56, 0xf2, 0x30, 0x72, 0xea, 0xfd, 0x20, 0x74, 0xec, 0x84, 0x80, 0x5f, 0x8b, + 0xe4, 0x7b, 0xc1, 0x05, 0xaf, 0xe7, 0x74, 0x27, 0x23, 0xcc, 0xbc, 0x3f, 0xa0, 0xfe, 0x9e, 0x45, + 0xf1, 0xe3, 0x83, 0xcb, 0x83, 0x5e, 0xb9, 0x94, 0x6a, 0x8a, 0x2d, 0x28, 0xa5, 0x4a, 0x9d, 0x67, + 0x78, 0xda, 0x24, 0xfe, 0xd0, 0xf4, 0xf8, 0x97, 0x60, 0x41, 0x8c, 0x8a, 0x8b, 0x23, 0x6a, 0xf2, + 0xea, 0x19, 0x83, 0xa5, 0x59, 0x44, 0xbf, 0x47, 0xed, 0x5c, 0xb5, 0x5c, 0xbe, 0x49, 0x6a, 0x7e, + 0xff, 0x84, 0x7c, 0x73, 0x2b, 0xfa, 0xbb, 0xb0, 0x78, 0x4b, 0x33, 0x32, 0x73, 0x89, 0x5e, 0xd8, + 0x9a, 0x32, 0xaa, 0x80, 0xe7, 0xc4, 0x49, 0xc5, 0x90, 0xe3, 0x4a, 0xd6, 0x80, 0xc1, 0xdc, 0x9e, + 0x78, 0xa4, 0xdc, 0xf8, 0xb0, 0x52, 0x25, 0x6b, 0xf8, 0x35, 0xd4, 0xa1, 0xd7, 0xe0, 0x19, 0x20, + 0x8c, 0x95, 0xdf, 0x90, 0xa6, 0x9e, 0x21, 0xc5, 0xde, 0x71, 0xe5, 0x5d, 0x3f, 0xed, 0xd6, 0xe6, + 0x3c, 0xef, 0x8c, 0x97, 0xf3, 0x93, 0x62, 0x5a, 0x53, 0xe0, 0x7b, 0xd4, 0xfb, 0x73, 0xa4, 0xd8, + 0xfc, 0xc9, 0xf9, 0xfd, 0x53, 0xf2, 0x47, 0x31, 0x36, 0x6f, 0x31, 0xb6, 0x37, 0x1a, 0xf7, 0x1d, + 0xdc, 0x66, 0x58, 0x27, 0x42, 0xb0, 0xe4, 0x8b, 0xaf, 0x9b, 0x48, 0xaf, 0x7c, 0x9e, 0xef, 0x0a, + 0xd7, 0xbe, 0x8a, 0xeb, 0xf7, 0x6c, 0x16, 0x61, 0x9b, 0x34, 0xc8, 0x58, 0xf6, 0x5b, 0x55, 0xfe, + 0x6d, 0x5d, 0xf4, 0x53, 0xce, 0x9e, 0x01, 0x6e, 0x42, 0xf1, 0xcd, 0x06, 0x55, 0xd4, 0x53, 0x96, + 0x48, 0x45, 0x94, 0x99, 0xf4, 0x7b, 0xc1, 0xf3, 0x33, 0x6a, 0x52, 0x24, 0xa4, 0x33, 0xe6, 0x25, + 0xd2, 0xa4, 0x27, 0xb3, 0x2b, 0xd2, 0x0e, 0xa5, 0x1b, 0x79, 0x19, 0x15, 0x95, 0xbc, 0x1e, 0x0c, + 0x77, 0x77, 0xb5, 0xb5, 0xd6, 0x2a, 0x16, 0xcb, 0x5f, 0x2c, 0x2c, 0x4b, 0x19, 0xfb, 0xb3, 0x1d, + 0x1b, 0x2e, 0xc9, 0x74, 0x4e, 0x77, 0x58, 0x19, 0x17, 0x24, 0xe7, 0xb8, 0x56, 0x60, 0x56, 0x60, + 0x8b, 0xe6, 0x96, 0xda, 0xb4, 0xcd, 0x8d, 0x18, 0x8a, 0x87, 0x67, 0x4c, 0x1d, 0x71, 0x72, 0x61, + 0xf6, 0x4d, 0x6d, 0x70, 0xfd, 0xef, 0xff, 0x99, 0xbc, 0x30, 0xf7, 0x4d, 0xf2, 0x11, 0x8b, 0xf6, + 0xae, 0xb5, 0xd9, 0xa9, 0xd9, 0xe4, 0xb5, 0xc9, 0x6f, 0x31, 0xda, 0x1e, 0x91, 0x9d, 0x21, 0xda, + 0xfe, 0xfb, 0x0f, 0x3e, 0x49, 0x18, 0xde, 0x8f, 0x4c, 0x1b, 0x04, 0xac, 0x7c, 0xce, 0xf1, 0xc8, + 0xc0, 0x10, 0x0f, 0x3a, 0x8a, 0x76, 0x16, 0x59, 0xbc, 0x06, 0x1e, 0x36, 0x39, 0xa2, 0x13, 0x76, + 0x21, 0x2c, 0xa1, 0x6e, 0x9f, 0x35, 0x84, 0x9b, 0x36, 0x55, 0x30, 0x45, 0x71, 0xc7, 0x17, 0x4d, + 0x73, 0x1c, 0xa2, 0x5a, 0x2c, 0x41, 0x07, 0x97, 0x03, 0xc2, 0x44, 0xbd, 0x5d, 0x28, 0xca, 0x5b, + 0x2b, 0x03, 0xa3, 0x3d, 0xbe, 0xff, 0xc0, 0x99, 0x81, 0x07, 0x41, 0xaa, 0x55, 0xb4, 0x70, 0xc4, + 0x70, 0xa4, 0x44, 0xb4, 0xa4, 0x6a, 0x0f, 0x6c, 0x35, 0x57, 0x72, 0xa8, 0xf7, 0xf5, 0x74, 0x09, + 0x24, 0xb0, 0x5f, 0xcf, 0xa9, 0xdb, 0x31, 0xed, 0x5e, 0x22, 0x34, 0x14, 0x48, 0xb7, 0xe3, 0xd8, + 0x36, 0xf0, 0x8a, 0x24, 0x7e, 0x55, 0x4b, 0xdc, 0xeb, 0xbf, 0xfd, 0x43, 0x81, 0x98, 0xc4, 0x83, + 0xe3, 0xda, 0x76, 0xcf, 0x85, 0x89, 0x46, 0x13, 0x8d, 0x1c, 0x62, 0x24, 0xf6, 0xc1, 0x71, 0x51, + 0x38, 0x6d, 0x58, 0x7f, 0x0d, 0xcb, 0xf3, 0xaf, 0xbf, 0x71, 0xcf, 0x92, 0xa0, 0x4e, 0xda, 0xed, + 0x03, 0x19, 0x0e, 0xe9, 0x3d, 0x18, 0x65, 0x85, 0x61, 0xea, 0x24, 0xd5, 0xc2, 0x56, 0x10, 0x44, + 0x7a, 0x74, 0x7a, 0x5c, 0x4b, 0x06, 0x92, 0xda, 0xfe, 0xf8, 0x30, 0x8c, 0x25, 0xcd, 0x44, 0x3a, + 0x2f, 0x96, 0x72, 0x0a, 0x2e, 0xba, 0x35, 0xc5, 0x0a, 0x0a, 0x94, 0x98, 0xc3, 0xb2, 0x19, 0xb2, + 0x2c, 0xde, 0x37, 0x11, 0x2d, 0x8f, 0xa4, 0x1c, 0xb1, 0x0b, 0xb9, 0x02, 0xdb, 0xe7, 0xc5, 0xe1, + 0xf6, 0x51, 0x65, 0xf6, 0x13, 0x22, 0x79, 0x1d, 0x47, 0xf0, 0xb5, 0x23, 0x63, 0x44, 0x49, 0x19, + 0x46, 0x77, 0x57, 0x63, 0xe1, 0xdc, 0x13, 0xf7, 0x75, 0x89, 0xee, 0xb7, 0x3d, 0xb3, 0xb7, 0x20, + 0x9d, 0x87, 0x80, 0x5f, 0x3d, 0x10, 0xe1, 0xf0, 0xb4, 0xcd, 0xca, 0xa1, 0x20, 0x4f, 0x39, 0x79, + 0xd9, 0x12, 0x94, 0xb8, 0xca, 0xab, 0xa8, 0x92, 0x99, 0xf2, 0x23, 0xae, 0xf2, 0xca, 0xca, 0x1b, + 0x92, 0x54, 0xf3, 0x50, 0xdc, 0xeb, 0x21, 0x34, 0x79, 0xdc, 0x23, 0x27, 0xd8, 0x5d, 0x0c, 0x64, + 0xc2, 0x20, 0x0c, 0x74, 0x00, 0xe3, 0xae, 0x39, 0x2f, 0x96, 0xca, 0x67, 0xc4, 0x3a, 0x95, 0x71, + 0xc4, 0x84, 0xdd, 0x76, 0x26, 0x13, 0xe7, 0x09, 0x3f, 0x45, 0x42, 0x40, 0x43, 0xe9, 0xd2, 0xa1, + 0x83, 0x31, 0x1f, 0x9b, 0x0b, 0xac, 0xc1, 0x51, 0xe2, 0x0c, 0x48, 0xbd, 0x5e, 0x67, 0x96, 0x06, + 0x60, 0x30, 0x60, 0xc9, 0x06, 0x9b, 0x4a, 0xb5, 0xfd, 0x7b, 0x22, 0xa5, 0x42, 0xad, 0xd2, 0x27, + 0x5f, 0xbd, 0x29, 0x17, 0x1e, 0xf3, 0x14, 0xb6, 0x7e, 0x86, 0x04, 0x17, 0x64, 0x50, 0x5c, 0xb5, + 0xad, 0x49, 0x0e, 0x9c, 0xa7, 0xff, 0xc4, 0x86, 0x31, 0xbc, 0xa0, 0x2e, 0x18, 0xca, 0xe3, 0xf0, + 0x41, 0x6c, 0x20, 0x7f, 0x2a, 0x7e, 0x62, 0x03, 0xb7, 0xba, 0xa4, 0xb5, 0xb7, 0x72, 0x07, 0x8f, + 0x2c, 0xaf, 0x04, 0x17, 0x58, 0xbc, 0xdd, 0x69, 0xc1, 0x84, 0x6e, 0x42, 0x13, 0x0b, 0x3a, 0xfd, + 0x1c, 0xd3, 0xe5, 0x99, 0x96, 0xe9, 0x5f, 0xe2, 0x5c, 0x46, 0x8a, 0x04, 0xf4, 0x39, 0x65, 0xe5, + 0xd8, 0x01, 0xb7, 0x5b, 0x99, 0x3d, 0x56, 0x5c, 0xa6, 0x1c, 0xde, 0x94, 0xcc, 0xfa, 0xc9, 0xf8, + 0x0a, 0xaf, 0x20, 0xd4, 0x5d, 0xfc, 0xb1, 0xc2, 0x13, 0x3c, 0x25, 0xf7, 0x73, 0xd2, 0xf7, 0x5e, + 0xeb, 0x30, 0x11, 0xfb, 0x3c, 0x01, 0x6f, 0x11, 0x83, 0xfb, 0x78, 0xec, 0x02, 0xd9, 0x96, 0x54, + 0x29, 0x95, 0x0b, 0x26, 0x4a, 0xd3, 0x26, 0x4c, 0x93, 0x32, 0xf2, 0xe9, 0xf2, 0x90, 0xaa, 0xf3, + 0x30, 0xa9, 0x8f, 0x0a, 0x7e, 0x5f, 0xeb, 0xdf, 0xee, 0xb0, 0x3b, 0x0e, 0x0f, 0x09, 0xde, 0x60, + 0x11, 0xdc, 0x7d, 0xb4, 0x73, 0x1c, 0xc5, 0xe1, 0x91, 0xb2, 0x21, 0xdb, 0x91, 0x91, 0x4a, 0x33, + 0xcb, 0xfa, 0x27, 0x30, 0xf5, 0xcd, 0xee, 0xc1, 0xf1, 0x0e, 0x33, 0x0c, 0x72, 0x4c, 0x47, 0xae, + 0x69, 0xb7, 0x12, 0xb6, 0xa3, 0x22, 0xb2, 0xeb, 0x2e, 0xfe, 0x29, 0xa4, 0x5c, 0x4f, 0x69, 0xca, + 0x4b, 0x3a, 0x5c, 0xb1, 0xe3, 0x76, 0xb1, 0x75, 0x07, 0x74, 0xbc, 0xb8, 0x98, 0x6a, 0xde, 0x6d, + 0xd5, 0x9b, 0xab, 0x6b, 0xf5, 0x66, 0x9d, 0xed, 0x5c, 0x8f, 0x0d, 0xbc, 0xdf, 0xd0, 0xde, 0x5c, + 0xf8, 0x9b, 0xf2, 0x67, 0xbd, 0x2f, 0x9b, 0xd5, 0xa5, 0x57, 0x9f, 0xd5, 0x2b, 0x5f, 0xc2, 0xff, + 0xfc, 0xcb, 0x4f, 0x66, 0x17, 0x5f, 0x6f, 0x45, 0x5e, 0xcd, 0xc1, 0x55, 0xf2, 0x10, 0x64, 0xca, + 0x85, 0x71, 0x39, 0x0b, 0x71, 0x45, 0x55, 0x3d, 0x85, 0x7f, 0xa4, 0xaf, 0xb5, 0xd5, 0x9e, 0x74, + 0x6c, 0x50, 0xec, 0x47, 0x86, 0x77, 0x36, 0x13, 0x07, 0xf3, 0xfa, 0x09, 0x1a, 0xb7, 0x56, 0x56, + 0xea, 0xc1, 0xbf, 0x22, 0x5c, 0x1c, 0x46, 0x83, 0xc5, 0xea, 0xbd, 0xf7, 0xb4, 0xdf, 0x3d, 0x6a, + 0x0b, 0x37, 0xc8, 0x2c, 0x94, 0x87, 0xda, 0x09, 0xb2, 0xaf, 0xd5, 0xd9, 0x9f, 0xf7, 0x8d, 0xad, + 0x0b, 0xae, 0x91, 0xda, 0xa0, 0xd5, 0x9b, 0xaf, 0x9e, 0x60, 0xde, 0x86, 0xd6, 0xe3, 0xcd, 0x17, + 0x50, 0x34, 0xa0, 0xd1, 0x05, 0x86, 0x47, 0x18, 0x87, 0xae, 0x63, 0x9b, 0x5e, 0xda, 0xa0, 0x04, + 0x63, 0xfe, 0x3b, 0xb0, 0xcf, 0xcc, 0xee, 0xf0, 0x0d, 0xda, 0x91, 0x21, 0x22, 0x19, 0x3c, 0xa8, + 0xe1, 0x3f, 0x4c, 0x99, 0xc0, 0x6a, 0x25, 0x58, 0x70, 0xec, 0x77, 0xf0, 0xd4, 0x61, 0xb3, 0x0e, + 0x1f, 0x58, 0x0e, 0xad, 0xb8, 0x99, 0x14, 0x4c, 0xf4, 0x64, 0xb1, 0x5b, 0xef, 0x2e, 0x2f, 0x66, + 0x6c, 0x1b, 0xb4, 0xf1, 0xe8, 0x96, 0xe1, 0x82, 0x82, 0x91, 0xe8, 0x0f, 0x29, 0x1f, 0x0f, 0x2f, + 0x3d, 0xf3, 0x0c, 0x8f, 0x70, 0x74, 0x87, 0xb5, 0x53, 0xda, 0x1d, 0xda, 0x5c, 0x3f, 0x7a, 0x80, + 0x47, 0x0e, 0x3c, 0xd0, 0x95, 0x7d, 0xc3, 0xf2, 0xd3, 0xfb, 0x0b, 0x85, 0x8e, 0x96, 0xa2, 0x17, + 0x82, 0x6b, 0x47, 0x73, 0x75, 0x2d, 0x87, 0x1e, 0xda, 0x45, 0xad, 0x7f, 0x56, 0xe1, 0x60, 0x9e, + 0xc2, 0xb9, 0x8c, 0x6e, 0x3c, 0xe6, 0x52, 0x9e, 0xca, 0xa7, 0xec, 0xf8, 0x86, 0xd6, 0xa5, 0xfc, + 0x87, 0x5f, 0x91, 0x7d, 0xd3, 0x1d, 0x5d, 0x18, 0x2e, 0x25, 0xcf, 0xd8, 0x66, 0x1a, 0x29, 0x3f, + 0x3d, 0xdd, 0xae, 0xbc, 0x55, 0x3f, 0x32, 0x20, 0x38, 0x27, 0x37, 0xb2, 0xb8, 0xab, 0x6a, 0xb6, + 0x63, 0x6a, 0xb3, 0xbb, 0x91, 0xff, 0xf4, 0x67, 0x74, 0x23, 0xbf, 0x60, 0x47, 0x2e, 0x06, 0xf2, + 0x11, 0x46, 0xc3, 0x0b, 0xa8, 0x7a, 0x66, 0xd8, 0x36, 0x69, 0xd5, 0x56, 0x30, 0xda, 0x6a, 0x82, + 0x91, 0x59, 0x3d, 0x63, 0x02, 0x5c, 0x10, 0x3f, 0x81, 0xf8, 0x0c, 0xed, 0xbc, 0x0e, 0x3f, 0xb0, + 0xc7, 0x22, 0xbf, 0x8e, 0x0e, 0x76, 0x1e, 0x9d, 0x32, 0xaf, 0x72, 0x1b, 0x40, 0x8e, 0x80, 0x9d, + 0xc0, 0x22, 0x1c, 0xa0, 0x93, 0x4c, 0x3a, 0x44, 0x13, 0xb4, 0xe1, 0xcd, 0xd1, 0xb5, 0x1c, 0x9e, + 0x9e, 0x7e, 0x0e, 0x4d, 0xa2, 0x2d, 0x9b, 0xed, 0x5f, 0x16, 0xe7, 0x34, 0x44, 0xe1, 0x05, 0xed, + 0x39, 0x0b, 0xc5, 0x71, 0x92, 0x44, 0x4a, 0xbd, 0xb4, 0x1c, 0x9b, 0xd1, 0x2f, 0x5d, 0x70, 0x77, + 0xf6, 0xf0, 0xfa, 0x9b, 0x9e, 0x5f, 0xaf, 0xd7, 0xdf, 0x94, 0x89, 0x19, 0xcc, 0xae, 0x1a, 0xa6, + 0x02, 0x31, 0xc9, 0xd0, 0xe9, 0x0e, 0x2d, 0xa3, 0x47, 0xed, 0x02, 0x6b, 0x45, 0xdf, 0xb4, 0x44, + 0xf4, 0x43, 0x5f, 0x00, 0xd9, 0x67, 0x4f, 0xf0, 0x22, 0xc0, 0xb1, 0xbf, 0xb9, 0x50, 0xef, 0x98, + 0x76, 0x96, 0xdc, 0x4f, 0x12, 0x6e, 0x0a, 0x51, 0x1f, 0x5b, 0x32, 0x0a, 0x8a, 0xf9, 0xa4, 0xa0, + 0x9d, 0x8c, 0x2d, 0xc7, 0xe8, 0x05, 0xfd, 0x9f, 0x5a, 0xd0, 0xaa, 0xb7, 0xed, 0xbe, 0x89, 0xc4, + 0x55, 0x48, 0xca, 0x5b, 0xd9, 0x51, 0xa8, 0x73, 0x19, 0xbd, 0x67, 0x27, 0x87, 0x53, 0xad, 0xee, + 0xc1, 0x88, 0x41, 0xbd, 0xc4, 0xfa, 0x8e, 0x69, 0x28, 0xd7, 0x17, 0x17, 0xe9, 0x4b, 0x63, 0x34, + 0xb6, 0x68, 0xbd, 0xeb, 0x8c, 0x16, 0x83, 0xc2, 0x79, 0x03, 0x3a, 0xc3, 0x42, 0x9e, 0x11, 0x0c, + 0x31, 0xcf, 0x25, 0x3e, 0x3d, 0xf6, 0x28, 0x90, 0xf6, 0x41, 0x64, 0x01, 0x01, 0x8a, 0x0f, 0xbd, + 0x3e, 0x29, 0x25, 0xcb, 0x83, 0xfa, 0x1c, 0x9a, 0x00, 0x78, 0xf2, 0xd9, 0xce, 0xd9, 0x46, 0x1f, + 0x47, 0x08, 0x16, 0x9d, 0x63, 0xd7, 0x19, 0xb8, 0xec, 0x10, 0x7d, 0xfe, 0xee, 0x7a, 0xfa, 0x86, + 0xd9, 0x54, 0xca, 0xf5, 0x8c, 0xfd, 0x4c, 0xed, 0x05, 0x9a, 0x53, 0xe6, 0xe7, 0xd4, 0x9c, 0xd5, + 0xd3, 0x0a, 0x57, 0x75, 0x7e, 0x8c, 0x14, 0x75, 0xc5, 0x1a, 0x65, 0x5d, 0x7f, 0x33, 0xe9, 0xe7, + 0x09, 0x43, 0x4d, 0x88, 0x89, 0x10, 0x15, 0x32, 0x0d, 0x54, 0xd2, 0x25, 0x7d, 0x89, 0x32, 0xfb, + 0x89, 0x45, 0x34, 0x11, 0xbc, 0x21, 0xb9, 0x6f, 0xe1, 0x1d, 0xc7, 0xdc, 0x69, 0xa8, 0xdb, 0x63, + 0x4c, 0x8e, 0xe3, 0x03, 0xc3, 0x4d, 0x6e, 0xbf, 0x24, 0x91, 0x4a, 0xe6, 0xd9, 0xbf, 0xdb, 0x98, + 0xe6, 0xee, 0xea, 0x14, 0xbe, 0xbc, 0xd7, 0xf2, 0xad, 0x7f, 0xac, 0x5d, 0x76, 0xdd, 0xdf, 0x46, + 0xe6, 0xcd, 0xb4, 0xb1, 0x9f, 0x58, 0xaa, 0xc4, 0x58, 0xbc, 0xbb, 0x48, 0xb5, 0xaf, 0xda, 0x21, + 0x55, 0x82, 0x6a, 0x7c, 0x38, 0x6b, 0x18, 0x86, 0x44, 0x4f, 0x3c, 0x44, 0x6d, 0x60, 0xe4, 0x9d, + 0x76, 0xd6, 0x26, 0xa3, 0x3f, 0x12, 0xe1, 0xf9, 0x29, 0x1c, 0xda, 0x3e, 0x3b, 0xac, 0xcd, 0xf9, + 0x6c, 0xda, 0xd5, 0x56, 0xa3, 0x0c, 0x03, 0xa2, 0xd3, 0xeb, 0xc2, 0x0c, 0x4f, 0x4f, 0xab, 0x0e, + 0xff, 0xe6, 0xff, 0x20, 0xfb, 0xec, 0x3c, 0x72, 0xec, 0x7a, 0x8d, 0xb7, 0xa8, 0x0a, 0x73, 0xfc, + 0x66, 0x4f, 0x61, 0x98, 0xaf, 0xdc, 0xfd, 0xe1, 0x1b, 0xf2, 0xd0, 0x85, 0xb9, 0xd0, 0xc7, 0x7e, + 0xb2, 0x23, 0xd8, 0x9f, 0x30, 0x8b, 0xa4, 0x88, 0x46, 0xc2, 0xb0, 0x13, 0xe6, 0x2b, 0xde, 0xb9, + 0x61, 0xed, 0xf0, 0x07, 0xc2, 0x26, 0xfd, 0x60, 0x9f, 0xfd, 0xc8, 0x7b, 0xa4, 0x7c, 0x21, 0x08, + 0xa3, 0xf3, 0x32, 0x5c, 0xc5, 0xb1, 0xfb, 0x45, 0x73, 0x12, 0x53, 0x85, 0x77, 0x80, 0x8a, 0x1d, + 0x1a, 0x5e, 0xe9, 0x88, 0xa1, 0xb4, 0x50, 0x7c, 0xcf, 0x3c, 0x33, 0xe1, 0x5a, 0x1e, 0x25, 0x5f, + 0x7f, 0x25, 0x78, 0x85, 0xf4, 0xaf, 0xbf, 0x73, 0x31, 0x50, 0xe4, 0xca, 0xc4, 0x64, 0x06, 0x2f, + 0xfd, 0xe9, 0x08, 0x29, 0x6e, 0x69, 0x79, 0xa7, 0x28, 0xd9, 0xe6, 0x38, 0xbd, 0x2d, 0x52, 0x26, + 0x4e, 0xf3, 0x14, 0xa0, 0x9f, 0x14, 0x19, 0x2c, 0xe7, 0xc2, 0x61, 0x31, 0xac, 0x4d, 0x75, 0x10, + 0x2b, 0xb9, 0x35, 0x53, 0xf4, 0x6a, 0x04, 0x3e, 0x88, 0x02, 0xc6, 0x34, 0xd3, 0xee, 0xa3, 0xd3, + 0x27, 0x87, 0xb1, 0xf8, 0xd5, 0x8c, 0xf1, 0x60, 0xc8, 0xc6, 0xf3, 0xbc, 0xc5, 0x60, 0x2e, 0x6c, + 0xad, 0x35, 0x66, 0x49, 0x90, 0xc5, 0x58, 0xc6, 0xd3, 0xa5, 0xc8, 0x02, 0x4b, 0x78, 0xad, 0xd1, + 0x50, 0xec, 0x65, 0x2f, 0x68, 0xe4, 0xdc, 0x4c, 0x09, 0xb2, 0xb8, 0xbc, 0x9a, 0x41, 0x0e, 0x0f, + 0x0d, 0x30, 0x65, 0x8d, 0xb1, 0xde, 0x31, 0xf1, 0x0d, 0x79, 0xe1, 0xb8, 0xfe, 0xc8, 0xf0, 0x5d, + 0xf3, 0xe5, 0xdb, 0x15, 0xc1, 0x02, 0xb5, 0x37, 0x29, 0x84, 0x7f, 0xff, 0x4f, 0xec, 0x7a, 0x19, + 0x6c, 0x86, 0x94, 0x9b, 0xcd, 0xeb, 0x7f, 0x6c, 0x36, 0x02, 0x31, 0x5c, 0xc9, 0x0f, 0x04, 0x90, + 0x57, 0xdf, 0x56, 0x72, 0xf5, 0x2d, 0xaa, 0xf3, 0x3d, 0x74, 0x9d, 0xeb, 0xaf, 0x3b, 0x51, 0x06, + 0xa3, 0xf2, 0x76, 0xed, 0x93, 0x4a, 0x95, 0xe7, 0xe5, 0x96, 0x1f, 0x1b, 0xb5, 0xab, 0x0a, 0xcb, + 0x89, 0xf9, 0xc0, 0x44, 0xc7, 0x1b, 0x0c, 0x07, 0x7a, 0xe1, 0xca, 0xb5, 0x4a, 0x9d, 0x3c, 0xa4, + 0xb6, 0x31, 0x21, 0xcd, 0x08, 0xf7, 0x7a, 0x56, 0x0e, 0x9e, 0xf4, 0x45, 0x32, 0x8c, 0x41, 0xc5, + 0x61, 0xbe, 0x53, 0x73, 0x3c, 0x96, 0x3c, 0x27, 0xcf, 0x46, 0x96, 0x31, 0x01, 0x8d, 0x41, 0x1c, + 0xeb, 0xba, 0x9a, 0x10, 0xd0, 0x07, 0xa9, 0xdb, 0x35, 0x30, 0xd2, 0xeb, 0xfa, 0x57, 0xaf, 0xff, + 0xee, 0x77, 0x46, 0x95, 0x5c, 0xff, 0x11, 0x7e, 0x3b, 0xf0, 0xfb, 0x2b, 0xf8, 0x3d, 0xc9, 0xd5, + 0x70, 0x50, 0x36, 0x83, 0x95, 0x65, 0x70, 0x8f, 0x45, 0x38, 0xca, 0x98, 0x74, 0x11, 0xa4, 0x46, + 0x33, 0xe6, 0x69, 0x6d, 0x36, 0x35, 0x12, 0x43, 0x16, 0x1a, 0x41, 0x58, 0x7a, 0x30, 0x94, 0x30, + 0x15, 0xb5, 0x55, 0xce, 0xe8, 0xe5, 0x18, 0x75, 0x54, 0x10, 0x07, 0xd4, 0x9f, 0xb8, 0x36, 0x59, + 0xfc, 0x14, 0x08, 0x7e, 0xfd, 0xab, 0xeb, 0x3f, 0x5e, 0x7f, 0x05, 0x24, 0xbe, 0xfe, 0xe6, 0xfa, + 0xdb, 0xeb, 0xef, 0x6a, 0x9f, 0x2f, 0x9a, 0x75, 0x4c, 0x79, 0x5d, 0xa6, 0xe7, 0x28, 0x8c, 0xa0, + 0x52, 0x05, 0x85, 0x70, 0x80, 0xb9, 0xce, 0xfb, 0x22, 0xda, 0x17, 0x57, 0x52, 0x15, 0xd2, 0xda, + 0x96, 0xc6, 0x19, 0xb1, 0x11, 0x31, 0xa0, 0x9c, 0x24, 0x0b, 0x6a, 0x07, 0xee, 0xe1, 0xf5, 0x37, + 0x20, 0x3b, 0x40, 0xcf, 0x26, 0x8b, 0xc8, 0x08, 0xda, 0xc4, 0x19, 0x69, 0xb0, 0x8c, 0x78, 0xea, + 0x54, 0xc1, 0xe2, 0x62, 0xaa, 0x02, 0x9b, 0xff, 0x05, 0x82, 0xee, 0x6e, 0xe9, 0xce, 0xe8, 0xf2, + 0x95, 0x84, 0x7f, 0x91, 0x6e, 0xbc, 0x83, 0x41, 0xa2, 0xbe, 0x34, 0xa2, 0x92, 0x14, 0xe0, 0x57, + 0x9f, 0x2d, 0xe8, 0xae, 0x2f, 0xfa, 0xc3, 0xaf, 0x48, 0xe0, 0xcf, 0xc6, 0xc3, 0x9a, 0x3d, 0xcc, + 0xea, 0x15, 0x24, 0x45, 0xba, 0x55, 0xec, 0xb4, 0xec, 0x4c, 0xcb, 0x80, 0x84, 0xab, 0x6a, 0x21, + 0xb8, 0xdb, 0x6d, 0xdd, 0xe9, 0xe8, 0x16, 0x82, 0x98, 0x98, 0x9d, 0x6d, 0x05, 0x08, 0xe4, 0xd8, + 0xd4, 0x6b, 0x80, 0xd0, 0x7c, 0x30, 0xd8, 0x44, 0xa7, 0x91, 0xa3, 0x92, 0x25, 0x34, 0xab, 0x8b, + 0xeb, 0x6f, 0x5d, 0xbc, 0x5a, 0xe0, 0x6d, 0x2e, 0x05, 0x32, 0x86, 0x37, 0xba, 0xea, 0x2d, 0x39, + 0xf3, 0xb2, 0x25, 0xb6, 0x26, 0xac, 0x36, 0xd8, 0x48, 0x60, 0xd9, 0x8b, 0x30, 0xd9, 0x15, 0x48, + 0xc5, 0xa5, 0x04, 0x79, 0x98, 0xa4, 0xee, 0xb1, 0x1d, 0x30, 0x71, 0xae, 0x8b, 0xc5, 0x0e, 0xc1, + 0x64, 0xb3, 0xea, 0x78, 0x32, 0xa3, 0x0e, 0xb3, 0x34, 0x90, 0xd6, 0x64, 0xec, 0x3a, 0x6c, 0xf4, + 0xeb, 0x73, 0x3f, 0x5c, 0xf6, 0xfa, 0xb7, 0xff, 0x8a, 0x1e, 0xf1, 0x24, 0x0a, 0x19, 0x9b, 0xb4, + 0x72, 0xa8, 0x65, 0x44, 0xf5, 0xa0, 0x66, 0x4e, 0x54, 0x65, 0xec, 0xe4, 0x4e, 0x33, 0x74, 0xa2, + 0xf0, 0x8b, 0x0b, 0x85, 0x07, 0x82, 0x51, 0xbc, 0xa2, 0xb9, 0x24, 0x45, 0x9a, 0x2f, 0xbc, 0x12, + 0x93, 0xf0, 0xb5, 0xce, 0xa0, 0xa2, 0xba, 0x05, 0x71, 0xec, 0x9a, 0x23, 0x76, 0x07, 0xa2, 0x2e, + 0x9c, 0x2a, 0x1e, 0x77, 0xd9, 0x5c, 0xd8, 0xfa, 0x19, 0x48, 0x03, 0xe1, 0xf9, 0x57, 0x87, 0x5b, + 0x6a, 0xaa, 0xae, 0x2c, 0x6c, 0x6d, 0xa3, 0x17, 0x3e, 0xdc, 0x36, 0x98, 0xaa, 0x36, 0xac, 0x61, + 0xbc, 0x3a, 0xac, 0xd0, 0x33, 0xd5, 0x0f, 0x9a, 0x6f, 0x46, 0xdb, 0x16, 0x65, 0xdc, 0x5f, 0xf5, + 0xf1, 0x12, 0x3a, 0x1f, 0x37, 0xe2, 0x2a, 0x53, 0x01, 0x5c, 0x0a, 0x10, 0x5a, 0x6a, 0x44, 0x00, + 0x87, 0x86, 0xd5, 0xa1, 0x33, 0x81, 0x5b, 0x85, 0x35, 0x9a, 0xf3, 0x0d, 0xed, 0x71, 0xb8, 0xab, + 0x12, 0xdc, 0x2f, 0x90, 0xe6, 0xb9, 0x60, 0x35, 0x71, 0xae, 0x73, 0x49, 0xc3, 0xf5, 0x9b, 0xff, + 0xca, 0xa6, 0x17, 0x69, 0x4e, 0x1f, 0x9c, 0x10, 0x4d, 0x81, 0x46, 0x42, 0x2f, 0x49, 0xb8, 0x93, + 0xaf, 0xea, 0x0f, 0xea, 0xe4, 0xf0, 0xe0, 0xc9, 0xd3, 0xa3, 0x9d, 0x83, 0xbd, 0xc3, 0xc3, 0xa7, + 0x0b, 0x6f, 0xb2, 0x23, 0xad, 0x9b, 0x74, 0xa4, 0x99, 0xdd, 0x91, 0xa7, 0x22, 0xc7, 0xdd, 0x1b, + 0xed, 0xc0, 0xd2, 0x4d, 0x3a, 0xd0, 0x9a, 0x6f, 0x07, 0xb4, 0x6a, 0x5a, 0xe2, 0x20, 0x6d, 0xe6, + 0x99, 0x59, 0xa5, 0x46, 0xd0, 0x96, 0x2e, 0x7e, 0x95, 0xb4, 0x82, 0xf0, 0xfc, 0xa8, 0x4a, 0x3d, + 0xd0, 0x6a, 0x35, 0xbf, 0xfb, 0xff, 0x92, 0x4b, 0x8c, 0xfa, 0x8c, 0x54, 0x4e, 0x0a, 0x90, 0x02, + 0x4a, 0x57, 0x12, 0xed, 0x94, 0xe2, 0x95, 0xec, 0x48, 0x96, 0x26, 0xf6, 0xc9, 0x84, 0xa5, 0xe5, + 0xe3, 0xe9, 0x4c, 0x6e, 0xa2, 0x7d, 0xa5, 0x35, 0x1e, 0x59, 0x2b, 0x98, 0x5e, 0xeb, 0x19, 0x31, + 0xf9, 0x64, 0x51, 0xbd, 0xce, 0xf3, 0xdb, 0x7f, 0x09, 0x84, 0x58, 0x0d, 0xd3, 0x7c, 0xbc, 0x55, + 0x8d, 0x27, 0xc2, 0xee, 0x1d, 0xd3, 0x77, 0x70, 0x0f, 0x7c, 0x99, 0xa7, 0x3d, 0x61, 0xfe, 0x36, + 0x91, 0xec, 0x96, 0x51, 0x49, 0xa8, 0x19, 0xa4, 0xcc, 0xb3, 0x82, 0x34, 0x6a, 0xcd, 0x56, 0xa3, + 0x52, 0x7f, 0x03, 0x62, 0x04, 0xec, 0x54, 0xc0, 0x80, 0x34, 0xc1, 0x66, 0xaf, 0x8b, 0xd6, 0x2b, + 0x05, 0x25, 0x8a, 0x7c, 0x04, 0x3b, 0x24, 0x72, 0x23, 0x99, 0x6b, 0x09, 0x93, 0xb9, 0xc5, 0x63, + 0x2d, 0x9b, 0xad, 0x37, 0x22, 0x10, 0x45, 0x4f, 0x5a, 0xa4, 0xdc, 0x9a, 0x4b, 0x4f, 0x9a, 0x05, + 0x7a, 0xb2, 0xfc, 0x26, 0x7b, 0xb2, 0x44, 0xca, 0x4b, 0x73, 0xe9, 0x49, 0xab, 0x40, 0x4f, 0x56, + 0xdf, 0x64, 0x4f, 0x96, 0x49, 0x79, 0x79, 0x2e, 0x3d, 0x59, 0x2a, 0xd0, 0x93, 0xb5, 0xef, 0x7f, + 0xb5, 0xe2, 0x3d, 0x3d, 0xa4, 0x05, 0xd6, 0xaa, 0xd0, 0x65, 0xa4, 0x5f, 0xab, 0x64, 0xc9, 0xf9, + 0xc6, 0x56, 0xaa, 0x38, 0xca, 0xef, 0xea, 0x3a, 0x15, 0xc9, 0xf2, 0xe9, 0x57, 0x29, 0xa8, 0x85, + 0x7e, 0x28, 0xbd, 0x7b, 0xf6, 0xff, 0x62, 0xbc, 0x7a, 0x0a, 0x65, 0xc8, 0x47, 0x64, 0xd7, 0x34, + 0x06, 0xb6, 0xe3, 0xd1, 0xb7, 0xba, 0x54, 0x09, 0x14, 0xdf, 0x9c, 0x9b, 0x16, 0x3b, 0x57, 0x7b, + 0xe2, 0xf4, 0x74, 0xa7, 0x6b, 0xf5, 0x97, 0xe2, 0x60, 0xc5, 0x91, 0xd3, 0xa3, 0x5e, 0xde, 0x95, + 0x38, 0x58, 0x50, 0x79, 0x97, 0x93, 0x66, 0xd7, 0x23, 0xbc, 0xd3, 0x09, 0x6b, 0xb6, 0xc1, 0xf6, + 0xb5, 0xa2, 0x3b, 0x9d, 0xe0, 0x49, 0xec, 0x4e, 0x27, 0x4f, 0xbc, 0x16, 0xe1, 0xb6, 0x1a, 0xf8, + 0xd2, 0xa5, 0x4d, 0x12, 0xcc, 0xad, 0x3d, 0x13, 0xd6, 0x54, 0xcb, 0x66, 0x89, 0xc6, 0xf4, 0x22, + 0x48, 0x7d, 0x0a, 0xf6, 0x0d, 0x76, 0xf9, 0x84, 0xef, 0xfb, 0x68, 0x7a, 0xcc, 0x77, 0x85, 0x8a, + 0x75, 0x94, 0x43, 0xda, 0x82, 0xee, 0x45, 0xb9, 0xe3, 0xde, 0x99, 0x6e, 0x6e, 0xe3, 0x05, 0xf7, + 0x9a, 0x4e, 0xb2, 0x4b, 0x9d, 0x0a, 0x75, 0x11, 0xa1, 0x70, 0xb3, 0x98, 0xab, 0x91, 0xef, 0xce, + 0x28, 0x3a, 0x17, 0xfa, 0x31, 0x74, 0x2e, 0x8a, 0x8e, 0x20, 0x96, 0xfc, 0x84, 0x9a, 0x16, 0x7d, + 0x87, 0xba, 0xb6, 0xe3, 0xe8, 0x47, 0x0e, 0x04, 0x52, 0xc1, 0xae, 0x21, 0x94, 0xad, 0xf6, 0x18, + 0xb7, 0x93, 0xa7, 0xe9, 0xdb, 0x8c, 0x51, 0xff, 0x71, 0x79, 0xb2, 0x03, 0xe2, 0xd3, 0x75, 0x52, + 0xd7, 0x21, 0x09, 0x99, 0x88, 0xf3, 0xe5, 0x68, 0x82, 0x17, 0xa3, 0x92, 0x32, 0xe8, 0xd9, 0xcd, + 0xa5, 0x4a, 0x81, 0xdd, 0x5a, 0x59, 0x3b, 0x01, 0xa9, 0x7d, 0x24, 0xbe, 0xc6, 0x95, 0x93, 0xe6, + 0x92, 0x94, 0x38, 0x6d, 0xf6, 0x4e, 0xb0, 0x79, 0x1d, 0xf6, 0x41, 0x1d, 0xd8, 0xa5, 0xee, 0x1a, + 0x46, 0x97, 0x41, 0xf7, 0xa6, 0xef, 0x0f, 0x86, 0xb9, 0x65, 0xf6, 0x26, 0xa1, 0x3c, 0x65, 0xed, + 0xcc, 0xa9, 0x10, 0x7b, 0x60, 0x7a, 0xb3, 0x21, 0x76, 0xea, 0x64, 0xa0, 0xd5, 0xbc, 0x11, 0x95, + 0x9d, 0x8b, 0x99, 0x68, 0xcc, 0xa6, 0x2b, 0x72, 0xce, 0xdd, 0x69, 0xf9, 0x06, 0xe4, 0x82, 0x92, + 0x6f, 0xee, 0xce, 0x85, 0x6b, 0x60, 0xc2, 0xcd, 0xd4, 0x1f, 0x3e, 0x47, 0xd9, 0x54, 0x68, 0x4c, + 0xdb, 0x23, 0x10, 0x07, 0xea, 0x99, 0xd0, 0xb8, 0x49, 0x97, 0xf4, 0x9a, 0x0c, 0xdb, 0xf0, 0x9f, + 0x2e, 0x36, 0x45, 0x90, 0x46, 0x11, 0x98, 0x32, 0x47, 0xbc, 0x66, 0x0d, 0xfc, 0x40, 0xe4, 0x1e, + 0x68, 0x83, 0x3f, 0x5a, 0x2b, 0x2b, 0x73, 0x88, 0xfe, 0x88, 0xb7, 0x31, 0xbf, 0x08, 0x10, 0x05, + 0xdc, 0x22, 0x51, 0x20, 0xfa, 0x6c, 0x7b, 0x5a, 0xb3, 0x2b, 0x65, 0x72, 0x61, 0xd3, 0x20, 0x4d, + 0x72, 0x6d, 0x2d, 0x7d, 0x18, 0xf6, 0xeb, 0xaf, 0x7e, 0x8d, 0xf2, 0xa8, 0xec, 0x55, 0xe4, 0x3b, + 0x5c, 0x6e, 0x15, 0xb0, 0x5b, 0x92, 0xb8, 0x74, 0x2d, 0x6a, 0xb8, 0xa8, 0x9c, 0xe4, 0x5b, 0x7e, + 0x3c, 0x35, 0x95, 0x0a, 0x9b, 0xff, 0xfb, 0x1f, 0x08, 0x53, 0x72, 0xa4, 0xdb, 0x79, 0x66, 0x89, + 0x09, 0x56, 0x19, 0xb8, 0x2b, 0xb9, 0xa7, 0xa2, 0xe3, 0xd7, 0x22, 0xc5, 0x37, 0x95, 0xb2, 0xcf, + 0xa0, 0x68, 0xce, 0x92, 0x84, 0xc1, 0x10, 0x5e, 0xc6, 0x65, 0xd6, 0xec, 0x26, 0x73, 0xb6, 0x65, + 0xbb, 0xce, 0x5d, 0x62, 0x28, 0x7f, 0xee, 0xaa, 0x8b, 0x71, 0x7b, 0x58, 0x94, 0x6b, 0x36, 0xd9, + 0xa2, 0xad, 0x2c, 0xc9, 0x65, 0x33, 0x26, 0xa0, 0x74, 0x3a, 0xd4, 0xae, 0x8a, 0xef, 0x77, 0xe1, + 0x3b, 0x5e, 0xad, 0x65, 0x2b, 0xeb, 0x08, 0xf9, 0x87, 0x95, 0x2c, 0xd3, 0x3e, 0xf3, 0xaa, 0xc1, + 0x93, 0x26, 0x3e, 0xc2, 0x83, 0x2c, 0xbe, 0x37, 0x75, 0x60, 0xa7, 0x30, 0xec, 0xa6, 0x37, 0x5a, + 0xc5, 0xa1, 0xcd, 0x4c, 0xc3, 0xf5, 0x37, 0xff, 0x95, 0x1c, 0xf3, 0x62, 0x35, 0x66, 0xbc, 0x96, + 0x1f, 0xbb, 0xa6, 0x38, 0x79, 0x8f, 0x87, 0xb6, 0xf0, 0x32, 0x9b, 0xb7, 0x69, 0xc4, 0x4a, 0x28, + 0xcf, 0xef, 0x08, 0xd4, 0xec, 0x37, 0x75, 0xe5, 0xe7, 0xb5, 0xce, 0xce, 0xa4, 0x25, 0x93, 0x36, + 0x83, 0x7f, 0x81, 0xd2, 0x03, 0x4c, 0x3b, 0x0e, 0xd4, 0x3b, 0x0b, 0xc8, 0x6f, 0x07, 0x17, 0x35, + 0xc2, 0x27, 0xdb, 0xe8, 0xe2, 0xc5, 0xee, 0x06, 0x5e, 0x38, 0xc4, 0xdd, 0xbd, 0x5f, 0xd0, 0x0b, + 0x60, 0x47, 0x4f, 0xbe, 0xee, 0xc9, 0x50, 0x84, 0x1b, 0x05, 0xe8, 0x3c, 0xa4, 0xec, 0xe2, 0x78, + 0x60, 0xc4, 0xfd, 0xeb, 0x6f, 0xa0, 0x99, 0x9c, 0xb9, 0xc4, 0x77, 0x09, 0x81, 0x8d, 0x3f, 0x22, + 0x4d, 0x72, 0xe6, 0xe0, 0xc1, 0x0c, 0x1f, 0x58, 0x63, 0xef, 0xe0, 0x88, 0x9c, 0x7b, 0x04, 0x7e, + 0xb5, 0xab, 0x78, 0xa1, 0xd3, 0xa2, 0x33, 0x04, 0x8b, 0xf8, 0xd9, 0xa3, 0x93, 0x8a, 0x12, 0xca, + 0xf3, 0x83, 0xbd, 0x93, 0x2a, 0x34, 0xf8, 0xd5, 0xd1, 0x3e, 0xcc, 0x9e, 0xbd, 0x47, 0x47, 0x2c, + 0xb1, 0xba, 0x80, 0x5d, 0xc6, 0xfd, 0x27, 0x76, 0xb5, 0x11, 0x8c, 0xe5, 0x98, 0x05, 0xdc, 0xe9, + 0xa1, 0x9c, 0xee, 0x1d, 0x92, 0xe7, 0x4f, 0x4f, 0x64, 0x88, 0xec, 0xbb, 0x0c, 0x1b, 0xbf, 0xe3, + 0x07, 0x25, 0x90, 0x80, 0x0e, 0xbb, 0x78, 0xc8, 0x4c, 0x0a, 0xab, 0xea, 0x1a, 0x75, 0x20, 0x61, + 0xb0, 0x45, 0xfa, 0xcb, 0xb0, 0x1c, 0xbf, 0x23, 0x54, 0x2a, 0xf8, 0xbf, 0xae, 0xae, 0x88, 0xf9, + 0x70, 0xc3, 0x85, 0x3e, 0xe5, 0xf1, 0xc3, 0x48, 0x70, 0xc1, 0x26, 0xc8, 0x25, 0x92, 0xe4, 0x8f, + 0x27, 0x87, 0x69, 0x35, 0xd4, 0x07, 0x52, 0x32, 0x82, 0xf9, 0x9b, 0x4b, 0x2b, 0x39, 0xd1, 0xfc, + 0x89, 0xb0, 0xfa, 0x78, 0xfe, 0x7c, 0x95, 0x10, 0x4f, 0xe6, 0x60, 0x51, 0x2d, 0x40, 0x7f, 0xfc, + 0x36, 0x2e, 0x52, 0x58, 0x17, 0x67, 0x3f, 0x98, 0x32, 0x8e, 0x88, 0x93, 0x48, 0xd9, 0x96, 0xb5, + 0x36, 0xa9, 0x8e, 0x2c, 0xae, 0xa8, 0x32, 0x17, 0x15, 0x95, 0xc2, 0x92, 0x64, 0x8a, 0x24, 0x71, + 0xb2, 0x30, 0x8a, 0x28, 0x03, 0x46, 0xc1, 0x95, 0x8a, 0x78, 0x5d, 0xd7, 0x1c, 0x4b, 0xfb, 0xe9, + 0x8b, 0x8b, 0x3f, 0xf8, 0x8b, 0x43, 0xe5, 0xbe, 0x6c, 0x3f, 0xdb, 0x7f, 0x7c, 0xb8, 0x7d, 0x7c, + 0xfc, 0x60, 0xfb, 0x64, 0x8f, 0x3c, 0xd8, 0x3b, 0xd9, 0x3b, 0xd8, 0x79, 0xb4, 0xf7, 0x5e, 0x76, + 0xb6, 0x3f, 0xb1, 0x19, 0x2b, 0x90, 0xf8, 0x72, 0x2b, 0x18, 0xe4, 0xa0, 0x57, 0x21, 0x5f, 0xc6, + 0x78, 0x1c, 0x98, 0x01, 0xb9, 0x9f, 0xbf, 0x86, 0xe5, 0x5f, 0xa7, 0x55, 0x47, 0x00, 0x36, 0x14, + 0xf5, 0xc5, 0xaa, 0xba, 0x49, 0x78, 0x08, 0xa4, 0x38, 0x59, 0x7a, 0x0a, 0xac, 0x4f, 0xfd, 0xfa, + 0xff, 0x02, 0xe2, 0xec, 0x92, 0x67, 0x5b, 0x73, 0xdc, 0x72, 0xa9, 0x1e, 0x5f, 0x8a, 0x4b, 0x00, + 0x50, 0x01, 0xd1, 0xf4, 0x76, 0x38, 0x10, 0xeb, 0x72, 0x27, 0x58, 0x4a, 0x01, 0xbe, 0xa8, 0x5b, + 0x67, 0x22, 0xec, 0xd0, 0xf4, 0xa0, 0x2d, 0xce, 0xcd, 0x5e, 0xb9, 0x14, 0x2e, 0xb9, 0x29, 0x90, + 0xc8, 0x01, 0x4c, 0xdb, 0xb4, 0x79, 0x94, 0x15, 0x2c, 0x47, 0x08, 0x05, 0xc3, 0xa7, 0xbb, 0x43, + 0xcb, 0xa4, 0xd7, 0x5f, 0x27, 0x66, 0xbe, 0xd9, 0x27, 0x65, 0x15, 0x06, 0x49, 0xea, 0x45, 0xf8, + 0xc2, 0xba, 0x28, 0x68, 0xed, 0x01, 0x9a, 0x9f, 0x96, 0xe4, 0xcb, 0x88, 0x4b, 0x55, 0x12, 0xcf, + 0x15, 0x2a, 0x7d, 0xc7, 0x73, 0xde, 0xd2, 0x57, 0x71, 0xd4, 0x45, 0x7e, 0x12, 0x04, 0x5d, 0x4b, + 0xcf, 0x02, 0x47, 0xbf, 0xf4, 0x48, 0x56, 0xa3, 0xa4, 0xc7, 0x89, 0xcb, 0x70, 0x3f, 0x4f, 0x9f, + 0x65, 0x92, 0x10, 0xaf, 0xc3, 0x8a, 0xb0, 0x07, 0xab, 0x78, 0x19, 0x54, 0x8e, 0xcd, 0x2d, 0x45, + 0x57, 0x43, 0xd2, 0xf4, 0xc8, 0xed, 0xcd, 0x70, 0x30, 0x0e, 0x94, 0x64, 0x89, 0x93, 0xc7, 0xf1, + 0x87, 0xd4, 0x6d, 0xe7, 0x72, 0x99, 0xd9, 0xab, 0xe8, 0x4f, 0x3f, 0x62, 0xcb, 0x31, 0x38, 0x1f, + 0x7d, 0x44, 0x6e, 0xcb, 0x0f, 0xf2, 0xb8, 0x22, 0x0b, 0x4d, 0x66, 0x5a, 0xaa, 0x61, 0x81, 0xb8, + 0x4e, 0x30, 0x57, 0x16, 0x10, 0xe0, 0x35, 0x9e, 0x74, 0xac, 0x76, 0xdc, 0x07, 0xed, 0x87, 0x1d, + 0xd6, 0x49, 0xf0, 0x1d, 0xbf, 0x8c, 0x56, 0xc3, 0x7a, 0x19, 0xf4, 0x7b, 0x44, 0x0d, 0x84, 0xb6, + 0x19, 0x47, 0x94, 0x1d, 0xff, 0x70, 0x26, 0x9e, 0xa0, 0x63, 0xdb, 0xec, 0xc0, 0x12, 0x3b, 0xc8, + 0x46, 0x32, 0x24, 0xa5, 0x00, 0x09, 0x94, 0x94, 0xbe, 0xd6, 0x7d, 0x63, 0xc0, 0xf2, 0x2c, 0x6e, + 0xc2, 0x20, 0x97, 0x1e, 0xb5, 0x4a, 0x79, 0x84, 0x4b, 0xe0, 0x79, 0x1a, 0x48, 0x03, 0x19, 0x66, + 0xbe, 0x14, 0xc8, 0x6b, 0x21, 0x44, 0x9a, 0xc3, 0x2f, 0x82, 0x54, 0x38, 0xaa, 0xbc, 0xca, 0xcc, + 0x83, 0x8a, 0x3f, 0xaf, 0x6e, 0xcd, 0xf6, 0x56, 0xfd, 0x26, 0xfd, 0xf4, 0x55, 0x02, 0x87, 0x57, + 0x69, 0x19, 0x16, 0x9c, 0xef, 0x0f, 0xf8, 0x68, 0x32, 0x52, 0x1a, 0xcf, 0x69, 0x21, 0xc9, 0x89, + 0x9c, 0xd1, 0x61, 0x3f, 0x49, 0x1f, 0x65, 0x8d, 0x24, 0x42, 0xf2, 0x65, 0x3e, 0x01, 0x4e, 0x5c, + 0xfd, 0xc1, 0xe3, 0x95, 0x78, 0xc9, 0x6e, 0x4a, 0xaa, 0x86, 0x32, 0x83, 0x33, 0x97, 0xee, 0xd2, + 0x6e, 0xc5, 0xd8, 0x46, 0xaf, 0x79, 0x9d, 0xa7, 0x63, 0x8a, 0xa2, 0xe4, 0x76, 0xe1, 0x25, 0x41, + 0xc5, 0xb1, 0x0e, 0x74, 0xda, 0x72, 0x06, 0xe5, 0x92, 0xa2, 0x2b, 0xeb, 0x20, 0x48, 0x95, 0x8d, + 0xde, 0x27, 0xa5, 0x01, 0xbd, 0xfe, 0xb6, 0xdf, 0xb7, 0xa9, 0x5f, 0x22, 0xeb, 0xf8, 0x0d, 0x27, + 0xb3, 0x03, 0x85, 0xec, 0x64, 0x43, 0xaf, 0x08, 0xb5, 0x3c, 0xaa, 0xe8, 0x0e, 0x90, 0x8f, 0xdd, + 0x5b, 0xcd, 0x65, 0x43, 0x38, 0xa4, 0x21, 0x60, 0x76, 0xf3, 0x4e, 0x55, 0x49, 0x62, 0xcc, 0x6a, + 0x2c, 0x35, 0x59, 0x94, 0x52, 0x7d, 0xb0, 0x6c, 0x68, 0x1e, 0x8f, 0x3d, 0xc0, 0xab, 0x73, 0xaf, + 0xff, 0x88, 0x28, 0xd8, 0x2c, 0x4d, 0x05, 0xcb, 0x8c, 0x2c, 0x76, 0xdb, 0x3c, 0x22, 0x65, 0xbd, + 0x03, 0x01, 0x66, 0xd8, 0x76, 0xf1, 0x21, 0x66, 0x2b, 0x1f, 0x93, 0xd9, 0x05, 0x47, 0x8c, 0x95, + 0x65, 0x49, 0x90, 0x59, 0x43, 0xca, 0x55, 0x06, 0x71, 0x40, 0x0c, 0xcb, 0x18, 0x94, 0xd0, 0xc3, + 0x7c, 0xa6, 0xe8, 0xd8, 0xe7, 0x69, 0x62, 0x52, 0xf3, 0x49, 0xd1, 0xeb, 0xa0, 0xc7, 0x87, 0x06, + 0xb3, 0x4e, 0x71, 0x72, 0x89, 0xbe, 0x52, 0xf4, 0x93, 0x9d, 0xa1, 0xdd, 0x27, 0x75, 0xf1, 0xc2, + 0xb4, 0x7b, 0xce, 0x05, 0x0a, 0x8f, 0x3d, 0x54, 0x74, 0x10, 0x77, 0x0a, 0x8a, 0x74, 0xb9, 0x84, + 0x59, 0x13, 0x80, 0x5d, 0x02, 0xed, 0xab, 0x9c, 0xa9, 0x66, 0xfd, 0x80, 0x34, 0x04, 0xaf, 0xa8, + 0x6a, 0x50, 0x54, 0x8f, 0x54, 0xae, 0xf0, 0x12, 0xd7, 0xe8, 0xa4, 0xba, 0x37, 0xe3, 0xb2, 0x8c, + 0x62, 0x93, 0x5d, 0xfd, 0x0e, 0xc3, 0x1b, 0x5b, 0x97, 0x15, 0xa3, 0x9b, 0xee, 0xcf, 0x30, 0x58, + 0x71, 0xbd, 0x19, 0x16, 0x5b, 0xec, 0xd5, 0x30, 0x5c, 0x5f, 0x87, 0xd3, 0x2f, 0xad, 0x09, 0xdd, + 0x7a, 0x78, 0xd3, 0x85, 0x14, 0x11, 0xf2, 0x0b, 0xad, 0x9d, 0xfe, 0xec, 0xcb, 0x65, 0xe1, 0xe5, + 0xee, 0x96, 0x6e, 0xf1, 0x7b, 0x25, 0xaf, 0x34, 0xef, 0x99, 0x21, 0xfa, 0xc9, 0xde, 0xc1, 0x29, + 0xc6, 0xde, 0x9c, 0x1c, 0xec, 0x9f, 0xbe, 0x97, 0x7d, 0xb4, 0x60, 0xe5, 0xa2, 0xde, 0xf8, 0x14, + 0x84, 0x9a, 0xe7, 0x1b, 0xa3, 0x31, 0xf0, 0x6d, 0x63, 0x23, 0xf6, 0xb6, 0x83, 0x67, 0xf7, 0x40, + 0x23, 0x8b, 0x97, 0x48, 0x1b, 0xb0, 0xc1, 0xd5, 0x32, 0x61, 0x69, 0x8d, 0x50, 0xb5, 0x9d, 0x0b, + 0x00, 0x61, 0xd3, 0x0b, 0x82, 0xd9, 0x7a, 0xca, 0x09, 0xd6, 0xcc, 0x38, 0x23, 0x1c, 0x02, 0x4e, + 0xec, 0x0e, 0xa5, 0x58, 0x15, 0x5a, 0x00, 0x7d, 0x88, 0x65, 0xa7, 0xa7, 0x58, 0xbe, 0xed, 0xbb, + 0x78, 0x63, 0x68, 0xa9, 0x47, 0x6b, 0xbb, 0x7b, 0xc9, 0xb9, 0xa0, 0xc0, 0x70, 0xe2, 0x77, 0xd9, + 0x0d, 0x51, 0xfe, 0xb0, 0xde, 0xb7, 0x1c, 0x98, 0xb4, 0x08, 0x10, 0xf0, 0x11, 0x9d, 0x5a, 0x44, + 0x3f, 0x57, 0x43, 0x69, 0x57, 0x3b, 0xfd, 0xbe, 0x07, 0x24, 0xdb, 0x24, 0x52, 0x8d, 0x2b, 0x30, + 0x5c, 0x9f, 0xb2, 0xe7, 0x50, 0xf7, 0xa7, 0x64, 0xb5, 0x11, 0xaf, 0xa8, 0x20, 0x2f, 0xb6, 0x5f, + 0x13, 0xb0, 0x32, 0x90, 0x45, 0xf1, 0x10, 0x1b, 0xba, 0x2d, 0xd2, 0xa8, 0x10, 0xe8, 0x74, 0x77, + 0x82, 0x29, 0x8e, 0x77, 0x5d, 0xb3, 0xef, 0xcb, 0xe4, 0x7d, 0x95, 0x3e, 0x40, 0x9e, 0x18, 0xbc, + 0xf0, 0xe0, 0x7b, 0x62, 0xe0, 0xb4, 0x83, 0x12, 0x4b, 0x0a, 0x00, 0xc3, 0xc2, 0x7c, 0x68, 0xf5, + 0xc8, 0x7d, 0xc8, 0xf6, 0x35, 0x55, 0x43, 0xa4, 0x85, 0x28, 0xa5, 0x3c, 0x08, 0xae, 0x30, 0xda, + 0x98, 0x0a, 0x17, 0x11, 0xe0, 0x3e, 0x17, 0x64, 0xe4, 0xb4, 0x01, 0x69, 0x6c, 0xb2, 0xe8, 0xc9, + 0x05, 0x32, 0x0e, 0x0d, 0x46, 0xa7, 0x68, 0x66, 0x82, 0xe9, 0xf1, 0x64, 0xee, 0x19, 0xcb, 0x6f, + 0x69, 0xc4, 0x4a, 0x08, 0xae, 0x17, 0x51, 0x66, 0x2a, 0xce, 0xe3, 0xe5, 0x0e, 0x70, 0xab, 0xd5, + 0x2b, 0x06, 0x8f, 0x97, 0xcd, 0x9c, 0x0e, 0xdc, 0xad, 0xc2, 0x91, 0x54, 0x2d, 0x41, 0x72, 0xa3, + 0x82, 0xdc, 0xc2, 0x13, 0x0a, 0x28, 0x94, 0x58, 0x9a, 0xf4, 0xd2, 0x86, 0x46, 0xf5, 0xc8, 0x16, + 0x03, 0x53, 0x4e, 0x29, 0xf8, 0xd1, 0xd5, 0xb7, 0xd8, 0x25, 0x15, 0xed, 0xa7, 0x58, 0x0f, 0xa0, + 0x94, 0xc3, 0x16, 0x11, 0x81, 0x60, 0x96, 0x55, 0x2a, 0x20, 0x30, 0xa0, 0x90, 0x90, 0x14, 0xc0, + 0x3b, 0x96, 0xd9, 0xa5, 0xe5, 0x46, 0x95, 0x34, 0x57, 0x15, 0x98, 0xe5, 0x50, 0x17, 0xc1, 0x8b, + 0x11, 0x0b, 0xee, 0xe0, 0x92, 0xb1, 0x28, 0x68, 0x70, 0x64, 0x13, 0x17, 0x1d, 0xcd, 0x25, 0xbd, + 0xc2, 0xac, 0xe7, 0xcc, 0x3e, 0xf5, 0xbb, 0xc3, 0x3d, 0x2e, 0x36, 0x52, 0x7c, 0xc9, 0x5e, 0x96, + 0x4b, 0x8b, 0xd0, 0x2f, 0xa6, 0xe6, 0x56, 0x52, 0x58, 0xd5, 0xc1, 0x3a, 0xb7, 0xcb, 0x2e, 0xc8, + 0x1d, 0xa0, 0x2e, 0x45, 0xdd, 0x32, 0xf8, 0x5c, 0xff, 0xc2, 0x43, 0x45, 0x5a, 0x57, 0x05, 0xe4, + 0x8b, 0xa1, 0xf7, 0x52, 0x69, 0x29, 0x2a, 0x04, 0x5c, 0x42, 0xe4, 0x23, 0xb0, 0xba, 0x9f, 0xa2, + 0x65, 0xf0, 0x93, 0x58, 0xd0, 0xc2, 0xd2, 0xec, 0x81, 0xba, 0x8a, 0x5e, 0x6c, 0xca, 0x3f, 0x5c, + 0x4c, 0xe2, 0xfd, 0x21, 0x78, 0x8f, 0x2c, 0xeb, 0x53, 0x66, 0xc9, 0xa3, 0xd3, 0x63, 0x6e, 0x5f, + 0xe3, 0x3d, 0x6a, 0xa8, 0x37, 0xb0, 0x5b, 0x49, 0xb8, 0xc5, 0xad, 0x4f, 0xb0, 0xc5, 0x95, 0xaa, + 0x34, 0x1d, 0xbb, 0x06, 0x8e, 0x0e, 0x75, 0x5d, 0x14, 0x63, 0x3a, 0x4a, 0x06, 0x56, 0x32, 0x2b, + 0x56, 0x2e, 0xed, 0xd3, 0xa1, 0x45, 0x5d, 0x34, 0x8d, 0xd9, 0x03, 0x0d, 0xb6, 0x53, 0x92, 0x5f, + 0x40, 0x55, 0xcc, 0xed, 0x98, 0x5e, 0xa8, 0x55, 0x12, 0x24, 0xb2, 0x24, 0x7a, 0x81, 0x56, 0x36, + 0x1a, 0xb0, 0xdc, 0x22, 0x24, 0x46, 0xc7, 0x9d, 0xf4, 0x71, 0x9f, 0x7c, 0x32, 0x92, 0x49, 0x77, + 0x35, 0x21, 0x63, 0xf7, 0xfa, 0xbb, 0x7e, 0x82, 0x6e, 0x01, 0xf7, 0xa2, 0x45, 0xb6, 0xd8, 0x65, + 0x10, 0xde, 0x1e, 0x07, 0x0b, 0xa9, 0xe6, 0x8f, 0x4f, 0x30, 0x58, 0x31, 0x4b, 0x04, 0x43, 0x19, + 0x4e, 0xd3, 0x8d, 0x6c, 0x40, 0x78, 0x0b, 0x42, 0x0e, 0x1c, 0x4e, 0x0f, 0x2c, 0x98, 0x0d, 0xad, + 0x87, 0x4c, 0x7d, 0x02, 0x94, 0x3f, 0x61, 0x52, 0x57, 0x0b, 0x52, 0x2e, 0x97, 0x0d, 0x11, 0xb3, + 0x02, 0xef, 0xc0, 0xaa, 0xea, 0xe7, 0x40, 0x94, 0xcb, 0xa5, 0x5c, 0x52, 0xf2, 0x3a, 0xc3, 0x26, + 0x69, 0x94, 0xc1, 0x18, 0x4d, 0x30, 0xf6, 0x48, 0xbe, 0xcc, 0x28, 0xcb, 0x0c, 0xe2, 0x93, 0x8b, + 0xf9, 0x5a, 0x82, 0x0c, 0xc5, 0x3e, 0x3b, 0xa8, 0xcd, 0x6e, 0xc3, 0x39, 0xa7, 0x6e, 0x87, 0xed, + 0x68, 0x6b, 0xeb, 0x07, 0x23, 0x87, 0x42, 0x96, 0x63, 0xa0, 0xf4, 0xc0, 0x28, 0xab, 0x04, 0xc9, + 0x96, 0x37, 0x89, 0xef, 0x4e, 0xb2, 0x2b, 0xe0, 0x58, 0xa5, 0x45, 0x19, 0x0e, 0x25, 0x10, 0xea, + 0xe9, 0x19, 0xfa, 0xaa, 0x5e, 0x7f, 0xf5, 0x6b, 0xe6, 0xa5, 0xc2, 0x74, 0x7c, 0xa5, 0x02, 0xad, + 0x8f, 0x0d, 0xdc, 0xf5, 0x10, 0x54, 0x17, 0x2b, 0x85, 0x33, 0x36, 0xba, 0xa6, 0xcf, 0x56, 0x8a, + 0x66, 0x49, 0x43, 0x75, 0x41, 0x35, 0x26, 0xf0, 0x6a, 0x38, 0xe8, 0x8c, 0x5c, 0x88, 0x47, 0x8d, + 0x8d, 0x18, 0x06, 0xfb, 0x00, 0x21, 0xf8, 0xf5, 0x09, 0x78, 0x5b, 0x29, 0xd2, 0xd7, 0x66, 0x57, + 0x5f, 0xbb, 0x60, 0xce, 0x9e, 0x1b, 0xb6, 0x5f, 0xc9, 0xb4, 0x5d, 0x65, 0x6e, 0xaa, 0xc4, 0x78, + 0xb0, 0xd0, 0x7a, 0x96, 0xf2, 0x38, 0x48, 0xac, 0x54, 0x89, 0x31, 0xe0, 0x54, 0xe0, 0xc4, 0x82, + 0xab, 0xe1, 0xb9, 0xdb, 0xb3, 0x30, 0x5d, 0xc8, 0x70, 0x55, 0x90, 0x58, 0xd4, 0xe5, 0x2c, 0xc7, + 0x29, 0x35, 0x1b, 0xe3, 0xe5, 0xb2, 0x51, 0x92, 0xef, 0xf2, 0x39, 0x35, 0xc5, 0x78, 0x25, 0x9e, + 0x51, 0x77, 0x0e, 0x1c, 0xd6, 0xa8, 0xaf, 0x66, 0x40, 0xc9, 0xbc, 0xb3, 0x34, 0xae, 0xd8, 0x86, + 0x3d, 0x9f, 0x91, 0x5f, 0xc5, 0x05, 0x2c, 0xa4, 0x2c, 0xae, 0x42, 0xa2, 0xec, 0xad, 0x94, 0xbc, + 0x7c, 0x7e, 0xfc, 0xaa, 0x53, 0x6e, 0x67, 0x65, 0xd8, 0x2c, 0x78, 0x5a, 0x15, 0x31, 0xc1, 0x86, + 0x3d, 0x1a, 0x32, 0xe2, 0x3b, 0xc8, 0x6d, 0xff, 0xf4, 0xf7, 0x3f, 0xb2, 0xda, 0xbb, 0xcf, 0x6a, + 0x6f, 0x4c, 0x11, 0x45, 0x06, 0x15, 0xea, 0x5b, 0xbe, 0x4e, 0xfa, 0xaa, 0xa0, 0xdb, 0x22, 0xa9, + 0xb5, 0x27, 0x50, 0x51, 0x79, 0x48, 0x50, 0xc8, 0xa7, 0x7c, 0x2e, 0xcc, 0x73, 0xa2, 0x73, 0x92, + 0xb3, 0xd1, 0xc1, 0x58, 0x09, 0x19, 0x52, 0x2d, 0x05, 0x64, 0x23, 0xab, 0xfa, 0x76, 0xc7, 0x0b, + 0x5c, 0x4a, 0x46, 0xc7, 0xe3, 0xc3, 0xaf, 0xe8, 0x77, 0x16, 0x08, 0xb4, 0x38, 0x72, 0x75, 0x37, + 0x2c, 0x54, 0xaa, 0x64, 0xa2, 0xf2, 0x5c, 0x98, 0xa4, 0xd9, 0x80, 0x44, 0x18, 0x76, 0x26, 0x24, + 0x31, 0x9c, 0x79, 0xa0, 0x78, 0xb1, 0x52, 0x91, 0xee, 0x86, 0x7d, 0x28, 0xce, 0xb5, 0xb7, 0xb4, + 0xd3, 0x4b, 0x37, 0xaa, 0x61, 0x43, 0xac, 0x8f, 0x71, 0x29, 0xf5, 0x71, 0x89, 0x7c, 0xcc, 0x5f, + 0x6e, 0xe8, 0xeb, 0xf1, 0x0e, 0xc5, 0x2b, 0xee, 0x9a, 0x3c, 0xb6, 0x95, 0x0c, 0x28, 0xac, 0xfa, + 0x21, 0x14, 0xf8, 0x5d, 0x8a, 0xa2, 0x29, 0xcf, 0x1d, 0xa5, 0xcd, 0x24, 0x29, 0x24, 0xac, 0xce, + 0xbd, 0xa9, 0xf1, 0x9e, 0x23, 0xc6, 0xc8, 0xa9, 0x31, 0xa4, 0x31, 0x4e, 0x54, 0x8f, 0xf5, 0x74, + 0xe4, 0x6d, 0x94, 0xa6, 0x44, 0xf2, 0x98, 0xba, 0x7d, 0x7a, 0xc6, 0xcd, 0x0d, 0x2e, 0x5c, 0x71, + 0x75, 0xbb, 0x4d, 0x5e, 0x7f, 0xf5, 0x7b, 0x15, 0x4e, 0xd3, 0x70, 0x18, 0xdb, 0x2c, 0xe1, 0x7b, + 0x3b, 0xa4, 0x54, 0xda, 0xd0, 0xf3, 0x11, 0x52, 0xe4, 0xde, 0x26, 0x59, 0xc9, 0x1c, 0x93, 0x08, + 0x64, 0xb4, 0xff, 0xc2, 0x5e, 0xd4, 0x06, 0x8e, 0xa3, 0xdc, 0x80, 0x49, 0x0e, 0xbb, 0x68, 0x66, + 0xa9, 0x31, 0x63, 0x3b, 0x17, 0xfc, 0x16, 0xeb, 0x8c, 0xa6, 0x66, 0x02, 0xdb, 0x31, 0xd4, 0xd8, + 0xcf, 0xe0, 0x7a, 0x52, 0x79, 0x4f, 0x14, 0x12, 0x9b, 0x69, 0xe0, 0x96, 0xb8, 0xa7, 0x35, 0x5b, + 0x34, 0x23, 0x5b, 0xa0, 0xb7, 0x4d, 0xf6, 0x24, 0xc6, 0xab, 0xff, 0x54, 0xe5, 0xac, 0x97, 0xe4, + 0x98, 0x71, 0xe9, 0xb5, 0x4d, 0xbb, 0xcb, 0x5c, 0x83, 0x65, 0xac, 0x5f, 0xb7, 0x9d, 0x8b, 0xd0, + 0xc9, 0x0f, 0x62, 0x3e, 0x06, 0x0e, 0x9f, 0xaf, 0xad, 0x2e, 0x03, 0xc0, 0xba, 0xef, 0xec, 0x9b, + 0x2f, 0x69, 0xaf, 0xdc, 0x9c, 0xc6, 0x3f, 0x18, 0xc0, 0xc9, 0xdb, 0xc1, 0x60, 0x3b, 0xa8, 0xa2, + 0x6f, 0xe1, 0x56, 0x46, 0x62, 0x1b, 0x83, 0xcd, 0xd2, 0x32, 0x88, 0x14, 0x3e, 0x77, 0xc3, 0x8e, + 0xe0, 0xe3, 0x53, 0x03, 0x43, 0x9f, 0x4b, 0x05, 0x3d, 0x8d, 0xd3, 0xa2, 0x5b, 0x3a, 0x72, 0xba, + 0x43, 0xb0, 0x6f, 0xf4, 0x0e, 0x48, 0xa5, 0x27, 0x99, 0x51, 0x32, 0x54, 0x75, 0xc8, 0xed, 0xcd, + 0xcc, 0x91, 0x35, 0x07, 0x76, 0x60, 0x1d, 0x47, 0x75, 0x70, 0xfd, 0xbe, 0xcf, 0xa4, 0xf4, 0xba, + 0x72, 0xce, 0xe6, 0x3b, 0x36, 0x0a, 0x51, 0x1e, 0xdb, 0xfe, 0x38, 0xd9, 0xb6, 0x10, 0x8a, 0x8b, + 0x40, 0x5b, 0x52, 0x2e, 0x05, 0xef, 0x43, 0x55, 0x8b, 0xbd, 0xc7, 0xa4, 0xde, 0x3c, 0xec, 0xfc, + 0xc6, 0xb4, 0xd7, 0x61, 0x5c, 0x3a, 0x62, 0x76, 0x25, 0x5e, 0xda, 0xd3, 0xc1, 0xac, 0x2d, 0x7e, + 0xf1, 0x41, 0xc8, 0x77, 0xd2, 0xa8, 0xdc, 0xac, 0xe1, 0xcb, 0xfc, 0x7d, 0xb3, 0x31, 0x3a, 0x59, + 0x77, 0x81, 0x13, 0x71, 0x36, 0xb1, 0xca, 0xfc, 0x51, 0xfe, 0xac, 0x91, 0x01, 0x3c, 0x72, 0x26, + 0xae, 0x17, 0xdf, 0x80, 0x8b, 0x01, 0xfb, 0x50, 0x00, 0x03, 0xa8, 0x4b, 0xab, 0x9a, 0x8d, 0x38, + 0x5e, 0x94, 0xc7, 0xe0, 0x67, 0xc3, 0x62, 0x20, 0x00, 0xd4, 0x6a, 0x23, 0x6b, 0x27, 0x44, 0x4b, + 0x3a, 0x0e, 0x27, 0x8f, 0xab, 0x24, 0xca, 0x04, 0xd3, 0x93, 0xf3, 0x90, 0xdc, 0x63, 0x78, 0x33, + 0x24, 0xd1, 0xc3, 0x00, 0x79, 0x78, 0x3c, 0x92, 0xd9, 0x29, 0x4b, 0xc2, 0xa6, 0x93, 0xa5, 0x2a, + 0x37, 0x9e, 0xc2, 0x24, 0xad, 0x19, 0xaa, 0x5b, 0x94, 0x3c, 0x58, 0x45, 0x5f, 0x9e, 0xa0, 0xab, + 0x8d, 0x07, 0x76, 0x0a, 0xc0, 0x10, 0xe9, 0x4d, 0xd5, 0xa0, 0x18, 0xce, 0x85, 0x21, 0xb1, 0xd2, + 0x99, 0x9b, 0x56, 0xb8, 0x67, 0x8d, 0x1d, 0x44, 0x0b, 0x4f, 0xf4, 0x93, 0x6f, 0xc3, 0x00, 0xf3, + 0x3d, 0x1b, 0x8f, 0xa9, 0xbb, 0x63, 0x78, 0xa9, 0xfd, 0x26, 0x05, 0x5e, 0x43, 0xa3, 0xc7, 0x13, + 0xe4, 0x22, 0xff, 0x2c, 0x7e, 0xca, 0xb3, 0xc8, 0x7e, 0xbe, 0xc8, 0x53, 0xc7, 0x22, 0xe4, 0x64, + 0x48, 0x5e, 0xd4, 0x66, 0xdd, 0xa5, 0x2c, 0x05, 0x4c, 0x79, 0xf1, 0xfa, 0x57, 0x8b, 0x83, 0x2a, + 0x29, 0x19, 0xa5, 0x42, 0x85, 0xff, 0xc8, 0x0a, 0x3b, 0xc5, 0x0a, 0x7f, 0xc5, 0x0a, 0x4f, 0x72, + 0x37, 0xf0, 0xe2, 0x44, 0x60, 0x81, 0xc0, 0x0c, 0x7b, 0xad, 0xe8, 0xe5, 0xc7, 0x31, 0x8e, 0x1d, + 0x4f, 0xa6, 0xa0, 0x17, 0xdc, 0xbe, 0xcd, 0x6e, 0x19, 0x48, 0x4b, 0xde, 0x44, 0x2b, 0xbc, 0x66, + 0x46, 0x31, 0x4c, 0x45, 0x16, 0x80, 0x64, 0xc7, 0xc9, 0xcb, 0x61, 0xb3, 0xd5, 0x08, 0x83, 0x4a, + 0x41, 0x7d, 0x3f, 0x1a, 0x2b, 0x9d, 0xf2, 0x14, 0x72, 0x59, 0x5c, 0x98, 0xb2, 0x93, 0x80, 0xbc, + 0x6a, 0x99, 0x56, 0xc8, 0xeb, 0xbf, 0xfb, 0x5d, 0x94, 0x03, 0x59, 0xa3, 0xab, 0x46, 0x90, 0xb8, + 0x8d, 0xd2, 0x15, 0xdb, 0xca, 0x25, 0x91, 0x39, 0x47, 0x53, 0x4d, 0x13, 0x25, 0xc5, 0xf6, 0x33, + 0x1d, 0x68, 0x1d, 0xa6, 0xaa, 0xd6, 0x94, 0x0e, 0x9b, 0x8d, 0x4d, 0x6d, 0x8d, 0x05, 0x5f, 0x25, + 0x2d, 0xb5, 0xc6, 0x83, 0x3f, 0x3c, 0x2f, 0x72, 0x71, 0x75, 0x4e, 0x33, 0x37, 0xf8, 0x7c, 0x4e, + 0xcf, 0x30, 0xfe, 0x7c, 0x23, 0x31, 0x1f, 0x03, 0x51, 0x11, 0x27, 0x7d, 0x90, 0xd5, 0x18, 0x25, + 0x9e, 0x80, 0x87, 0xb2, 0x91, 0xa5, 0x38, 0x2e, 0x65, 0xf0, 0xf4, 0xe2, 0xa2, 0x24, 0x37, 0x70, + 0x6a, 0xfe, 0x0d, 0x26, 0x79, 0x36, 0x6a, 0x57, 0x9f, 0xd5, 0x3e, 0xff, 0xe9, 0x4f, 0xa4, 0xf9, + 0x19, 0xe1, 0x95, 0x20, 0x46, 0x1a, 0x42, 0x59, 0x02, 0xf1, 0xcb, 0xdd, 0xc3, 0xc6, 0x8b, 0x47, + 0x95, 0x42, 0x90, 0x52, 0x70, 0xee, 0xaf, 0x23, 0xa4, 0x46, 0xed, 0x2e, 0x17, 0x17, 0x45, 0xe0, + 0xa5, 0x26, 0x6c, 0x40, 0x5c, 0x98, 0xa8, 0x98, 0xf6, 0xfb, 0xa3, 0x8f, 0x78, 0x03, 0x2a, 0xce, + 0x96, 0x68, 0x9b, 0x64, 0x46, 0x7e, 0xb4, 0x58, 0xc1, 0x8c, 0x9a, 0x99, 0x00, 0x16, 0x15, 0x79, + 0x78, 0xfd, 0x9d, 0xe5, 0x9b, 0x83, 0xcc, 0x4a, 0x85, 0xda, 0x91, 0x6c, 0x1b, 0xd1, 0x9b, 0x2d, + 0xec, 0xcb, 0x94, 0x5d, 0xe0, 0xe7, 0x91, 0xa7, 0xe8, 0xc2, 0x3f, 0xfd, 0x3d, 0xf9, 0x64, 0x42, + 0x2c, 0x10, 0x27, 0xb7, 0xa7, 0xea, 0x83, 0xb2, 0x21, 0xa9, 0x0f, 0xb7, 0x75, 0x03, 0x20, 0xe2, + 0x38, 0x6c, 0xf6, 0x1e, 0x67, 0xe6, 0x13, 0xf4, 0x8c, 0xa5, 0xe7, 0xc5, 0x88, 0x39, 0xcc, 0x16, + 0x3f, 0xfd, 0x1b, 0x64, 0x8e, 0x90, 0x35, 0x16, 0xd3, 0x93, 0x94, 0x45, 0x5c, 0x24, 0xc0, 0xa5, + 0x1c, 0x70, 0x0a, 0x21, 0xc1, 0x11, 0x09, 0x2e, 0x45, 0x07, 0x04, 0x92, 0x40, 0xea, 0x98, 0x58, + 0x5e, 0x75, 0x2b, 0x50, 0xaa, 0x0b, 0x8a, 0xca, 0x9f, 0x36, 0x3e, 0xd7, 0xd4, 0x0c, 0xe2, 0xb1, + 0x7f, 0xf1, 0xcc, 0x1e, 0x70, 0xe6, 0x01, 0x8d, 0x25, 0x48, 0x7d, 0x5c, 0xfa, 0xc9, 0x97, 0x12, + 0xa4, 0x57, 0x25, 0x02, 0x6b, 0xfc, 0x71, 0x80, 0xe2, 0x4f, 0xbe, 0x0c, 0xb0, 0x7d, 0xf5, 0x8b, + 0x4a, 0x11, 0x53, 0x7e, 0xce, 0x8c, 0xa2, 0x42, 0x78, 0xa1, 0xf4, 0x71, 0x8c, 0x12, 0x20, 0x8f, + 0x16, 0x10, 0x69, 0x14, 0x51, 0x01, 0xb6, 0xf3, 0xe2, 0xab, 0xe9, 0x66, 0x02, 0x3b, 0x4f, 0x3c, + 0x45, 0x07, 0xff, 0xf4, 0x67, 0x9c, 0x09, 0x67, 0x13, 0xf7, 0x6a, 0xba, 0x89, 0xa0, 0x6a, 0x27, + 0x63, 0x35, 0xc0, 0x23, 0xa9, 0x86, 0xaf, 0x5a, 0x92, 0x32, 0x43, 0x50, 0x12, 0xb5, 0xe6, 0xae, + 0xa3, 0xea, 0x55, 0xc0, 0x50, 0x7d, 0xfa, 0xcc, 0x66, 0xea, 0x53, 0x21, 0xed, 0xa9, 0x1e, 0xca, + 0x30, 0x8d, 0x04, 0xf3, 0x45, 0x97, 0x7c, 0xb6, 0xaf, 0xa0, 0x32, 0x4c, 0xe1, 0x3d, 0x8a, 0x42, + 0x98, 0x68, 0x2c, 0x7c, 0x12, 0x7e, 0xdd, 0x23, 0x12, 0x68, 0x7c, 0xf0, 0x31, 0x8a, 0xfb, 0x4a, + 0xd6, 0x29, 0x2e, 0x8e, 0x40, 0xd4, 0x14, 0xd4, 0x28, 0x7d, 0x66, 0x6b, 0xf4, 0x8d, 0x58, 0x31, + 0xd6, 0x94, 0x37, 0xe9, 0x78, 0xbe, 0x5b, 0x36, 0xab, 0xd8, 0xce, 0x4c, 0xce, 0x33, 0x8d, 0x3a, + 0x19, 0x36, 0x55, 0xc9, 0x8c, 0x8d, 0x98, 0x49, 0xaf, 0x54, 0xea, 0x96, 0x61, 0x83, 0x39, 0xe5, + 0x67, 0x56, 0x32, 0x67, 0xf1, 0x6e, 0x25, 0xee, 0x34, 0x50, 0x38, 0xb6, 0x58, 0x48, 0x8a, 0x3b, + 0x2a, 0x97, 0xc2, 0x6b, 0x49, 0x8c, 0x49, 0x3f, 0xba, 0xce, 0xe0, 0x4a, 0xce, 0x52, 0x78, 0x5f, + 0x7d, 0xfc, 0x2d, 0x08, 0x71, 0x61, 0x8d, 0x45, 0xec, 0xaf, 0x24, 0x43, 0x46, 0xa0, 0x0b, 0x52, + 0x48, 0x15, 0xe8, 0x52, 0x34, 0xd8, 0x05, 0x7f, 0x0c, 0x8b, 0xba, 0x7e, 0x19, 0xa3, 0x15, 0x82, + 0xfc, 0x8a, 0x03, 0x44, 0xea, 0xca, 0xbf, 0x9d, 0x15, 0x6e, 0x8e, 0x07, 0x2f, 0x72, 0x55, 0x58, + 0x0d, 0x62, 0x89, 0x6d, 0xa7, 0x00, 0x01, 0x10, 0xe6, 0x62, 0x47, 0x89, 0x09, 0x6a, 0xbe, 0xa7, + 0x54, 0x99, 0x25, 0x36, 0x2e, 0x8e, 0x9c, 0x3e, 0x36, 0x2e, 0x83, 0xf0, 0x33, 0x10, 0xfd, 0x46, + 0xf1, 0x71, 0x11, 0x2a, 0xe1, 0xec, 0x40, 0x48, 0x19, 0xa6, 0x4b, 0x8e, 0x0d, 0x51, 0x60, 0xbf, + 0x2f, 0x27, 0xc4, 0x2c, 0x7b, 0x15, 0x78, 0xcf, 0x0e, 0x08, 0xb4, 0x8f, 0xd9, 0xd9, 0xf4, 0x93, + 0xda, 0xfe, 0xb3, 0xa3, 0xc7, 0xa7, 0x07, 0x4f, 0x8f, 0xf6, 0x8e, 0x48, 0xf9, 0xe1, 0xde, 0xe9, + 0xc9, 0xde, 0xd1, 0xd1, 0x69, 0xe5, 0xbd, 0xea, 0x75, 0x7a, 0xbe, 0xe0, 0x21, 0x2e, 0x65, 0x1c, + 0x29, 0x5b, 0x80, 0x83, 0x8d, 0xd1, 0x2a, 0xfb, 0x18, 0xc5, 0xec, 0xc6, 0x8f, 0x18, 0xcc, 0x23, + 0x1c, 0x7a, 0xaa, 0xa0, 0x65, 0x39, 0x5e, 0x3a, 0x88, 0xd4, 0xcd, 0x6f, 0x32, 0x19, 0xd3, 0xab, + 0xde, 0x2f, 0xba, 0x1d, 0x2f, 0xac, 0x5b, 0x0f, 0x25, 0xc9, 0xf5, 0xc0, 0x84, 0x75, 0x0c, 0x37, + 0x2e, 0x26, 0xa3, 0x45, 0x91, 0x9b, 0x05, 0x8f, 0x3a, 0x0d, 0x68, 0x87, 0xda, 0x5a, 0x41, 0x5a, + 0xcc, 0x8a, 0x17, 0x7c, 0xf7, 0x24, 0x08, 0x50, 0xd8, 0x03, 0xb0, 0x46, 0x87, 0xb2, 0x83, 0x8a, + 0x2c, 0x6b, 0x8a, 0xe5, 0x9c, 0x01, 0x26, 0x7c, 0x13, 0x90, 0x65, 0xde, 0x18, 0x03, 0x60, 0x1e, + 0xc6, 0x74, 0x35, 0x21, 0xcf, 0x4e, 0x77, 0xc8, 0x99, 0x63, 0x9f, 0xc3, 0x77, 0x75, 0x88, 0xaa, + 0x14, 0x5f, 0x9d, 0xdc, 0x83, 0x49, 0x90, 0x41, 0xb7, 0xfb, 0x32, 0xf1, 0xbb, 0x72, 0x9c, 0xae, + 0xe4, 0xa4, 0x0d, 0xa1, 0xe6, 0x9d, 0xbb, 0x48, 0x06, 0x8a, 0xb7, 0x59, 0x82, 0x60, 0x2f, 0x08, + 0xb9, 0x96, 0x41, 0xe4, 0x1c, 0xc4, 0x60, 0x9a, 0x43, 0xfc, 0x14, 0x86, 0xbc, 0xdd, 0x1f, 0x03, + 0xcf, 0x83, 0x79, 0x3f, 0xc9, 0x21, 0x52, 0xd6, 0x59, 0xd2, 0x36, 0xc6, 0xd5, 0x61, 0xf5, 0x1a, + 0x23, 0xbf, 0xbc, 0xf5, 0x84, 0x21, 0x8c, 0x1d, 0x3c, 0xd4, 0xe8, 0x7b, 0x58, 0xa0, 0x92, 0x89, + 0xa5, 0x44, 0xb3, 0xd4, 0xee, 0x55, 0x45, 0xdd, 0xc1, 0xc4, 0x4c, 0x64, 0xe8, 0x3c, 0x06, 0x9e, + 0x03, 0x74, 0x46, 0x98, 0x4d, 0xca, 0xc6, 0xab, 0xcf, 0xed, 0xeb, 0x6f, 0xc1, 0x36, 0xaa, 0x26, + 0x91, 0xc9, 0x39, 0x8d, 0x7a, 0x34, 0x71, 0x39, 0x3b, 0x85, 0x49, 0xa1, 0x49, 0x79, 0x04, 0x5f, + 0x77, 0x27, 0xa3, 0xd1, 0x65, 0x8d, 0x65, 0x8e, 0xb7, 0x79, 0xae, 0x21, 0x96, 0x28, 0xcf, 0x5e, + 0x8c, 0x8e, 0xcb, 0x56, 0x54, 0x5e, 0x7c, 0x17, 0x85, 0x41, 0x69, 0x11, 0xe5, 0xcc, 0xfd, 0xb0, + 0xd7, 0x9b, 0xb8, 0xca, 0x47, 0x34, 0xf8, 0x58, 0xa7, 0x6b, 0x94, 0x3e, 0xf2, 0xaf, 0xf8, 0xa8, + 0x85, 0x35, 0xa4, 0xae, 0x67, 0x54, 0xb3, 0xdd, 0xcd, 0xd6, 0xca, 0xca, 0x47, 0xf6, 0x80, 0xff, + 0xea, 0xe0, 0x2f, 0x84, 0xc0, 0x42, 0x82, 0xb0, 0x27, 0xfa, 0xaa, 0x1e, 0xaf, 0xea, 0xf1, 0xaa, + 0xde, 0x34, 0x55, 0xa3, 0x43, 0x9d, 0x9b, 0x6b, 0x0d, 0x56, 0x47, 0x0a, 0x44, 0xca, 0xae, 0x1a, + 0x28, 0x01, 0x9b, 0xa5, 0x8d, 0x14, 0xa3, 0x1d, 0x52, 0x76, 0x20, 0x92, 0x47, 0x23, 0x5e, 0x7f, + 0x83, 0xa7, 0x97, 0xed, 0x5b, 0x0a, 0xc5, 0x06, 0x68, 0xfd, 0xd6, 0xb4, 0x19, 0x49, 0x75, 0x0c, + 0x64, 0x1e, 0xe8, 0x8e, 0x82, 0x65, 0xf4, 0xfa, 0xa3, 0xe4, 0x2d, 0x95, 0x4f, 0x31, 0x68, 0x1d, + 0x9e, 0x05, 0x54, 0x99, 0x94, 0x0e, 0x09, 0xcc, 0x6e, 0x8e, 0xf0, 0x32, 0x0e, 0xce, 0xbe, 0x1a, + 0x95, 0xf2, 0x95, 0x66, 0x31, 0x0c, 0x6e, 0x8b, 0xd4, 0x1c, 0x7c, 0x0b, 0x8f, 0x36, 0x65, 0x86, + 0x75, 0xe7, 0x9d, 0x80, 0x12, 0x1b, 0xa4, 0xd2, 0xd9, 0xa4, 0xcc, 0x00, 0xec, 0xcc, 0x33, 0x4c, + 0x11, 0xbc, 0x88, 0xfd, 0xb2, 0xa0, 0xc9, 0x67, 0xfe, 0x03, 0x58, 0xda, 0xae, 0x9e, 0x3c, 0x7c, + 0xc0, 0x8e, 0xc1, 0xbe, 0x3c, 0x75, 0x4e, 0x06, 0x9d, 0xb2, 0xd4, 0xb1, 0x4a, 0x46, 0x8f, 0x12, + 0xb5, 0x64, 0xfc, 0x15, 0x29, 0x0d, 0x50, 0xe2, 0x70, 0x59, 0x42, 0x3e, 0x92, 0x0e, 0xdf, 0x6b, + 0xb2, 0xd2, 0x67, 0x8a, 0x95, 0x46, 0x30, 0x51, 0x31, 0xad, 0x1e, 0x93, 0x62, 0xf1, 0x49, 0x93, + 0x2f, 0x66, 0x10, 0x42, 0xa6, 0x58, 0xc1, 0x16, 0x42, 0xda, 0xd4, 0x99, 0x83, 0x09, 0xc5, 0x4c, + 0xfc, 0xf1, 0x80, 0x3f, 0xee, 0x24, 0x1e, 0x77, 0xb2, 0x60, 0x7b, 0x1c, 0x76, 0x44, 0x42, 0x01, + 0xdc, 0x1b, 0x24, 0x9f, 0x73, 0xe8, 0x5e, 0x27, 0xf9, 0x3c, 0x13, 0xbc, 0x24, 0x9b, 0xb0, 0x9a, + 0xc4, 0x2b, 0x1f, 0x17, 0x94, 0x4a, 0xef, 0xbe, 0x24, 0x4a, 0xdf, 0xd5, 0x9a, 0x27, 0x93, 0xbe, + 0x07, 0xf9, 0xa2, 0x33, 0x4e, 0x51, 0xe1, 0x0e, 0x2f, 0x09, 0xdc, 0x2c, 0x6e, 0x2e, 0x26, 0x67, + 0x6f, 0x04, 0x22, 0xf8, 0x28, 0xb9, 0xcc, 0xbc, 0xd0, 0x65, 0x96, 0x76, 0x6d, 0x04, 0xa5, 0x85, + 0xab, 0xec, 0x36, 0xdf, 0xbc, 0x50, 0x29, 0xc0, 0x12, 0x49, 0x42, 0x3f, 0xc8, 0x68, 0xe2, 0x79, + 0xf2, 0x1d, 0xa7, 0xcc, 0x87, 0x0f, 0xf2, 0xde, 0xb4, 0x6f, 0x7f, 0x66, 0x8b, 0x34, 0x2a, 0x9c, + 0x54, 0x89, 0x86, 0x14, 0xa3, 0xa2, 0x52, 0x8e, 0x5f, 0xa5, 0x31, 0xbe, 0x1d, 0xee, 0x1b, 0x35, + 0x6a, 0x77, 0xe5, 0xad, 0xa3, 0xa0, 0x85, 0x4a, 0x0e, 0xf2, 0x28, 0x79, 0x1e, 0xc4, 0xaf, 0x76, + 0xad, 0x12, 0xed, 0x45, 0xae, 0x4a, 0x06, 0x2a, 0x80, 0xaa, 0x10, 0x71, 0x21, 0xa5, 0xde, 0x4d, + 0xb9, 0xf6, 0x43, 0x55, 0x97, 0x70, 0xee, 0xd9, 0x5d, 0xa7, 0x47, 0x9f, 0x9d, 0x1c, 0xec, 0x38, + 0xa3, 0x31, 0xe6, 0x18, 0x93, 0x38, 0x60, 0xe3, 0x1d, 0x12, 0x4f, 0xd2, 0x0d, 0xa2, 0xdf, 0xa7, + 0x5c, 0xd2, 0x3b, 0xcf, 0xc2, 0x9c, 0xc7, 0x4a, 0xe5, 0x07, 0x6f, 0xea, 0x90, 0x05, 0x53, 0x22, + 0x2b, 0x06, 0xcb, 0x11, 0xfd, 0x69, 0x22, 0xa7, 0xff, 0xe7, 0xeb, 0xc2, 0xc2, 0xcf, 0xd2, 0x5b, + 0xba, 0x79, 0x0a, 0x50, 0x98, 0x5f, 0xfb, 0xe6, 0xda, 0x4f, 0x3c, 0xa3, 0xb4, 0x1a, 0x9e, 0x02, + 0xb8, 0x3b, 0xe8, 0xc8, 0x1a, 0x4d, 0x57, 0xa1, 0x01, 0xa5, 0x84, 0x79, 0x30, 0x97, 0x45, 0x62, + 0x98, 0xfb, 0x48, 0x3f, 0xc6, 0xaf, 0xf8, 0x21, 0x5e, 0x17, 0x8b, 0xe2, 0x56, 0xc0, 0x47, 0x5c, + 0x07, 0x80, 0xd6, 0xc4, 0xe2, 0x3f, 0x08, 0xbf, 0xf3, 0x45, 0xbf, 0x13, 0x7e, 0xef, 0x68, 0x20, + 0x68, 0xd7, 0xf9, 0x9c, 0x5d, 0x12, 0x3e, 0xba, 0x2c, 0x7b, 0x10, 0xbb, 0xdf, 0xa0, 0xa4, 0x77, + 0xbc, 0xf0, 0xbb, 0x09, 0xb2, 0xa8, 0x1c, 0xde, 0x5e, 0xa0, 0xf7, 0xb5, 0x84, 0x08, 0x43, 0xd9, + 0x4d, 0x1e, 0x40, 0x80, 0x75, 0xb4, 0x1b, 0xb8, 0x11, 0x7e, 0x2c, 0x4b, 0x78, 0x06, 0x7a, 0x7d, + 0xd7, 0xc9, 0x43, 0x0e, 0x33, 0x16, 0xe9, 0x51, 0x0b, 0x92, 0xc0, 0xe4, 0x00, 0x39, 0x75, 0x0a, + 0xf4, 0x0e, 0x91, 0x61, 0xdd, 0x63, 0x58, 0xe1, 0x10, 0xfa, 0x0e, 0xb7, 0x65, 0x9d, 0x22, 0x5d, + 0xc5, 0x13, 0x97, 0xda, 0x8e, 0xba, 0xd9, 0xe7, 0x36, 0xc3, 0xab, 0x00, 0x0a, 0xa0, 0x09, 0x65, + 0x39, 0x67, 0x39, 0x17, 0x05, 0xd0, 0x02, 0xf6, 0xcf, 0x40, 0x0b, 0xde, 0x66, 0xea, 0x2f, 0x41, + 0x3e, 0xff, 0x02, 0x68, 0x41, 0x59, 0x86, 0x16, 0xfc, 0x2e, 0xbe, 0xa1, 0xf9, 0x06, 0x45, 0x7c, + 0x2c, 0x7f, 0x59, 0x70, 0xb1, 0x13, 0xba, 0xcd, 0x59, 0x20, 0xf4, 0x0c, 0x32, 0x7b, 0x06, 0x21, + 0x2d, 0x25, 0x83, 0xd7, 0x6c, 0x6f, 0xc4, 0x04, 0x0e, 0x2b, 0x3e, 0xa7, 0x5d, 0x8e, 0xec, 0x4d, + 0x84, 0x3d, 0xfc, 0xa5, 0xd9, 0x43, 0x78, 0x5f, 0xf7, 0x0e, 0x8e, 0xb7, 0x4f, 0x4f, 0xf7, 0x4e, + 0x8e, 0xc8, 0xe9, 0x5e, 0xfb, 0xf4, 0xfd, 0x4e, 0x6e, 0x9b, 0x4e, 0x48, 0xad, 0xce, 0xb7, 0xc6, + 0x4e, 0x80, 0xec, 0x9a, 0xe7, 0x59, 0x22, 0x20, 0x95, 0xba, 0x39, 0xa9, 0x02, 0x85, 0x50, 0x8a, + 0x1d, 0x22, 0x4a, 0x16, 0x8f, 0x12, 0xc6, 0xb0, 0x90, 0x88, 0x5e, 0x93, 0x76, 0xfb, 0xcd, 0x9c, + 0x3a, 0x51, 0x04, 0x45, 0xa3, 0xbb, 0xb2, 0xbc, 0xda, 0xd0, 0x16, 0x8f, 0x47, 0x69, 0xfc, 0xf6, + 0xbf, 0xc5, 0x93, 0x5a, 0x5b, 0xd7, 0xdf, 0x4c, 0xfa, 0x7e, 0xbd, 0x5e, 0x17, 0xdb, 0x02, 0x17, + 0x2c, 0xc7, 0x35, 0x58, 0x57, 0x98, 0x51, 0xbc, 0xb6, 0x14, 0xe4, 0x14, 0xaf, 0x94, 0x94, 0x8a, + 0x69, 0x69, 0x31, 0x96, 0x9b, 0xff, 0x6d, 0xe9, 0xa8, 0x79, 0xf4, 0x5b, 0xa6, 0xbd, 0x9e, 0xa1, + 0x09, 0x52, 0xd0, 0xd3, 0xb1, 0xb9, 0xb2, 0x72, 0xa7, 0xb5, 0x9c, 0x5b, 0x2d, 0x15, 0xc2, 0x16, + 0xa3, 0xa7, 0xd1, 0x91, 0xd2, 0x2e, 0xde, 0x26, 0x78, 0xe2, 0xe9, 0xd9, 0xd0, 0x25, 0x57, 0x2c, + 0x33, 0xfe, 0x85, 0x49, 0x31, 0xd5, 0x1b, 0xde, 0x71, 0x6a, 0x04, 0x89, 0x3a, 0x99, 0x75, 0x64, + 0xd8, 0xf5, 0xd2, 0xc6, 0x4d, 0xc3, 0x35, 0x33, 0x98, 0x30, 0xeb, 0x6c, 0x7a, 0x95, 0xac, 0xcc, + 0xec, 0xcb, 0x9c, 0x69, 0x78, 0xfa, 0x6b, 0xbd, 0x3b, 0x33, 0x0c, 0xcf, 0x9d, 0x56, 0xb3, 0x3b, + 0xf5, 0xf0, 0xa8, 0x16, 0xae, 0xe9, 0x53, 0x88, 0x84, 0x7a, 0x34, 0x7c, 0x50, 0x8b, 0x11, 0x60, + 0xeb, 0x89, 0xe5, 0xb3, 0x80, 0xcc, 0x0f, 0xee, 0x97, 0x3f, 0x35, 0x6a, 0xfd, 0xcf, 0x7a, 0x9f, + 0x7f, 0xd9, 0x7a, 0x55, 0xd1, 0x7d, 0xfe, 0xc9, 0xa2, 0x59, 0xa7, 0x2f, 0x69, 0x97, 0x81, 0x8c, + 0x63, 0xc4, 0x4d, 0xf4, 0x00, 0xe4, 0x7d, 0x05, 0x91, 0x31, 0x71, 0x3d, 0xd8, 0x8b, 0xf4, 0x00, + 0x2c, 0x47, 0x5e, 0xec, 0xd3, 0xe6, 0xe7, 0x2c, 0x71, 0x50, 0x35, 0x55, 0x76, 0x90, 0x2e, 0xdb, + 0xd2, 0x95, 0xed, 0xa4, 0xcb, 0x2e, 0xf1, 0xb2, 0x09, 0x1d, 0x6b, 0x9d, 0xd8, 0x13, 0xcb, 0xca, + 0x5c, 0xff, 0x43, 0x61, 0x9a, 0xce, 0x84, 0xb9, 0xfb, 0xf4, 0xc9, 0x0e, 0xbf, 0x03, 0xe3, 0xd0, + 0x31, 0x7a, 0x34, 0x33, 0x2b, 0xa6, 0xda, 0x7a, 0xdb, 0xb6, 0x2c, 0x9d, 0x01, 0x07, 0x5a, 0x5a, + 0x90, 0x88, 0xd2, 0xe5, 0x69, 0x51, 0x54, 0x6c, 0xca, 0x5e, 0x29, 0x50, 0x03, 0x3b, 0x1c, 0x95, + 0xf5, 0x0c, 0x84, 0x72, 0xc3, 0x23, 0xd2, 0x57, 0xae, 0x85, 0x89, 0xc1, 0x8a, 0xcd, 0xc8, 0x4c, + 0xc8, 0xb1, 0x7b, 0xd0, 0xe6, 0x0a, 0x38, 0xba, 0xfa, 0x6b, 0x9e, 0x60, 0xa5, 0x1b, 0xb8, 0xa6, + 0x04, 0xab, 0x0d, 0x4a, 0x8b, 0xae, 0x66, 0xca, 0xb5, 0xff, 0xe6, 0x34, 0x58, 0x05, 0x32, 0x25, + 0xa8, 0x10, 0xd3, 0x1b, 0x7e, 0xf3, 0x19, 0xea, 0x59, 0xd1, 0xd2, 0x18, 0x69, 0xf3, 0x60, 0x93, + 0x19, 0x51, 0xd2, 0x19, 0x68, 0xf3, 0x60, 0xb1, 0x69, 0x73, 0x0f, 0x6c, 0xfc, 0x55, 0x66, 0x1d, + 0x7d, 0x7a, 0xba, 0x4d, 0x9e, 0x1d, 0xef, 0x6e, 0x9f, 0xee, 0x91, 0xfd, 0x67, 0x47, 0x3b, 0x18, + 0x56, 0xd4, 0x7e, 0xcf, 0x43, 0x89, 0x30, 0xf4, 0x0e, 0xba, 0xcd, 0x0e, 0x06, 0xeb, 0x4c, 0x53, + 0xc7, 0x37, 0x16, 0x4d, 0x96, 0xe0, 0xe0, 0xed, 0x26, 0xf5, 0x3a, 0xa7, 0xae, 0x07, 0x38, 0x9e, + 0xf2, 0x88, 0x62, 0x76, 0x7c, 0x50, 0x3c, 0xd2, 0xbb, 0xc5, 0xf1, 0x6c, 0x6e, 0x78, 0x48, 0xb4, + 0x33, 0x31, 0xad, 0xde, 0x6e, 0x70, 0x88, 0x34, 0xfe, 0x98, 0x85, 0x1f, 0xc1, 0xe3, 0xca, 0xb4, + 0x02, 0x5d, 0x5c, 0xda, 0xf1, 0x9c, 0x63, 0x92, 0x38, 0x81, 0x28, 0xa1, 0xac, 0x49, 0x71, 0x12, + 0xf3, 0x46, 0x20, 0xc3, 0x21, 0xe9, 0x43, 0x6f, 0xc4, 0x46, 0x7e, 0x9d, 0x7d, 0x97, 0x52, 0xbc, + 0x89, 0xac, 0x4b, 0xb1, 0x16, 0x8b, 0x43, 0x61, 0x6a, 0x25, 0x3f, 0x5f, 0xd9, 0x87, 0xb7, 0xec, + 0x25, 0x8b, 0x45, 0x69, 0x2d, 0xf3, 0xe3, 0xca, 0x8f, 0x1f, 0x94, 0xe6, 0xa9, 0xdb, 0x26, 0x7c, + 0x08, 0x41, 0x2f, 0x48, 0xc2, 0x99, 0x30, 0x4f, 0xc2, 0x96, 0x64, 0x77, 0x39, 0xcb, 0x42, 0x3e, + 0x53, 0x16, 0x3c, 0x64, 0xf7, 0x7d, 0xd3, 0x1d, 0x81, 0x85, 0xa7, 0xcb, 0x0e, 0xda, 0x37, 0x2d, + 0x9e, 0xa3, 0x33, 0xcb, 0x18, 0xee, 0x0b, 0x18, 0xfb, 0x66, 0x3a, 0x95, 0x74, 0x04, 0x06, 0x43, + 0xa6, 0x03, 0x68, 0x75, 0xfc, 0xe4, 0xb1, 0x13, 0x1d, 0xe9, 0x3d, 0x31, 0x7c, 0x97, 0xb3, 0xed, + 0x25, 0x2c, 0xd3, 0xeb, 0x6f, 0x80, 0x0c, 0x36, 0x69, 0x9b, 0x2c, 0xef, 0x3a, 0x25, 0x41, 0x67, + 0x6a, 0xc8, 0xe6, 0x26, 0xa6, 0x0e, 0x9b, 0x75, 0xc7, 0x2b, 0x44, 0xa4, 0x8e, 0xda, 0x64, 0x9d, + 0xda, 0x3d, 0xef, 0x85, 0xe9, 0xc3, 0xfc, 0xaf, 0x77, 0x4c, 0xbb, 0x54, 0x64, 0x57, 0x0e, 0x0b, + 0xb2, 0x38, 0x34, 0x13, 0x6f, 0x8c, 0x31, 0xc1, 0xd2, 0xc1, 0x0b, 0xc1, 0x26, 0x1d, 0xff, 0x46, + 0x28, 0x85, 0xd1, 0xdb, 0xcf, 0x58, 0x1a, 0x03, 0x0c, 0xa3, 0x3b, 0xb3, 0x4c, 0xcc, 0xab, 0xcf, + 0xaf, 0xa2, 0xba, 0xff, 0x99, 0xfd, 0x99, 0xbd, 0x6b, 0x78, 0xe4, 0x21, 0x75, 0xaf, 0xbf, 0xe1, + 0xf7, 0x01, 0x10, 0x9b, 0x4e, 0x70, 0xbb, 0x86, 0x95, 0xc0, 0xe6, 0x55, 0xd8, 0x17, 0x40, 0x40, + 0x0c, 0xa5, 0xe3, 0x8e, 0x76, 0x99, 0xbc, 0x62, 0x91, 0x76, 0xfb, 0xe2, 0x6b, 0x32, 0x8a, 0x37, + 0x28, 0x56, 0x67, 0xd9, 0xd2, 0x25, 0x26, 0x41, 0x4d, 0x1a, 0x07, 0x38, 0x31, 0xf2, 0x5a, 0xe6, + 0x02, 0x79, 0x7b, 0xec, 0x3a, 0x03, 0x97, 0xef, 0x75, 0x14, 0x5a, 0xcb, 0xb3, 0x60, 0xe1, 0xe1, + 0x78, 0x63, 0x90, 0x3a, 0xcf, 0x8e, 0x93, 0x28, 0x64, 0x1f, 0x32, 0x74, 0xba, 0xc3, 0x7a, 0xbd, + 0x5e, 0x52, 0x86, 0x95, 0xbc, 0x1c, 0xba, 0xa2, 0xef, 0xff, 0xf6, 0xc9, 0xe1, 0x23, 0xdf, 0x1f, + 0x9f, 0x50, 0xb0, 0x44, 0x3c, 0x96, 0xae, 0x33, 0x56, 0x1c, 0x0a, 0xd6, 0xf9, 0x24, 0x53, 0x98, + 0x15, 0xe3, 0xa0, 0x4f, 0x92, 0x61, 0xa1, 0xe4, 0x7a, 0x96, 0x3e, 0x48, 0x6c, 0x34, 0xe3, 0xee, + 0xe0, 0xc4, 0xc7, 0x8c, 0x59, 0xd9, 0x87, 0x1c, 0xc6, 0xd4, 0xed, 0x42, 0x6b, 0x3b, 0xfc, 0x7e, + 0x3b, 0x1a, 0x44, 0xe8, 0x71, 0xc9, 0x88, 0xd0, 0x98, 0x91, 0x05, 0x42, 0x11, 0x8f, 0x23, 0xfb, + 0x18, 0xa1, 0xca, 0x32, 0x54, 0x64, 0x66, 0x11, 0x05, 0xb9, 0x16, 0x8c, 0x43, 0x39, 0x01, 0xbf, + 0x4a, 0x4a, 0x8f, 0x80, 0x62, 0x16, 0x0a, 0x22, 0x6e, 0x55, 0x27, 0x11, 0x00, 0xb1, 0xfb, 0x61, + 0x81, 0x04, 0x1e, 0x2a, 0x0a, 0x4e, 0x7f, 0x6d, 0x42, 0x40, 0x35, 0xac, 0xed, 0x89, 0xec, 0x40, + 0xa0, 0x52, 0xb6, 0x1a, 0xda, 0xa4, 0x26, 0xe9, 0x1e, 0x02, 0x35, 0xaa, 0x84, 0xc7, 0x86, 0xf1, + 0x89, 0x46, 0xdd, 0xbe, 0x63, 0x0d, 0xd8, 0x25, 0x0f, 0xb7, 0xc9, 0x11, 0x9d, 0xb0, 0xd9, 0x44, + 0x60, 0x8a, 0x2f, 0x85, 0x49, 0x6a, 0x90, 0x63, 0x2a, 0x37, 0xf6, 0xd9, 0x60, 0xc4, 0x28, 0xbb, + 0x35, 0xc0, 0xa5, 0xd8, 0xd1, 0x8c, 0x13, 0xb6, 0x3a, 0x27, 0x4d, 0x56, 0x9e, 0x95, 0xd0, 0x37, + 0x21, 0x54, 0x14, 0xf2, 0xb3, 0xf6, 0xd3, 0xa3, 0x3a, 0x33, 0xf1, 0x19, 0xbd, 0x82, 0x37, 0xa7, + 0xe9, 0xa3, 0xe5, 0x7a, 0x62, 0x31, 0x52, 0x25, 0xfc, 0x2a, 0xa1, 0xe6, 0x33, 0xe2, 0x13, 0x6e, + 0xda, 0xf5, 0x2f, 0x67, 0xda, 0x6b, 0xac, 0xc4, 0x99, 0xf8, 0x89, 0xad, 0xcf, 0x79, 0x0c, 0xa5, + 0xef, 0xf5, 0x11, 0xf5, 0xaf, 0x2e, 0xa8, 0x7b, 0xd6, 0x97, 0xd6, 0xe4, 0x67, 0x6c, 0xda, 0xab, + 0xb8, 0xa1, 0x20, 0x27, 0xcc, 0x9b, 0x2e, 0x55, 0xb2, 0x94, 0xe6, 0x16, 0x25, 0x7d, 0x1c, 0x10, + 0xd7, 0xe5, 0xd2, 0xf1, 0xd3, 0xf6, 0x29, 0x5e, 0xf8, 0xc1, 0xd4, 0xdd, 0x89, 0xb2, 0x37, 0x6c, + 0x7a, 0xa1, 0x68, 0x0f, 0x44, 0x7d, 0x12, 0xda, 0xd8, 0xb1, 0x2c, 0x20, 0x58, 0x98, 0x7b, 0xb8, + 0x40, 0x4e, 0x5e, 0xdc, 0xd5, 0x7c, 0x76, 0x72, 0xa8, 0x51, 0x46, 0x26, 0xae, 0x55, 0x58, 0x17, + 0x01, 0x28, 0x6a, 0x55, 0x84, 0xef, 0x69, 0x07, 0xa0, 0x82, 0xa4, 0x0c, 0xae, 0x39, 0x2a, 0xab, + 0x42, 0x8a, 0x6e, 0xe3, 0x2e, 0x5c, 0x11, 0x5d, 0x84, 0xc5, 0xca, 0x47, 0x9a, 0x08, 0x34, 0x8f, + 0x1f, 0x6e, 0xb4, 0xd6, 0x43, 0xd3, 0x75, 0x26, 0x66, 0x84, 0xea, 0x31, 0x84, 0xb5, 0x66, 0x7d, + 0x71, 0x51, 0xdc, 0x71, 0xa3, 0x7a, 0xeb, 0xb1, 0xd7, 0x39, 0xf8, 0x22, 0x6a, 0x2c, 0xd4, 0x09, + 0x63, 0xa2, 0x05, 0x4c, 0xe2, 0xa0, 0x17, 0x3a, 0x00, 0x01, 0x3c, 0x3c, 0xc0, 0xd5, 0xd1, 0x9e, + 0xab, 0xaa, 0x72, 0x0e, 0xa3, 0x8c, 0x6d, 0xcb, 0x9a, 0x0a, 0x4b, 0x34, 0x82, 0x5b, 0xa5, 0x78, + 0x86, 0xf0, 0x8d, 0x29, 0x2e, 0xdf, 0xb3, 0x6e, 0x01, 0x3a, 0x7c, 0x07, 0x03, 0xb1, 0xd8, 0x79, + 0x86, 0x93, 0x43, 0x85, 0x62, 0x21, 0xdb, 0x96, 0x40, 0x8e, 0xfb, 0xf0, 0x4f, 0x17, 0x16, 0x84, + 0xfc, 0xf8, 0xd6, 0x2c, 0xcf, 0x30, 0x61, 0x92, 0x37, 0xe9, 0x76, 0xa1, 0x73, 0x59, 0x0e, 0x19, + 0xb5, 0x80, 0xdc, 0x75, 0x2e, 0x6c, 0x14, 0x1e, 0xd1, 0x30, 0x66, 0xac, 0x92, 0x99, 0x42, 0xa3, + 0xf0, 0x12, 0x57, 0x7c, 0x8d, 0x62, 0x5d, 0xcb, 0x5e, 0x9f, 0xa6, 0x5c, 0xbc, 0xdf, 0x80, 0xe8, + 0xce, 0x16, 0xe1, 0x59, 0x6e, 0xab, 0x19, 0xed, 0xd9, 0x62, 0xb4, 0xcb, 0x32, 0x6a, 0xa7, 0x20, + 0xd8, 0x1b, 0x20, 0x96, 0x9e, 0x50, 0xc5, 0xf2, 0xc2, 0x2b, 0x14, 0xdd, 0x2a, 0x09, 0xb8, 0x44, + 0xb9, 0x2e, 0x05, 0xba, 0xfc, 0x03, 0x23, 0x33, 0x06, 0x4c, 0xea, 0xd2, 0x03, 0x0c, 0x68, 0x50, + 0xde, 0xa0, 0xc1, 0xdb, 0xc9, 0xd9, 0x7d, 0x96, 0x65, 0x4d, 0x72, 0xd1, 0x8d, 0x5a, 0x10, 0x54, + 0xbb, 0x30, 0x7b, 0x2c, 0x4b, 0x8a, 0xe8, 0x0b, 0xd7, 0xc5, 0x37, 0xb4, 0x95, 0x64, 0xd1, 0x95, + 0xa8, 0x92, 0x8e, 0xb8, 0x52, 0x53, 0x05, 0x7f, 0xa2, 0x8e, 0xc4, 0x20, 0x8a, 0xc7, 0xb9, 0xab, + 0x87, 0x28, 0x07, 0x75, 0xbb, 0xd6, 0xa4, 0x47, 0x3d, 0xb6, 0x74, 0xa9, 0x17, 0x80, 0x74, 0x8f, + 0x93, 0xbb, 0x8b, 0x39, 0x89, 0x35, 0x14, 0x6d, 0x7d, 0xf5, 0xeb, 0x19, 0xdb, 0xca, 0x4a, 0x44, + 0x32, 0x3d, 0xb8, 0xe4, 0xb5, 0xd7, 0x77, 0x1b, 0xd9, 0xb7, 0x5e, 0x97, 0x0a, 0x5c, 0x5e, 0x87, + 0x71, 0x7d, 0xc0, 0x3f, 0x22, 0x69, 0x3f, 0x1e, 0xb8, 0x3b, 0x67, 0xa7, 0x1d, 0xf9, 0xf6, 0x5d, + 0x7a, 0x52, 0x24, 0x04, 0xb2, 0xe2, 0x44, 0x79, 0x0a, 0x9a, 0x32, 0xb0, 0x0a, 0x83, 0x78, 0x82, + 0x02, 0x8a, 0x2a, 0x99, 0x1c, 0xa1, 0xc2, 0x17, 0x84, 0x4c, 0x08, 0x4e, 0x27, 0x65, 0xe4, 0x75, + 0xd5, 0x13, 0x61, 0x1a, 0xd3, 0x9e, 0x52, 0xd7, 0xad, 0x9f, 0x45, 0xd7, 0xd0, 0xd8, 0x3a, 0x6a, + 0xda, 0x81, 0x04, 0xc8, 0xbb, 0x4c, 0x8d, 0x8b, 0x03, 0xca, 0xaf, 0x4e, 0x6b, 0xd3, 0xc4, 0x5d, + 0x50, 0x0c, 0x98, 0x78, 0xa9, 0x3f, 0x67, 0x97, 0x2d, 0xd5, 0x19, 0x8c, 0x80, 0x03, 0xab, 0xb9, + 0x37, 0x99, 0x96, 0xf6, 0x1d, 0xd0, 0x35, 0xbb, 0x43, 0x17, 0xd4, 0x5d, 0x69, 0xf9, 0x0c, 0x00, + 0x30, 0x19, 0xc1, 0xbd, 0xcd, 0x12, 0xd2, 0xf0, 0xd0, 0xab, 0x64, 0x5e, 0xfc, 0x96, 0xb7, 0x92, + 0x4f, 0xcf, 0x3b, 0xc9, 0x9f, 0x02, 0xcc, 0x9e, 0x39, 0x66, 0x7c, 0xa5, 0x2b, 0x70, 0x6f, 0xac, + 0xd6, 0x8b, 0xa0, 0x52, 0x3b, 0xb2, 0xd6, 0x4f, 0x85, 0x8c, 0x8a, 0xd3, 0x9a, 0x65, 0x65, 0xd2, + 0xbb, 0x33, 0xe6, 0xe1, 0xda, 0xc8, 0x51, 0xd3, 0xd4, 0x3a, 0x47, 0xf6, 0x9b, 0x82, 0x19, 0x15, + 0xb2, 0xaf, 0x36, 0x94, 0x3c, 0xed, 0x22, 0xf3, 0x33, 0x0a, 0x28, 0xae, 0x8e, 0xe4, 0x39, 0xdb, + 0x53, 0x3b, 0x79, 0xcc, 0x87, 0xf2, 0x57, 0x11, 0xe5, 0xf7, 0xe2, 0x60, 0xff, 0x80, 0xf0, 0x04, + 0xfd, 0x3b, 0x4f, 0x8f, 0xf6, 0x0f, 0x1e, 0xfe, 0x55, 0xed, 0xe9, 0xe1, 0x55, 0x18, 0xfc, 0x32, + 0x1f, 0xed, 0xb6, 0xde, 0xf7, 0x72, 0x5d, 0x8f, 0xb8, 0x68, 0x88, 0xa5, 0xa4, 0x67, 0xd7, 0x20, + 0xe3, 0x8d, 0x57, 0xf6, 0xc0, 0x9b, 0xce, 0x25, 0x04, 0xf3, 0x55, 0xdc, 0x1d, 0x12, 0xbb, 0x59, + 0x20, 0xb8, 0x43, 0x44, 0xbc, 0xd3, 0x88, 0x3b, 0x40, 0xe1, 0x61, 0x74, 0x92, 0x83, 0x92, 0x76, + 0xfb, 0x60, 0x17, 0xb4, 0x34, 0xf7, 0x4c, 0x73, 0x59, 0x88, 0xe2, 0xca, 0x5c, 0xa9, 0x2d, 0x7c, + 0x41, 0x7e, 0xf9, 0x4b, 0x96, 0x7d, 0x48, 0xd7, 0xdc, 0xb1, 0xe1, 0x79, 0x17, 0xb0, 0x96, 0x10, + 0x9e, 0x63, 0x66, 0xea, 0xbe, 0x8a, 0xfa, 0xbd, 0x44, 0xb2, 0x91, 0xba, 0xf4, 0x2a, 0x40, 0x61, + 0x5a, 0xd0, 0xbb, 0xc3, 0xee, 0x58, 0x49, 0x43, 0x7c, 0x31, 0x3d, 0xb8, 0x83, 0x63, 0x05, 0x8e, + 0x07, 0xc7, 0xb3, 0x62, 0xf7, 0x10, 0x84, 0xf5, 0x85, 0x71, 0xa9, 0x80, 0x29, 0xde, 0xcc, 0x0a, + 0xb8, 0x3d, 0xe9, 0xe0, 0xa5, 0xd4, 0x69, 0xb8, 0xfc, 0x05, 0x03, 0xdb, 0x5a, 0x59, 0xa9, 0x07, + 0xff, 0x1a, 0xb3, 0x90, 0xf6, 0xa8, 0xad, 0x68, 0x00, 0x9e, 0xe6, 0xb1, 0x4b, 0x38, 0x41, 0x26, + 0xd3, 0x4f, 0x8b, 0xf0, 0xfa, 0x1c, 0x65, 0x4a, 0x61, 0xe9, 0x3d, 0xb9, 0xaf, 0xdf, 0x42, 0xbf, + 0xe7, 0x61, 0x5a, 0x58, 0xa6, 0x9e, 0x6f, 0x2e, 0xb0, 0xe0, 0xc3, 0x75, 0x02, 0xab, 0x25, 0xb5, + 0x37, 0x16, 0xb6, 0x30, 0xd8, 0xf3, 0x79, 0x70, 0xb9, 0xce, 0xbd, 0x45, 0x2c, 0xb9, 0x55, 0x22, + 0xeb, 0xd3, 0x01, 0x73, 0x61, 0x72, 0x22, 0xa8, 0x7f, 0x24, 0x47, 0xf1, 0xdb, 0x7a, 0x02, 0x80, + 0xd3, 0x53, 0x9b, 0xe5, 0xea, 0x66, 0xec, 0xa7, 0xea, 0xb6, 0x78, 0xcb, 0x28, 0xff, 0xfa, 0x6f, + 0xff, 0x30, 0x03, 0xfc, 0x13, 0x10, 0x13, 0xf9, 0x34, 0x0d, 0x9f, 0x61, 0x71, 0x4c, 0x96, 0xcd, + 0x1b, 0xd3, 0x8d, 0x34, 0xbb, 0x9c, 0x63, 0x26, 0x09, 0x18, 0xdd, 0x9e, 0x94, 0x9e, 0xbd, 0xd1, + 0xbb, 0x8d, 0xa9, 0x81, 0xb6, 0x41, 0x5f, 0x8c, 0x4e, 0x79, 0xc8, 0xf7, 0x64, 0xb1, 0x17, 0x8c, + 0x80, 0x63, 0xbf, 0x83, 0xa7, 0x1c, 0x9b, 0x75, 0xf8, 0x50, 0xef, 0xd1, 0xbc, 0xfe, 0xe9, 0x19, + 0x59, 0xbe, 0x0b, 0xea, 0x30, 0x27, 0xf1, 0x7c, 0x22, 0x62, 0x5d, 0x97, 0x80, 0x5e, 0x86, 0xf4, + 0xd3, 0x3c, 0x2b, 0x21, 0x82, 0xd5, 0xf6, 0xd9, 0xed, 0xd5, 0x39, 0xa9, 0xdf, 0x8b, 0x41, 0x0a, + 0x2e, 0xe7, 0x50, 0x5c, 0x30, 0xf6, 0x7b, 0x96, 0x40, 0xfd, 0xf5, 0x9f, 0xfe, 0x3c, 0xcb, 0x65, + 0x3a, 0x52, 0xd7, 0x12, 0x8c, 0x28, 0xb5, 0x1b, 0x04, 0xba, 0x88, 0x4e, 0xcd, 0xe8, 0x4c, 0x9c, + 0x01, 0x87, 0x8c, 0x69, 0xf5, 0x4a, 0xcb, 0x1f, 0x4c, 0x0b, 0xf0, 0xf0, 0xa8, 0xee, 0xc1, 0x71, + 0x6d, 0x9f, 0x5a, 0xe8, 0x92, 0xa7, 0xa6, 0xbd, 0x18, 0xdd, 0x80, 0xa6, 0x4e, 0x34, 0xc7, 0x6e, + 0x79, 0x65, 0x95, 0xbb, 0x07, 0xc7, 0xfb, 0x26, 0x54, 0xf4, 0xca, 0x95, 0x22, 0x01, 0x37, 0x4c, + 0xb4, 0x8a, 0x35, 0x9c, 0x6f, 0x09, 0x67, 0x84, 0xde, 0xcc, 0x2b, 0x3e, 0x46, 0x6e, 0x34, 0x37, + 0x44, 0x46, 0xe3, 0xb1, 0x43, 0x1f, 0x06, 0x2a, 0x6c, 0xed, 0xae, 0x01, 0x44, 0x57, 0x5c, 0x06, + 0x85, 0x05, 0xd4, 0x9a, 0x4a, 0x6c, 0xa9, 0x01, 0xa2, 0x3f, 0x32, 0xad, 0xbe, 0x07, 0xca, 0xe2, + 0x19, 0x2a, 0x60, 0xeb, 0xa4, 0x6d, 0x0e, 0x6c, 0x00, 0xe5, 0x5f, 0x7f, 0x03, 0x1a, 0x10, 0xcb, + 0x4f, 0xf4, 0xcc, 0x36, 0xd1, 0x09, 0x5f, 0x6b, 0x5f, 0x8e, 0x3a, 0x8e, 0x95, 0xd6, 0x2f, 0x81, + 0x29, 0x78, 0x25, 0x5e, 0xa0, 0xec, 0x7a, 0x9e, 0xa9, 0xf2, 0x92, 0xe0, 0x73, 0x98, 0xce, 0xb5, + 0x15, 0x98, 0xd0, 0x22, 0x9e, 0xbb, 0xf4, 0xfa, 0x8f, 0xff, 0x12, 0xfe, 0x15, 0xf9, 0x09, 0xda, + 0x74, 0xe8, 0x92, 0xc1, 0xc4, 0xd7, 0xd6, 0x5f, 0x55, 0xd7, 0xff, 0x57, 0x51, 0xff, 0x61, 0x46, + 0xd5, 0x3b, 0x8a, 0xaa, 0xff, 0x1a, 0x55, 0x7d, 0x82, 0x1b, 0x59, 0x96, 0x2a, 0xf0, 0xbc, 0x14, + 0x16, 0x15, 0xa5, 0x55, 0xdc, 0xdb, 0x1d, 0x5e, 0x18, 0xdd, 0xa1, 0xc6, 0x96, 0x4a, 0x90, 0x19, + 0xc3, 0x9a, 0xba, 0x43, 0xeb, 0xfa, 0x3b, 0xcf, 0xa3, 0x98, 0xe7, 0xa0, 0x28, 0xa5, 0xf7, 0xec, + 0xae, 0x7b, 0x39, 0x66, 0x99, 0x1a, 0x39, 0xb5, 0xa9, 0xdd, 0x3d, 0xbd, 0x1c, 0x53, 0xe5, 0x9d, + 0x4b, 0xfc, 0x15, 0x33, 0x96, 0xef, 0xa0, 0xa8, 0x96, 0x1f, 0xac, 0xe9, 0xb7, 0x92, 0xa0, 0xb7, + 0xff, 0xf8, 0x5b, 0x41, 0x92, 0x17, 0xc7, 0xdb, 0xad, 0x45, 0xf8, 0x6f, 0x89, 0x94, 0xa5, 0x43, + 0x1c, 0x8b, 0x1e, 0xd3, 0x96, 0x2b, 0x19, 0xe7, 0xf9, 0x22, 0x50, 0xff, 0x51, 0x80, 0x7a, 0xda, + 0xef, 0x43, 0xcd, 0x17, 0x7b, 0xc7, 0xa4, 0x3c, 0xb1, 0x93, 0x00, 0x94, 0xa9, 0x16, 0x80, 0xb9, + 0x71, 0xb2, 0x94, 0x43, 0x46, 0xd6, 0x9c, 0x57, 0x82, 0x37, 0x3c, 0x0a, 0x3e, 0x33, 0x03, 0x0b, + 0x57, 0xce, 0x4b, 0x8a, 0xec, 0x25, 0x9c, 0x94, 0x2c, 0x9a, 0xca, 0x05, 0x31, 0x43, 0x7a, 0xd4, + 0x23, 0x7c, 0x66, 0xd9, 0xf1, 0x95, 0x2a, 0x6a, 0x29, 0x2e, 0xec, 0xee, 0x39, 0xe3, 0x20, 0xf1, + 0xff, 0x04, 0xd4, 0x1a, 0xd0, 0x65, 0xbe, 0xfe, 0x6f, 0x02, 0x02, 0x2a, 0x33, 0xfd, 0xeb, 0xef, + 0x06, 0x1d, 0x0c, 0xab, 0x09, 0x76, 0xca, 0x69, 0xbd, 0x5e, 0xbf, 0xb7, 0xc8, 0x2b, 0x25, 0xb5, + 0x1b, 0xa9, 0x8d, 0xd4, 0xe5, 0x70, 0x7a, 0xc3, 0x0d, 0x69, 0xf5, 0x56, 0xcd, 0xb6, 0x5d, 0xd7, + 0x19, 0xf7, 0x9c, 0x0b, 0x30, 0x2f, 0xa9, 0x32, 0x2d, 0xd9, 0x54, 0xe4, 0xaa, 0xd5, 0xb8, 0xd9, + 0x05, 0x82, 0x3e, 0x08, 0x69, 0xab, 0xd5, 0x64, 0x02, 0x29, 0xc1, 0xc7, 0x65, 0x9c, 0x70, 0x19, + 0x0b, 0x39, 0x98, 0xa3, 0x60, 0x50, 0x1f, 0xac, 0xa4, 0x33, 0x2f, 0xbc, 0xde, 0x34, 0x78, 0x90, + 0x93, 0x63, 0x56, 0xd6, 0x65, 0x82, 0xa1, 0x64, 0x37, 0x33, 0x25, 0xc4, 0xa6, 0xe7, 0x88, 0x44, + 0x64, 0xa4, 0x3c, 0xbc, 0xfe, 0x16, 0x93, 0x53, 0xe0, 0xde, 0x27, 0x4c, 0xf9, 0x8c, 0xab, 0x3a, + 0xe3, 0x78, 0x20, 0x84, 0x72, 0xd9, 0xa8, 0x92, 0x0e, 0xf3, 0xec, 0x76, 0xea, 0x4c, 0x84, 0xd5, + 0x88, 0xc1, 0x3e, 0x54, 0xb2, 0x2f, 0xcc, 0x8b, 0x70, 0x1b, 0x9a, 0xf6, 0xd5, 0x04, 0xd9, 0x2f, + 0xe3, 0xe6, 0xcb, 0x78, 0xc3, 0xc1, 0x41, 0x11, 0xf1, 0x20, 0x7f, 0xb7, 0x4f, 0xa4, 0x7d, 0x1b, + 0x8b, 0x8c, 0xd7, 0xe1, 0xfc, 0xeb, 0xba, 0x14, 0x74, 0x27, 0x31, 0x05, 0xcb, 0x25, 0x5e, 0x20, + 0xcf, 0xa3, 0xc6, 0x4b, 0x85, 0xda, 0xa6, 0x40, 0xa2, 0x8e, 0x8c, 0x94, 0xe3, 0xad, 0x84, 0x6e, + 0xc7, 0xc5, 0x27, 0xe5, 0x19, 0xce, 0xf8, 0xc0, 0xb0, 0x64, 0x20, 0x49, 0x99, 0x5b, 0xa0, 0x5b, + 0x9e, 0xb4, 0xb0, 0x01, 0x3e, 0xc9, 0xa5, 0x2e, 0xc0, 0x4f, 0x8c, 0x49, 0x3e, 0x3c, 0xdc, 0x4f, + 0x97, 0xa1, 0xa5, 0xc4, 0x79, 0x00, 0x91, 0x86, 0x2f, 0x2a, 0xf9, 0x1d, 0xc7, 0x68, 0x4b, 0x16, + 0x16, 0x5d, 0x3e, 0x03, 0x75, 0x89, 0xe0, 0x34, 0xab, 0xf0, 0xde, 0x8b, 0x99, 0xb6, 0x55, 0x84, + 0xe8, 0x22, 0x55, 0xf3, 0x2f, 0x7e, 0xf2, 0xa5, 0xdc, 0xeb, 0x57, 0xe4, 0x27, 0x5f, 0x46, 0x58, + 0xe3, 0x37, 0x79, 0x50, 0x5e, 0xfd, 0x22, 0x1f, 0xbb, 0xb4, 0x77, 0xe5, 0xdc, 0x71, 0x31, 0xe1, + 0xf0, 0x99, 0x26, 0xa3, 0x61, 0x72, 0xca, 0x86, 0x73, 0x1c, 0xa7, 0xab, 0xdc, 0x3a, 0x5b, 0xc8, + 0xb4, 0x8b, 0x43, 0x46, 0x57, 0x79, 0xba, 0xe3, 0x02, 0xb7, 0x6e, 0xa6, 0x9d, 0x3e, 0x42, 0xcc, + 0xe4, 0x57, 0x7b, 0x95, 0x4d, 0x18, 0x49, 0x36, 0xe2, 0x35, 0x5f, 0x4e, 0x30, 0xd6, 0x5a, 0x70, + 0x95, 0x69, 0xb5, 0xe9, 0x17, 0xd4, 0x8e, 0x25, 0x4f, 0x12, 0xc4, 0xe7, 0x59, 0x67, 0x30, 0xcb, + 0x08, 0x2c, 0x19, 0xf0, 0xbe, 0xcf, 0xef, 0x97, 0xbb, 0x98, 0xb8, 0x3d, 0x5a, 0x85, 0x7e, 0x39, + 0xfe, 0x55, 0x8f, 0x8e, 0x72, 0x25, 0x47, 0x6a, 0x60, 0x6e, 0xc7, 0x69, 0x94, 0x6f, 0xb4, 0xcd, + 0x41, 0x64, 0x24, 0xc4, 0x45, 0x88, 0x41, 0x6e, 0x0d, 0x89, 0xd7, 0x83, 0x3a, 0xaf, 0x98, 0x86, + 0x13, 0x10, 0xab, 0xf2, 0x8b, 0x5c, 0x18, 0x85, 0x99, 0x28, 0xb6, 0x0c, 0x7a, 0x00, 0xfc, 0x01, + 0x05, 0x29, 0x4b, 0xc5, 0x98, 0x57, 0xe5, 0xf7, 0xfc, 0x91, 0xf7, 0x69, 0xf3, 0xf3, 0xca, 0x06, + 0x9b, 0xd9, 0xb8, 0xb6, 0x2c, 0x28, 0x97, 0xc7, 0x85, 0x29, 0xd9, 0x61, 0x61, 0xcf, 0xb5, 0xe9, + 0xc4, 0x67, 0x5a, 0x15, 0x28, 0x36, 0x0b, 0xe4, 0x29, 0x6b, 0x2b, 0x3b, 0x48, 0x12, 0xca, 0x3e, + 0xbd, 0xd9, 0x30, 0xc9, 0x40, 0xc2, 0x81, 0x2a, 0xfd, 0xfc, 0xe7, 0xfc, 0xf9, 0xcf, 0x7f, 0x5e, + 0x2a, 0x50, 0x4d, 0x8c, 0x56, 0xe9, 0xf5, 0xdf, 0xfd, 0xbf, 0x24, 0xde, 0x0b, 0xdd, 0xd1, 0xd7, + 0xf8, 0xe4, 0x92, 0x81, 0x55, 0xf4, 0xee, 0x89, 0x05, 0x91, 0xa3, 0x35, 0x20, 0x0d, 0xcf, 0xce, + 0x8a, 0xb9, 0xb4, 0x81, 0xe0, 0x7b, 0xa6, 0xed, 0xbb, 0xc6, 0x20, 0x83, 0x5c, 0x3c, 0xdb, 0xea, + 0x0d, 0xc9, 0x25, 0x03, 0x91, 0xc9, 0xc5, 0x9f, 0x6b, 0xc9, 0x15, 0xab, 0x16, 0x92, 0xeb, 0xab, + 0xdf, 0x04, 0x69, 0x67, 0xc3, 0x64, 0xb6, 0x3c, 0xf6, 0xaa, 0x00, 0xcd, 0x64, 0x88, 0x3a, 0x9a, + 0xa9, 0x55, 0xd5, 0x8c, 0x6b, 0x89, 0xe3, 0x26, 0x6b, 0xc6, 0xcd, 0xbf, 0x31, 0x2b, 0x1d, 0x7d, + 0x8c, 0x68, 0x07, 0xd4, 0x98, 0xc4, 0x8a, 0x9d, 0x26, 0xe7, 0xfb, 0x89, 0x09, 0xa5, 0xee, 0xbe, + 0x5a, 0xa7, 0x5b, 0x47, 0x9d, 0x0e, 0x1d, 0x22, 0x91, 0x7a, 0x14, 0x48, 0xbf, 0xd2, 0x1b, 0x36, + 0xf7, 0x39, 0xe6, 0xf9, 0x17, 0xee, 0x4e, 0x67, 0x63, 0x7c, 0xf5, 0x8f, 0x5c, 0x86, 0x63, 0xdc, + 0x2d, 0xa7, 0x09, 0xde, 0x37, 0x98, 0xab, 0x36, 0xab, 0x64, 0x01, 0x2e, 0x6a, 0x69, 0xf6, 0x9f, + 0x80, 0xf0, 0xc1, 0x1b, 0xe7, 0x39, 0xe2, 0x3f, 0x4a, 0x8a, 0x1f, 0x67, 0x7b, 0xe1, 0xd9, 0x9e, + 0x9b, 0x3e, 0xc0, 0xb0, 0x7b, 0x16, 0x6d, 0x87, 0x20, 0x55, 0x51, 0xe7, 0x33, 0x5a, 0xf7, 0xba, + 0xb1, 0xca, 0x8d, 0x9c, 0x16, 0x30, 0x78, 0xce, 0x72, 0x65, 0x7e, 0x45, 0x89, 0x04, 0xd2, 0x49, + 0x5d, 0x89, 0x45, 0x2b, 0xea, 0x34, 0xd3, 0x71, 0x56, 0x4c, 0x87, 0xc4, 0xa7, 0x1c, 0x1d, 0x91, + 0xc7, 0xae, 0xa2, 0x8d, 0xda, 0xd2, 0x21, 0x13, 0x32, 0x8f, 0x06, 0x99, 0x54, 0x26, 0xf2, 0xe0, + 0x16, 0xf5, 0x5b, 0x6a, 0x3e, 0xe3, 0x61, 0xe2, 0x85, 0xcf, 0x10, 0xcb, 0x95, 0xfa, 0x40, 0xea, + 0x54, 0x0c, 0x6b, 0x56, 0x3e, 0xee, 0x34, 0x6e, 0x7a, 0xd7, 0x6f, 0x26, 0x76, 0x9a, 0xf0, 0x48, + 0xb9, 0x4e, 0x38, 0xdf, 0x8a, 0x04, 0x8d, 0x45, 0xf9, 0xf5, 0x98, 0xb3, 0x39, 0xd8, 0x67, 0x7d, + 0x6e, 0x7a, 0x66, 0xc7, 0xb4, 0x4c, 0xff, 0x52, 0xc3, 0xbd, 0x63, 0x51, 0xb0, 0x08, 0xf3, 0x45, + 0xfb, 0xba, 0x2a, 0x26, 0xe6, 0x0d, 0x1f, 0x74, 0x1d, 0x3b, 0x3b, 0x2d, 0x0f, 0x87, 0x71, 0x1a, + 0x96, 0x56, 0x43, 0xf3, 0xce, 0x07, 0xcf, 0xd8, 0xf1, 0x97, 0x08, 0x6c, 0x32, 0x19, 0xe0, 0xc4, + 0xa3, 0xca, 0x49, 0x10, 0xeb, 0x52, 0xdd, 0x0f, 0x1c, 0x8a, 0x61, 0xd3, 0x3a, 0xbe, 0x0b, 0xb7, + 0xbd, 0x03, 0x7e, 0xc3, 0x6b, 0x73, 0x6d, 0xd2, 0xc6, 0x44, 0x31, 0xf0, 0xe5, 0x6a, 0x42, 0x1e, + 0x5b, 0x18, 0x20, 0xfd, 0x52, 0xe1, 0x24, 0x51, 0xb5, 0x49, 0x4a, 0x58, 0x56, 0x31, 0xc8, 0x52, + 0x97, 0x7c, 0xd3, 0x67, 0xe7, 0x2e, 0x4b, 0x61, 0xe3, 0xb8, 0xb7, 0x48, 0xdd, 0x81, 0x72, 0x41, + 0xe0, 0x44, 0xc1, 0xfb, 0x6a, 0xb6, 0x7d, 0xdf, 0x35, 0x3b, 0x13, 0x9f, 0x96, 0x4b, 0x43, 0x97, + 0xf6, 0xf1, 0x20, 0xc8, 0x07, 0x26, 0x00, 0xac, 0xd1, 0x4b, 0x5a, 0x3b, 0xc7, 0x41, 0x4f, 0x1f, + 0xf4, 0xcc, 0xe2, 0xeb, 0x74, 0xf3, 0xa4, 0x0c, 0x1d, 0x16, 0x7d, 0x2f, 0xda, 0xdf, 0x90, 0xc2, + 0x53, 0xf6, 0x39, 0x20, 0xf8, 0xec, 0x5d, 0x1e, 0x9a, 0x3d, 0x85, 0x9a, 0x54, 0x64, 0xa6, 0x24, + 0xb7, 0x65, 0x94, 0xb3, 0xc4, 0xf4, 0x30, 0xbe, 0x20, 0x67, 0x7a, 0xc4, 0x63, 0x13, 0x0a, 0x1e, + 0x4a, 0xf0, 0x44, 0xf3, 0x3b, 0x22, 0xb0, 0x25, 0x25, 0x29, 0x44, 0xd3, 0xf7, 0x85, 0xc8, 0xc0, + 0x0d, 0xb9, 0xa4, 0x64, 0xd3, 0xa5, 0x01, 0xce, 0x08, 0xaa, 0x09, 0x26, 0xbf, 0x6b, 0x8c, 0x3c, + 0xb1, 0x29, 0xf9, 0xec, 0xe4, 0xb0, 0x4d, 0x0d, 0xb7, 0x3b, 0x3c, 0x66, 0x4f, 0xcb, 0x2a, 0x3f, + 0x34, 0x18, 0x79, 0xeb, 0xdc, 0x6e, 0x8f, 0x94, 0xb1, 0x01, 0x65, 0x36, 0x9f, 0x8f, 0xfb, 0x52, + 0x36, 0x2a, 0x65, 0x1e, 0x93, 0x62, 0x28, 0x25, 0x47, 0x81, 0x98, 0xec, 0x03, 0x6d, 0xc1, 0x8e, + 0x64, 0x8d, 0x62, 0x81, 0xc0, 0x39, 0xfb, 0x66, 0x96, 0x53, 0x2f, 0x5c, 0x24, 0xa7, 0x5a, 0x4d, + 0x53, 0xae, 0x5b, 0x78, 0xfb, 0x5c, 0x92, 0xc1, 0x85, 0x57, 0xdb, 0xcc, 0x05, 0x4e, 0x86, 0x1a, + 0x21, 0xaa, 0xca, 0x52, 0x58, 0x60, 0x29, 0x0d, 0xd7, 0x75, 0xdc, 0x3d, 0x51, 0x17, 0xd2, 0x49, + 0xbb, 0x83, 0x81, 0xed, 0xe0, 0xc5, 0xd0, 0x54, 0xad, 0x6c, 0x33, 0xaf, 0x39, 0xd9, 0x06, 0x83, + 0xde, 0x18, 0x5a, 0xda, 0xc3, 0x4a, 0x7f, 0xfa, 0xb3, 0xee, 0xa0, 0x77, 0xe0, 0x0f, 0x28, 0x7e, + 0x40, 0x49, 0x27, 0x9d, 0x92, 0x04, 0x93, 0xbb, 0x98, 0x19, 0x93, 0xcc, 0xb9, 0x3b, 0x3c, 0xde, + 0x2c, 0x05, 0x61, 0x55, 0xa7, 0x0c, 0xd5, 0x82, 0xc9, 0xd7, 0x64, 0x33, 0xaf, 0x91, 0xec, 0x4d, + 0xba, 0x0d, 0xc6, 0x96, 0xd5, 0x08, 0xeb, 0xdc, 0x0a, 0xe1, 0xaa, 0x5a, 0x9d, 0x2a, 0xa8, 0x2a, + 0x17, 0x2e, 0x13, 0x47, 0xd5, 0x69, 0xa2, 0xa9, 0xa6, 0xe9, 0xe6, 0xc1, 0x71, 0x0e, 0xec, 0x28, + 0xb4, 0x2a, 0x17, 0x56, 0x10, 0x36, 0x55, 0x9d, 0x26, 0xb8, 0x2a, 0x7f, 0x20, 0x78, 0xcc, 0x54, + 0x75, 0x8a, 0xc0, 0xaa, 0x7c, 0x9a, 0x1e, 0xb5, 0xf3, 0x48, 0x1a, 0x45, 0x51, 0x65, 0x43, 0x93, + 0x22, 0x62, 0xaa, 0x53, 0xc6, 0xcd, 0x14, 0x1e, 0xa7, 0x28, 0x40, 0xa6, 0x3a, 0x45, 0x14, 0x4d, + 0x25, 0x73, 0x1f, 0x0f, 0x93, 0x86, 0xb3, 0x03, 0xe4, 0xbc, 0x29, 0xdf, 0x11, 0x41, 0x27, 0x95, + 0xb7, 0x96, 0x49, 0x10, 0x84, 0xd7, 0x03, 0x8a, 0x3b, 0x59, 0xbe, 0x39, 0x98, 0xd8, 0x03, 0xaf, + 0x67, 0x1a, 0x96, 0x33, 0xe0, 0xfb, 0x09, 0x41, 0x70, 0xb4, 0xd6, 0x25, 0x1c, 0x9e, 0x79, 0x0c, + 0x9d, 0x36, 0x8f, 0xd9, 0x0a, 0x39, 0x71, 0x0d, 0xb1, 0x85, 0x1d, 0x25, 0xcd, 0xc6, 0x03, 0x8f, + 0x4f, 0x70, 0x7b, 0xcc, 0x17, 0x92, 0xad, 0x17, 0x9d, 0x7e, 0xfc, 0x02, 0xdd, 0x6e, 0xec, 0xf8, + 0xa3, 0x7c, 0x5a, 0xb2, 0xfc, 0x74, 0x68, 0xd3, 0xe8, 0xf0, 0xf9, 0x05, 0x75, 0xd1, 0x75, 0x8d, + 0xb9, 0x03, 0x59, 0x53, 0x7b, 0xb1, 0x7b, 0x03, 0xb8, 0xa3, 0xdb, 0x38, 0xf3, 0xcd, 0xf3, 0x8a, + 0xfa, 0x30, 0x4b, 0x6c, 0xcb, 0x4c, 0xc0, 0xec, 0x4d, 0x60, 0x89, 0x86, 0xbe, 0x0e, 0xb3, 0x76, + 0x26, 0x84, 0x7c, 0xd6, 0x9e, 0xd4, 0x04, 0xbb, 0x1b, 0xd1, 0x8d, 0xa5, 0x8f, 0xc4, 0x0e, 0x62, + 0x06, 0xc9, 0x66, 0x23, 0x3a, 0x2f, 0x9f, 0xe5, 0xdb, 0x96, 0xee, 0x18, 0x44, 0xa0, 0xa5, 0x4a, + 0xe6, 0x76, 0x02, 0x1f, 0xd8, 0x22, 0xa7, 0xf3, 0x02, 0x5d, 0x83, 0x9a, 0xbe, 0xd8, 0xbb, 0x94, + 0x30, 0x62, 0xdd, 0x60, 0x69, 0x0c, 0xf2, 0xb7, 0x44, 0xa6, 0x3b, 0x14, 0x18, 0xae, 0xfc, 0xc5, + 0x4e, 0xf7, 0x27, 0xa2, 0xd4, 0x31, 0x72, 0x2b, 0xef, 0x80, 0xc7, 0xab, 0x1c, 0x0a, 0x15, 0x8e, + 0xb2, 0x57, 0x3a, 0x21, 0x43, 0x06, 0x81, 0x45, 0x77, 0x40, 0xad, 0xeb, 0x6f, 0x41, 0x11, 0x2a, + 0x8b, 0x83, 0xad, 0x18, 0xbf, 0x01, 0xd3, 0x1e, 0x38, 0xc6, 0xf6, 0x2b, 0xa5, 0x02, 0x3d, 0x7a, + 0x4f, 0xe9, 0x3f, 0x63, 0x78, 0x59, 0x32, 0x40, 0x9d, 0x87, 0x8b, 0x9c, 0x30, 0xfc, 0x88, 0x23, + 0xcf, 0xf8, 0x37, 0x7d, 0xf2, 0x73, 0x9a, 0x7c, 0xfc, 0x85, 0x7d, 0x5e, 0xef, 0xd9, 0x41, 0x8a, + 0xed, 0x67, 0xa7, 0x4f, 0x6b, 0x0f, 0x4e, 0x0e, 0x1e, 0x3e, 0x3a, 0x3d, 0xda, 0x6b, 0xb7, 0xdf, + 0xf7, 0x63, 0x14, 0xec, 0xb6, 0x60, 0xb4, 0xc2, 0x1f, 0x19, 0xde, 0x3e, 0xba, 0xb3, 0xd4, 0xb1, + 0x76, 0x96, 0xe1, 0xf9, 0x87, 0xe6, 0x39, 0x3d, 0x76, 0xe9, 0xb9, 0x49, 0x2f, 0xc4, 0x6d, 0x86, + 0x8d, 0x78, 0x21, 0x91, 0xd2, 0x6a, 0x7b, 0x77, 0x27, 0x50, 0xb8, 0x13, 0x05, 0x26, 0x1e, 0x75, + 0x0f, 0xbc, 0xed, 0xde, 0x17, 0xc0, 0xee, 0xb0, 0xe8, 0xab, 0x9b, 0x32, 0x82, 0xd7, 0x62, 0xfa, + 0x87, 0xa7, 0xb9, 0xe4, 0x42, 0xd1, 0x9d, 0x01, 0xdc, 0xb9, 0xfa, 0x14, 0x34, 0x16, 0x45, 0x74, + 0x4c, 0x64, 0xcf, 0x52, 0xff, 0x19, 0x34, 0x1e, 0x36, 0x9d, 0xb2, 0x68, 0xd3, 0xa8, 0x29, 0xb6, + 0x6f, 0x60, 0xe4, 0x03, 0xa4, 0x62, 0xd7, 0xe8, 0xa6, 0x4c, 0xbb, 0x64, 0x1f, 0xb4, 0x07, 0x26, + 0x03, 0x11, 0x97, 0xaa, 0x90, 0x7b, 0x5d, 0x0b, 0x4a, 0xd6, 0x95, 0x48, 0xb0, 0x32, 0x39, 0x72, + 0xfd, 0x2b, 0xbc, 0x6b, 0x05, 0xc5, 0xb5, 0x48, 0x3b, 0xcc, 0x33, 0x52, 0x51, 0x1b, 0x8d, 0x6b, + 0xcf, 0x23, 0xec, 0x6a, 0x3c, 0x96, 0x87, 0xd8, 0xb0, 0x4c, 0x0f, 0xef, 0x60, 0x14, 0x3a, 0x46, + 0x0c, 0xba, 0x62, 0x00, 0x0a, 0x08, 0xe3, 0xdc, 0xb1, 0x55, 0x2e, 0x3b, 0xdb, 0x13, 0xdf, 0xa9, + 0x85, 0xa9, 0xae, 0x18, 0xce, 0x4c, 0xa3, 0x41, 0xdc, 0x4a, 0xca, 0xd3, 0x5c, 0x8d, 0x1c, 0xcf, + 0x3b, 0x4a, 0x55, 0x84, 0x1a, 0xdd, 0xa0, 0x91, 0x73, 0x30, 0xc8, 0x80, 0xc2, 0x11, 0x3b, 0x7d, + 0x1f, 0x47, 0x84, 0x82, 0x08, 0x5d, 0xed, 0xba, 0xa8, 0x55, 0xbe, 0x8d, 0x58, 0x47, 0xb5, 0x21, + 0xf2, 0x34, 0xef, 0x84, 0x10, 0x46, 0xd8, 0x48, 0x6c, 0x81, 0x0c, 0x73, 0x01, 0xab, 0xfd, 0xba, + 0xae, 0x7c, 0x0d, 0x54, 0x68, 0x7b, 0x02, 0xac, 0xef, 0x0a, 0x35, 0x74, 0x40, 0x5d, 0x4c, 0xb0, + 0x25, 0x18, 0xcc, 0x46, 0x4f, 0x9e, 0xaf, 0xaf, 0x1c, 0xdd, 0xa0, 0xe6, 0xd5, 0xda, 0x94, 0x85, + 0x8c, 0x86, 0x70, 0xae, 0xbf, 0xed, 0xf7, 0xf1, 0x4c, 0x8a, 0xe9, 0xe9, 0xb5, 0xf0, 0xdb, 0x71, + 0x89, 0xc5, 0xb2, 0xa5, 0x24, 0xd8, 0x0f, 0x9f, 0xa9, 0x84, 0xc4, 0x4c, 0x29, 0x47, 0xe3, 0x64, + 0x7e, 0x62, 0xda, 0x20, 0xe2, 0x92, 0x87, 0x06, 0x46, 0xec, 0x29, 0x3a, 0x52, 0x5a, 0x8d, 0xc6, + 0xc6, 0x7c, 0xda, 0x60, 0x62, 0xf4, 0x0d, 0x37, 0x64, 0xbc, 0x54, 0x75, 0x86, 0x3d, 0xc5, 0x36, + 0xd6, 0x5a, 0xf3, 0x6a, 0x43, 0xdd, 0x99, 0x78, 0x43, 0x59, 0x26, 0x8c, 0xcc, 0x34, 0x0f, 0x28, + 0x3b, 0xcd, 0x9a, 0xa3, 0x4a, 0x16, 0xa7, 0xb5, 0x82, 0xc6, 0xd1, 0x7b, 0xc4, 0x6e, 0x2e, 0x23, + 0xaa, 0x1b, 0xce, 0xb9, 0x37, 0x65, 0xbc, 0x54, 0xd0, 0x39, 0xde, 0xca, 0xda, 0x5c, 0x9a, 0xd1, + 0x8d, 0x69, 0xba, 0xad, 0xac, 0x91, 0x6d, 0x8f, 0x0d, 0xdb, 0xe6, 0x46, 0x2d, 0xfa, 0x0e, 0x8f, + 0x5d, 0x07, 0xd6, 0xd4, 0xf8, 0x42, 0x95, 0x31, 0xca, 0xfc, 0x00, 0x34, 0x70, 0x91, 0xd0, 0x4d, + 0xb2, 0x54, 0x7e, 0x5e, 0x36, 0xc2, 0x6e, 0x97, 0xfb, 0xc9, 0xcb, 0x53, 0x07, 0x9f, 0x89, 0xdb, + 0xe2, 0x00, 0x63, 0x68, 0x98, 0x5d, 0x39, 0xcb, 0xa2, 0x69, 0x04, 0xea, 0xfa, 0x2d, 0x47, 0xb6, + 0x00, 0xa6, 0xd4, 0x24, 0x46, 0xb6, 0xe8, 0x71, 0x06, 0x17, 0x70, 0x0f, 0xb5, 0xd1, 0xeb, 0x1e, + 0x8b, 0xcc, 0x1d, 0xb1, 0xcc, 0x7f, 0x49, 0xc8, 0x2c, 0x29, 0xea, 0x52, 0x66, 0xee, 0xbf, 0xbc, + 0xfc, 0xa4, 0x48, 0x57, 0xde, 0x54, 0xe2, 0x04, 0x4c, 0xb2, 0xad, 0x2d, 0xd2, 0x20, 0xf7, 0x65, + 0xcc, 0xc0, 0x98, 0xa8, 0xd5, 0x6a, 0xa5, 0x22, 0x11, 0x30, 0x4c, 0x0b, 0x90, 0x78, 0xe6, 0x6d, + 0x1f, 0x59, 0xd1, 0xb4, 0x3f, 0xeb, 0xe9, 0x95, 0xd8, 0x2e, 0x4a, 0x5c, 0x17, 0xd1, 0xdd, 0x5b, + 0xc6, 0x65, 0xfa, 0x66, 0x94, 0x8b, 0x7f, 0xa6, 0xb5, 0x48, 0x13, 0x1d, 0xf0, 0x72, 0x46, 0xd8, + 0xb1, 0xa5, 0x41, 0x09, 0x5b, 0x28, 0x16, 0x59, 0xfb, 0x24, 0x39, 0x2a, 0x4a, 0x5a, 0xc3, 0x06, + 0x76, 0x32, 0x7b, 0x26, 0x53, 0x63, 0xd7, 0xf1, 0x2e, 0x12, 0xbc, 0xe0, 0xb0, 0x67, 0xf6, 0xfb, + 0x28, 0x04, 0xae, 0x58, 0x2e, 0x03, 0xd0, 0x58, 0x5d, 0xcc, 0x89, 0x7a, 0x4b, 0x39, 0x33, 0x58, + 0x0e, 0x75, 0xcc, 0x44, 0xc3, 0xfb, 0x5d, 0x13, 0xc4, 0x55, 0xa1, 0xff, 0xe4, 0xe0, 0xe8, 0xe7, + 0x27, 0xdb, 0x47, 0x0f, 0xf7, 0x08, 0x66, 0x8f, 0x54, 0x6c, 0xd1, 0x04, 0xfd, 0x03, 0x6d, 0x22, + 0x84, 0x7c, 0x2f, 0xaa, 0x96, 0x95, 0x88, 0xed, 0x4f, 0x7f, 0xfe, 0x9f, 0xff, 0xfd, 0xb7, 0x04, + 0x47, 0x7f, 0x14, 0x9c, 0xfa, 0x92, 0x56, 0x30, 0x97, 0x0e, 0xf8, 0xb9, 0x98, 0x33, 0xdc, 0xf4, + 0x92, 0x5c, 0x79, 0x92, 0x52, 0x7e, 0x5b, 0x24, 0x50, 0xd3, 0x48, 0xb3, 0xd2, 0x2e, 0x68, 0x5f, + 0xc1, 0x42, 0x58, 0x86, 0xc1, 0x82, 0xae, 0x02, 0xb9, 0xf8, 0xcd, 0xde, 0x57, 0x13, 0x72, 0x66, + 0x51, 0x53, 0x58, 0xf4, 0x21, 0xee, 0x2c, 0x0d, 0x5b, 0x06, 0x48, 0x41, 0x6e, 0x6a, 0x7b, 0x31, + 0x42, 0x73, 0x28, 0x11, 0xb5, 0x44, 0x36, 0xb7, 0x0c, 0x40, 0xdc, 0x35, 0x88, 0x6a, 0x60, 0xe0, + 0xfb, 0x84, 0x7f, 0x00, 0x7d, 0x11, 0xf0, 0xac, 0x05, 0x5a, 0x22, 0x4f, 0x4a, 0xd7, 0xa3, 0x41, + 0xc7, 0xa5, 0xa2, 0x86, 0x4c, 0xb7, 0x13, 0x41, 0xac, 0x7a, 0x49, 0x15, 0xbb, 0x83, 0x0a, 0x34, + 0xf2, 0x52, 0xc7, 0x79, 0x29, 0x57, 0x8b, 0xc1, 0xbd, 0x35, 0x47, 0x8d, 0x5a, 0x63, 0xd6, 0x14, + 0xce, 0xaf, 0x9b, 0xb7, 0x6b, 0x9a, 0xe1, 0x90, 0xa7, 0xf9, 0x1e, 0xff, 0x3c, 0xfc, 0x0b, 0x3b, + 0xff, 0xf9, 0xac, 0x81, 0xa6, 0xf8, 0x87, 0x9c, 0xc2, 0x5c, 0x54, 0x54, 0xc5, 0xa4, 0xcb, 0x85, + 0x2c, 0x5d, 0x6f, 0x58, 0x9d, 0x45, 0xe6, 0x85, 0x42, 0x29, 0x17, 0xad, 0x9b, 0xb5, 0x14, 0x29, + 0x52, 0x15, 0xdd, 0xb6, 0x46, 0xc2, 0x7c, 0x7c, 0x07, 0x36, 0x38, 0xc2, 0xeb, 0x3c, 0x7f, 0x1f, + 0x66, 0xa3, 0xc9, 0x88, 0x0a, 0x66, 0x8e, 0x04, 0xc9, 0x03, 0xa8, 0xb0, 0xc8, 0x03, 0x2f, 0x82, + 0x3a, 0x9b, 0x5c, 0x51, 0xbb, 0x7f, 0x1a, 0xdf, 0xc8, 0x2c, 0x3e, 0x92, 0x1c, 0x17, 0x46, 0xdc, + 0x87, 0x54, 0x40, 0xcb, 0xd3, 0x3b, 0x12, 0x64, 0x07, 0xee, 0x8f, 0xce, 0xd9, 0xb7, 0xe9, 0x9c, + 0x95, 0xfc, 0xb2, 0xcf, 0xb7, 0x0f, 0x0f, 0x76, 0x0f, 0xf6, 0x4e, 0x9e, 0x1d, 0x3d, 0x24, 0x1f, + 0x91, 0xed, 0xa3, 0x4f, 0xf6, 0x0e, 0x1e, 0xee, 0xbd, 0xdf, 0xf7, 0xdb, 0x69, 0xad, 0x97, 0x04, + 0x47, 0xe1, 0xf1, 0x45, 0x93, 0x82, 0x44, 0xd9, 0x17, 0xe7, 0x71, 0xc5, 0x16, 0x1e, 0xd3, 0x32, + 0x46, 0x78, 0xfe, 0x18, 0x26, 0xf3, 0xf5, 0xb7, 0xbe, 0x39, 0x60, 0x51, 0x37, 0x6c, 0xc1, 0xfd, + 0xb0, 0xb6, 0x1d, 0x84, 0xad, 0x79, 0xe2, 0x98, 0x3a, 0x4b, 0x6a, 0x9f, 0x04, 0xfc, 0x02, 0x21, + 0x61, 0x0c, 0xce, 0x63, 0x67, 0x34, 0x86, 0x72, 0x2c, 0x3a, 0x10, 0x77, 0x09, 0x81, 0x59, 0x3b, + 0x74, 0x68, 0x58, 0x3e, 0x3a, 0x8f, 0x8c, 0x0e, 0xb0, 0xef, 0xc8, 0xc0, 0xf6, 0x58, 0xab, 0x1e, + 0x6b, 0x36, 0x53, 0x57, 0x3e, 0x47, 0xb5, 0x2f, 0xd6, 0x3d, 0x90, 0xf3, 0x7a, 0x75, 0x39, 0x37, + 0xe0, 0x50, 0xb1, 0x66, 0x68, 0xb4, 0xe3, 0x69, 0x41, 0xe1, 0xa2, 0xb0, 0xa1, 0xc6, 0xea, 0x39, + 0x8b, 0x19, 0x0a, 0x97, 0x99, 0x00, 0xcf, 0x6c, 0xed, 0x3c, 0x59, 0x49, 0x60, 0xa4, 0xde, 0x4d, + 0x0f, 0x0e, 0x3e, 0x41, 0x87, 0xc8, 0x16, 0x1a, 0x7f, 0x2f, 0xd7, 0xf1, 0xbf, 0x98, 0xe6, 0x43, + 0xdd, 0xe1, 0xf5, 0xb7, 0x43, 0x85, 0x5b, 0x5a, 0x60, 0xb8, 0xb5, 0x29, 0x9a, 0xd5, 0x5f, 0x90, + 0x0a, 0xba, 0x09, 0x42, 0x15, 0xd6, 0x25, 0xd4, 0x2b, 0xaf, 0x35, 0xaa, 0x41, 0x0f, 0x3f, 0x26, + 0x2b, 0x42, 0x06, 0x62, 0x99, 0xb5, 0x86, 0x22, 0x0c, 0xf5, 0x65, 0x3c, 0x06, 0x95, 0x83, 0xdb, + 0xb8, 0x35, 0x3f, 0x4f, 0x83, 0x0e, 0x62, 0x41, 0x0b, 0xff, 0xd5, 0xb4, 0xbc, 0x68, 0xbc, 0xfc, + 0x91, 0x17, 0x15, 0xbc, 0x88, 0x0c, 0xd0, 0x33, 0xdc, 0xbe, 0x10, 0x2e, 0x5b, 0xc0, 0x0d, 0xc4, + 0x03, 0xbb, 0x23, 0xcd, 0x7a, 0x1c, 0x36, 0x16, 0x50, 0x66, 0x13, 0x4d, 0x72, 0x8c, 0xca, 0x3f, + 0x35, 0x3b, 0xb7, 0x24, 0xa1, 0xbd, 0xd2, 0x4c, 0x2a, 0xe8, 0xcd, 0xbd, 0x4d, 0x9c, 0x5b, 0xcc, + 0xf2, 0x8c, 0x4d, 0xaa, 0x73, 0x58, 0x79, 0xf1, 0xd8, 0x81, 0xab, 0xed, 0x1b, 0xd4, 0x14, 0x64, + 0x07, 0x73, 0x31, 0xec, 0x6f, 0x23, 0x7b, 0x92, 0x99, 0x76, 0x38, 0xc9, 0x80, 0xc5, 0x1a, 0xd5, + 0xa0, 0x62, 0x0d, 0xe7, 0x58, 0x9a, 0x4c, 0xb1, 0x41, 0x14, 0xd3, 0xc0, 0xb4, 0x67, 0x27, 0x55, + 0xda, 0x29, 0xa9, 0x82, 0x18, 0xdf, 0x82, 0x2e, 0x30, 0xc5, 0xb2, 0x26, 0x15, 0xd8, 0x07, 0x73, + 0x96, 0xec, 0xcc, 0x4b, 0x31, 0xb7, 0x09, 0xa5, 0x87, 0xf6, 0x5e, 0xcb, 0x77, 0xf4, 0x14, 0x4a, + 0x12, 0xbe, 0xa5, 0x72, 0x18, 0xbe, 0x01, 0xb9, 0xae, 0xd8, 0x18, 0x50, 0x01, 0x7d, 0x55, 0x98, + 0xb5, 0xe6, 0x2b, 0xa8, 0xdf, 0x4b, 0xd6, 0x7a, 0x97, 0xa4, 0x9c, 0x9a, 0xcf, 0xe6, 0x2f, 0xe6, + 0x34, 0x7c, 0xa6, 0x97, 0x74, 0xfa, 0xfc, 0xe1, 0xf2, 0x56, 0x43, 0x5a, 0xf3, 0x6e, 0x03, 0x2b, + 0x82, 0xf2, 0x8b, 0x1b, 0x19, 0xfc, 0x0e, 0xf9, 0x1a, 0xe7, 0x13, 0x4c, 0x51, 0x35, 0x74, 0x1d, + 0x5b, 0xb5, 0x9d, 0x11, 0x32, 0x81, 0xa8, 0x3c, 0x1f, 0x0e, 0x15, 0xb2, 0x7d, 0x5a, 0x68, 0x82, + 0x4c, 0x1a, 0x8e, 0x9b, 0x1a, 0xc3, 0x2c, 0xae, 0x17, 0x0b, 0xf5, 0xb4, 0xd0, 0x42, 0x0c, 0x93, + 0xb4, 0x0f, 0xb6, 0x5f, 0x80, 0xea, 0xb4, 0x3b, 0xc4, 0x53, 0x37, 0xe5, 0x46, 0x8d, 0x6d, 0x81, + 0x6c, 0x12, 0xfc, 0xd0, 0xf8, 0xb0, 0xa2, 0x26, 0x94, 0x72, 0x37, 0x25, 0x1c, 0x0f, 0xc1, 0x31, + 0x19, 0xbb, 0x29, 0x61, 0x7f, 0xd4, 0x80, 0x02, 0xb2, 0xa9, 0x01, 0xdd, 0x4a, 0xb0, 0x3e, 0xeb, + 0x5e, 0xc8, 0xa5, 0x11, 0x76, 0xf1, 0x26, 0x03, 0xe2, 0x45, 0x05, 0xc3, 0xd6, 0xd3, 0x84, 0x11, + 0x6e, 0xe2, 0x9a, 0xe4, 0x5b, 0x97, 0x8e, 0x29, 0xd9, 0x13, 0x97, 0xbc, 0x30, 0x5c, 0xdc, 0x82, + 0xab, 0x92, 0x33, 0x16, 0x7f, 0xcf, 0xbc, 0x40, 0xbb, 0xa1, 0x07, 0x15, 0xde, 0x54, 0xb2, 0xbd, + 0xed, 0xb2, 0x0c, 0x92, 0x3b, 0x5b, 0x81, 0x39, 0x2e, 0x0b, 0xb5, 0xd8, 0xbb, 0x22, 0x3e, 0x79, + 0xc5, 0x69, 0x5c, 0x6c, 0x12, 0xf1, 0xe5, 0xee, 0x26, 0x2d, 0xeb, 0xc8, 0xe5, 0x94, 0x27, 0xbd, + 0x8a, 0xba, 0xf4, 0x45, 0x80, 0x84, 0x20, 0x51, 0x48, 0xb8, 0x6a, 0xe4, 0x77, 0x3e, 0x3a, 0xd8, + 0x79, 0x74, 0x9a, 0xed, 0x71, 0x66, 0x79, 0xba, 0x24, 0x8c, 0x74, 0x6e, 0x2f, 0xb9, 0xcc, 0x74, + 0x37, 0x9b, 0xc6, 0x6a, 0xc6, 0x93, 0xd5, 0xf1, 0xcd, 0x88, 0x60, 0xb3, 0x20, 0xd8, 0x1c, 0xe0, + 0xd9, 0xbc, 0xe5, 0xcd, 0x01, 0xa0, 0x44, 0xca, 0xd3, 0x5f, 0xa9, 0xc7, 0x77, 0x31, 0x4e, 0x0a, + 0x6c, 0x5d, 0xd4, 0xf3, 0xaf, 0x6e, 0xca, 0x38, 0x4d, 0x16, 0x12, 0x5a, 0x7f, 0x38, 0x72, 0x3e, + 0xd4, 0x2c, 0x76, 0xc9, 0x54, 0xe6, 0x92, 0x80, 0xf2, 0xa9, 0x4d, 0x7d, 0x95, 0x46, 0xcb, 0xe2, + 0xc5, 0xd3, 0xbb, 0xa4, 0x1a, 0x0e, 0x7b, 0x0e, 0x5a, 0x1d, 0xee, 0xbe, 0xb8, 0xf1, 0xcd, 0x0f, + 0x8f, 0x5c, 0x7f, 0xd5, 0x61, 0x89, 0x7d, 0x5c, 0x74, 0xb0, 0xd8, 0xaa, 0xfb, 0xa6, 0x12, 0x91, + 0x6d, 0x0a, 0x5f, 0xef, 0xac, 0x91, 0x2d, 0x09, 0xf4, 0xe7, 0xbc, 0x00, 0xe7, 0x42, 0x2f, 0xb2, + 0xb5, 0x8f, 0x37, 0x54, 0x49, 0x91, 0x89, 0x65, 0xcd, 0x16, 0xd1, 0x73, 0xd3, 0x63, 0x1b, 0xf6, + 0x1e, 0xd9, 0xa7, 0xb4, 0x87, 0xb7, 0x30, 0x68, 0xd4, 0x95, 0x8e, 0x8f, 0xba, 0x0a, 0xc5, 0x1b, + 0xc4, 0xea, 0xbe, 0xe1, 0x42, 0xbf, 0x36, 0x34, 0x25, 0x1d, 0xe8, 0x9e, 0x69, 0x1b, 0x96, 0xb8, + 0xb1, 0x15, 0x2a, 0x46, 0xb3, 0x2e, 0x5d, 0x27, 0xf6, 0x9a, 0x1f, 0x68, 0xff, 0x3d, 0xe6, 0x0e, + 0xc2, 0x83, 0x00, 0x25, 0x75, 0xf1, 0xe2, 0x17, 0x50, 0x4c, 0x11, 0x30, 0x9d, 0xc4, 0x43, 0xee, + 0xc5, 0x86, 0xb6, 0x86, 0x12, 0x95, 0xbb, 0x77, 0xef, 0xaa, 0xaf, 0x22, 0x13, 0x89, 0x48, 0xb3, + 0x43, 0xff, 0x80, 0x27, 0x78, 0xe0, 0xc4, 0x80, 0x13, 0x01, 0xd8, 0xbe, 0x8f, 0x9b, 0xed, 0x09, + 0xae, 0x28, 0x7a, 0x0a, 0x55, 0x3a, 0x53, 0xf6, 0x18, 0x65, 0x9b, 0x21, 0x22, 0x34, 0x5c, 0x16, + 0x94, 0x26, 0xa5, 0xa9, 0xab, 0x93, 0xd4, 0xb9, 0x05, 0x68, 0x99, 0x39, 0x41, 0xed, 0xeb, 0x6f, + 0x78, 0x32, 0x33, 0xbc, 0x7c, 0x25, 0xd8, 0x70, 0x9c, 0x5e, 0x16, 0x28, 0x4c, 0x90, 0x1f, 0xa0, + 0x2c, 0x48, 0x06, 0x86, 0xcd, 0x4d, 0x16, 0x28, 0x8d, 0xbe, 0x1f, 0x65, 0xc1, 0x5f, 0xb5, 0x2c, + 0x10, 0x51, 0x01, 0xef, 0x8f, 0x2c, 0x48, 0xb1, 0x62, 0xda, 0x52, 0x3c, 0x05, 0x73, 0xd0, 0xf7, + 0x2d, 0xba, 0xce, 0xc3, 0x6f, 0x31, 0x2d, 0xc4, 0x4a, 0xa3, 0x31, 0xf2, 0x58, 0x5d, 0xc0, 0x86, + 0x9d, 0x48, 0xf3, 0xd0, 0x8e, 0x41, 0xd4, 0x75, 0xdc, 0x2a, 0x8c, 0x6c, 0xe7, 0x02, 0x86, 0x81, + 0x65, 0x73, 0x86, 0x8f, 0xc9, 0xb9, 0x81, 0xd2, 0x07, 0x4b, 0xd4, 0x94, 0x71, 0xfc, 0xf7, 0xd8, + 0x25, 0x19, 0x85, 0x6f, 0xde, 0x8b, 0xed, 0x91, 0x2a, 0x8f, 0x05, 0x40, 0x53, 0x6a, 0x13, 0x04, + 0xfb, 0x22, 0x45, 0xc1, 0x90, 0x8e, 0xc1, 0x8d, 0xe1, 0x1e, 0xa3, 0x3a, 0xcb, 0xea, 0xc2, 0x0e, + 0xb8, 0xe1, 0x58, 0xb0, 0x20, 0x11, 0xfb, 0x07, 0x1b, 0x2b, 0x95, 0x11, 0xd5, 0xa4, 0xf2, 0xac, + 0x60, 0xab, 0xdc, 0xb3, 0x82, 0xe1, 0x13, 0x1a, 0x5f, 0x8a, 0x14, 0xb1, 0xc6, 0xdb, 0xe3, 0xd7, + 0x3c, 0x0e, 0xdc, 0xeb, 0x6f, 0xaf, 0xbf, 0xc6, 0xc8, 0x79, 0xcb, 0x23, 0xbc, 0x93, 0xcc, 0x07, + 0x3f, 0xeb, 0xe5, 0x8e, 0xe8, 0xc4, 0x30, 0x47, 0x13, 0x8b, 0x9d, 0xee, 0x66, 0xd6, 0xe0, 0xc8, + 0x44, 0x3e, 0x75, 0x33, 0xc6, 0x0e, 0xf9, 0x35, 0x77, 0xfc, 0xf8, 0xca, 0x76, 0x81, 0xdc, 0x2d, + 0xe2, 0x81, 0x70, 0xee, 0x61, 0x36, 0x5a, 0x61, 0x99, 0x78, 0x9c, 0xe7, 0xf1, 0xdd, 0x73, 0x07, + 0xd7, 0x39, 0x63, 0x92, 0x3e, 0x56, 0x8f, 0xa8, 0x01, 0x9f, 0xf7, 0xf8, 0x40, 0xc9, 0xf7, 0x1e, + 0x0b, 0xce, 0xf8, 0x38, 0x88, 0x42, 0x01, 0x43, 0xbb, 0xa5, 0x73, 0x82, 0x25, 0x17, 0xe3, 0x48, + 0x26, 0x60, 0xfc, 0x54, 0x15, 0xbf, 0x73, 0x44, 0xcd, 0x21, 0xdf, 0x11, 0xf5, 0x7b, 0x18, 0x47, + 0x6e, 0xa7, 0xf0, 0x01, 0xf3, 0x09, 0xd0, 0x50, 0x2f, 0xee, 0xf7, 0x53, 0xcd, 0xac, 0xc7, 0xf0, + 0xdf, 0x50, 0x99, 0xa4, 0x30, 0x1f, 0x03, 0x36, 0xe0, 0xb0, 0x55, 0xf1, 0x6b, 0x41, 0xd9, 0xad, + 0xb0, 0xaf, 0xa2, 0x2c, 0xfb, 0x96, 0xee, 0xb3, 0x3c, 0x70, 0x81, 0x73, 0x64, 0x1d, 0xa3, 0xf6, + 0x99, 0xb3, 0x8f, 0x70, 0x5a, 0x8a, 0xaf, 0x52, 0x61, 0x25, 0xe9, 0x46, 0x01, 0x8b, 0x0d, 0x5d, + 0x61, 0x47, 0x62, 0xc4, 0x59, 0xf9, 0x1e, 0x88, 0xfa, 0x4a, 0x95, 0x9c, 0x5d, 0x7f, 0x8b, 0x69, + 0x00, 0xc8, 0xe1, 0xde, 0xae, 0x47, 0xce, 0xf8, 0xfd, 0xd0, 0x3c, 0x6b, 0x83, 0xc7, 0x4c, 0x4e, + 0x9e, 0xb5, 0x14, 0x93, 0x79, 0xf5, 0xaf, 0xcc, 0xd1, 0x28, 0x99, 0x65, 0x19, 0x99, 0x8f, 0xfb, + 0x01, 0x4c, 0x1b, 0x28, 0x35, 0x22, 0x2b, 0x1f, 0x92, 0x9e, 0x81, 0x37, 0x99, 0x8e, 0x0c, 0xb0, + 0x36, 0x1d, 0x30, 0x5f, 0x2f, 0x0c, 0x80, 0x65, 0xd2, 0xa1, 0xaf, 0x16, 0x0e, 0x52, 0x1c, 0xe7, + 0x26, 0x09, 0x78, 0x82, 0x21, 0x07, 0xe3, 0xd1, 0x80, 0x11, 0x68, 0x36, 0x34, 0x53, 0x3f, 0x56, + 0xb3, 0xd5, 0x58, 0xe6, 0x7b, 0x9c, 0x6b, 0x8d, 0x0f, 0xd1, 0x65, 0x8a, 0xc8, 0xa4, 0x06, 0xbf, + 0x23, 0xd7, 0x88, 0xb7, 0xfd, 0x71, 0x8c, 0x35, 0x71, 0x74, 0x6a, 0xe1, 0xb8, 0xfe, 0x94, 0xcd, + 0x79, 0xa9, 0x70, 0x2d, 0x5e, 0x19, 0x39, 0xb7, 0x9c, 0x88, 0x5f, 0xac, 0xa8, 0x12, 0x6a, 0xb0, + 0xc8, 0x90, 0x21, 0x06, 0x14, 0x8e, 0xdd, 0xeb, 0xef, 0xfa, 0x2a, 0xe1, 0x22, 0xa1, 0x78, 0x0f, + 0x79, 0x32, 0x86, 0x72, 0x63, 0x23, 0xab, 0xfc, 0x56, 0x9c, 0x28, 0x89, 0xba, 0xb1, 0x77, 0x69, + 0xdc, 0x82, 0x08, 0x69, 0xb2, 0x6f, 0xb8, 0x78, 0x73, 0x2e, 0x72, 0x00, 0x4a, 0x09, 0xf6, 0x35, + 0x76, 0x90, 0x79, 0xe8, 0x60, 0x1c, 0x75, 0xb9, 0x0f, 0x2b, 0xa0, 0x87, 0xb9, 0x53, 0x31, 0x77, + 0x56, 0x32, 0x69, 0x0c, 0x12, 0x1b, 0xbd, 0x8e, 0xad, 0x95, 0x95, 0x2a, 0x19, 0x04, 0x1f, 0x3a, + 0xfc, 0xc3, 0x86, 0x72, 0x41, 0x74, 0x47, 0x86, 0xb5, 0x83, 0x17, 0x70, 0xe4, 0xfa, 0xe9, 0xa5, + 0xb2, 0x25, 0xe5, 0xca, 0x19, 0x07, 0xa5, 0x77, 0x75, 0xbb, 0x03, 0xc4, 0x68, 0x48, 0x5f, 0x9e, + 0x3a, 0x27, 0x83, 0x4e, 0xaa, 0xa2, 0x72, 0xe1, 0x09, 0xbd, 0x19, 0x83, 0x8e, 0xd6, 0x89, 0x01, + 0x40, 0xe1, 0x75, 0x5d, 0x73, 0xbf, 0xc1, 0x40, 0xbc, 0x1e, 0x68, 0x54, 0x36, 0xf1, 0xba, 0x93, + 0xef, 0xe8, 0x48, 0xf1, 0x17, 0x93, 0x80, 0x81, 0x28, 0xae, 0x3d, 0xa0, 0x98, 0x8a, 0x0f, 0x03, + 0x37, 0xf7, 0xda, 0xc7, 0x20, 0x7f, 0x70, 0x74, 0x71, 0x9a, 0x57, 0x94, 0x81, 0x71, 0x16, 0xed, + 0xf9, 0xb0, 0x1c, 0xdc, 0x1f, 0x39, 0x3d, 0xba, 0x09, 0x65, 0x3f, 0x72, 0xd9, 0x95, 0xb7, 0x2e, + 0xfa, 0x94, 0x3e, 0x1a, 0xb0, 0xcf, 0x03, 0xf6, 0xb9, 0xc3, 0x3e, 0x77, 0xf8, 0xe7, 0x90, 0xa3, + 0xf8, 0xc3, 0x88, 0xf9, 0x0a, 0x44, 0xcf, 0x7d, 0x99, 0x1f, 0x69, 0x8e, 0xca, 0x4a, 0x2d, 0xe8, + 0xd0, 0x3a, 0xca, 0xbd, 0x4d, 0xe1, 0x03, 0xc3, 0xe6, 0xab, 0x92, 0xe8, 0x4b, 0xb4, 0xcf, 0xbc, + 0x63, 0xcc, 0x5f, 0x26, 0x4d, 0x6b, 0x29, 0xee, 0x0f, 0xc4, 0xc5, 0x4f, 0xd9, 0x8d, 0x6d, 0x78, + 0x4d, 0x1e, 0x88, 0xc1, 0x93, 0x87, 0x0f, 0x36, 0xcb, 0x61, 0x7f, 0xab, 0x61, 0x6f, 0xab, 0x61, + 0x5f, 0x2b, 0x6f, 0x32, 0x2b, 0x63, 0xac, 0x9f, 0x05, 0x32, 0x33, 0x6a, 0xc2, 0xc7, 0x2e, 0xc0, + 0x0a, 0x75, 0x2e, 0x14, 0x57, 0xaa, 0xb3, 0xfb, 0xc2, 0x33, 0x6e, 0x54, 0xe7, 0xd6, 0x9a, 0xc6, + 0x54, 0x0b, 0x36, 0x90, 0x9d, 0x0b, 0x30, 0x56, 0x51, 0x67, 0x4c, 0x16, 0x60, 0x2c, 0xb4, 0xe7, + 0x8d, 0x55, 0xef, 0xb0, 0xe5, 0x9d, 0x21, 0xa8, 0x67, 0x8e, 0x31, 0x56, 0xbd, 0x6b, 0x8f, 0x69, + 0xd7, 0x34, 0xac, 0x17, 0x8e, 0xdb, 0xf3, 0x54, 0xef, 0x61, 0x5d, 0x99, 0xf8, 0xf4, 0x90, 0xaa, + 0xdf, 0x3e, 0x3d, 0xdd, 0x3e, 0xb0, 0xfb, 0x8e, 0xea, 0x55, 0xec, 0x08, 0xf7, 0xad, 0x82, 0x91, + 0x82, 0xf1, 0x29, 0x25, 0x5f, 0x69, 0x99, 0x22, 0x42, 0x55, 0x75, 0x7f, 0x8b, 0x5c, 0x43, 0xa6, + 0x4a, 0xea, 0x28, 0x64, 0xb2, 0xb0, 0xd4, 0x99, 0x2a, 0x59, 0x6d, 0xe4, 0x17, 0x8e, 0xba, 0xc7, + 0xaf, 0xd7, 0xcd, 0x2b, 0xaf, 0xea, 0x70, 0x15, 0x8f, 0xa4, 0x35, 0x44, 0x6c, 0x50, 0xf2, 0x50, + 0x05, 0x13, 0x17, 0x2d, 0x2f, 0x71, 0xaa, 0x27, 0x29, 0x72, 0x18, 0x9f, 0x91, 0x80, 0xd1, 0xb8, + 0x3e, 0xb8, 0xfb, 0x68, 0xe7, 0xb8, 0x16, 0xf8, 0xda, 0x0b, 0xe7, 0xc5, 0x12, 0xd9, 0x69, 0xd2, + 0xbc, 0x0b, 0x53, 0x02, 0xd6, 0x1d, 0xe0, 0x5e, 0x55, 0x06, 0xaf, 0xe4, 0x90, 0xc9, 0xe6, 0x55, + 0x78, 0xf7, 0x8e, 0xd2, 0x76, 0x70, 0xfa, 0x7d, 0xa0, 0x11, 0x37, 0x7f, 0x10, 0x1f, 0x1c, 0xa6, + 0x2b, 0xc7, 0xa6, 0x4f, 0xd9, 0xf3, 0x32, 0xae, 0xfa, 0x6c, 0x1c, 0x54, 0x75, 0x2d, 0x76, 0x61, + 0x53, 0xfb, 0xa9, 0x30, 0xa0, 0xca, 0x61, 0x4b, 0xdc, 0x6a, 0xe3, 0xa0, 0x2b, 0x95, 0xba, 0xef, + 0x40, 0xa1, 0x20, 0xa0, 0xb8, 0xee, 0x59, 0x66, 0x97, 0xe2, 0x6e, 0x6a, 0x73, 0xb5, 0x52, 0x30, + 0x5f, 0x18, 0xcf, 0x2d, 0x85, 0xa0, 0xb1, 0x29, 0xc9, 0xff, 0x22, 0x63, 0x90, 0x5e, 0xcc, 0x77, + 0x0d, 0xf7, 0x8c, 0x3c, 0x01, 0x41, 0xbe, 0x1e, 0x4b, 0x77, 0x6e, 0xe3, 0x33, 0x58, 0xd7, 0xd3, + 0xc7, 0xf0, 0x40, 0x79, 0xf3, 0x4f, 0x87, 0x34, 0x3e, 0x7d, 0x5f, 0x55, 0x36, 0xde, 0xdb, 0xc8, + 0xd4, 0xdd, 0xed, 0x93, 0xc7, 0xe4, 0xc9, 0xd3, 0xdd, 0x3d, 0x72, 0xfa, 0xf4, 0xe1, 0xc3, 0xc3, + 0xbd, 0xf7, 0xfc, 0xd2, 0x45, 0x69, 0x7c, 0xd3, 0x8e, 0x0d, 0x15, 0x8b, 0xa0, 0xfa, 0xc7, 0x78, + 0xac, 0xed, 0x3b, 0xae, 0x01, 0x46, 0x46, 0x9a, 0x67, 0x44, 0xf6, 0x35, 0xcc, 0x11, 0xca, 0x40, + 0x07, 0x5c, 0x29, 0x6a, 0x20, 0x2f, 0x1f, 0xf8, 0x74, 0x54, 0x2e, 0xf9, 0xf8, 0x56, 0xbd, 0xa5, + 0x3c, 0x76, 0x41, 0x47, 0x71, 0x3d, 0xc6, 0xaf, 0x9b, 0xc1, 0xf2, 0x35, 0xc2, 0x05, 0xf4, 0x09, + 0xed, 0x99, 0x46, 0xb9, 0x54, 0x16, 0x25, 0x6a, 0xec, 0x6e, 0xb6, 0x1a, 0xba, 0x55, 0x47, 0xc0, + 0xd5, 0x3d, 0xa8, 0x00, 0x0b, 0x32, 0x2f, 0x4a, 0xbd, 0x8c, 0x19, 0x40, 0xa4, 0xd3, 0x2a, 0xe9, + 0x33, 0xd2, 0x50, 0xb2, 0x59, 0x27, 0x7b, 0x2f, 0xc7, 0x96, 0x79, 0x65, 0xfa, 0x72, 0x3a, 0x20, + 0x66, 0x9d, 0x97, 0xb0, 0x9d, 0x12, 0x01, 0x36, 0x39, 0x49, 0x56, 0x6b, 0xd5, 0xf9, 0xed, 0x70, + 0x5e, 0xac, 0xd2, 0xb3, 0xa3, 0x5d, 0xd2, 0xbe, 0x04, 0xc9, 0x35, 0x02, 0xbb, 0x0d, 0x74, 0xe3, + 0xab, 0xc9, 0xc0, 0x8f, 0x70, 0x49, 0xa7, 0x94, 0x93, 0x88, 0x87, 0xd9, 0xdb, 0x78, 0x73, 0xbf, + 0xfc, 0x25, 0x29, 0xdf, 0x96, 0x5e, 0x7d, 0xf4, 0x91, 0x4c, 0x27, 0x65, 0xea, 0xa0, 0x50, 0x80, + 0x74, 0x9c, 0xde, 0x65, 0xbd, 0x6b, 0x19, 0x9e, 0x87, 0x02, 0x94, 0x25, 0xd3, 0x65, 0x50, 0x6b, + 0xa8, 0xd6, 0xa9, 0x34, 0x18, 0xbe, 0xae, 0xb1, 0x86, 0x30, 0x5d, 0x63, 0x19, 0xd3, 0x28, 0x14, + 0xf5, 0xdb, 0x25, 0xeb, 0xb2, 0x93, 0x04, 0x33, 0x24, 0x65, 0x54, 0x73, 0x66, 0x98, 0x8b, 0x91, + 0x33, 0x87, 0xae, 0x8b, 0x1c, 0x44, 0xa2, 0x97, 0xc9, 0xd1, 0x3a, 0x18, 0xc5, 0xb9, 0x39, 0x18, + 0xb1, 0x84, 0x7f, 0x40, 0x66, 0x5f, 0x2f, 0xce, 0xbe, 0xd5, 0x00, 0x93, 0xfb, 0xc1, 0x30, 0xad, + 0xe3, 0x7d, 0xe0, 0xb0, 0x5a, 0x2a, 0xdb, 0xc3, 0xbc, 0xa8, 0xfa, 0xf3, 0xb0, 0x49, 0xc2, 0x71, + 0xd0, 0x39, 0x01, 0x79, 0x9a, 0x4a, 0xea, 0x2c, 0xc5, 0x13, 0x3b, 0x2f, 0x35, 0x2b, 0xeb, 0x56, + 0x8d, 0xa5, 0xd4, 0x84, 0xd2, 0x9a, 0x88, 0x0f, 0xc7, 0x99, 0x06, 0x0e, 0x16, 0x57, 0xee, 0xdc, + 0xab, 0x51, 0x55, 0x4f, 0x55, 0xf2, 0xfa, 0xef, 0x7e, 0x87, 0x3e, 0x05, 0x9b, 0x4a, 0x21, 0x0f, + 0x57, 0x93, 0x11, 0x79, 0x36, 0x42, 0xed, 0x98, 0x39, 0xab, 0xae, 0x26, 0x4c, 0xf3, 0x4f, 0x2b, + 0xdf, 0xa2, 0xdf, 0x53, 0xa4, 0x07, 0x16, 0x3d, 0x2c, 0xb4, 0xd9, 0x9c, 0xb5, 0x09, 0x7e, 0x88, + 0xac, 0x90, 0xec, 0xc6, 0x13, 0x07, 0xfd, 0x6d, 0x19, 0xbd, 0x60, 0x64, 0x29, 0xde, 0x0b, 0x5d, + 0x1a, 0x61, 0x6d, 0x27, 0x54, 0xdd, 0x7e, 0xf5, 0x57, 0x70, 0xf0, 0xa4, 0x7d, 0xbc, 0xb7, 0x73, + 0xb0, 0x7d, 0x48, 0x5e, 0x3c, 0x3d, 0xd9, 0xc5, 0x94, 0x40, 0x8f, 0x31, 0x25, 0xd0, 0xde, 0xd1, + 0xfb, 0x7d, 0xde, 0x24, 0x6d, 0x4e, 0x69, 0x52, 0xa8, 0x78, 0xbc, 0x10, 0xe6, 0x97, 0xf4, 0x16, + 0x07, 0xd4, 0x7f, 0x7b, 0xd9, 0x53, 0xfa, 0x60, 0x13, 0x97, 0x59, 0x0e, 0x23, 0xe6, 0xcd, 0x82, + 0x5f, 0xf7, 0xc8, 0x12, 0xfc, 0xfa, 0xf8, 0xe3, 0x99, 0x12, 0x7d, 0x48, 0x1d, 0x41, 0x3b, 0xdd, + 0x4c, 0x64, 0x36, 0x60, 0x1d, 0xfc, 0xd4, 0xfc, 0x3c, 0xeb, 0x1e, 0xde, 0x57, 0xba, 0x83, 0x76, + 0x81, 0xf5, 0x64, 0x65, 0xdd, 0x8b, 0x1c, 0x5e, 0xe1, 0x65, 0x8a, 0xd2, 0x37, 0xed, 0x46, 0x00, + 0x27, 0x99, 0xa4, 0x21, 0x78, 0x3e, 0x65, 0x27, 0x80, 0x1d, 0xae, 0x10, 0xf2, 0xf5, 0xb7, 0xa0, + 0xdb, 0x61, 0x94, 0xa3, 0xcf, 0x4e, 0xf0, 0xba, 0xd1, 0xce, 0xb4, 0xd9, 0xcf, 0x4b, 0x7f, 0xc0, + 0x56, 0x02, 0x21, 0x53, 0x04, 0xda, 0x99, 0x89, 0x76, 0x25, 0x0e, 0x14, 0xa1, 0xe6, 0xba, 0x1c, + 0x74, 0x81, 0xb1, 0x66, 0xef, 0x8d, 0xc6, 0xfe, 0x25, 0xab, 0x11, 0x1b, 0xba, 0x7a, 0xdf, 0x04, + 0x41, 0xe9, 0x96, 0xd9, 0x15, 0xd2, 0xc0, 0x53, 0xec, 0x37, 0x68, 0x43, 0xf8, 0xbb, 0x0e, 0x56, + 0xd5, 0x08, 0x38, 0xfc, 0x36, 0xcf, 0x7a, 0xab, 0x3f, 0xbf, 0x18, 0x03, 0x5f, 0xf0, 0x42, 0xb5, + 0x78, 0x6f, 0xd9, 0xe9, 0x4e, 0xb0, 0x98, 0x7d, 0xde, 0xf3, 0x38, 0xc0, 0x2f, 0x1c, 0xd3, 0x2e, + 0x97, 0xf0, 0x0c, 0xf5, 0xac, 0xf9, 0xe6, 0x32, 0x1b, 0x2b, 0xce, 0xb6, 0x05, 0x5c, 0x51, 0x09, + 0xa7, 0x93, 0x7c, 0x6c, 0xf1, 0xd0, 0x60, 0xa9, 0x2b, 0xe1, 0x6b, 0x9c, 0x65, 0x22, 0x27, 0x54, + 0x81, 0xdc, 0x09, 0x99, 0xc2, 0x87, 0x0f, 0xf6, 0x85, 0x18, 0xe4, 0x4f, 0x3f, 0x4f, 0xb8, 0x90, + 0xa6, 0x12, 0x0c, 0x11, 0xac, 0x02, 0x9c, 0x98, 0x94, 0x0e, 0x82, 0x73, 0xd4, 0x6e, 0x5d, 0xc6, + 0x5a, 0x21, 0x93, 0x34, 0x9b, 0x95, 0xfc, 0xd3, 0x9f, 0x2f, 0x30, 0xa7, 0x39, 0xbb, 0xf7, 0xc5, + 0xfc, 0xb8, 0xc9, 0xaf, 0x74, 0xb9, 0xc2, 0xac, 0x89, 0xf6, 0x80, 0x6d, 0x0a, 0x00, 0x10, 0xf2, + 0x09, 0xd3, 0x36, 0xed, 0xca, 0x6d, 0xfd, 0x65, 0x23, 0xe9, 0x6d, 0x3e, 0xf5, 0x30, 0xf3, 0x89, + 0x31, 0x9e, 0x78, 0x43, 0x86, 0x6b, 0x65, 0xce, 0x27, 0xd8, 0xa7, 0x1b, 0x87, 0xc4, 0xf9, 0xed, + 0x90, 0xd2, 0x55, 0x12, 0x88, 0xde, 0xdc, 0xc4, 0x68, 0x91, 0x98, 0xd5, 0x5d, 0xf1, 0x25, 0xec, + 0x00, 0x51, 0xae, 0xe0, 0x80, 0x27, 0xe5, 0x68, 0xe6, 0xb9, 0xf3, 0xb0, 0x70, 0x35, 0x6c, 0x46, + 0x77, 0x7c, 0x3c, 0xb6, 0x74, 0xbe, 0x4b, 0x87, 0xc7, 0x7f, 0x9d, 0x94, 0xf5, 0x72, 0x6a, 0x5b, + 0x1d, 0xdb, 0x29, 0x1c, 0xb0, 0xcc, 0x15, 0xc8, 0xd7, 0x83, 0x9c, 0x84, 0x3e, 0x05, 0x04, 0xce, + 0x34, 0x67, 0xa4, 0x73, 0xe4, 0x8b, 0x8b, 0x91, 0x26, 0x99, 0x02, 0x26, 0x96, 0xef, 0x37, 0x41, + 0x0c, 0xdc, 0xf3, 0x6e, 0xfb, 0x86, 0x0d, 0x96, 0x5b, 0x2f, 0x9e, 0x0b, 0xf0, 0xbe, 0x3a, 0x17, + 0xaf, 0x72, 0xbc, 0x19, 0x0e, 0x9a, 0xcc, 0xb7, 0x33, 0x8c, 0x6f, 0xd1, 0x31, 0x4e, 0x8c, 0xf3, + 0x27, 0x1c, 0x7d, 0x11, 0x7a, 0x73, 0x3b, 0x2b, 0xc1, 0x6b, 0x9e, 0x83, 0x3d, 0x63, 0x28, 0x0b, + 0x0d, 0xa7, 0x66, 0xfc, 0xfe, 0x5a, 0xac, 0x8c, 0x27, 0x07, 0x47, 0xcf, 0x4e, 0xf7, 0x70, 0x4f, + 0xad, 0xfd, 0x57, 0x65, 0x64, 0xc8, 0x7b, 0x32, 0x1a, 0x13, 0x63, 0xc4, 0x8a, 0x58, 0xf4, 0x5d, + 0x30, 0x30, 0x96, 0x67, 0x37, 0x30, 0xc2, 0x6e, 0xa8, 0xcc, 0x0b, 0xec, 0x1d, 0x2c, 0x71, 0x6f, + 0x5b, 0x41, 0xe3, 0xd4, 0xb7, 0x6b, 0xb8, 0x95, 0x3b, 0x95, 0x7a, 0x96, 0x31, 0x6c, 0x62, 0x5b, + 0x83, 0x16, 0xd6, 0xcd, 0x96, 0x73, 0x74, 0xb3, 0x80, 0x50, 0xf9, 0x81, 0x59, 0x4a, 0x12, 0x6b, + 0xb4, 0x33, 0xd3, 0x3b, 0x32, 0x8e, 0xca, 0xe2, 0xa0, 0x0a, 0x18, 0x75, 0xbc, 0x95, 0x7b, 0xa4, + 0x11, 0x7d, 0x01, 0xa5, 0xad, 0xd5, 0x28, 0xa0, 0xb5, 0x01, 0xf5, 0x12, 0x4a, 0xdb, 0xc4, 0x1e, + 0x5c, 0x7f, 0x67, 0xf9, 0xe6, 0x80, 0x9d, 0x42, 0x02, 0x20, 0x73, 0xd0, 0xd6, 0x90, 0xa2, 0x5c, + 0x59, 0x53, 0x75, 0xeb, 0x8d, 0x68, 0x6b, 0xcb, 0x45, 0xb5, 0xb5, 0x80, 0xe4, 0x55, 0x22, 0x18, + 0x39, 0x1b, 0x3b, 0xc5, 0xcc, 0x7e, 0xb7, 0xf4, 0x1f, 0x79, 0x5e, 0xe4, 0x6a, 0x3f, 0xdf, 0x83, + 0xf2, 0x92, 0x31, 0xfd, 0x62, 0xaa, 0x4b, 0xac, 0x1f, 0x33, 0x2b, 0x2e, 0xd2, 0x30, 0xfd, 0x20, + 0xd5, 0x16, 0xfd, 0xbe, 0xff, 0x5b, 0x52, 0x5a, 0xf0, 0xff, 0x7b, 0x8b, 0x5e, 0xd7, 0x35, 0xc7, + 0xfe, 0xd6, 0xad, 0x7b, 0x8b, 0xb8, 0xed, 0x80, 0xbf, 0x87, 0xfe, 0xc8, 0xda, 0xfa, 0xff, 0x01, + 0xc2, 0x2f, 0xcc, 0x4b, 0xca, 0xd7, 0x01, 0x00 +}; + +const size_t HTML_PAGE_GZIP_LEN = 21032; + +#endif // HTML_H diff --git a/include/vars.inc b/include/vars.inc new file mode 100644 index 0000000..d6383c4 --- /dev/null +++ b/include/vars.inc @@ -0,0 +1,92 @@ +#ifndef _VARS_INC_ + #define _VARS_INC_ + + #include +// ════════════════════════════════════════════════════════════════ +// GLOBALE VARIABLEN +// ════════════════════════════════════════════════════════════════ + + +unsigned long bootTime = 0; +unsigned long lastSyncTime = 0; +float driftRate = 0.0; +int syncCount = 0; + +int8_t isConfigured = 0xFF; + +const unsigned long AP_TIMEOUT = 300000; +bool powerLossDetected = false; //Flag fΓΌr Stromausfall + +char AP_SSID[AP_SSID_LENGHT] = "CharGrap-01\0"; +char AP_PASSWORD[AP_PASSWORD_LENGTH] = "MeinPasswort123\0"; + + +uint16_t bootCounter = 0; + +bool apActive = false; +unsigned long apStartTime = 0; +static int lastDisplayedMinute = -1; +unsigned long lastUpdateTime = 0; +const unsigned long updateInterval = 1000; // 1 Sekunde + +// ════════════════════════════════════════════════════════════════ +// OTA UPDATE VARIABLEN +// ════════════════════════════════════════════════════════════════ +char firmwareVersion[32] = ""; +bool otaInProgress = false; +int otaProgress = 0; +String otaError = ""; +unsigned long otaStartTime = 0; + +// ════════════════════════════════════════════════════════════════ +// WIFI STATION VARIABLEN +// ════════════════════════════════════════════════════════════════ +char staSsid[32] = ""; +char staPassword[64] = ""; +bool staEnabled = false; +bool staDhcp = true; +IPAddress staIP(0, 0, 0, 0); +IPAddress staGateway(0, 0, 0, 0); +IPAddress staSubnet(255, 255, 255, 0); +IPAddress staDNS(0, 0, 0, 0); + +// ════════════════════════════════════════════════════════════════ +// NTP VARIABLEN +// ════════════════════════════════════════════════════════════════ +char ntpServer[32] = "ptbtime1.ptb.de"; +unsigned long lastNtpSync = 0; +bool ntpEnabled = false; +bool ntpSyncSuccessful = false; +unsigned long nextNtpCheck = 0; + +// ════════════════════════════════════════════════════════════════ +// AUTO-BRIGHTNESS VARIABLEN +// ════════════════════════════════════════════════════════════════ +bool autoBrightnessEnabled = false; +uint16_t autoBrightnessMinADC = 200; // Min ADC-Wert (ca. 0.2V bei 1024=1V) +uint16_t autoBrightnessMaxADC = 820; // Max ADC-Wert (ca. 0.8V bei 1024=1V) +uint8_t autoBrightnessMin = 0; // Minimale Helligkeit (bei wenig Licht) +uint8_t autoBrightnessMax = 80; // Maximale Helligkeit (bei viel Licht) - 80 = 100% +const uint8_t MAX_BRIGHTNESS = 80; // Maximum fΓΌr Helligkeit (100%) + +// ════════════════════════════════════════════════════════════════ +// SPECIAL WORD VARIABLEN +// ════════════════════════════════════════════════════════════════ +uint8_t specialWordInterval = 60; // Anzeigeintervall in Minuten (Default: jede Stunde) + +// ════════════════════════════════════════════════════════════════ +// PATTERN TEST VARIABLEN +// ════════════════════════════════════════════════════════════════ +bool patternTestRunning = false; // Pattern-Test aktiv + +// Intervall-Konstanten +#define INTERVAL_EVERY_MINUTE 1 +#define INTERVAL_EVERY_5_MINUTES 5 +#define INTERVAL_EVERY_10_MINUTES 10 +#define INTERVAL_EVERY_15_MINUTES 15 +#define INTERVAL_EVERY_30_MINUTES 30 +#define INTERVAL_EVERY_60_MINUTES 60 + +//#include +#include +#endif \ No newline at end of file diff --git a/include/version.inc b/include/version.inc new file mode 100644 index 0000000..4ff4b2b --- /dev/null +++ b/include/version.inc @@ -0,0 +1,33 @@ +#ifndef _VERSION_INC_ +#define _VERSION_INC_ + +// Auto-generated version from build date/time +#define BUILD_DATE __DATE__ +#define BUILD_TIME __TIME__ + +// Convert build date/time to version string format: YYYYMMDD-HHMM +// Example: 20250121-1430 +inline void generateVersion(char* buffer, size_t bufferSize) { + const char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + + char month[4], day[3], year[5], time[9]; + sscanf(BUILD_DATE, "%s %s %s", month, day, year); + strcpy(time, BUILD_TIME); + + int monthNum = 1; + for (int i = 0; i < 12; i++) { + if (strcmp(month, months[i]) == 0) { + monthNum = i + 1; + break; + } + } + + int h, m, s; + sscanf(time, "%d:%d:%d", &h, &m, &s); + + snprintf(buffer, bufferSize, "%s%02d%02d-%02d%02d", + year, monthNum, atoi(day), h, m); +} + +#endif diff --git a/lib/CharGraphTimeLogic/.claude/settings.local.json b/lib/CharGraphTimeLogic/.claude/settings.local.json new file mode 100644 index 0000000..c9c5173 --- /dev/null +++ b/lib/CharGraphTimeLogic/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(python -m py_compile:*)", + "Bash(node -c:*)" + ] + } +} diff --git a/lib/CharGraphTimeLogic/blackboxtest/README.md b/lib/CharGraphTimeLogic/blackboxtest/README.md new file mode 100644 index 0000000..728d35b --- /dev/null +++ b/lib/CharGraphTimeLogic/blackboxtest/README.md @@ -0,0 +1,186 @@ +# CharGraph TimeLogic - Black Box Test Suite + +Automated testing framework for the CharGraphTimeLogic library. + +## Overview + +The Black Box Test Suite provides a standardized way to test the CharGraph time-to-words conversion logic across multiple patterns and time windows. + +## Files + +### blackboxtest.ino +Main test application. Loads patterns from `config.h`, runs tests, and outputs results to serial. + +**Features:** +- Tests multiple patterns sequentially +- Configurable time window and minute increments +- Table-formatted output compatible with markdown +- Test statistics and pass/fail summary + +### config.h +Configuration file for test parameters. + +**Parameters:** + +```cpp +// Test patterns (each must be exactly 110 characters) +const char testpatterns[][111] = { + "PATTERN1_110_CHARS", + "PATTERN2_110_CHARS", + // Add more patterns here +}; + +// Number of patterns to test +const uint8_t numPatterns = 2; + +// Time window +uint8_t hourStart = 23; // Start hour (0-23) +uint8_t hourEnd = 0; // End hour (can wrap around midnight) +uint8_t minuteStart = 23; // Start minute (0-59) +uint8_t minuteEnd = 27; // End minute (0-59) +uint8_t hops = 1; // Minute increments (1=every min, 5=every 5 min, etc.) +``` + +## Usage + +### Setup + +1. Place `blackboxtest.ino` and `config.h` in your Arduino project folder +2. Ensure CharGraphTimeLogic library is properly installed +3. Modify patterns in `config.h` as needed + +### Configuration + +#### Adding Test Patterns + +In `config.h`: +```cpp +const char testpatterns[][111] = { + "ESGISTWZEHNFÜNFVIERTELVORNACHBALDHALBKZWEINSYWAYOLOVDCQZEHNEUNACHTELFÜNFZWΓ–LFSECHSIEBENGEDREIVIERTMDCPAUSEUHRW", // Pattern 1 + "ESUISTKFASTBALDKURZEHNFÜNFZEITVORDREIVIERTELNACHRWDHALBHSECHSVIERKELFZWΓ–LFÜNFZEHNEUNACHTDREINSIEBENMZWEIEHUHRQ", // Pattern 2 + // "PATTERN3...", +}; +const uint8_t numPatterns = 2; +``` + +#### Changing Time Window + +Default (Standard Black Box Test): +```cpp +uint8_t hourStart = 23; // 23:23 +uint8_t hourEnd = 0; // to 00:27 +uint8_t minuteStart = 23; +uint8_t minuteEnd = 27; +uint8_t hops = 1; // Every minute +``` + +For testing just 1 hour: +```cpp +uint8_t hourStart = 14; +uint8_t hourEnd = 14; +uint8_t minuteStart = 0; +uint8_t minuteEnd = 59; +uint8_t hops = 1; +``` + +For sparse sampling (every 5 minutes): +```cpp +uint8_t hops = 5; +``` + +### Running Tests + +1. Connect Arduino to computer via USB +2. Open Arduino IDE +3. Load `blackboxtest.ino` +4. Select correct board and port +5. Upload sketch +6. Open Serial Monitor (baud rate: 115200) +7. Tests will run automatically and output results + +### Output Format + +``` +======================================== +Pattern 1 +======================================== +Pattern: ESGISTWZEHNFÜNF... + +Uhrzeit | Text | MinutenLeds | Links | Rechts +--------|------|-------------|-------|------- +23:23 | ES IST ZEHN VOR HALB ZWΓ–LF | *** | L | R +23:24 | ES IST ZEHN VOR HALB ZWΓ–LF | **** | L | R +... + +======================================== +TEST SUMMARY +======================================== +Total Tests: 130 +Passed: 130 +Failed: 0 + +βœ“ ALL TESTS PASSED +``` + +## Test Statistics + +After each pattern, a summary is printed: +- **Total Tests**: Number of minutes tested +- **Passed**: Successful word generation +- **Failed**: Errors or invalid results + +## LED Output Format + +In the serial output: +- `*` = One LED +- `****` = Four LEDs +- `L` = LED direction LEFT (additive) +- `R` = LED direction RIGHT (subtractive) + +## Standard Time Windows + +### Black Box Test (Default) +- **Start:** 23:23 +- **End:** 00:27 +- **Duration:** 65 minutes +- **Reason:** Tests full hour transition and critical minutes + +### Full Hour Test +- **Start:** HH:00 +- **End:** HH:59 +- **Duration:** 60 minutes + +### Sparse Test (Debug) +- **Start:** 23:23 +- **End:** 00:27 +- **Hops:** 5 minutes +- **Duration:** 13 tests (instead of 65) + +## Troubleshooting + +### Serial Output Garbled +- Check baud rate: Should be 115200 +- Verify USB cable connection +- Try different USB port + +### Pattern Not Found +- Verify pattern length is exactly 110 characters +- Check for accented characters (Γ€, ΓΆ, ΓΌ) +- Ensure pattern is uppercase + +### Memory Issues +- Reduce number of patterns in config.h +- Increase hops value (test fewer minutes) +- Test on Arduino with more SRAM (Mega) + +## Design Goals + +βœ“ Simple, intuitive configuration +βœ“ Consistent time window (23:23-00:27 by default) +βœ“ Markdown-compatible output +βœ“ Deterministic, repeatable results +βœ“ Works on standard Arduino boards + +## License + +Part of CharGraph TimeLogic library. See main LICENSE for details. diff --git a/lib/CharGraphTimeLogic/blackboxtest/blackboxtest.ino b/lib/CharGraphTimeLogic/blackboxtest/blackboxtest.ino new file mode 100644 index 0000000..40ae34a --- /dev/null +++ b/lib/CharGraphTimeLogic/blackboxtest/blackboxtest.ino @@ -0,0 +1,285 @@ +/** + * CharGraph TimeLogic - Black Box Test + * + * Automated test suite for CharGraphTimeLogic library + * Tests multiple patterns across a configurable time window + */ + +#include "config.h" +#include "../src/CharGraphTimeLogic.h" + +// ============================================================================ +// GLOBAL TEST STATISTICS +// ============================================================================ + +struct TestStats { + uint32_t totalTests; + uint32_t passedTests; + uint32_t failedTests; +}; + +TestStats stats = {0, 0, 0}; + +// ============================================================================ +// SETUP AND INITIALIZATION +// ============================================================================ + +void setup() { + Serial.begin(SERIAL_BAUD); + delay(1000); + + Serial.println("\n\n"); + Serial.println("========================================"); + Serial.println("CharGraph TimeLogic - Black Box Test"); + Serial.println("========================================\n"); + + Serial.print("Test Patterns: "); + Serial.println(numPatterns); + Serial.print("Time Window: "); + Serial.print(hourStart); + Serial.print(":"); + if (minuteStart < 10) Serial.print("0"); + Serial.print(minuteStart); + Serial.print(" to "); + Serial.print(hourEnd); + Serial.print(":"); + if (minuteEnd < 10) Serial.print("0"); + Serial.print(minuteEnd); + Serial.println(); + Serial.print("Minute Hops: "); + Serial.println(hops); + Serial.println(); + + // Run all tests + for (uint8_t patternIdx = 0; patternIdx < numPatterns; patternIdx++) { + runPatternTest(patternIdx, testpatterns[patternIdx]); + } + + // Print summary + printTestSummary(); + + Serial.println("\nTest completed. Ready for next test.\n"); +} + +void loop() { + // Loop does nothing - tests run in setup + delay(1000); +} + +// ============================================================================ +// MAIN TEST RUNNER +// ============================================================================ + +void runPatternTest(uint8_t patternIdx, const char* pattern) { + Serial.println("========================================"); + Serial.print("Pattern "); + Serial.print(patternIdx + 1); + Serial.println(); + Serial.println("========================================"); + Serial.print("Pattern: "); + Serial.println(pattern); + + // Verify pattern length + if (strlen(pattern) != GRID_SIZE) { + Serial.print("ERROR: Pattern length "); + Serial.print(strlen(pattern)); + Serial.print(" != "); + Serial.println(GRID_SIZE); + return; + } + + Serial.println("\nUhrzeit | Text | MinutenLeds | Links | Rechts"); + Serial.println("--------|------|-------------|-------|-------"); + + // Calculate total test count + uint16_t testCount = calculateTestCount(); + + // Iterate through time window + uint8_t hour = hourStart; + uint8_t minute = minuteStart; + uint16_t count = 0; + + while (count < testCount) { + // Get words for this time + CharGraphTimeWords result; + bool success = getCharGraphWords(pattern, hour, minute, result); + + // Print result line + printResultLine(hour, minute, result, success); + + // Update statistics + stats.totalTests++; + if (success) { + stats.passedTests++; + } else { + stats.failedTests++; + } + + // Advance time by hop increment + minute += hops; + if (minute > 59) { + minute -= 60; + hour++; + if (hour > 23) { + hour = 0; + } + } + + count++; + } + + Serial.println(); +} + +// ============================================================================ +// HELPER FUNCTIONS +// ============================================================================ + +uint16_t calculateTestCount() { + /** + * Calculate how many minutes will be tested based on: + * - Start and end times + * - Hop increment + */ + + uint16_t count = 0; + uint8_t hour = hourStart; + uint8_t minute = minuteStart; + + // Safety limit: maximum 10000 iterations to prevent infinite loops + for (uint16_t i = 0; i < 10000; i++) { + count++; + + // Check if we've reached the end + if (hour == hourEnd && minute == minuteEnd) { + break; + } + + // Advance time + minute += hops; + if (minute > 59) { + minute -= 60; + hour++; + if (hour > 23) { + hour = 0; + } + } + } + + return count; +} + +void printResultLine(uint8_t hour, uint8_t minute, const CharGraphTimeWords& result, bool success) { + /** + * Print one result line in table format: + * Uhrzeit | Text | MinutenLeds | Links | Rechts + */ + + // Uhrzeit + if (hour < 10) Serial.print("0"); + Serial.print(hour); + Serial.print(":"); + if (minute < 10) Serial.print("0"); + Serial.print(minute); + Serial.print(" | "); + + // Text (first 37 chars max, padded) + char textBuf[100]; + if (success) { + buildTextFromWords(result.words, result.wordCount, textBuf); + } else { + strcpy(textBuf, "ERROR"); + } + + int len = strlen(textBuf); + Serial.print(textBuf); + for (int i = len; i < 37; i++) { + Serial.print(" "); + } + Serial.print(" | "); + + // MinutenLeds (LED count display) + if (result.ledCount > 0) { + for (uint8_t i = 0; i < result.ledCount; i++) { + Serial.print("*"); // Use * instead of emoji for reliability + } + } + for (uint8_t i = result.ledCount; i < 11; i++) { + Serial.print(" "); + } + Serial.print(" | "); + + // Links / Rechts + if (result.ledCount > 0) { + if (result.ledDirection == LEFT) { + Serial.print("L"); + Serial.print(" | "); + Serial.print("R"); + } else { + Serial.print("R"); + Serial.print(" | "); + Serial.print("L"); + } + } else { + Serial.print(" "); + Serial.print(" | "); + Serial.print(" "); + } + + Serial.println(); +} + +void buildTextFromWords(const char* const* words, uint8_t wordCount, char* outText) { + /** + * Build readable text from word array + */ + + if (!words || !outText || wordCount == 0) { + outText[0] = '\0'; + return; + } + + uint16_t pos = 0; + const uint16_t maxLen = 99; + + for (uint8_t i = 0; i < wordCount; i++) { + if (i > 0 && pos < maxLen) { + outText[pos++] = ' '; + } + + // Load word from PROGMEM and copy + char wordBuf[11]; + strcpy_P(wordBuf, words[i]); + + for (uint8_t j = 0; wordBuf[j] != '\0' && pos < maxLen; j++) { + outText[pos++] = wordBuf[j]; + } + } + + outText[pos] = '\0'; +} + +void printTestSummary() { + /** + * Print test statistics summary + */ + + Serial.println("\n========================================"); + Serial.println("TEST SUMMARY"); + Serial.println("========================================"); + Serial.print("Total Tests: "); + Serial.println(stats.totalTests); + Serial.print("Passed: "); + Serial.println(stats.passedTests); + Serial.print("Failed: "); + Serial.println(stats.failedTests); + + if (stats.failedTests == 0) { + Serial.println("\nβœ“ ALL TESTS PASSED"); + } else { + Serial.print("\nβœ— "); + Serial.print(stats.failedTests); + Serial.println(" TESTS FAILED"); + } + Serial.println("========================================"); +} diff --git a/lib/CharGraphTimeLogic/blackboxtest/config.h b/lib/CharGraphTimeLogic/blackboxtest/config.h new file mode 100644 index 0000000..bb12bd7 --- /dev/null +++ b/lib/CharGraphTimeLogic/blackboxtest/config.h @@ -0,0 +1,48 @@ +/** + * CharGraph TimeLogic - Black Box Test Configuration + * + * Define test patterns and time windows here + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#include + +// ============================================================================ +// TEST PATTERNS +// ============================================================================ +// Each pattern must be exactly 110 characters + +const char testpatterns[][111] = { + // Pattern 1: Standard Pattern + "ESGISTWZEHNFÜNFVIERTELVORNACHBALDHALBKZWEINSYWAYOLOVDCQZEHNEUNACHTELFÜNFZWΓ–LFSECHSIEBENGEDREIVIERTMDCPAUSEUHRW", + + // Pattern 2: DREIVIERTEL Pattern + "ESUISTKFASTBALDKURZEHNFÜNFZEITVORDREIVIERTELNACHRWDHALBHSECHSVIERKELFZWΓ–LFÜNFZEHNEUNACHTDREINSIEBENMZWEIEHUHRQ", + + // Add more patterns here as needed + // "PATTERN3...", + // "PATTERN4...", +}; + +// Number of test patterns +const uint8_t numPatterns = 2; + +// ============================================================================ +// TEST TIME WINDOW +// ============================================================================ + +uint8_t hourStart = 23; // Start hour (0-23) +uint8_t hourEnd = 0; // End hour (0-23, can wrap around) +uint8_t minuteStart = 23; // Start minute (0-59) +uint8_t minuteEnd = 27; // End minute (0-59) +uint8_t hops = 1; // Minute increments (1 = every minute, 5 = every 5 minutes, etc.) + +// ============================================================================ +// SERIAL OUTPUT CONFIGURATION +// ============================================================================ + +#define SERIAL_BAUD 115200 + +#endif // CONFIG_H diff --git a/lib/CharGraphTimeLogic/examples/BasicExample.ino b/lib/CharGraphTimeLogic/examples/BasicExample.ino new file mode 100644 index 0000000..fc0403e --- /dev/null +++ b/lib/CharGraphTimeLogic/examples/BasicExample.ino @@ -0,0 +1,117 @@ +/** + * CharGraph Time Logic - Basic Example + * + * Simple example showing how to use the library: + * - Convert time to German words + * - Display LED hex value + * + * Tested on: WEMOS D1 Mini (ESP8266) + * + * Hardware: + * - ESP8266 / WEMOS D1 Mini + * - USB Serial connection + * + * Instructions: + * 1. Install library in Arduino/libraries/CharGraphTimeLogic + * 2. Open this sketch in Arduino IDE + * 3. Select Tools > Board > LOLIN(WEMOS) D1 mini (ESP8266) + * 4. Select Tools > Upload Speed > 921600 + * 5. Upload and open Serial Monitor (115200 baud) + */ + +#include + +// 110-character pattern (10 rows Γ— 11 columns) +// Normal word order +const char PATTERN_NORMAL[] PROGMEM = + "ESIST-FUENFZEHNVIERTELVOR" + "NACHABFASTHALBALDZWEI----" + "DREEINSIEBENEFL-ZWOELF" + "FUENF-SECHSVIER-ZEHNEUNACHT" + "-------UHR"; + +// Swapped pattern: NACH before VIERTEL (triggers fallback) +// This tests the multi-level fallback for :15-:19 +const char PATTERN_SWAPPED[] PROGMEM = + "ESIST-FUENFZEHN-NACH-VOR-" + "VIERTELFASTHALBALDZWEI----" + "DREEINSIEBENEFL-ZWOELF" + "FUENF-SECHSVIER-ZEHNEUNACHT" + "-------UHR"; + +void setup() { + Serial.begin(115200); + delay(100); + + Serial.println("\n\n=== CharGraph Time Logic - Basic Example ===\n"); +} + +void loop() { + // Test 1: Normal pattern (reference) + Serial.println("=== TEST 1: NORMAL PATTERN ==="); + testPattern(PATTERN_NORMAL, "NORMAL"); + testTime(PATTERN_NORMAL, 6, 0); // 6:00 β†’ ES IST SECHS + testTime(PATTERN_NORMAL, 6, 5); // 6:05 β†’ ES IST FÜNF NACH SECHS + testTime(PATTERN_NORMAL, 6, 15); // 6:15 β†’ ES IST VIERTEL NACH SECHS + testTime(PATTERN_NORMAL, 6, 30); // 6:30 β†’ ES IST HALB SIEBEN + testTime(PATTERN_NORMAL, 6, 45); // 6:45 β†’ ES IST VIERTEL VOR SIEBEN + testTime(PATTERN_NORMAL, 6, 55); // 6:55 β†’ ES IST FÜNF VOR SIEBEN + + // Test 2: Swapped pattern (triggers fallback) + Serial.println("\n=== TEST 2: SWAPPED PATTERN (Fallback Test) ==="); + testPattern(PATTERN_SWAPPED, "SWAPPED"); + testTime(PATTERN_SWAPPED, 1, 15); // 1:15 β†’ Fallback: ES IST VIERTEL EINS (no NACH) + testTime(PATTERN_SWAPPED, 1, 16); // 1:16 β†’ Fallback: ES IST ZEHN VOR HALB ZWEI (4 LEDs LEFT) + testTime(PATTERN_SWAPPED, 1, 17); // 1:17 β†’ Fallback: ES IST ZEHN VOR HALB ZWEI (3 LEDs LEFT) + testTime(PATTERN_SWAPPED, 1, 18); // 1:18 β†’ Fallback: ES IST ZEHN VOR HALB ZWEI (2 LEDs LEFT) + testTime(PATTERN_SWAPPED, 1, 19); // 1:19 β†’ Fallback: ES IST ZEHN VOR HALB ZWEI (1 LED LEFT) + + Serial.println("\n--- Test completed ---\n"); + delay(5000); // Wait 5 seconds before next test +} + +void testPattern(const char* pattern, const char* patternName) { + Serial.print("Pattern: "); + Serial.print(patternName); + Serial.print(" (Length: "); + Serial.print(strlen(pattern)); + Serial.println(")"); + delay(1000); +} + +void testTime(const char* pattern, uint8_t hour, uint8_t minute) { + Serial.print(" Time: "); + if (hour < 10) Serial.print("0"); + Serial.print(hour); + Serial.print(":"); + if (minute < 10) Serial.print("0"); + Serial.println(minute); + + CharGraphTimeWords result; + + // Call library function + if (getCharGraphWords(pattern, hour, minute, result)) { + // Success - print results + Serial.print(" Text: "); + Serial.println(result.text); + + Serial.print(" Words: "); + for (uint8_t i = 0; i < result.wordCount; i++) { + Serial.print((const __FlashStringHelper*) result.words[i]); + if (i < result.wordCount - 1) Serial.print(" "); + } + Serial.println(); + + Serial.print(" LED: Count="); + Serial.print(result.ledCount); + Serial.print(", Direction="); + Serial.print((const __FlashStringHelper*) result.ledDirection); + Serial.print(", Hex=0x"); + if (result.ledHex < 0x10) Serial.print("0"); + Serial.println(result.ledHex, HEX); + } else { + // Error - validation failed + Serial.println(" ERROR: Validation failed!"); + delay(200); + } +} diff --git a/lib/CharGraphTimeLogic/examples/FullExample.ino b/lib/CharGraphTimeLogic/examples/FullExample.ino new file mode 100644 index 0000000..b58d974 --- /dev/null +++ b/lib/CharGraphTimeLogic/examples/FullExample.ino @@ -0,0 +1,199 @@ +/** + * CharGraph Time Logic - Full Example with Time Sync + * + * Complete example showing: + * - Pattern validation + * - Real-time conversion + * - LED output + * - Debug information + * + * Tested on: WEMOS D1 Mini (ESP8266) + * + * Hardware: + * - ESP8266 / WEMOS D1 Mini + * - USB Serial connection + * - Optional: DS3231 RTC module (I2C) + * + * Instructions: + * 1. Install library + * 2. Configure pattern below + * 3. Upload and monitor serial output + */ + +#include + +// Define this to enable debug output +// #define CHARGRAPH_DEBUG + +// 110-character pattern - MUST be exactly 110 characters +// Format: 10 rows of 11 characters each +// Row 1: ESIST-FUENF... (E S I S T - F Ü E N F = 11 chars) +// Row 2-10: similar +const char PATTERN[] PROGMEM = + "ESIST-FUENFZEHNVIERTELVOR" + "NACHABFASTHALBALDZWEI----" + "DREEINSIEBENEFL-ZWOELF" + "FUENF-SECHSVIER-ZEHNEUNACHT" + "-------UHR"; // Total: 110 characters + +// Current time (simulated or from RTC) +uint8_t currentHour = 14; +uint8_t currentMinute = 30; + +void setup() { + Serial.begin(115200); + delay(100); + + Serial.println("\n\n"); + Serial.println("╔════════════════════════════════════════╗"); + Serial.println("β•‘ CharGraph Time Logic - Full Example β•‘"); + Serial.println("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + Serial.println(); + + // Verify pattern length + size_t patternLen = strlen_P(PATTERN); + Serial.print("Pattern length: "); + Serial.println(patternLen); + + if (patternLen == 110) { + Serial.println("βœ“ Pattern length is correct (110 chars)"); + } else { + Serial.print("βœ— Pattern length ERROR! Expected 110, got "); + Serial.println(patternLen); + } + + Serial.println(); + Serial.println("Starting time simulation..."); + Serial.println(); +} + +void loop() { + // Convert time to words + CharGraphTimeWords result; + + if (getCharGraphWords(PATTERN, currentHour, currentMinute, result)) { + // ===== Display current time ===== + displayTime(currentHour, currentMinute); + + // ===== Display words ===== + Serial.print("Words: "); + for (uint8_t i = 0; i < result.wordCount; i++) { + Serial.print((const __FlashStringHelper*) result.words[i]); + if (i < result.wordCount - 1) Serial.print(" "); + } + Serial.println(); + + // ===== Display LED information ===== + Serial.print("LED: "); + Serial.print(result.ledCount); + Serial.print(" Γ— "); + Serial.print((const __FlashStringHelper*) result.ledDirection); + Serial.print(" = 0x"); + if (result.ledHex < 0x10) Serial.print("0"); + Serial.println(result.ledHex, HEX); + + // ===== Display binary representation ===== + Serial.print("Bits: "); + for (int8_t i = 3; i >= 0; i--) { + Serial.print((result.ledHex >> i) & 1); + } + Serial.println(); + + Serial.println(); + } else { + Serial.print("ERROR at "); + if (currentHour < 10) Serial.print("0"); + Serial.print(currentHour); + Serial.print(":"); + if (currentMinute < 10) Serial.print("0"); + Serial.println(currentMinute); + Serial.println("Pattern validation failed!"); + Serial.println(); + } + + // ===== Advance time ===== + currentMinute += 5; + if (currentMinute >= 60) { + currentMinute = 0; + currentHour += 1; + if (currentHour >= 24) { + currentHour = 0; + Serial.println("═══════════════════════════════════════"); + Serial.println(" Daily cycle complete"); + Serial.println("═══════════════════════════════════════"); + Serial.println(); + } + } + + delay(1000); // 1 second per step (demonstrates all 60 minutes quickly) +} + +// ============================================================================ +// HELPER FUNCTIONS +// ============================================================================ + +void displayTime(uint8_t hour, uint8_t minute) { + Serial.print("Time: "); + if (hour < 10) Serial.print("0"); + Serial.print(hour); + Serial.print(":"); + if (minute < 10) Serial.print("0"); + Serial.print(minute); + Serial.print(" β†’ "); +} + +// ============================================================================ +// OPTIONAL: RTC INTEGRATION +// ============================================================================ + +// If you have a DS3231 RTC module, uncomment and modify this section: +/* +#include +#include + +RTC_DS3231 rtc; + +void setupRTC() { + if (!rtc.begin()) { + Serial.println("RTC not found!"); + while (1); + } + + // Set time (only on first run) + // rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); +} + +void updateTimeFromRTC() { + DateTime now = rtc.now(); + currentHour = now.hour(); + currentMinute = now.minute(); +} +*/ + +// ============================================================================ +// OPTIONAL: WIFI TIME SYNC (NTP) +// ============================================================================ + +// If you want to sync time from internet, add WiFi + time library: +/* +#include + +void setupWiFi() { + WiFi.begin("SSID", "PASSWORD"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + // Sync time from NTP + configTime(2 * 3600, 0, "pool.ntp.org", "time.nist.gov"); + Serial.println("Time synced"); +} + +void updateTimeFromNTP() { + time_t now = time(nullptr); + struct tm* timeinfo = localtime(&now); + currentHour = timeinfo->tm_hour; + currentMinute = timeinfo->tm_min; +} +*/ diff --git a/lib/CharGraphTimeLogic/examples/MinuteRangeTest.ino b/lib/CharGraphTimeLogic/examples/MinuteRangeTest.ino new file mode 100644 index 0000000..705d4f9 --- /dev/null +++ b/lib/CharGraphTimeLogic/examples/MinuteRangeTest.ino @@ -0,0 +1,195 @@ +/** + * CharGraph Time Logic - Minute Range Test + * + * Tests a full minute range with detailed LED and word output + * Time Range: 12:29 to 13:35 (67 minutes) + * + * Output Format: + * Uhrzeit | Text | MinutenLeds | Links | Rechts + * 12:29 | ES IST VOR HALB EINS | βœ…βœ…βœ…βœ… | ❌ | βœ… + * 12:30 | ES IST HALB DREI | | ❌ | ❌ + * + * Tested on: WEMOS D1 Mini (ESP8266) + * + * Instructions: + * 1. Install CharGraphTimeLogic library in Arduino/libraries/ + * 2. Open this sketch in Arduino IDE + * 3. Select Tools > Board > LOLIN(WEMOS) D1 mini (ESP8266) + * 4. Upload and open Serial Monitor (115200 baud) + * 5. Copy the table output + */ + +#include + +// 110-character pattern for testing +// User's pattern: "ESGISTWZEHNFÜNFVIERTELVORNACHBALDHALBKZWEINSYWAYOLOVDCQZEHNEUNACHTELFÜNFZWΓ–LFSECHSIEBENGEDREIVIERTMDCPAUSEUHRW" +const char TEST_PATTERN[] PROGMEM = + "ESGISTWZEHNFÜNFVIERTEL" + "VORNACHBALDHALBKZWEINS" + "YWAYOLOVDCQZEHNEUNACHT" + "ELFÜNFZWΓ–LFSECHSIEBENGED" + "REIVIERTMDCPAUSEUHRW"; + +// Test result structure +struct TestResult { + uint8_t hour; + uint8_t minute; + char text[100]; + uint8_t ledCount; + const char* ledDirection; + uint8_t ledHex; + boolean success; +}; + +// Storage for all test results (67 minutes) +TestResult results[68]; +uint8_t resultCount = 0; + +void setup() { + Serial.begin(115200); + delay(100); + + Serial.println("\n\n=== CharGraph Time Logic - Minute Range Test ==="); + Serial.println("Time Range: 12:29 to 13:35"); + Serial.println("Pattern Length: 110 characters\n"); + + // Validate pattern length + if (strlen_P(TEST_PATTERN) != 110) { + Serial.print("ERROR: Pattern length is "); + Serial.print(strlen_P(TEST_PATTERN)); + Serial.println(" (expected 110)"); + } + + // Run all tests + runMinuteRangeTest(); + + // Print results table + printResultsTable(); + + Serial.println("\n=== Test Completed ===\n"); +} + +void loop() { + // Nothing to do after setup + delay(1000); +} + +void runMinuteRangeTest() { + Serial.println("Running tests..."); + + uint8_t startHour = 12; + uint8_t startMinute = 29; + uint8_t endHour = 13; + uint8_t endMinute = 35; + + uint8_t hour = startHour; + uint8_t minute = startMinute; + + while (resultCount < 68) { + // Test this time + TestResult result; + result.hour = hour; + result.minute = minute; + + CharGraphTimeWords timeWords; + + // Call library function + if (getCharGraphWords(TEST_PATTERN, hour, minute, timeWords)) { + result.success = true; + strcpy(result.text, timeWords.text); + result.ledCount = timeWords.ledCount; + result.ledDirection = timeWords.ledDirection; + result.ledHex = timeWords.ledHex; + } else { + result.success = false; + strcpy(result.text, "ERROR"); + result.ledCount = 0; + result.ledDirection = "left"; + result.ledHex = 0; + } + + results[resultCount++] = result; + + // Move to next minute + minute++; + if (minute > 59) { + minute = 0; + hour++; + } + + // Stop after 13:35 + if (hour == endHour && minute > endMinute) { + break; + } + } + + Serial.print("Tested "); + Serial.print(resultCount); + Serial.println(" minutes"); +} + +void printResultsTable() { + Serial.println("\n=== RESULTS TABLE ===\n"); + + // Header + Serial.println("Uhrzeit | Text | MinutenLeds | Links | Rechts"); + Serial.println("--------|--------------------------|-------------|-------|-------"); + + // Data rows + for (uint8_t i = 0; i < resultCount; i++) { + TestResult& res = results[i]; + + // Uhrzeit + if (res.hour < 10) Serial.print("0"); + Serial.print(res.hour); + Serial.print(":"); + if (res.minute < 10) Serial.print("0"); + Serial.print(res.minute); + Serial.print(" | "); + + // Text (truncate to 24 characters) + char textBuf[25]; + strncpy(textBuf, res.text, 24); + textBuf[24] = '\0'; + + // Pad to 24 characters + for (int j = strlen(textBuf); j < 24; j++) { + textBuf[j] = ' '; + } + textBuf[24] = '\0'; + + Serial.print(textBuf); + Serial.print(" | "); + + // MinutenLeds (LED visualization) + if (res.ledCount > 0) { + for (uint8_t led = 0; led < res.ledCount; led++) { + Serial.print("βœ…"); + } + // Pad with spaces if less than 4 LEDs + for (uint8_t space = res.ledCount; space < 4; space++) { + Serial.print(" "); + } + } else { + Serial.print(" "); // 9 spaces for no LEDs + } + Serial.print(" | "); + + // Links (LEFT direction) + if (res.success && strcmp_P(res.ledDirection, PSTR("left")) == 0 && res.ledCount > 0) { + Serial.print("βœ… "); + } else { + Serial.print("❌ "); + } + Serial.print(" | "); + + // Rechts (RIGHT direction) + if (res.success && strcmp_P(res.ledDirection, PSTR("right")) == 0 && res.ledCount > 0) { + Serial.print("βœ…"); + } else { + Serial.print("❌"); + } + + Serial.println(); + } +} diff --git a/lib/CharGraphTimeLogic/library.properties b/lib/CharGraphTimeLogic/library.properties new file mode 100644 index 0000000..a50a5c9 --- /dev/null +++ b/lib/CharGraphTimeLogic/library.properties @@ -0,0 +1,10 @@ +name=CharGraph Time Logic +version=1.0.0 +author=CharGraph +maintainer=CharGraph +sentence=German word clock time-to-words conversion library for ESP8266/Arduino +paragraph=Converts time (hour:minute) to German words and calculates minute LED positions. Includes pattern validation and 24-minute rules with modifiers (KURZ, BALD, FAST). Optimized for ESP8266/WEMOS D1 Mini with PROGMEM storage. +category=Timing +url=https://github.com/CharGraph/CharGraphTimeLogic +architectures=esp8266,esp32,avr +includes=CharGraphTimeLogic.h diff --git a/lib/CharGraphTimeLogic/src/CharGraphTimeLogic.cpp b/lib/CharGraphTimeLogic/src/CharGraphTimeLogic.cpp new file mode 100644 index 0000000..7f6ab8c --- /dev/null +++ b/lib/CharGraphTimeLogic/src/CharGraphTimeLogic.cpp @@ -0,0 +1,167 @@ +/** + * CharGraph Time Logic Library - Main Implementation + */ + +#include "CharGraphTimeLogic.h" +#include "WordMatcher.h" +#include "Validator.h" +#include "Constants.h" +#include + +// ============================================================================ +// PUBLIC API: GET CHARGRAPH WORDS +// ============================================================================ + +int8_t getCharGraphWords( + const char* pattern, + uint8_t hour, + uint8_t minute, + CharGraphTimeWords& outResult +) { + if (!pattern) { + outResult.wordCount = 0; + return -1; + } + + // Validate pattern length + if (strlen(pattern) != GRID_SIZE) { + outResult.wordCount = 0; + return -2; + } + + // Validate input ranges + if (hour > 23 || minute > 59) { + outResult.wordCount = 0; + return -3; + } + + // Validate pattern structure (ES/IST, HALB, UHR placement) + ValidationResult structValidation = validateStructure(pattern); + if (!structValidation.valid) { + outResult.wordCount = 0; + if (structValidation.reason) { +#ifdef CHARGRAPH_DEBUG + Serial.print("Structure validation failed: "); + Serial.println((const __FlashStringHelper*) structValidation.reason); +#endif + } + return -4; + } + + // Validate mandatory words (FÜNF, ZEHN, VIERTEL, VOR, NACH with gaps) + ValidationResult mandatoryValidation = validateMandatoryWords(pattern); + if (!mandatoryValidation.valid) { + outResult.wordCount = 0; + if (mandatoryValidation.reason) { +#ifdef CHARGRAPH_DEBUG + Serial.print("Mandatory words validation failed: "); + Serial.println((const __FlashStringHelper*) mandatoryValidation.reason); +#endif + } + return -5; + } + + // Get words for time + LEDInfo ledInfo; + const char* words[10]; + + uint8_t wordCount = getWordsForTime( + pattern, + hour, + minute, + words, + ledInfo + ); + + if (wordCount == 0) { + outResult.wordCount = 0; + return -6; + } + + // Fill result structure + for (uint8_t i = 0; i < wordCount; i++) { + outResult.words[i] = words[i]; + } + outResult.wordCount = wordCount; + outResult.ledCount = ledInfo.count; + outResult.ledDirection = ledInfo.direction; + outResult.ledHex = ledInfo.hex; + + // Build text representation + buildCharGraphText(outResult.words, outResult.wordCount, outResult.text); + + return 0; +} + +// ============================================================================ +// HELPER: BUILD TEXT FROM WORDS +// ============================================================================ + +uint16_t buildCharGraphText( + const char* const* words, + uint8_t wordCount, + char* outText +) { + if (!words || !outText || wordCount == 0) { + outText[0] = '\0'; + return 0; + } + + uint16_t pos = 0; + const uint16_t maxLen = 99; // Leave room for null terminator + + for (uint8_t i = 0; i < wordCount; i++) { + if (i > 0 && pos < maxLen) { + outText[pos++] = ' '; + } + + // Load word from PROGMEM + char wordBuf[12]; // Max 11 chars (DREIVIERTEL) + null terminator + strcpy_P(wordBuf, words[i]); + + // Copy word to output + for (uint8_t j = 0; wordBuf[j] != '\0' && pos < maxLen; j++) { + outText[pos++] = wordBuf[j]; + } + } + + outText[pos] = '\0'; + return pos; +} + +// ============================================================================ +// DEBUG FUNCTIONS +// ============================================================================ + +#ifdef CHARGRAPH_DEBUG + +void debugPrintValidationError(const char* gridStr) { + if (!gridStr) return; + + ValidationResult result = validateStructure(gridStr); + + if (!result.valid) { + Serial.print("Validation Error: "); + if (result.reason) { + Serial.println((const __FlashStringHelper*) result.reason); + } else { + Serial.println("Unknown error"); + } + } else { + Serial.println("Pattern structure is valid"); + } +} + +void debugPrintLEDInfo(const CharGraphTimeWords& result) { + Serial.print("LED Count: "); + Serial.println(result.ledCount); + + Serial.print("LED Direction: "); + Serial.println((const __FlashStringHelper*) result.ledDirection); + + Serial.print("LED Hex: 0x"); + if (result.ledHex < 0x10) Serial.print("0"); + Serial.println(result.ledHex, HEX); +} + +#endif // CHARGRAPH_DEBUG diff --git a/lib/CharGraphTimeLogic/src/CharGraphTimeLogic.h b/lib/CharGraphTimeLogic/src/CharGraphTimeLogic.h new file mode 100644 index 0000000..2585443 --- /dev/null +++ b/lib/CharGraphTimeLogic/src/CharGraphTimeLogic.h @@ -0,0 +1,114 @@ +/** + * CharGraph Time Logic Library for Arduino/ESP8266 + * + * Converts German time (hour:minute) to word sequence and calculates + * minute LED positions for word clock displays. + * + * Usage: + * #include + * + * const char pattern[] = "ESIST..."; // 110 chars + * CharGraphTimeWords result; + * + * if (getCharGraphWords(pattern, 14, 30, result)) { + * Serial.println(result.text); // "ES IST HALB DREI" + * Serial.println(result.ledHex); // 0x00 + * } + */ + +#ifndef CHARGRAPH_TIME_LOGIC_H +#define CHARGRAPH_TIME_LOGIC_H + +#include + +// ============================================================================ +// PUBLIC TYPES +// ============================================================================ + +/** + * Result structure for time-to-words conversion + */ +struct CharGraphTimeWords { + const char* words[10]; // Array of word pointers (PROGMEM) + uint8_t wordCount; // Number of words (1-10) + + // LED Information + uint8_t ledCount; // 0-4 LEDs + const char* ledDirection; // "left" or "right" (PROGMEM) + uint8_t ledHex; // 4-bit value (0x00-0x0F) + + // Text representation + char text[100]; // Full text (optional, built by helper) +}; + +// ============================================================================ +// PUBLIC API +// ============================================================================ + +/** + * Convert time to CharGraph words and LED positions + * + * @param pattern 110-character grid (uppercase, uppercase A-Z and 0-9) + * @param hour Hour (0-23) + * @param minute Minute (0-59) + * @param outResult Result structure (filled on success) + * @return true if successful, false on error (pattern validation failed) + * + * Example: + * const char pattern[] PROGMEM = "ESIST-FÜNFZEHN..."; + * CharGraphTimeWords result; + * if (getCharGraphWords(pattern, 14, 25, result)) { + * Serial.print("Words: "); + * for (int i = 0; i < result.wordCount; i++) { + * Serial.print((const __FlashStringHelper*) pgm_read_ptr(&result.words[i])); + * Serial.print(" "); + * } + * } + */ +int8_t getCharGraphWords( + const char* pattern, + uint8_t hour, + uint8_t minute, + CharGraphTimeWords& outResult +); + +/** + * Build human-readable text from word array (optional helper) + * + * @param words Array of word pointers (PROGMEM strings) + * @param wordCount Number of words + * @param outText Output buffer (at least 100 bytes) + * @return Length of generated text + * + * Example: + * char text[100]; + * buildCharGraphText(result.words, result.wordCount, text); + * Serial.println(text); // "ES IST HALB DREI" + */ +uint16_t buildCharGraphText( + const char* const* words, + uint8_t wordCount, + char* outText +); + +// ============================================================================ +// DEBUG FUNCTIONS (Optional, can be disabled) +// ============================================================================ + +#ifdef CHARGRAPH_DEBUG + +/** + * Print validation error message to Serial + * Only available if CHARGRAPH_DEBUG is defined + */ +void debugPrintValidationError(const char* gridStr); + +/** + * Print LED calculation details to Serial + * Only available if CHARGRAPH_DEBUG is defined + */ +void debugPrintLEDInfo(const CharGraphTimeWords& result); + +#endif + +#endif // CHARGRAPH_TIME_LOGIC_H diff --git a/lib/CharGraphTimeLogic/src/Constants.cpp b/lib/CharGraphTimeLogic/src/Constants.cpp new file mode 100644 index 0000000..0ed198d --- /dev/null +++ b/lib/CharGraphTimeLogic/src/Constants.cpp @@ -0,0 +1,151 @@ +/** + * CharGraph Time Logic - Constants Implementation + * + * All string constants stored in PROGMEM + */ + +#include "Constants.h" + +// ============================================================================ +// HOUR WORDS +// ============================================================================ + +const char HOUR_ZERO[] PROGMEM = "ZWoLF"; +const char HOUR_ONE[] PROGMEM = "EINS"; +const char HOUR_TWO[] PROGMEM = "ZWEI"; +const char HOUR_THREE[] PROGMEM = "DREI"; +const char HOUR_FOUR[] PROGMEM = "VIER"; +const char HOUR_FIVE[] PROGMEM = "FuNF"; +const char HOUR_SIX[] PROGMEM = "SECHS"; +const char HOUR_SEVEN[] PROGMEM = "SIEBEN"; +const char HOUR_EIGHT[] PROGMEM = "ACHT"; +const char HOUR_NINE[] PROGMEM = "NEUN"; +const char HOUR_TEN[] PROGMEM = "ZEHN"; +const char HOUR_ELEVEN[] PROGMEM = "ELF"; + +const char* const HOURS[12] PROGMEM = { + HOUR_ONE, + HOUR_TWO, + HOUR_THREE, + HOUR_FOUR, + HOUR_FIVE, + HOUR_SIX, + HOUR_SEVEN, + HOUR_EIGHT, + HOUR_NINE, + HOUR_TEN, + HOUR_ELEVEN, + HOUR_ZERO +}; + +// ============================================================================ +// MANDATORY/OPTIONAL WORDS +// ============================================================================ + +const char ES[] PROGMEM = "ES"; +const char IST[] PROGMEM = "IST"; +const char HALB[] PROGMEM = "HALB"; +const char VIERTEL[] PROGMEM = "VIERTEL"; +const char VOR[] PROGMEM = "VOR"; +const char NACH[] PROGMEM = "NACH"; +const char UHR[] PROGMEM = "UHR"; +const char EIN[] PROGMEM = "EIN"; + +// ============================================================================ +// OPTIONAL MODIFIERS +// ============================================================================ + +const char KURZ[] PROGMEM = "KURZ"; +const char BALD[] PROGMEM = "BALD"; +const char FAST[] PROGMEM = "FAST"; +const char ZWANZIG[] PROGMEM = "ZWANZIG"; +const char DREIVIERTEL[] PROGMEM = "DREIVIERTEL"; +const char NACHT[] PROGMEM = "NACHT"; +const char WIR[] PROGMEM = "WIR"; +const char HABEN[] PROGMEM = "HABEN"; + +// ============================================================================ +// MINUTE WORDS +// ============================================================================ + +const char FUENF[] PROGMEM = "FuNF"; +const char ZEHN[] PROGMEM = "ZEHN"; +const char EINS[] PROGMEM = "EINS"; + +// ============================================================================ +// LED DIRECTION STRINGS +// ============================================================================ + +const char LEFT[] PROGMEM = "left"; +const char RIGHT[] PROGMEM = "right"; + +// ============================================================================ +// ERROR MESSAGES +// ============================================================================ + +const char ERR_NO_WORDS[] PROGMEM = "No words"; +const char ERR_WORD_NOT_FOUND[] PROGMEM = "Word not found"; +const char ERR_NO_GAP[] PROGMEM = "No gap between words"; +const char ERR_NO_ES[] PROGMEM = "ES missing"; +const char ERR_NO_IST[] PROGMEM = "IST missing"; +const char ERR_NO_HALB[] PROGMEM = "HALB missing"; +const char ERR_NO_WIR[] PROGMEM = "WIR missing"; +const char ERR_NO_HABEN[] PROGMEM = "HABEN missing"; +const char ERR_NO_GAP_ES_IST[] PROGMEM = "No gap between ES and IST"; +const char ERR_NO_GAP_WIR_HABEN[] PROGMEM = "No gap between WIR and HABEN"; +const char ERR_UHR_NOT_LAST[] PROGMEM = "UHR must be last word"; + +// ============================================================================ +// MANDATORY WORDS ERROR MESSAGES +// ============================================================================ + +const char ERR_NO_FUENF[] PROGMEM = "FÜNF missing"; +const char ERR_NO_ZEHN[] PROGMEM = "ZEHN missing"; +const char ERR_NO_VIERTEL[] PROGMEM = "VIERTEL missing"; +const char ERR_NO_VOR[] PROGMEM = "VOR missing"; +const char ERR_NO_NACH[] PROGMEM = "NACH missing"; +const char ERR_NO_GAP_VIERTEL_VOR[] PROGMEM = "No gap between VIERTEL and VOR"; +const char ERR_NO_GAP_VIERTEL_NACH[] PROGMEM = "No gap between VIERTEL and NACH"; +const char ERR_NO_VIERTEL_SEQUENCE[] PROGMEM = "Cannot display :45 (VIERTEL before VOR required or DREIVIERTEL missing)"; + +// ============================================================================ +// OPTIONAL WORDS +// ============================================================================ + +const char ZEIT[] PROGMEM = "ZEIT"; +const char ALARM[] PROGMEM = "ALARM"; +const char PAUSE[] PROGMEM = "PAUSE"; +const char RWD[] PROGMEM = "RWD"; + +// ============================================================================ +// OPTIONAL WORDS WARNING MESSAGES +// ============================================================================ + +const char WARN_NO_GAP_NACHT[] PROGMEM = "INFO: No gap between IST and NACHT"; +const char WARN_NO_GAP_ZEIT[] PROGMEM = "INFO: No gap between IST and ZEIT"; +const char WARN_NO_GAP_ALARM[] PROGMEM = "INFO: No gap between IST and ALARM"; +const char WARN_NO_GAP_PAUSE[] PROGMEM = "INFO: No gap between IST and PAUSE"; +const char WARN_NO_GAP_RWD[] PROGMEM = "INFO: No gap between IST and RWD"; + +// ============================================================================ +// HELPER: Compare PROGMEM string with C-string +// ============================================================================ + +bool wordEquals(const char* word_progmem, const char* cstr) { + if (!word_progmem || !cstr) return false; + char buf[12]; // Max 11 chars (DREIVIERTEL) + null terminator + strcpy_P(buf, word_progmem); + return strcmp(buf, cstr) == 0; +} + +// ============================================================================ +// HELPER: Get hour word from PROGMEM array +// ============================================================================ + +const char* getHourWord(uint8_t h12) { + if (h12 <= 0) h12 = 12; + else if (h12 > 12) h12 -= 12; + + // Index into HOURS array (0-11 for h12 1-12) + return (const char*)pgm_read_ptr(&HOURS[h12 - 1]); +} diff --git a/lib/CharGraphTimeLogic/src/Constants.h b/lib/CharGraphTimeLogic/src/Constants.h new file mode 100644 index 0000000..60d86c4 --- /dev/null +++ b/lib/CharGraphTimeLogic/src/Constants.h @@ -0,0 +1,139 @@ +/** + * CharGraph Time Logic - Constants + * + * All arrays stored in PROGMEM for ESP8266 memory efficiency + * Port of lib/time-logic/constants.ts + */ + +#ifndef CHARGRAPH_CONSTANTS_H +#define CHARGRAPH_CONSTANTS_H + +#include + +// ============================================================================ +// GRID PARAMETERS +// ============================================================================ + +#define GRID_SIZE 110 // 10 rows Γ— 11 cols +#define GRID_ROWS 10 +#define GRID_COLS 11 + +// ============================================================================ +// HOUR WORDS (PROGMEM) +// ============================================================================ + +extern const char* const HOURS[12] PROGMEM; +extern const char HOUR_ZERO[] PROGMEM; +extern const char HOUR_ONE[] PROGMEM; +extern const char HOUR_TWO[] PROGMEM; +extern const char HOUR_THREE[] PROGMEM; +extern const char HOUR_FOUR[] PROGMEM; +extern const char HOUR_FIVE[] PROGMEM; +extern const char HOUR_SIX[] PROGMEM; +extern const char HOUR_SEVEN[] PROGMEM; +extern const char HOUR_EIGHT[] PROGMEM; +extern const char HOUR_NINE[] PROGMEM; +extern const char HOUR_TEN[] PROGMEM; +extern const char HOUR_ELEVEN[] PROGMEM; + +// ============================================================================ +// MANDATORY/OPTIONAL WORDS (PROGMEM) +// ============================================================================ + +extern const char ES[] PROGMEM; +extern const char IST[] PROGMEM; +extern const char HALB[] PROGMEM; +extern const char VIERTEL[] PROGMEM; +extern const char VOR[] PROGMEM; +extern const char NACH[] PROGMEM; +extern const char UHR[] PROGMEM; +extern const char EIN[] PROGMEM; + +// ============================================================================ +// OPTIONAL MODIFIERS (PROGMEM) +// ============================================================================ + +extern const char KURZ[] PROGMEM; +extern const char BALD[] PROGMEM; +extern const char FAST[] PROGMEM; +extern const char ZWANZIG[] PROGMEM; +extern const char DREIVIERTEL[] PROGMEM; +extern const char NACHT[] PROGMEM; +extern const char WIR[] PROGMEM; +extern const char HABEN[] PROGMEM; + +// ============================================================================ +// MINUTE WORDS (PROGMEM) +// ============================================================================ + +extern const char FUENF[] PROGMEM; +extern const char ZEHN[] PROGMEM; +extern const char EINS[] PROGMEM; + +// ============================================================================ +// LED DIRECTION STRINGS (PROGMEM) +// ============================================================================ + +extern const char LEFT[] PROGMEM; +extern const char RIGHT[] PROGMEM; + +// ============================================================================ +// LED BIT VALUES +// ============================================================================ + +#define LED1_LEFT 0x08 // Bit 3 +#define LED2_LEFT 0x04 // Bit 2 +#define LED2_RIGHT 0x02 // Bit 1 +#define LED1_RIGHT 0x01 // Bit 0 + +// ============================================================================ +// ERROR MESSAGES (PROGMEM) +// ============================================================================ + +extern const char ERR_NO_WORDS[] PROGMEM; +extern const char ERR_WORD_NOT_FOUND[] PROGMEM; +extern const char ERR_NO_GAP[] PROGMEM; +extern const char ERR_NO_ES[] PROGMEM; +extern const char ERR_NO_IST[] PROGMEM; +extern const char ERR_NO_HALB[] PROGMEM; +extern const char ERR_NO_WIR[] PROGMEM; +extern const char ERR_NO_HABEN[] PROGMEM; +extern const char ERR_NO_GAP_ES_IST[] PROGMEM; +extern const char ERR_NO_GAP_WIR_HABEN[] PROGMEM; +extern const char ERR_UHR_NOT_LAST[] PROGMEM; +extern const char ERR_NO_FUENF[] PROGMEM; +extern const char ERR_NO_ZEHN[] PROGMEM; +extern const char ERR_NO_VIERTEL[] PROGMEM; +extern const char ERR_NO_VOR[] PROGMEM; +extern const char ERR_NO_NACH[] PROGMEM; +extern const char ERR_NO_GAP_VIERTEL_VOR[] PROGMEM; +extern const char ERR_NO_GAP_VIERTEL_NACH[] PROGMEM; +extern const char ERR_NO_VIERTEL_SEQUENCE[] PROGMEM; + +// ============================================================================ +// OPTIONAL WORDS +// ============================================================================ + +extern const char ZEIT[] PROGMEM; +extern const char ALARM[] PROGMEM; +extern const char PAUSE[] PROGMEM; +extern const char RWD[] PROGMEM; + +// ============================================================================ +// OPTIONAL WORDS WARNING MESSAGES +// ============================================================================ + +extern const char WARN_NO_GAP_NACHT[] PROGMEM; +extern const char WARN_NO_GAP_ZEIT[] PROGMEM; +extern const char WARN_NO_GAP_ALARM[] PROGMEM; +extern const char WARN_NO_GAP_PAUSE[] PROGMEM; +extern const char WARN_NO_GAP_RWD[] PROGMEM; + +// ============================================================================ +// HELPER FUNCTIONS +// ============================================================================ + +extern bool wordEquals(const char* word_progmem, const char* cstr); +extern const char* getHourWord(uint8_t h12); + +#endif // CHARGRAPH_CONSTANTS_H diff --git a/lib/CharGraphTimeLogic/src/LEDCalculator.cpp b/lib/CharGraphTimeLogic/src/LEDCalculator.cpp new file mode 100644 index 0000000..77cc061 --- /dev/null +++ b/lib/CharGraphTimeLogic/src/LEDCalculator.cpp @@ -0,0 +1,370 @@ +/** + * CharGraph Time Logic - LED Calculator Implementation + */ + +#include "LEDCalculator.h" +#include "Constants.h" +#include + +// ============================================================================ +// TARGET MINUTE CALCULATION +// ============================================================================ + +uint8_t getTargetMinute( + const char* const* words, + uint8_t wordCount, + bool& isLeftDirection +) { + // Default: left (NACH = count up) + isLeftDirection = true; + + // Look for minute-indicator words (FÜNF, ZEHN, VIERTEL, ZWANZIG, DREIVIERTEL) + for (uint8_t i = 0; i < wordCount; i++) { + if (wordEquals(words[i], "FuNF")) { + // FÜNF can be NACH (5 min) or VOR (55 min) + for (uint8_t j = i + 1; j < wordCount; j++) { + if (wordEquals(words[j], "VOR")) { + isLeftDirection = false; + return 55; // FÜNF VOR = :55 + } + if (wordEquals(words[j], "NACH")) { + isLeftDirection = true; + return 5; // FÜNF NACH = :05 + } + } + // Default for FÜNF alone (rare) + return 5; + } + if (wordEquals(words[i], "ZEHN")) { + // ZEHN can be NACH (10 min) or VOR (50 min) + for (uint8_t j = i + 1; j < wordCount; j++) { + if (wordEquals(words[j], "VOR")) { + isLeftDirection = false; + return 50; // ZEHN VOR = :50 + } + if (wordEquals(words[j], "NACH")) { + isLeftDirection = true; + return 10; // ZEHN NACH = :10 + } + } + return 10; + } + if (wordEquals(words[i], "VIERTEL")) { + // VIERTEL is NACH (15 min) or VOR (45 min) + for (uint8_t j = i + 1; j < wordCount; j++) { + if (wordEquals(words[j], "VOR")) { + isLeftDirection = false; + return 45; // VIERTEL VOR = :45 + } + if (wordEquals(words[j], "NACH")) { + isLeftDirection = true; + return 15; // VIERTEL NACH = :15 + } + } + return 15; + } + if (wordEquals(words[i], "ZWANZIG")) { + // ZWANZIG is NACH (20 min) or VOR (40 min) + for (uint8_t j = i + 1; j < wordCount; j++) { + if (wordEquals(words[j], "VOR")) { + isLeftDirection = false; + return 40; // ZWANZIG VOR = :40 + } + if (wordEquals(words[j], "NACH")) { + isLeftDirection = true; + return 20; // ZWANZIG NACH = :20 + } + } + return 20; + } + if (wordEquals(words[i], "DREIVIERTEL")) { + // DREIVIERTEL is always :45 + isLeftDirection = true; + return 45; + } + } + + // Check for HALB + for (uint8_t i = 0; i < wordCount; i++) { + if (wordEquals(words[i], "HALB")) { + // HALB NACH or HALB VOR? + for (uint8_t j = i + 1; j < wordCount; j++) { + if (wordEquals(words[j], "VOR")) { + isLeftDirection = false; + return 20; // Something like "... VOR HALB" β†’ target :20 + } + if (wordEquals(words[j], "NACH")) { + isLeftDirection = true; + return 35; // Something like "... NACH HALB" β†’ target :35 + } + } + // HALB at :30 + return 30; + } + } + + return 0; +} + +// ============================================================================ +// LED COUNT TO HEX +// ============================================================================ + +uint8_t ledCountToHex(uint8_t count, bool isLeft) { + count = constrain(count, 0, 4); + + if (isLeft) { + // LEFT: additive from top (bits 3β†’0) + // 0 LEDs: 0000 = 0x00 + // 1 LED: 1000 = 0x08 + // 2 LEDs: 1100 = 0x0C + // 3 LEDs: 1110 = 0x0E + // 4 LEDs: 1111 = 0x0F + const uint8_t leftTable[5] = {0x00, 0x08, 0x0C, 0x0E, 0x0F}; + return leftTable[count]; + } else { + // RIGHT: additive from bottom (bits 0β†’3) + // 0 LEDs: 0000 = 0x00 + // 1 LED: 0001 = 0x01 + // 2 LEDs: 0011 = 0x03 + // 3 LEDs: 0111 = 0x07 + // 4 LEDs: 1111 = 0x0F + const uint8_t rightTable[5] = {0x00, 0x01, 0x03, 0x07, 0x0F}; + return rightTable[count]; + } +} + +// ============================================================================ +// LED CALCULATION (11-TIER PRIORITY) +// ============================================================================ + +LEDInfo calculateLEDs( + const char* const* words, + uint8_t wordCount, + uint8_t mm +) { + LEDInfo result = {0, LEFT, 0x00}; + + if (!words || wordCount == 0) { + return result; + } + + uint8_t remainder = 0; + bool isLeftDir = true; + + // ===== PRIORITY 0: NACHT (special - Midnight context) ===== + for (uint8_t i = 0; i < wordCount; i++) { + if (wordEquals(words[i], "NACHT")) { + // NACHT at :00-:04 - additive counting after midnight + remainder = mm; // 0, 1, 2, 3, 4 + if (remainder > 4) remainder = 0; + isLeftDir = true; + result.direction = LEFT; + result.count = remainder; + result.hex = ledCountToHex(remainder, isLeftDir); + return result; + } + } + + // ===== PRIORITY 1: DREIVIERTEL ===== + // Only at :45, shows remainder towards :50 + for (uint8_t i = 0; i < wordCount; i++) { + if (wordEquals(words[i], "DREIVIERTEL")) { + remainder = mm - 45; + if (remainder > 4) remainder = 0; // Out of valid range + isLeftDir = true; + result.direction = LEFT; + result.count = remainder; + result.hex = ledCountToHex(remainder, isLeftDir); + return result; + } + } + + // ===== PRIORITY 2-3: BALD/FAST ===== + bool hasBald = false; + bool hasFast = false; + bool hasHalb = false; + + for (uint8_t i = 0; i < wordCount; i++) { + if (wordEquals(words[i], "BALD")) hasBald = true; + if (wordEquals(words[i], "FAST")) hasFast = true; + if (wordEquals(words[i], "HALB")) hasHalb = true; + } + + if (hasFast || hasBald) { + if (hasHalb) { + // PRIORITY 2: BALD/FAST + HALB β†’ remainder = target - mm, direction = right + bool dummy; + uint8_t target = getTargetMinute(words, wordCount, dummy); + remainder = (target > mm) ? (target - mm) : 0; + if (remainder > 4) remainder = 0; + isLeftDir = false; + } else { + // PRIORITY 3: BALD/FAST (no HALB, :57-59) β†’ remainder = 60 - mm, direction = right + remainder = 60 - mm; + if (remainder > 4) remainder = 0; + isLeftDir = false; + } + result.direction = RIGHT; + result.count = remainder; + result.hex = ledCountToHex(remainder, isLeftDir); + return result; + } + + // ===== PRIORITY 4-5: KURZ VOR ===== + bool hasKurz = false; + bool hasVor = false; + bool hasZehn = false; + + for (uint8_t i = 0; i < wordCount; i++) { + if (wordEquals(words[i], "KURZ")) hasKurz = true; + if (wordEquals(words[i], "VOR")) hasVor = true; + if (wordEquals(words[i], "ZEHN")) hasZehn = true; + } + + if (hasKurz && hasVor) { + if (hasHalb) { + // PRIORITY 4: KURZ VOR HALB β†’ remainder = target - mm, direction = right + bool dummy; + uint8_t target = getTargetMinute(words, wordCount, dummy); + remainder = (target > mm) ? (target - mm) : 0; + if (remainder > 4) remainder = 0; + } else { + // PRIORITY 5: KURZ VOR (no HALB, :58) β†’ remainder = 60 - mm, direction = right + remainder = 60 - mm; + if (remainder > 4) remainder = 0; + } + isLeftDir = false; + result.direction = RIGHT; + result.count = remainder; + result.hex = ledCountToHex(remainder, isLeftDir); + return result; + } + + // ===== PRIORITY 6-7: KURZ NACH ===== + bool hasNach = false; + for (uint8_t i = 0; i < wordCount; i++) { + if (wordEquals(words[i], "NACH")) hasNach = true; + } + + if (hasKurz && hasNach) { + if (hasHalb) { + // PRIORITY 7: KURZ NACH HALB β†’ remainder = mm % 5, direction = left + remainder = mm % 5; + } else { + // PRIORITY 6: KURZ NACH (no HALB, :01-02) β†’ remainder = mm % 5, direction = left + remainder = mm % 5; + } + isLeftDir = true; + result.direction = LEFT; + result.count = remainder; + result.hex = ledCountToHex(remainder, isLeftDir); + return result; + } + + // ===== PRIORITY 8: NACH (no HALB) ===== + if (hasNach && !hasHalb) { + bool dummy; + uint8_t target = getTargetMinute(words, wordCount, dummy); + remainder = (mm > target) ? (mm - target) : 0; + if (remainder > 4) remainder = 0; + isLeftDir = true; + result.direction = LEFT; + result.count = remainder; + result.hex = ledCountToHex(remainder, isLeftDir); + return result; + } + + // ===== PRIORITY 9-10: HALB + NACH/VOR ===== + if (hasHalb && hasNach) { + // PRIORITY 9: HALB + NACH β†’ remainder = mm % 5, direction = left + remainder = mm % 5; + isLeftDir = true; + result.direction = LEFT; + result.count = remainder; + result.hex = ledCountToHex(remainder, isLeftDir); + return result; + } + + // ===== PRIORITY 10b: SPECIAL CASE :16-:19 FALLBACK ZEHN VOR HALB ===== + if (hasHalb && hasVor && hasZehn && mm >= 16 && mm <= 19) { + // Fallback scenario: :16-:19 with "ZEHN VOR HALB" + // LEDs = 20 - mm, direction = LEFT (additive towards :20) + remainder = 20 - mm; + if (remainder > 4) remainder = 0; // Safety + isLeftDir = true; + result.direction = LEFT; + result.count = remainder; + result.hex = ledCountToHex(remainder, isLeftDir); + return result; + } + + // ===== PRIORITY 10c: SPECIAL CASE VIERTEL at :15-:19 (including fallback VIERTEL [next_hour]) ===== + bool hasViertel = false; + for (uint8_t i = 0; i < wordCount; i++) { + if (wordEquals(words[i], "VIERTEL")) { + hasViertel = true; + break; + } + } + + if (hasViertel && !hasHalb && mm >= 15 && mm <= 19) { + // :15-:19 VIERTEL (either "VIERTEL NACH [h]" or fallback "VIERTEL [next_hour]") + // Count minutes after :15 + remainder = mm - 15; // 0, 1, 2, 3, 4 for :15-:19 + isLeftDir = true; + result.direction = LEFT; + result.count = remainder; + result.hex = ledCountToHex(remainder, isLeftDir); + return result; + } + + if (hasHalb && hasVor) { + // PRIORITY 10: HALB + VOR β†’ remainder = mm % 5, direction = right + remainder = mm % 5; + isLeftDir = false; + result.direction = RIGHT; + result.count = remainder; + result.hex = ledCountToHex(remainder, isLeftDir); + return result; + } + + // ===== PRIORITY 11: VOR (no HALB) ===== + if (hasVor && !hasHalb) { + bool dummy; + uint8_t target = getTargetMinute(words, wordCount, dummy); + + // Check for FÜNF word + bool hasFunf = false; + for (uint8_t i = 0; i < wordCount; i++) { + if (wordEquals(words[i], "FuNF")) hasFunf = true; + } + + // ALL VOR (without HALB): Calculate distance to target minute + // If mm < target: still X minutes away β†’ LEFT direction (additive) + // If mm >= target: already X minutes past β†’ RIGHT direction (subtractive) + if (mm < target) { + // Haven't reached target yet: minutes remaining + remainder = target - mm; + isLeftDir = true; + result.direction = LEFT; + } else { + // Already past target: minutes since target + remainder = mm - target; + isLeftDir = false; + result.direction = RIGHT; + } + + // Safety: cap at 4 LEDs + if (remainder > 4) remainder = 0; + + result.count = remainder; + result.hex = ledCountToHex(remainder, isLeftDir); + return result; + } + + // ===== DEFAULT: 0 LEDs ===== + result.count = 0; + result.hex = 0x00; + return result; +} diff --git a/lib/CharGraphTimeLogic/src/LEDCalculator.h b/lib/CharGraphTimeLogic/src/LEDCalculator.h new file mode 100644 index 0000000..a0bcd49 --- /dev/null +++ b/lib/CharGraphTimeLogic/src/LEDCalculator.h @@ -0,0 +1,86 @@ +/** + * CharGraph Time Logic - LED Calculator + * + * Port of lib/time-logic/led-calculator.ts + * Minute LED calculation with 11-tier priority system + */ + +#ifndef CHARGRAPH_LED_CALCULATOR_H +#define CHARGRAPH_LED_CALCULATOR_H + +#include "Types.h" + +/** + * Get target minute for NACH/VOR-based LED calculation + * + * Returns the minute boundary that the current time counts towards or from. + * Examples: + * - FÜNF NACH :05 β†’ target :05 + * - ZEHN NACH :10 β†’ target :10 + * - VIERTEL NACH :15 β†’ target :15 + * - ZEHN VOR :50 β†’ target :50 (for :40-:44) + * - FÜNF VOR :55 β†’ target :55 (for :50-:54) + * - VIERTEL VOR :45 β†’ target :45 (for :40-:44) + * + * @param words Array of word pointers + * @param wordCount Number of words + * @param isLeftDirection Returns true if additive (left/NACH), false if subtractive (right/VOR) + * @return Target minute value (0-59) + */ +uint8_t getTargetMinute( + const char* const* words, + uint8_t wordCount, + bool& isLeftDirection +); + +/** + * Convert LED count and direction to 4-bit hex value + * + * LEFT direction (additive): + * - 0 LEDs β†’ 0x00 + * - 1 LED β†’ 0x08 (bit 3) + * - 2 LEDs β†’ 0x0C (bits 3+2) + * - 3 LEDs β†’ 0x0E (bits 3+2+1) + * - 4 LEDs β†’ 0x0F (bits 3+2+1+0) + * + * RIGHT direction (subtractive): + * - 0 LEDs β†’ 0x00 + * - 1 LED β†’ 0x01 (bit 0) + * - 2 LEDs β†’ 0x03 (bits 0+1) + * - 3 LEDs β†’ 0x07 (bits 0+1+2) + * - 4 LEDs β†’ 0x0F (bits 0+1+2+3) + * + * @param count LED count (0-4) + * @param isLeft true for left direction, false for right + * @return 4-bit hex value (0x00-0x0F) + */ +uint8_t ledCountToHex(uint8_t count, bool isLeft); + +/** + * Calculate LED position and direction for given words and minute + * + * 11-Tier Priority System (from charMatrixV03.html): + * 1. DREIVIERTEL β†’ remainder = mm - 45, direction = left + * 2. BALD/FAST + HALB β†’ remainder = targetMinute - mm, direction = right + * 3. BALD/FAST (no HALB, :57-59) β†’ remainder = 60 - mm, direction = right + * 4. KURZ VOR + HALB β†’ remainder = targetMinute - mm, direction = right + * 5. KURZ VOR (no HALB, :58) β†’ remainder = 60 - mm, direction = right + * 6. KURZ NACH (no HALB, :01-02) β†’ remainder = mm - targetMinute, direction = left + * 7. KURZ NACH + HALB β†’ remainder = mm % 5, direction = left + * 8. NACH (no HALB) β†’ remainder = mm - targetMinute, direction = left + * 9. HALB + NACH β†’ remainder = mm % 5, direction = left + * 10. HALB + VOR β†’ remainder = mm % 5, direction = right + * 11. VOR (no HALB) β†’ remainder = |mm - targetMinute|, direction = right + * + * @param words Array of word pointers (PROGMEM strings) + * @param wordCount Number of words + * @param mm Current minute (0-59) + * @return LEDInfo with count, direction, and hex value + */ +LEDInfo calculateLEDs( + const char* const* words, + uint8_t wordCount, + uint8_t mm +); + +#endif // CHARGRAPH_LED_CALCULATOR_H diff --git a/lib/CharGraphTimeLogic/src/MinuteRules.cpp b/lib/CharGraphTimeLogic/src/MinuteRules.cpp new file mode 100644 index 0000000..c44cc01 --- /dev/null +++ b/lib/CharGraphTimeLogic/src/MinuteRules.cpp @@ -0,0 +1,495 @@ +/** + * CharGraph Time Logic - Minute Rules Implementation + * + * All 24 rules for minutes :00-:59 + */ + +#include "MinuteRules.h" +#include "Constants.h" +#include + +// ============================================================================ +// INDIVIDUAL RULE HANDLERS +// ============================================================================ + +// :00 - Full hour (with or without UHR) +static uint8_t rule_00(const RuleContext& ctx, const char** outWords) { + if (ctx.hasUhrAtEnd) { + // Check if hour word starts with "EI" (EINS in PROGMEM) + // Must use pgm_read_byte() to safely read from PROGMEM + char first = pgm_read_byte(ctx.hourWord); + char second = pgm_read_byte(ctx.hourWord + 1); + outWords[0] = (first == 'E' && second == 'I') ? EIN : ctx.hourWord; + outWords[1] = UHR; + return 2; + } + outWords[0] = ctx.hourWord; + return 1; +} + +// :01-:02 - KURZ NACH or NACH +static uint8_t rule_01_02(const RuleContext& ctx, const char** outWords) { + if (ctx.hasKurz) { + outWords[0] = KURZ; + outWords[1] = NACH; + outWords[2] = ctx.hourWord; + return 3; + } + outWords[0] = NACH; + outWords[1] = ctx.hourWord; + return 2; +} + +// :03-:04 - NACH +static uint8_t rule_03_04(const RuleContext& ctx, const char** outWords) { + outWords[0] = NACH; + outWords[1] = ctx.hourWord; + return 2; +} + +// :05-:09 - FÜNF NACH +static uint8_t rule_05_09(const RuleContext& ctx, const char** outWords) { + outWords[0] = FUENF; + outWords[1] = NACH; + outWords[2] = ctx.hourWord; + return 3; +} + +// :10-:14 - ZEHN NACH +static uint8_t rule_10_14(const RuleContext& ctx, const char** outWords) { + outWords[0] = ZEHN; + outWords[1] = NACH; + outWords[2] = ctx.hourWord; + return 3; +} + +// :15-:19 - VIERTEL NACH (with fallback support) +static uint8_t rule_15_19(const RuleContext& ctx, const char** outWords) { + if (ctx.fallbackLevel == 0) { + // Primary: VIERTEL NACH [h] + // BUT: Check if NACH comes BEFORE VIERTEL in pattern + // If so, VIERTEL NACH is not sequentially possible - fallback to VIERTEL [h+1] + int16_t nach_pos = -1; + int16_t viertel_pos = -1; + + if (ctx.gridStr) { + // Find positions in pattern + char nach_buf[5]; // NACH = 4 chars + null terminator + strcpy_P(nach_buf, NACH); + const char* nach_ptr = strstr(ctx.gridStr, nach_buf); + if (nach_ptr) nach_pos = nach_ptr - ctx.gridStr; + + char viertel_buf[8]; // VIERTEL = 7 chars + null terminator + strcpy_P(viertel_buf, VIERTEL); + const char* viertel_ptr = strstr(ctx.gridStr, viertel_buf); + if (viertel_ptr) viertel_pos = viertel_ptr - ctx.gridStr; + } + + // If NACH comes BEFORE VIERTEL, use next hour instead + if (nach_pos != -1 && viertel_pos != -1 && nach_pos < viertel_pos) { + // Fallback: VIERTEL [next_hour] + uint8_t next_h12 = (ctx.h12 % 12) + 1; + const char* next_hour_word = getHourWord(next_h12); + outWords[0] = VIERTEL; + outWords[1] = next_hour_word; + return 2; + } else { + // Normal case: VIERTEL NACH [h] + outWords[0] = VIERTEL; + outWords[1] = NACH; + outWords[2] = ctx.hourWord; + return 3; + } + } + else if (ctx.fallbackLevel == 1) { + // Secondary Fallback: VIERTEL [h+1] (when NACH not usable) + uint8_t next_h12 = (ctx.h12 % 12) + 1; + const char* next_hour_word = getHourWord(next_h12); + outWords[0] = VIERTEL; + outWords[1] = next_hour_word; + return 2; + } + + // Tertiary fallback (should not reach here) + else if (ctx.mm >= 16 && ctx.mm <= 19) { + // :16-:19 Fallback: ZEHN VOR HALB [h+1] + uint8_t next_h12 = (ctx.h12 % 12) + 1; + const char* next_hour_word = getHourWord(next_h12); + outWords[0] = ZEHN; + outWords[1] = VOR; + outWords[2] = HALB; + outWords[3] = next_hour_word; + return 4; + } + + // Default (should not be reached) + return 0; +} + +// :20-:24 - ZWANZIG NACH or ZEHN VOR HALB (with fallback support) +static uint8_t rule_20_24(const RuleContext& ctx, const char** outWords) { + if (ctx.fallbackLevel == 0 && ctx.hasZwanzig) { + // Primary: ZWANZIG NACH [h] + outWords[0] = ZWANZIG; + outWords[1] = NACH; + outWords[2] = ctx.hourWord; + return 3; + } + // Fallback: ZEHN VOR HALB [h+1] + outWords[0] = ZEHN; + outWords[1] = VOR; + outWords[2] = HALB; + outWords[3] = ctx.hourWord; + return 4; +} + +// :25-:26 - FÜNF VOR HALB +static uint8_t rule_25_26(const RuleContext& ctx, const char** outWords) { + outWords[0] = FUENF; + outWords[1] = VOR; + outWords[2] = HALB; + outWords[3] = ctx.hourWord; + return 4; +} + +// :27 - BALD HALB or FÜNF VOR HALB +static uint8_t rule_27(const RuleContext& ctx, const char** outWords) { + if (ctx.hasBald) { + outWords[0] = BALD; + outWords[1] = HALB; + outWords[2] = ctx.hourWord; + return 3; + } + outWords[0] = FUENF; + outWords[1] = VOR; + outWords[2] = HALB; + outWords[3] = ctx.hourWord; + return 4; +} + +// :28 - KURZ VOR HALB > BALD HALB +static uint8_t rule_28(const RuleContext& ctx, const char** outWords) { + if (ctx.hasKurz) { + outWords[0] = KURZ; + outWords[1] = VOR; + outWords[2] = HALB; + outWords[3] = ctx.hourWord; + return 4; + } + if (ctx.hasBald) { + outWords[0] = BALD; + outWords[1] = HALB; + outWords[2] = ctx.hourWord; + return 3; + } + outWords[0] = FUENF; + outWords[1] = VOR; + outWords[2] = HALB; + outWords[3] = ctx.hourWord; + return 4; +} + +// :29 - FAST > KURZ > BALD +static uint8_t rule_29(const RuleContext& ctx, const char** outWords) { + if (ctx.hasFast) { + outWords[0] = FAST; + outWords[1] = HALB; + outWords[2] = ctx.hourWord; + return 3; + } + if (ctx.hasKurz) { + outWords[0] = KURZ; + outWords[1] = VOR; + outWords[2] = HALB; + outWords[3] = ctx.hourWord; + return 4; + } + if (ctx.hasBald) { + outWords[0] = BALD; + outWords[1] = HALB; + outWords[2] = ctx.hourWord; + return 3; + } + outWords[0] = FUENF; + outWords[1] = VOR; + outWords[2] = HALB; + outWords[3] = ctx.hourWord; + return 4; +} + +// :30 - HALB +static uint8_t rule_30(const RuleContext& ctx, const char** outWords) { + outWords[0] = HALB; + outWords[1] = ctx.hourWord; + return 2; +} + +// :31-:32 - KURZ NACH HALB or NACH HALB +static uint8_t rule_31_32(const RuleContext& ctx, const char** outWords) { + if (ctx.hasKurz) { + outWords[0] = KURZ; + outWords[1] = NACH; + outWords[2] = HALB; + outWords[3] = ctx.hourWord; + return 4; + } + outWords[0] = NACH; + outWords[1] = HALB; + outWords[2] = ctx.hourWord; + return 3; +} + +// :33-:34 - NACH HALB +static uint8_t rule_33_34(const RuleContext& ctx, const char** outWords) { + outWords[0] = NACH; + outWords[1] = HALB; + outWords[2] = ctx.hourWord; + return 3; +} + +// :35-:39 - FÜNF NACH HALB +static uint8_t rule_35_39(const RuleContext& ctx, const char** outWords) { + outWords[0] = FUENF; + outWords[1] = NACH; + outWords[2] = HALB; + outWords[3] = ctx.hourWord; + return 4; +} + +// :40-:44 - ZEHN NACH HALB or ZWANZIG VOR (with fallback support) +static uint8_t rule_40_44(const RuleContext& ctx, const char** outWords) { + if (ctx.fallbackLevel == 0 && ctx.hasZwanzig) { + // Primary: ZWANZIG VOR [h] + outWords[0] = ZWANZIG; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; + } + // Fallback: ZEHN NACH HALB [h] + outWords[0] = ZEHN; + outWords[1] = NACH; + outWords[2] = HALB; + outWords[3] = ctx.hourWord; + return 4; +} + +// :45 - DREIVIERTEL or VIERTEL VOR (pattern validation ensures one is always possible) +static uint8_t rule_45(const RuleContext& ctx, const char** outWords) { + // DREIVIERTEL always takes priority if present, REGARDLESS of fallback level + // This is critical: DREIVIERTEL must be returned on ALL attempts + if (ctx.hasDreiviertel) { + outWords[0] = DREIVIERTEL; + outWords[1] = ctx.hourWord; + return 2; + } + + // Fallback: VIERTEL VOR [h] + // This is only reached if DREIVIERTEL is NOT in the pattern + // Pattern validation guarantees that if DREIVIERTEL is missing, + // then VIERTEL must come BEFORE VOR (enabling VIERTEL VOR sequence) + outWords[0] = VIERTEL; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; +} + +// :46-:47 - VIERTEL VOR (with fallback support) +static uint8_t rule_46_47(const RuleContext& ctx, const char** outWords) { + if (ctx.fallbackLevel == 0) { + // Primary: VIERTEL VOR [h] + outWords[0] = VIERTEL; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; + } + // Fallback: ZEHN VOR [h] + outWords[0] = ZEHN; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; +} + +// :48-:49 - ZEHN VOR (target 50, LED from left/added) +static uint8_t rule_48_49(const RuleContext& ctx, const char** outWords) { + outWords[0] = ZEHN; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; +} + +// :50-:52 - ZEHN VOR +static uint8_t rule_50_52(const RuleContext& ctx, const char** outWords) { + outWords[0] = ZEHN; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; +} + +// :53-:54 - FÜNF VOR (with LEFT direction for clarity: 5+2=7, 5+1=6) +static uint8_t rule_53_54(const RuleContext& ctx, const char** outWords) { + outWords[0] = FUENF; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; +} + +// :55-:56 - FÜNF VOR +static uint8_t rule_55_56(const RuleContext& ctx, const char** outWords) { + outWords[0] = FUENF; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; +} + +// :57 - BALD [HOUR] or FÜNF VOR +static uint8_t rule_57(const RuleContext& ctx, const char** outWords) { + if (ctx.hasBald) { + outWords[0] = BALD; + outWords[1] = ctx.hourWord; + return 2; + } + outWords[0] = FUENF; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; +} + +// :58 - KURZ VOR [HOUR] > BALD +static uint8_t rule_58(const RuleContext& ctx, const char** outWords) { + if (ctx.hasKurz) { + outWords[0] = KURZ; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; + } + if (ctx.hasBald) { + outWords[0] = BALD; + outWords[1] = ctx.hourWord; + return 2; + } + outWords[0] = FUENF; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; +} + +// :59 - FAST [HOUR] > KURZ VOR > BALD +static uint8_t rule_59(const RuleContext& ctx, const char** outWords) { + if (ctx.hasFast) { + outWords[0] = FAST; + outWords[1] = ctx.hourWord; + return 2; + } + if (ctx.hasKurz) { + outWords[0] = KURZ; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; + } + if (ctx.hasBald) { + outWords[0] = BALD; + outWords[1] = ctx.hourWord; + return 2; + } + outWords[0] = FUENF; + outWords[1] = VOR; + outWords[2] = ctx.hourWord; + return 3; +} + +// ============================================================================ +// EXECUTE MINUTE RULE +// ============================================================================ + +uint8_t executeMinuteRule(uint8_t minute, const RuleContext& ctx, const char** outWords) { + switch (minute) { + case 0: + return rule_00(ctx, outWords); + case 1: + case 2: + return rule_01_02(ctx, outWords); + case 3: + case 4: + return rule_03_04(ctx, outWords); + case 5: + case 6: + case 7: + case 8: + case 9: + return rule_05_09(ctx, outWords); + case 10: + case 11: + case 12: + case 13: + case 14: + return rule_10_14(ctx, outWords); + case 15: + case 16: + case 17: + case 18: + case 19: + return rule_15_19(ctx, outWords); + case 20: + case 21: + case 22: + case 23: + case 24: + return rule_20_24(ctx, outWords); + case 25: + case 26: + return rule_25_26(ctx, outWords); + case 27: + return rule_27(ctx, outWords); + case 28: + return rule_28(ctx, outWords); + case 29: + return rule_29(ctx, outWords); + case 30: + return rule_30(ctx, outWords); + case 31: + case 32: + return rule_31_32(ctx, outWords); + case 33: + case 34: + return rule_33_34(ctx, outWords); + case 35: + case 36: + case 37: + case 38: + case 39: + return rule_35_39(ctx, outWords); + case 40: + case 41: + case 42: + case 43: + case 44: + return rule_40_44(ctx, outWords); + case 45: + return rule_45(ctx, outWords); + case 46: + case 47: + return rule_46_47(ctx, outWords); + case 48: + case 49: + return rule_48_49(ctx, outWords); + case 50: + case 51: + case 52: + return rule_50_52(ctx, outWords); + case 53: + case 54: + return rule_53_54(ctx, outWords); + case 55: + case 56: + return rule_55_56(ctx, outWords); + case 57: + return rule_57(ctx, outWords); + case 58: + return rule_58(ctx, outWords); + case 59: + return rule_59(ctx, outWords); + default: + outWords[0] = nullptr; + return 0; + } +} diff --git a/lib/CharGraphTimeLogic/src/MinuteRules.h b/lib/CharGraphTimeLogic/src/MinuteRules.h new file mode 100644 index 0000000..d6f22de --- /dev/null +++ b/lib/CharGraphTimeLogic/src/MinuteRules.h @@ -0,0 +1,33 @@ +/** + * CharGraph Time Logic - Minute Rules + * + * Port of lib/time-logic/minute-rules.ts + * 24 rules for minutes :00-:59 with handler functions + */ + +#ifndef CHARGRAPH_MINUTE_RULES_H +#define CHARGRAPH_MINUTE_RULES_H + +#include "Types.h" + +/** + * Minute rule handler function type + * Called with RuleContext and returns array of word pointers + * + * @param ctx Rule context with modifiers and grid + * @param outWords Output array for result words (max 6 words) + * @return Number of words in output + */ +typedef uint8_t (*MinuteRuleHandler)(const RuleContext& ctx, const char** outWords); + +/** + * Find and execute the minute rule for a given minute + * + * @param minute Minute value (0-59) + * @param ctx Rule context with pattern and modifiers + * @param outWords Output array for result words + * @return Number of words returned by handler + */ +uint8_t executeMinuteRule(uint8_t minute, const RuleContext& ctx, const char** outWords); + +#endif // CHARGRAPH_MINUTE_RULES_H diff --git a/lib/CharGraphTimeLogic/src/Types.h b/lib/CharGraphTimeLogic/src/Types.h new file mode 100644 index 0000000..e62d22b --- /dev/null +++ b/lib/CharGraphTimeLogic/src/Types.h @@ -0,0 +1,67 @@ +/** + * CharGraph Time Logic - Data Types + * + * TypeScriptβ†’C++ Port of lib/time-logic/types.ts + * Optimized for ESP8266 with minimal memory footprint + */ + +#ifndef CHARGRAPH_TYPES_H +#define CHARGRAPH_TYPES_H + +#include + +// ============================================================================ +// VALIDATION RESULT +// ============================================================================ + +struct ValidationResult { + bool valid; + const char* reason; // Points to PROGMEM string +}; + +// ============================================================================ +// MINUTE RULE CONTEXT +// ============================================================================ + +struct RuleContext { + uint8_t mm; // Minute (0-59) + uint8_t h12; // Hour 12-format (0-11) + const char* hourWord; // Points to PROGMEM hour word + bool hasUhrAtEnd; // UHR present at pattern end + + // Optional modifiers + bool hasKurz; // Valid at :01-02, :28, :31-32, :58, :59 + bool hasBald; // Valid at :27-29, :57-59 + bool hasFast; // Valid at :29, :59 + bool hasZwanzig; // Alternative for :20-24, :40-44 + bool hasDreiviertel; // Alternative for :45 + bool hasNacht; // Night indicator (optional) + + const char* gridStr; // Pattern (110 chars) + + // Fallback control (NEW) + uint8_t fallbackLevel; // 0 = primary, 1+ = fallback levels +}; + +// ============================================================================ +// LED INFO +// ============================================================================ + +struct LEDInfo { + uint8_t count; // 0-4 LEDs + const char* direction; // "left" or "right" (PROGMEM) + uint8_t hex; // 4-bit value (0x00-0x0F) +}; + +// ============================================================================ +// TIME WORDS RESPONSE +// ============================================================================ + +struct TimeWordsResponse { + const char** words; // Array of word pointers (PROGMEM) + uint8_t wordCount; // Number of words (max 10) + LEDInfo ledInfo; + uint8_t ledHex; // Same as ledInfo.hex +}; + +#endif // CHARGRAPH_TYPES_H diff --git a/lib/CharGraphTimeLogic/src/Validator.cpp b/lib/CharGraphTimeLogic/src/Validator.cpp new file mode 100644 index 0000000..c5ba6f2 --- /dev/null +++ b/lib/CharGraphTimeLogic/src/Validator.cpp @@ -0,0 +1,337 @@ +/** + * CharGraph Time Logic - Validator Implementation + */ + +#include "Validator.h" +#include "Constants.h" + +// ============================================================================ +// HELPER: Check if gap exists between two positions +// ============================================================================ + +static bool hasGap(uint16_t endIdxPrev, uint16_t startIdxNext) { + const uint8_t rowPrev = endIdxPrev / GRID_COLS; + const uint8_t rowNext = startIdxNext / GRID_COLS; + // Gap exists if: different row OR gap in same row (>1 char distance) + return rowNext > rowPrev || startIdxNext > endIdxPrev + 1; +} + +// ============================================================================ +// HELPER: Find string in PROGMEM pattern +// ============================================================================ + +static int16_t findWord(const char* pattern, const char* word_progmem, uint16_t searchStart) { + if (!pattern || !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); + + // Search from searchStart + const char* result = strstr(pattern + searchStart, wordBuf); + if (result) { + return (result - pattern); + } + return -1; +} + +// ============================================================================ +// HELPER: Check if PROGMEM string contains uppercase letters +// ============================================================================ + +static bool containsUppercase(const char* pattern, uint16_t startPos) { + if (!pattern) return false; + + for (uint16_t i = startPos; pattern[i] != '\0'; i++) { + if (pattern[i] >= 'A' && pattern[i] <= 'Z') { + return true; + } + } + return false; +} + +// ============================================================================ +// WORD INTEGRITY: Check if word fits in line +// ============================================================================ + +bool wordFitsInLine(uint16_t startPos, uint8_t wordLen) { + const uint8_t startRow = startPos / GRID_COLS; + const uint8_t endRow = (startPos + wordLen - 1) / GRID_COLS; + // Word fits if start and end are in same row + return startRow == endRow; +} + +// ============================================================================ +// STRUCTURE VALIDATION (with Word Integrity Check) +// ============================================================================ + +ValidationResult validateStructure(const char* gridStr) { + if (!gridStr) { + return {false, ERR_NO_ES}; + } + + // ========== INTEGRITY CHECK: Words must not wrap over line boundaries ========== + // Find entry words + int16_t esPos = findWord(gridStr, ES, 0); + int16_t wirPos = findWord(gridStr, WIR, 0); + int16_t istPos = findWord(gridStr, IST, 0); + int16_t habenPos = findWord(gridStr, HABEN, 0); + int16_t halbPos = findWord(gridStr, HALB, 0); + int16_t uhrPos = findWord(gridStr, UHR, 0); + + // Check ES integrity + if (esPos != -1 && !wordFitsInLine(esPos, 2)) { // ES = 2 chars + return {false, ERR_NO_ES}; // Generic error - word integrity issue + } + + // Check IST integrity + if (istPos != -1 && !wordFitsInLine(istPos, 3)) { // IST = 3 chars + return {false, ERR_NO_IST}; + } + + // Check WIR integrity + if (wirPos != -1 && !wordFitsInLine(wirPos, 3)) { // WIR = 3 chars + return {false, ERR_NO_WIR}; + } + + // Check HABEN integrity + if (habenPos != -1 && !wordFitsInLine(habenPos, 5)) { // HABEN = 5 chars + return {false, ERR_NO_HABEN}; + } + + // Check HALB integrity + if (halbPos != -1 && !wordFitsInLine(halbPos, 4)) { // HALB = 4 chars + return {false, ERR_NO_HALB}; + } + + // Check UHR integrity + if (uhrPos != -1 && !wordFitsInLine(uhrPos, 3)) { // UHR = 3 chars + return {false, ERR_UHR_NOT_LAST}; + } + + // ========== INTRO WORDS VALIDATION ========== + const bool useAlternative = (esPos == -1 && wirPos != -1); + + if (useAlternative) { + // WIR HABEN variant + if (wirPos == -1) { + return {false, ERR_NO_WIR}; + } + if (habenPos == -1) { + return {false, ERR_NO_HABEN}; + } + if (!hasGap(wirPos + 2, habenPos)) { + return {false, ERR_NO_GAP_WIR_HABEN}; + } + } else { + // ES IST variant (standard) + if (esPos == -1) { + return {false, ERR_NO_ES}; + } + if (istPos == -1) { + return {false, ERR_NO_IST}; + } + if (!hasGap(esPos + 1, istPos)) { + return {false, ERR_NO_GAP_ES_IST}; + } + } + + // ========== HALB VALIDATION ========== + if (halbPos == -1) { + return {false, ERR_NO_HALB}; + } + + // ========== UHR VALIDATION ========== + if (uhrPos != -1) { + // UHR is present - check if at end + // After UHR, only placeholders allowed (no uppercase letters) + if (containsUppercase(gridStr, uhrPos + 3)) { + return {false, ERR_UHR_NOT_LAST}; + } + } + + return {true, nullptr}; +} + +// ============================================================================ +// MANDATORY WORDS VALIDATION (with Word Integrity Check) +// ============================================================================ + +ValidationResult validateMandatoryWords(const char* gridStr) { + if (!gridStr) { + return {false, ERR_NO_FUENF}; + } + + // ========== INTEGRITY CHECK: Minute words must not wrap over line boundaries ========== + + // Check FÜNF (mandatory for minute display) + int16_t fuenfPos = findWord(gridStr, FUENF, 0); + if (fuenfPos == -1) { + return {false, ERR_NO_FUENF}; + } + if (!wordFitsInLine(fuenfPos, 4)) { // FÜNF = 4 chars + return {false, ERR_NO_FUENF}; + } + + // Check ZEHN (mandatory for minute display) + int16_t zehnPos = findWord(gridStr, ZEHN, 0); + if (zehnPos == -1) { + return {false, ERR_NO_ZEHN}; + } + if (!wordFitsInLine(zehnPos, 4)) { // ZEHN = 4 chars + return {false, ERR_NO_ZEHN}; + } + + // Check VIERTEL (mandatory) + int16_t viertelPos = findWord(gridStr, VIERTEL, 0); + if (viertelPos == -1) { + return {false, ERR_NO_VIERTEL}; + } + if (!wordFitsInLine(viertelPos, 7)) { // VIERTEL = 7 chars + return {false, ERR_NO_VIERTEL}; + } + + // Check VOR (mandatory) + int16_t vorPos = findWord(gridStr, VOR, 0); + if (vorPos == -1) { + return {false, ERR_NO_VOR}; + } + if (!wordFitsInLine(vorPos, 3)) { // VOR = 3 chars + return {false, ERR_NO_VOR}; + } + + // Check NACH (mandatory) + int16_t nachPos = findWord(gridStr, NACH, 0); + if (nachPos == -1) { + return {false, ERR_NO_NACH}; + } + if (!wordFitsInLine(nachPos, 4)) { // NACH = 4 chars + return {false, ERR_NO_NACH}; + } + + // ========== GAP VALIDATION ========== + // Check if DREIVIERTEL is present (must check BEFORE VIERTEL gap validation) + int16_t dreiviertelPos = findWord(gridStr, DREIVIERTEL, 0); + bool hasDreiviertel = (dreiviertelPos != -1); + + // Check gap between VIERTEL and VOR + // BUT: Skip this check if DREIVIERTEL is present (VIERTEL is substring of DREIVIERTEL) + if (!hasDreiviertel && !hasGap(viertelPos + 6, vorPos)) { + return {false, ERR_NO_GAP_VIERTEL_VOR}; + } + + // Check gap between VIERTEL and NACH + // BUT: Skip this check if DREIVIERTEL is present (VIERTEL is substring of DREIVIERTEL) + if (!hasDreiviertel && !hasGap(viertelPos + 6, nachPos)) { + return {false, ERR_NO_GAP_VIERTEL_NACH}; + } + + // ========== CRITICAL SEQUENCE VALIDATION for :45 ========== + // :45 requires EITHER DREIVIERTEL OR the sequence VIERTEL VOR (in that order) + // If DREIVIERTEL is NOT present, VIERTEL MUST come BEFORE VOR + // Otherwise :45 cannot be displayed (e.g., "quarter to X" can't be formed) + if (!hasDreiviertel && viertelPos > vorPos) { + // VIERTEL comes AFTER VOR β†’ can't form VIERTEL VOR sequence + // And DREIVIERTEL is missing β†’ can't display :45 at all + return {false, ERR_NO_VIERTEL_SEQUENCE}; + } + + return {true, nullptr}; +} + +// ============================================================================ +// OPTIONAL WORDS VALIDATION (INFO ONLY, NO ERROR) +// ============================================================================ + +ValidationResult validateOptionalWord( + const char* gridStr, + const char* optionalWord, + int16_t istPos +) { + if (!gridStr || !optionalWord || istPos == -1) { + return {true, nullptr}; // No error, just skip + } + + // Find optional word in pattern + int16_t optWordPos = findWord(gridStr, optionalWord, 0); + if (optWordPos == -1) { + // Optional word not present - that's OK, no warning + return {true, nullptr}; + } + + // Optional word is present - check gap after IST/HABEN + // IST is 3 chars, HABEN is 5 chars + int16_t gapStart = istPos + 3; // Assuming IST (3 chars) + + // Check if there's a gap (should be different row or at least 1 char distance) + if (!hasGap(gapStart - 1, optWordPos)) { + // No gap - return warning based on which word + if (wordEquals(optionalWord, "NACHT")) { + return {true, WARN_NO_GAP_NACHT}; + } else if (wordEquals(optionalWord, "ZEIT")) { + return {true, WARN_NO_GAP_ZEIT}; + } else if (wordEquals(optionalWord, "ALARM")) { + return {true, WARN_NO_GAP_ALARM}; + } else if (wordEquals(optionalWord, "PAUSE")) { + return {true, WARN_NO_GAP_PAUSE}; + } else if (wordEquals(optionalWord, "RWD")) { + return {true, WARN_NO_GAP_RWD}; + } + } + + return {true, nullptr}; +} + +// ============================================================================ +// WORD SEQUENCE VALIDATION +// ============================================================================ + +ValidationResult validateWordSequence( + const char* const* words, + uint8_t wordCount, + const char* gridStr +) { + if (!words || wordCount == 0) { + return {false, ERR_NO_WORDS}; + } + + // Find all words sequentially + uint16_t searchStart = 0; + uint16_t positions[10]; // Max 10 words + uint16_t positionEnds[10]; + + for (uint8_t i = 0; i < wordCount; i++) { + int16_t pos = findWord(gridStr, words[i], searchStart); + + if (pos == -1) { + return {false, ERR_WORD_NOT_FOUND}; + } + + // Load word from PROGMEM to get length + char wordBuf[12]; // Max 11 chars (DREIVIERTEL) + null terminator + strcpy_P(wordBuf, words[i]); + uint8_t wordLen = strlen(wordBuf); + + positions[i] = pos; + positionEnds[i] = pos + wordLen - 1; + + // Next search starts after this word + searchStart = pos + wordLen; + } + + // Check gaps between words + for (uint8_t i = 0; i < wordCount - 1; i++) { + const uint16_t currentEnd = positionEnds[i]; + const uint16_t nextStart = positions[i + 1]; + + const uint8_t currentRow = currentEnd / GRID_COLS; + const uint8_t nextRow = nextStart / GRID_COLS; + + // If same row, must have gap (placeholder) + if (currentRow == nextRow && nextStart == currentEnd + 1) { + return {false, ERR_NO_GAP}; + } + } + + return {true, nullptr}; +} diff --git a/lib/CharGraphTimeLogic/src/Validator.h b/lib/CharGraphTimeLogic/src/Validator.h new file mode 100644 index 0000000..b97b245 --- /dev/null +++ b/lib/CharGraphTimeLogic/src/Validator.h @@ -0,0 +1,88 @@ +/** + * CharGraph Time Logic - Validator + * + * Port of lib/time-logic/validator.ts + * Pattern structure validation and word sequence validation + */ + +#ifndef CHARGRAPH_VALIDATOR_H +#define CHARGRAPH_VALIDATOR_H + +#include "Types.h" + +/** + * Check if word fits completely in one 11-character line (no wrap) + * + * @param startPos Starting position of word + * @param wordLen Length of word + * @return true if word fits in line, false if wraps over boundary + */ +bool wordFitsInLine(uint16_t startPos, uint8_t wordLen); + +/** + * Validate pattern structure (entry words + word integrity) + * + * Checks: + * - All words fit completely in one 11-character line (no wrap!) + * - ES/IST or WIR/HABEN with gap between + * - HALB is required + * - UHR is optional, but if present must be at pattern end + * + * @param gridStr 110-character pattern (uppercase) + * @return ValidationResult with valid flag and error message + */ +ValidationResult validateStructure(const char* gridStr); + +/** + * Validate mandatory words presence and gaps + * + * Checks all mandatory words that MUST be in every pattern: + * - FÜNF (mandatory for minute display) + * - ZEHN (mandatory for minute display) + * - VIERTEL (mandatory, must have gap before VOR/NACH) + * - VOR (mandatory) + * - NACH (mandatory) + * - Gaps between VIERTEL and VOR, VIERTEL and NACH + * + * @param gridStr 110-character pattern (uppercase) + * @return ValidationResult with valid flag and error message + */ +ValidationResult validateMandatoryWords(const char* gridStr); + +/** + * Validate optional words presence and gaps + * + * Info function (no error, just warning) to check optional words: + * - NACHT, ZEIT, ALARM, PAUSE, RWD + * - Must have gap between IST/HABEN and optional word + * + * @param gridStr 110-character pattern (uppercase) + * @param optionalWord PROGMEM pointer to optional word to check (e.g. NACHT) + * @param istPos Position of IST or HABEN in pattern + * @return ValidationResult with valid flag and optional warning message + */ +ValidationResult validateOptionalWord( + const char* gridStr, + const char* optionalWord, + int16_t istPos +); + +/** + * Validate word sequence in pattern + * + * Checks: + * - All words found sequentially in pattern + * - Gaps/placeholders between words in same row + * + * @param words Array of word pointers (PROGMEM strings) + * @param wordCount Number of words + * @param gridStr 110-character pattern + * @return ValidationResult with valid flag + */ +ValidationResult validateWordSequence( + const char* const* words, + uint8_t wordCount, + const char* gridStr +); + +#endif // CHARGRAPH_VALIDATOR_H diff --git a/lib/CharGraphTimeLogic/src/WordMatcher.cpp b/lib/CharGraphTimeLogic/src/WordMatcher.cpp new file mode 100644 index 0000000..d7c5979 --- /dev/null +++ b/lib/CharGraphTimeLogic/src/WordMatcher.cpp @@ -0,0 +1,213 @@ +/** + * CharGraph Time Logic - Word Matcher Implementation + */ + +#include "WordMatcher.h" +#include "Constants.h" +#include "MinuteRules.h" +#include "LEDCalculator.h" +#include "Validator.h" +#include + +// ============================================================================ +// HELPER: Find string in pattern (case-sensitive) +// ============================================================================ + +static int16_t findWord(const char* pattern, const char* word_progmem) { + if (!pattern || !word_progmem) return -1; + + char wordBuf[12]; // Max 11 chars (DREIVIERTEL) + null terminator + strcpy_P(wordBuf, word_progmem); + + const char* result = strstr(pattern, wordBuf); + if (result) { + return (result - pattern); + } + return -1; +} + +// ============================================================================ +// HELPER: Check if PROGMEM string is in pattern +// ============================================================================ + +static bool hasWord(const char* pattern, const char* word_progmem) { + return findWord(pattern, word_progmem) != -1; +} + +// ============================================================================ +// MAIN FUNCTION: GET WORDS FOR TIME +// ============================================================================ + +uint8_t getWordsForTime( + const char* pattern, + uint8_t hour, + uint8_t minute, + const char** outWords, + LEDInfo& outLedInfo +) { + if (!pattern || !outWords) { + outLedInfo = {0, LEFT, 0x00}; + return 0; + } + + // ========== STEP 1: Recognize modifiers from pattern ========== + bool hasKurz = hasWord(pattern, KURZ); + bool hasBald = hasWord(pattern, BALD); + bool hasFast = hasWord(pattern, FAST); + bool hasZwanzig = hasWord(pattern, ZWANZIG); + bool hasDreiviertel = hasWord(pattern, DREIVIERTEL); + bool hasNacht = hasWord(pattern, NACHT); + + // ========== STEP 2: Detect alternative intro (WIR/HABEN vs ES/IST) ========== + bool hasWir = hasWord(pattern, WIR); + bool hasHaben = hasWord(pattern, HABEN); + bool useAlternative = hasWir && hasHaben; + + uint8_t wordIdx = 0; + + if (useAlternative) { + outWords[wordIdx++] = WIR; + outWords[wordIdx++] = HABEN; + } else { + outWords[wordIdx++] = ES; + outWords[wordIdx++] = IST; + } + + // ========== STEP 3: Check for UHR at pattern end ========== + int16_t uhrPos = findWord(pattern, UHR); + bool hasUhrAtEnd = false; + if (uhrPos != -1) { + // Check if UHR is truly at the end (only placeholders after) + bool afterUhrEmpty = true; + for (uint16_t i = uhrPos + 3; pattern[i] != '\0'; i++) { + if (pattern[i] >= 'A' && pattern[i] <= 'Z') { + afterUhrEmpty = false; + break; + } + } + hasUhrAtEnd = afterUhrEmpty; + } + + // ========== STEP 4: Calculate display hour (advance at mm >= 20) ========== + uint8_t h12 = hour % 12; + if (minute >= 20) { + h12 = (h12 + 1) % 12; + } + + const char* hourWord = getHourWord(h12); + + // ========== STEP 4b: SPECIAL CASE - NACHT (:00-:04 at hour 0 only) ========== + // If NACHT is present and it's midnight (hour 0) and minute is 0-4, return only intro + NACHT (no minute words) + if (hasNacht && hour == 0 && minute <= 4) { + outWords[wordIdx++] = useAlternative ? WIR : ES; + outWords[wordIdx++] = useAlternative ? HABEN : IST; + outWords[wordIdx++] = NACHT; + + // Validate NACHT sequence + ValidationResult nachtValidation = validateWordSequence(outWords, 3, pattern); + if (!nachtValidation.valid) { + outLedInfo = {0, LEFT, 0x00}; + return 0; + } + + // Calculate LEDs (should be 0 for NACHT at :00-:04) + outLedInfo = calculateLEDs(outWords, 3, minute); + return 3; + } + + // ========== STEP 5: Apply minute rule handler ========== + RuleContext ctx; + ctx.mm = minute; + ctx.h12 = h12; + ctx.hourWord = hourWord; + ctx.hasUhrAtEnd = hasUhrAtEnd; + ctx.hasKurz = hasKurz; + ctx.hasBald = hasBald; + ctx.hasFast = hasFast; + ctx.hasZwanzig = hasZwanzig; + ctx.hasDreiviertel = hasDreiviertel; + ctx.hasNacht = hasNacht; + ctx.gridStr = pattern; + ctx.fallbackLevel = 0; // Initialize with 0 (primary) + + const char* minuteWords[6]; + uint8_t minuteWordCount = executeMinuteRule(minute, ctx, minuteWords); + + if (minuteWordCount == 0) { + outLedInfo = {0, LEFT, 0x00}; + return 0; + } + + // Add minute words to output + for (uint8_t i = 0; i < minuteWordCount; i++) { + outWords[wordIdx++] = minuteWords[i]; + } + + uint8_t totalWords = wordIdx; + + // ========== STEP 6: Validate word sequence ========== + ValidationResult validation = validateWordSequence(outWords, totalWords, pattern); + + // ========== STEP 7: Multi-Level Fallback if validation fails ========== + if (!validation.valid) { + bool fallbackSuccess = false; + + // Try up to 3 fallback levels + for (uint8_t fbLevel = 1; fbLevel <= 3 && !fallbackSuccess; fbLevel++) { + RuleContext ctxFallback = ctx; + + // Level 1: Remove modifiers (existing logic) + if (fbLevel == 1 && (hasKurz || hasBald || hasFast)) { + ctxFallback.hasKurz = false; + ctxFallback.hasBald = false; + ctxFallback.hasFast = false; + ctxFallback.fallbackLevel = 0; // Still use primary rule + } + // Level 2: Use rule's first fallback alternative + else if (fbLevel == 2) { + ctxFallback.fallbackLevel = 1; // Signal first fallback + } + // Level 3: Use rule's second fallback (rare) + else if (fbLevel == 3) { + ctxFallback.fallbackLevel = 2; + } + else { + continue; // Skip this level + } + + // Execute minute rule with fallback context + const char* minuteWordsFallback[6]; + uint8_t minuteWordCountFallback = executeMinuteRule(minute, ctxFallback, minuteWordsFallback); + + if (minuteWordCountFallback > 0) { + // Rebuild full word list + wordIdx = 0; + if (useAlternative) { + outWords[wordIdx++] = WIR; + outWords[wordIdx++] = HABEN; + } else { + outWords[wordIdx++] = ES; + outWords[wordIdx++] = IST; + } + + for (uint8_t i = 0; i < minuteWordCountFallback; i++) { + outWords[wordIdx++] = minuteWordsFallback[i]; + } + + totalWords = wordIdx; + + // Re-validate + ValidationResult validationFallback = validateWordSequence(outWords, totalWords, pattern); + if (validationFallback.valid) { + validation = validationFallback; + fallbackSuccess = true; + } + } + } + } + + // ========== STEP 8: Calculate LED info ========== + outLedInfo = calculateLEDs(outWords, totalWords, minute); + + return totalWords; +} diff --git a/lib/CharGraphTimeLogic/src/WordMatcher.h b/lib/CharGraphTimeLogic/src/WordMatcher.h new file mode 100644 index 0000000..36187ea --- /dev/null +++ b/lib/CharGraphTimeLogic/src/WordMatcher.h @@ -0,0 +1,40 @@ +/** + * CharGraph Time Logic - Word Matcher + * + * Port of lib/time-logic/word-matcher.ts + * Main orchestration: getWordsForTime() function + */ + +#ifndef CHARGRAPH_WORD_MATCHER_H +#define CHARGRAPH_WORD_MATCHER_H + +#include "Types.h" + +/** + * Convert time (hour:minute) to word sequence + * + * Main algorithm: + * 1. Recognize modifiers from pattern (KURZ, BALD, FAST, ZWANZIG, DREIVIERTEL, NACHT) + * 2. Detect alternative intro (WIR/HABEN vs ES/IST) + * 3. Check for UHR at pattern end + * 4. Calculate display hour (advance at mm >= 20) + * 5. Apply minute rule handler + * 6. Validate word sequence in pattern + * 7. Fallback: if validation fails, remove optional modifiers and retry + * + * @param pattern 110-character grid (uppercase) + * @param hour Hour (0-23) + * @param minute Minute (0-59) + * @param outWords Output array for words (up to 10 words) + * @param outLedInfo Output LED information + * @return Number of words, or 0 on error + */ +uint8_t getWordsForTime( + const char* pattern, + uint8_t hour, + uint8_t minute, + const char** outWords, + LEDInfo& outLedInfo +); + +#endif // CHARGRAPH_WORD_MATCHER_H diff --git a/lib/PowerOnDetector/PowerOnDetector.cpp b/lib/PowerOnDetector/PowerOnDetector.cpp new file mode 100644 index 0000000..b6c367c --- /dev/null +++ b/lib/PowerOnDetector/PowerOnDetector.cpp @@ -0,0 +1,132 @@ +#include + +// Flag setzen wΓ€hrend System lΓ€uft +void setRunningFlag() +{ + EEPROM.write(ADDR_RUNNING_FLAG, RUNNING_FLAG_MAGIC); + EEPROM.commit(); +} + +// Flag lΓΆschen bei sauberem Shutdown +void clearRunningFlag() +{ + EEPROM.write(ADDR_RUNNING_FLAG, 0x00); + EEPROM.commit(); +} + +bool detectPowerLossFromResetReason() +{ + Serial.println("\n╔════════════════════════════════════════╗"); + Serial.println( "β•‘ BOOT-GRUND ANALYSE β•‘"); + Serial.println( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + + rst_info* resetInfo = ESP.getResetInfoPtr(); + + Serial.print("Reset Reason: "); + Serial.println(resetInfo->reason); + + switch (resetInfo->reason) { + case REASON_DEFAULT_RST: + Serial.println(" β†’ Power-On Reset"); + Serial.println("⚠ STROMAUSFALL oder erste Inbetriebnahme"); + return true; + + case REASON_WDT_RST: + Serial.println(" β†’ Watchdog Reset"); + Serial.println("⚠ System abgestΓΌrzt"); + return true; + + case REASON_EXCEPTION_RST: + Serial.println(" β†’ Exception Reset"); + Serial.println("⚠ Software-Fehler"); + return true; + + case REASON_SOFT_WDT_RST: + Serial.println(" β†’ Software Watchdog"); + Serial.println("⚠ System hing"); + return true; + + case REASON_SOFT_RESTART: + Serial.println(" β†’ Software Restart"); + Serial.println("βœ“ Normaler Software-Neustart"); + return false; + + case REASON_DEEP_SLEEP_AWAKE: + Serial.println(" β†’ Deep Sleep Awake"); + Serial.println("βœ“ Aufwachen aus Deep Sleep"); + return false; + + case REASON_EXT_SYS_RST: + Serial.println(" β†’ External Reset (Button)"); + Serial.println("βœ“ Reset-Taste gedrΓΌckt"); + return false; + + default: + Serial.println(" β†’ Unbekannt"); + return false; + } + + Serial.println("════════════════════════════════════════\n"); +} + +bool detectPowerLossWithoutRTC() +{ + Serial.println("\n╔════════════════════════════════════════╗"); + Serial.println( "β•‘ STROMAUSFALL-PRÜFUNG (ohne RTC) β•‘"); + Serial.println( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + + // PrΓΌfe ob Running-Flag gesetzt war + uint8_t runningFlag = EEPROM.read(ADDR_RUNNING_FLAG); + + if (runningFlag == RUNNING_FLAG_MAGIC) { + // Flag war gesetzt β†’ System lief und wurde abrupt unterbrochen + Serial.println("\n╔═════════════════════════════════════════════╗"); + Serial.println( "β•‘ ⚠⚠⚠ STROMAUSFALL ERKANNT! β•‘"); + Serial.println( "β•‘ System wurde nicht sauber heruntergefahren β•‘"); + Serial.println( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n"); + + // LΓΆsche Flag (wird spΓ€ter wieder gesetzt wenn System lΓ€uft) + EEPROM.write(ADDR_RUNNING_FLAG, 0x00); + EEPROM.commit(); + + return true; + } else { + Serial.println("\n╔═════════════════════════════════════════════╗"); + Serial.println ("β•‘ βœ“ Normaler Start (Flag nicht gesetzt) β•‘"); + Serial.println( "β•‘ oder erste Inbetriebnahme β•‘"); + Serial.println( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n"); + return false; + } +} + +void checkPowerLoss() +{ + // Lade Boot-Counter + bootCounter = EEPROM.read(ADDR_BOOT_COUNTER) << 8; + bootCounter |= EEPROM.read(ADDR_BOOT_COUNTER + 1); + + // PrΓΌfe Clean-Shutdown-Flag + bool cleanShutdown = EEPROM.read(ADDR_CLEAN_SHUTDOWN) == 0xAA; + + bootCounter++; + + DEBUG_PRINTLN("\n╔════════════════════════════════════════╗"); + DEBUG_PRINTLN( "β•‘ BOOT-ANALYSE β•‘"); + DEBUG_PRINTLN( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + DEBUG_PRINTF("\nBoot #%d\n", bootCounter); + + if (cleanShutdown) { + DEBUG_PRINTLN("βœ“ Letzter Shutdown war sauber (Timeout)"); + EEPROM.write(ADDR_CLEAN_SHUTDOWN, 0x00); // Reset + } else { + DEBUG_PRINTLN("⚠ STROMAUSFALL ERKANNT!"); + DEBUG_PRINTLN(" (Kein Clean-Shutdown-Flag)"); + } + + // Speichere neuen Boot-Counter + EEPROM.write(ADDR_BOOT_COUNTER, (bootCounter >> 8) & 0xFF); + EEPROM.write(ADDR_BOOT_COUNTER + 1, bootCounter & 0xFF); + EEPROM.commit(); + + DEBUG_PRINTLN("════════════════════════════════════════\n"); +} diff --git a/lib/PowerOnDetector/PowerOnDetector.h b/lib/PowerOnDetector/PowerOnDetector.h new file mode 100644 index 0000000..7dab6d9 --- /dev/null +++ b/lib/PowerOnDetector/PowerOnDetector.h @@ -0,0 +1,18 @@ +#ifndef _POWERONDETECTOR_H_ + #define _POWERONDETECTOR_H_ + #include + //#include + #include + //#include + #include // FΓΌr rst_info + #include + #include + //#include + extern bool detectPowerLossWithoutRTC(); + extern void setRunningFlag(); + extern void clearRunningFlag(); + extern bool detectPowerLossFromResetReason(); + extern void powerLossLoop(); + extern void checkPowerLoss(); + extern uint16_t bootCounter; +#endif \ No newline at end of file diff --git a/lib/PowerOnDetector/library.json b/lib/PowerOnDetector/library.json new file mode 100644 index 0000000..5df4c1f --- /dev/null +++ b/lib/PowerOnDetector/library.json @@ -0,0 +1,8 @@ +{ + "name": "PowerOnDetector", + "version": "1.0.0", + "dependencies": { + "fastled/FastLED": "^3.6.0", + "adafruit/RTClib": "^2.1.1" + } +} diff --git a/lib/info/info.cpp b/lib/info/info.cpp new file mode 100644 index 0000000..52190f1 --- /dev/null +++ b/lib/info/info.cpp @@ -0,0 +1,47 @@ +#include +// ════════════════════════════════════════════════════════════════ +// Verdrahtungshinweis +// ════════════════════════════════════════════════════════════════ +void showConnect() +{ + Serial.println(""); + Serial.println(" β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”"); + Serial.println(" β”‚ 5V Netzteil β”‚"); + Serial.println(" β”‚ (3-5 Ampere) β”‚"); + Serial.println(" β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"); + Serial.println(" β”‚"); + Serial.println(" β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”"); + Serial.println(" β”‚ β”‚"); + Serial.println(" 5V GND"); + Serial.println(" β”‚ β”‚"); + Serial.println(" β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”"); + Serial.println(" β”‚ β”‚ β”‚ β”‚"); + Serial.println(" β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β” β”‚"); + Serial.println(" β”‚ β”‚ β”‚ β”‚ β”‚ β”‚"); + Serial.println(" β”Œβ”€β”€β”€β”΄β”€β”€β”΄β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”€β”"); + Serial.println(" β”‚ Wemos β”‚ β”‚ WS2812B Strip β”‚ β”‚ Optional: β”‚"); + Serial.println(" β”‚ D1 Mini β”‚ β”‚ (118 LEDs) β”‚ β”‚ 1000Β΅F Cap β”‚"); + Serial.println(" β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ ─ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"); + Serial.println(" β”‚ β”‚ β”‚ β”‚"); + Serial.println(" β”‚ D6(GPIO12 β”œβ”€β”€β”€β”€β”€ DIN β”‚"); + Serial.println(" β”‚ β”‚ β”‚ (evtl. via 470Ξ©) β”‚"); + Serial.println(" β”‚ 5V β”œβ”€β”€β”€β”¬β”€ 5V β”‚"); + Serial.println(" β”‚ β”‚ β”‚β”‚ β”‚"); + Serial.println(" β”‚ GND β”œβ”¬β”€β”€β”‚β”€ GND β”‚"); + Serial.println(" β”‚ β”‚β”‚ β”‚β”‚ β”‚"); + Serial.println(" β”‚ β”‚β”‚ β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"); + Serial.println(" β”‚ β”‚β”‚ β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”"); + Serial.println(" β”‚ β”‚β”‚ β”‚β”‚ DS1307 RTC Modul β”‚"); + Serial.println(" β”‚ β”‚β”‚ β”‚β”‚ β”‚"); + Serial.println(" β”‚ β”‚β”‚ β”‚β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€"); + Serial.println(" β”‚ β”‚β”‚ β”‚β”‚ β”‚"); + Serial.println(" β”‚ D1 (GPIO5) β”œβ”‚β”€β”€β”‚β”€ SCL β”‚"); + Serial.println(" β”‚ β”‚β”‚ β”‚β”‚ β”‚"); + Serial.println(" β”‚ D2 (GPIO4) β”œβ”‚β”€β”€β”‚β”€ SDA β”‚"); + Serial.println(" β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚ β”‚β”‚ β”‚"); + Serial.println(" β”‚ └─ VCC β”‚"); + Serial.println(" β”‚ β”‚ β”‚"); + Serial.println(" └──── GND β”‚"); + Serial.println(" β”‚ β”‚"); + Serial.println(" β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"); +} diff --git a/lib/info/info.h b/lib/info/info.h new file mode 100644 index 0000000..4ac2df2 --- /dev/null +++ b/lib/info/info.h @@ -0,0 +1,6 @@ +#ifndef _INFO_INC_ + #define _INFO_INC_ + + #include + extern void showConnect(); +#endif \ No newline at end of file diff --git a/lib/rgbPanel/rgbPanel.cpp b/lib/rgbPanel/rgbPanel.cpp new file mode 100644 index 0000000..6baf010 --- /dev/null +++ b/lib/rgbPanel/rgbPanel.cpp @@ -0,0 +1,499 @@ +#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(); + } +} diff --git a/lib/rgbPanel/rgbPanel.h b/lib/rgbPanel/rgbPanel.h new file mode 100644 index 0000000..5497554 --- /dev/null +++ b/lib/rgbPanel/rgbPanel.h @@ -0,0 +1,38 @@ +#ifndef _RGBPANEL_H_ + #define _RGBPANEL_H_ + #include + #include + #include + // ════════════════════════════════════════════════════════════════ + // HARDWARE DEFINITIONEN + // ════════════════════════════════════════════════════════════════ + #define LED_PIN D6 + #define NUM_LEDS 121 + #define COLS 11 + #define ROWS 11 + + extern CRGB normalColor; + extern CRGB specialColor; + extern CRGB leds[NUM_LEDS]; + extern uint8_t brightness; + + extern const int hourpattern[][2]; + extern int MINUTE_LEDS[4]; + extern char testPattern[]; + extern bool customCharsoap; + extern char charsoap[COLS * ROWS * 2]; + extern const char DEFAULT_CHARSOAP[]; + extern char SPECIAL_WORD[3][12]; // MAXWORDS = 3 + + extern void showLEDs(); + extern int bridgeLED(int pos); + extern void rgbTest(); + extern void checkPattern(); + extern int findWord(const char* word, int occurrence = 0, bool searchBackward = false); + extern int setWord(const char* word, CRGB color, int occurrence = 0, bool searchBackward = false); + extern void getLedsFromPosition(int startPos, int length, int* ledArray); + extern void displayTime(int hours, int minutes); + extern void showSpecialWordThenTime(int hours, int minutes); + extern void fadeOutAll(uint8_t steps, uint16_t stepDelayMs); + extern void fadeInCurrentFrame(uint8_t targetBrightness, uint8_t steps, uint16_t stepDelayMs); +#endif diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..4f668b9 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,120 @@ +[platformio] +default_envs = esp8266clone +;default_envs = d1_mini + +[common] +lib_ldf_mode = deep +; Monitor-Einstellungen +monitor_speed = 115200 +; RTS/DTR fΓΌr Auto-Reset (wichtig fΓΌr Clones!) +monitor_dtr = 1 +monitor_rts = 1 + +monitor_filters = + default + time + esp8266_exception_decoder + +[env] +framework = arduino +platform = espressif8266 + +; Bibliotheken +lib_deps = + fastled/FastLED @ ^3.6.0 + adafruit/RTClib @ ^2.1.1 ; NEU: RTC-Bibliothek + +;|-- DNSServer @ 1.1.1 +;|-- EEPROM @ 1.0 +;|-- ESP8266WebServer @ 1.0 +;|-- ESP8266WiFi @ 1.0 +;|-- Wire @ 1.0 + +; Build-Flags fΓΌr Debug +build_flags = + -I include + -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY + -DDEBUG_ESP_PORT=Serial + -DDEBUG_ESP_CORE=false + -DDEBUG_MODE=false ;true = Debug, false = Produktiv + -DNDEBUG + -DDEBUG_SOURCE=true + -DDEBUG_ESP_WIFI=0 + -DUSE_RTC=false + -DTEST_RGB_ONLY=false + -DTEST_RGB=false + -DPOWERLOSSDETECT=false + +; Monitor-Einstellungen +monitor_filters = + default + time + esp8266_exception_decoder + +extra_scripts = pre:html_to_header.py + +[env:esp8266clone] +board = esp12e ; ESP8266MOD = ESP-12E/F kompatibel +framework = ${env.framework} +platform = ${env.platform} + +; Serial-Einstellungen +monitor_speed = ${common.monitor_speed} + +upload_speed = 115200 ; Langsam fΓΌr Clone-StabilitΓ€t +upload_port = COM13 + +; Flash-Einstellungen (sicher fΓΌr alle Clones) +board_build.flash_mode = dio ; Statt qio - kompatibler! +# CPU-Frequenz auf 160MHz erhΓΆhen +board_build.f_cpu = 160000000L +; Flash-Einstellungen (4MB Flash fΓΌr Standard D1 Mini) +board_build.f_flash = 40000000L +board_build.ldscript = eagle.flash.4m2m.ld ;2 MB Sketch, 2 MB OTA kein SPIFFS + +; Build-Flags fΓΌr Debug +build_flags = + ${env.build_flags} + ;-DBRIDGE_LEDS_POS77 + +; Bibliotheken +lib_deps = ${env.lib_deps} + +; Monitor-Einstellungen +monitor_filters = ${common.monitor_filters} + +; RTS/DTR fΓΌr Auto-Reset (wichtig fΓΌr Clones!) +monitor_dtr = 1 +monitor_rts = 1 + +[env:d1_mini] +board = d1_mini +platform = ${env.platform} +framework = ${env.framework} + +; Serial-Einstellungen +monitor_speed = ${common.monitor_speed} + +; Upload-Einstellungen +;upload_speed = 921600 +upload_speed = 115200 +;upload_port = COM13 ; Automatische Erkennung oder manuell anpassen +upload_port = COM5 + +board_build.flash_mode = dio +# CPU-Frequenz auf 160MHz erhΓΆht +board_build.f_cpu = 160000000L ; Statt 80000000L +; Flash-Einstellungen (4MB Flash fΓΌr Standard D1 Mini) +board_build.f_flash = 40000000L +board_build.ldscript = eagle.flash.4m2m.ld + +; Build-Flags fΓΌr Debug +build_flags = + ${env.build_flags} + ;-DBRIDGE_LEDS_POS77 + +; Bibliotheken +lib_deps = ${env.lib_deps} + +; Monitor-Einstellungen +monitor_filters = ${common.monitor_filters} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..9e08b8b --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,2655 @@ +#include +#include + +RTC_DS1307 rtc; + +// ════════════════════════════════════════════════════════════════ +// NETZWERK +// ════════════════════════════════════════════════════════════════ +ESP8266WebServer server(80); +DNSServer dnsServer; +const byte DNS_PORT = 53; + + + +// ════════════════════════════════════════════════════════════════ +// CHARSOAP / ZEICHENSATZ +// ════════════════════════════════════════════════════════════════ +void initCharsoap() { + int writePos = 0; + int readPos = 0; + int sourceLen = strlen(DEFAULT_CHARSOAP); + + while (readPos < sourceLen && writePos < (ROWS * COLS)) { + unsigned char c = DEFAULT_CHARSOAP[readPos]; + + // UTF-8 Umlaute erkennen und zu lowercase konvertieren + if (c == 0xC3 && readPos + 1 < sourceLen) { + unsigned char next = DEFAULT_CHARSOAP[readPos + 1]; + switch(next) { + case 0x84: charsoap[writePos++] = 'a'; break; // Γ„ β†’ a + case 0x96: charsoap[writePos++] = 'o'; break; // Γ– β†’ o + case 0x9C: charsoap[writePos++] = 'u'; break; // Ü β†’ u + default: + charsoap[writePos++] = c; + if (writePos < (ROWS * COLS)) charsoap[writePos++] = next; + break; + } + readPos += 2; + } else { + charsoap[writePos++] = c; + readPos++; + } + } + + charsoap[writePos] = '\0'; + + DEBUG_PRINTLN("βœ“ Default charsoap initialisiert (Umlaute β†’ lowercase)"); + DEBUG_PRINTLN(charsoap); +} + +void loadCharsoap() { + customCharsoap = EEPROM.read(ADDR_CHARSOAP_SET) == 1; + + if (customCharsoap) { + DEBUG_PRINTLN("Lade charsoap aus EEPROM..."); + + char rawData[256]; + int rawLen = 0; + + for (int i = 0; i < 200 && rawLen < 255; i++) { + byte b = EEPROM.read(ADDR_CHARSOAP + i); + if (b == 0 || b == 0xFF) break; + rawData[rawLen++] = b; + } + rawData[rawLen] = '\0'; + + DEBUG_PRINTF("Raw Length: %d bytes\n", rawLen); + + int writePos = 0; + int readPos = 0; + + while (readPos < rawLen && writePos < (ROWS * COLS)) { + unsigned char c = rawData[readPos]; + + if (c == 0xC3 && readPos + 1 < rawLen) { + unsigned char next = rawData[readPos + 1]; + switch(next) { + case 0x84: charsoap[writePos++] = 'a'; break; + case 0x96: charsoap[writePos++] = 'o'; break; + case 0x9C: charsoap[writePos++] = 'u'; break; + case 0xA4: charsoap[writePos++] = 'a'; break; + case 0xB6: charsoap[writePos++] = 'o'; break; + case 0xBC: charsoap[writePos++] = 'u'; break; + default: + DEBUG_PRINTF("⚠ Unbekannt: 0xC3 0x%02X\n", next); + charsoap[writePos++] = c; + if (writePos < (COLS * ROWS)) + charsoap[writePos++] = next; + break; + } + readPos += 2; + } else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '-') { + charsoap[writePos++] = c; + readPos++; + } else if (c < 0x80) { + readPos++; + } else { + if ((c & 0xE0) == 0xC0) readPos += 2; + else if ((c & 0xF0) == 0xE0) readPos += 3; + else if ((c & 0xF8) == 0xF0) readPos += 4; + else readPos++; + } + } + + charsoap[writePos] = '\0'; + + DEBUG_PRINTF("Konvertierte LΓ€nge: %d Zeichen\n", writePos); + + if (writePos == (COLS * (ROWS-1))) { + DEBUG_PRINTLN("βœ“ Charsoap geladen (UTF-8 bereinigt)"); + DEBUG_PRINTLN(charsoap); + + if (rawLen != (COLS * ROWS)) { + DEBUG_PRINTLN("β†’ Speichere bereinigte Version..."); + for (int i = 0; i < (COLS * ROWS); i++) { + EEPROM.write(ADDR_CHARSOAP + i, charsoap[i]); + } + EEPROM.commit(); + } + } else { + DEBUG_PRINTF("❌ Falsche LΓ€nge: %d\n", writePos); + strcpy(charsoap, DEFAULT_CHARSOAP); + initCharsoap(); // UTF-8 zu ASCII konvertieren! + customCharsoap = false; + DEBUG_PRINTLN("β†’ Verwende Default"); + } + } else { + strcpy(charsoap, DEFAULT_CHARSOAP); + initCharsoap(); // UTF-8 zu ASCII konvertieren! + DEBUG_PRINTLN("βœ“ Standard charsoap"); + } +} + +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]; + uint16_t writePos = 0; + uint16_t readPos = 0; + + while (readPos < (uint16_t)strlen(newCharsoap) && writePos < (ROWS * COLS)) { + unsigned char c = newCharsoap[readPos]; + + if (c == 0xC3 && readPos + 1 < (uint16_t)strlen(newCharsoap)) { + unsigned char next = newCharsoap[readPos + 1]; + switch(next) { + case 0x84: cleaned[writePos++] = 'a'; break; + case 0x96: cleaned[writePos++] = 'o'; break; + case 0x9C: cleaned[writePos++] = 'u'; break; + case 0xA4: cleaned[writePos++] = 'a'; break; + case 0xB6: cleaned[writePos++] = 'o'; break; + case 0xBC: cleaned[writePos++] = 'u'; break; + default: + DEBUG_PRINTF("❌ UTF-8: 0x%02X\n", next); + return; + } + readPos += 2; + } else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '-') { + cleaned[writePos++] = c; + readPos++; + } else { + DEBUG_PRINTF("❌ Zeichen: 0x%02X\n", c); + return; + } + } + + cleaned[writePos] = '\0'; + + if (writePos != (ROWS * COLS)) return; + + for (uint8_t i = 0; i < uint8_t (ROWS * COLS); i++) { + EEPROM.write(ADDR_CHARSOAP + i, cleaned[i]); + } + EEPROM.write(ADDR_CHARSOAP_SET, 1); + EEPROM.commit(); + + strcpy(charsoap, cleaned); + customCharsoap = true; + + DEBUG_PRINTLN("βœ“ Charsoap gespeichert"); +} + +void resetCharsoap() { + strcpy(charsoap, DEFAULT_CHARSOAP); + initCharsoap(); // UTF-8 zu ASCII konvertieren! + customCharsoap = false; + EEPROM.write(ADDR_CHARSOAP_SET, 0); + EEPROM.commit(); +} + +// ════════════════════════════════════════════════════════════════ +// SPECIAL WORD FUNKTIONEN +// ════════════════════════════════════════════════════════════════ +void loadSpecialWords() { + // PrΓΌfen ob Custom Special Words gesetzt sind + if (EEPROM.read(ADDR_SPECIAL_WORD_SET) == SPECIAL_WORD_MAGIC) { + DEBUG_PRINTLN("Lade SPECIAL_WORD aus EEPROM..."); + + // 3 Special Words laden + for (int w = 0; w < MAXWORDS; w++) { + int baseAddr = ADDR_SPECIAL_WORD_1 + (w * 12); + for (int i = 0; i < 12; i++) { + char c = EEPROM.read(baseAddr + i); + SPECIAL_WORD[w][i] = c; + if (c == '\0') break; + } + SPECIAL_WORD[w][11] = '\0'; // Sicherstellen, dass terminiert + } + + DEBUG_PRINTLN("βœ“ SPECIAL_WORD aus EEPROM geladen"); + for (int w = 0; w < MAXWORDS; w++) { + if (strlen(SPECIAL_WORD[w]) > 0) { + DEBUG_PRINTF(" [%d]: %s\n", w, SPECIAL_WORD[w]); + } + } + } else { + // Erste Initialisierung: Default-Werte setzen + DEBUG_PRINTLN("Setze Standard SPECIAL_WORD..."); + const char DEFAULT_SPECIAL_WORD[MAXWORDS][12] = { + "RWD", + "", + "" + }; + + for (int w = 0; w < MAXWORDS; w++) { + strcpy((char*)SPECIAL_WORD[w], DEFAULT_SPECIAL_WORD[w]); + } + + DEBUG_PRINTLN("βœ“ Standard SPECIAL_WORD gesetzt"); + for (int w = 0; w < MAXWORDS; w++) { + if (strlen(SPECIAL_WORD[w]) > 0) { + DEBUG_PRINTF(" [%d]: %s\n", w, SPECIAL_WORD[w]); + } + } + } +} + +void saveSpecialWords(const char words[MAXWORDS][12]) { + DEBUG_PRINTLN("Speichere SPECIAL_WORD ins EEPROM..."); + + // Validierung: max 11 Zeichen pro Wort + for (int w = 0; w < MAXWORDS; w++) { + if (strlen(words[w]) > 11) { + DEBUG_PRINTF("❌ Wort %d zu lang: %d Zeichen\n", w, strlen(words[w])); + return; + } + } + + // Speichern + for (int w = 0; w < MAXWORDS; w++) { + int baseAddr = ADDR_SPECIAL_WORD_1 + (w * 12); + for (int i = 0; i < 12; i++) { + char c = (i < (int) strlen(words[w])) ? words[w][i] : '\0'; + EEPROM.write(baseAddr + i, c); + } + + // Ins globale Array kopieren + strcpy((char*)SPECIAL_WORD[w], words[w]); + } + + EEPROM.write(ADDR_SPECIAL_WORD_SET, SPECIAL_WORD_MAGIC); + EEPROM.commit(); + + DEBUG_PRINTLN("βœ“ SPECIAL_WORD gespeichert"); +} + +void resetSpecialWords() { + DEBUG_PRINTLN("Setze SPECIAL_WORD auf Standard zurΓΌck..."); + + // Standard-Werte aus rgbPanel.cpp + const char DEFAULT_SPECIAL_WORD[MAXWORDS][12] = { + "RWD", + "\0", + "\0" + }; + + // Ins globale Array kopieren + for (int w = 0; w < MAXWORDS; w++) { + strcpy((char*)SPECIAL_WORD[w], DEFAULT_SPECIAL_WORD[w]); + } + + EEPROM.write(ADDR_SPECIAL_WORD_SET, 0); + EEPROM.commit(); + + DEBUG_PRINTLN("βœ“ SPECIAL_WORD zurΓΌckgesetzt"); +} + +// ════════════════════════════════════════════════════════════════ +// MINUTE LEDS FUNKTIONEN +// ════════════════════════════════════════════════════════════════ +void loadMinuteLeds() { + // PrΓΌfen ob Custom Minute LEDs gesetzt sind + if (EEPROM.read(ADDR_MINUTE_LEDS_SET) == MINUTE_LEDS_MAGIC) { + DEBUG_PRINTLN("Lade MINUTE_LEDS aus EEPROM..."); + + // 4 LED-Positionen laden + for (int i = 0; i < 4; i++) { + MINUTE_LEDS[i] = EEPROM.read(ADDR_MINUTE_LEDS + i); + } + + DEBUG_PRINTLN("βœ“ MINUTE_LEDS aus EEPROM geladen"); + DEBUG_PRINTF(" Positionen: [%d, %d, %d, %d]\n", + MINUTE_LEDS[0], MINUTE_LEDS[1], + MINUTE_LEDS[2], MINUTE_LEDS[3]); + } else { + DEBUG_PRINTLN("βœ“ Standard MINUTE_LEDS (aus Code)"); + } +} + +void saveMinuteLeds(const uint8_t leds[4]) { + DEBUG_PRINTLN("Speichere MINUTE_LEDS ins EEPROM..."); + + // Validierung: LEDs mΓΌssen im gΓΌltigen Bereich sein (0-120) + for (int i = 0; i < 4; i++) { + if (leds[i] >= NUM_LEDS) { + DEBUG_PRINTF("❌ LED %d ungΓΌltig: %d (max %d)\n", i, leds[i], NUM_LEDS - 1); + return; + } + } + + // Speichern + for (int i = 0; i < 4; i++) { + EEPROM.write(ADDR_MINUTE_LEDS + i, leds[i]); + MINUTE_LEDS[i] = leds[i]; + } + + EEPROM.write(ADDR_MINUTE_LEDS_SET, MINUTE_LEDS_MAGIC); + EEPROM.commit(); + + DEBUG_PRINTLN("βœ“ MINUTE_LEDS gespeichert"); + DEBUG_PRINTF(" Positionen: [%d, %d, %d, %d]\n", + leds[0], leds[1], leds[2], leds[3]); +} + +void resetMinuteLeds() { + DEBUG_PRINTLN("Setze MINUTE_LEDS auf Standard zurΓΌck..."); + + // Standard-Werte aus rgbPanel.cpp + const uint8_t DEFAULT_MINUTE_LEDS[4] = {112, 114, 116, 118}; + + // Ins globale Array kopieren + for (int i = 0; i < 4; i++) { + MINUTE_LEDS[i] = DEFAULT_MINUTE_LEDS[i]; + } + + EEPROM.write(ADDR_MINUTE_LEDS_SET, 0); + EEPROM.commit(); + + DEBUG_PRINTLN("βœ“ MINUTE_LEDS zurΓΌckgesetzt"); +} + +// ════════════════════════════════════════════════════════════════ +// SPECIAL WORD INTERVAL FUNKTIONEN +// ════════════════════════════════════════════════════════════════ +void loadSpecialWordInterval() { + uint8_t stored = EEPROM.read(ADDR_SPECIAL_WORD_INTERVAL); + + // Validierung: nur erlaubte Werte + if (stored == 1 || stored == 5 || stored == 10 || + stored == 15 || stored == 30 || stored == 60) { + specialWordInterval = stored; + DEBUG_PRINTF("βœ“ Spezialwort-Intervall aus EEPROM geladen: %d Minuten\n", specialWordInterval); + } else { + specialWordInterval = 60; // Default: jede Stunde + DEBUG_PRINTLN("βœ“ Standard Spezialwort-Intervall: 60 Minuten"); + } +} + +void saveSpecialWordInterval(uint8_t interval) { + // Validierung + if (interval != 1 && interval != 5 && interval != 10 && + interval != 15 && interval != 30 && interval != 60) { + DEBUG_PRINTF("❌ UngΓΌltiges Intervall: %d\n", interval); + return; + } + + specialWordInterval = interval; + EEPROM.write(ADDR_SPECIAL_WORD_INTERVAL, interval); + EEPROM.commit(); + + DEBUG_PRINTF("βœ“ Spezialwort-Intervall gespeichert: %d Minuten\n", interval); +} + +void resetSpecialWordInterval() { + specialWordInterval = 60; + EEPROM.write(ADDR_SPECIAL_WORD_INTERVAL, 60); + EEPROM.commit(); + DEBUG_PRINTLN("βœ“ Spezialwort-Intervall zurΓΌckgesetzt auf 60 Minuten"); +} + +// Hilfsfunktion: PrΓΌft, ob Spezialwort angezeigt werden soll +bool shouldShowSpecialWord(int minutes) { + // PrΓΌfe ob aktuelle Minute ein Vielfaches des Intervalls ist + return (minutes % specialWordInterval) == 0; +} + +// ════════════════════════════════════════════════════════════════ +// EEPROM / PERSISTENZ +// ════════════════════════════════════════════════════════════════ +void loadConfig() { + isConfigured = EEPROM.read(ADDR_CONFIGURED); + + if (isConfigured == MAGIC_BYTE_INIT) { + brightness = EEPROM.read(ADDR_BRIGHTNESS); + if (brightness < 10 || brightness > 80) brightness = 80; + + normalColor.r = EEPROM.read(ADDR_COLOR_R); + normalColor.g = EEPROM.read(ADDR_COLOR_G); + normalColor.b = EEPROM.read(ADDR_COLOR_B); + + specialColor.r = EEPROM.read(ADDR_SPECIAL_R); + specialColor.g = EEPROM.read(ADDR_SPECIAL_G); + specialColor.b = EEPROM.read(ADDR_SPECIAL_B); + + unsigned long savedTime = 0; + savedTime |= ((unsigned long)EEPROM.read(ADDR_TIMESTAMP)) << 24; + savedTime |= ((unsigned long)EEPROM.read(ADDR_TIMESTAMP + 1)) << 16; + savedTime |= ((unsigned long)EEPROM.read(ADDR_TIMESTAMP + 2)) << 8; + savedTime |= EEPROM.read(ADDR_TIMESTAMP + 3); + + if (savedTime > 1735689600 && savedTime < 2147483647) { + bootTime = savedTime; + } + + DEBUG_PRINTLN("βœ“ Konfiguration geladen"); + DEBUG_PRINTF(" Helligkeit: %d\n", brightness); + DEBUG_PRINTF(" Farbe: RGB(%d,%d,%d)\n", normalColor.r, normalColor.g, normalColor.b); + } + else + { + // ════════════════════════════════════════════════════════════════ + // ERSTE INITIALISIERUNG - DEFAULT_CHARSOAP ins EEPROM schreiben + // ════════════════════════════════════════════════════════════════ + DEBUG_PRINTLN("╔════════════════════════════════════════════════════╗"); + DEBUG_PRINTLN("β•‘ ERSTE INITIALISIERUNG β•‘"); + DEBUG_PRINTLN("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + + // DEFAULT_CHARSOAP vorbereiten (UTF-8 β†’ ASCII) + strcpy(charsoap, DEFAULT_CHARSOAP); + initCharsoap(); + + DEBUG_PRINTLN("β†’ Speichere DEFAULT_CHARSOAP ins EEPROM..."); + + // Ins EEPROM schreiben + for (int i = 0; i < (COLS * ROWS); i++) { + EEPROM.write(ADDR_CHARSOAP + i, charsoap[i]); + } + EEPROM.write(ADDR_CHARSOAP_SET, 1); + + customCharsoap = true; + + DEBUG_PRINTLN("βœ“ DEFAULT_CHARSOAP ins EEPROM gespeichert"); + DEBUG_PRINTF(" LΓ€nge: %d Zeichen\n", strlen(charsoap)); + + // Weitere Initialisierungen kΓΆnnen hier folgen + // SPECIAL_WORD und MINUTE_LEDS werden separat initialisiert + + EEPROM.commit(); + } +} + +void saveConfig() { + EEPROM.write(ADDR_BRIGHTNESS, brightness); + EEPROM.write(ADDR_COLOR_R, normalColor.r); + EEPROM.write(ADDR_COLOR_G, normalColor.g); + EEPROM.write(ADDR_COLOR_B, normalColor.b); + EEPROM.write(ADDR_SPECIAL_R, specialColor.r); + EEPROM.write(ADDR_SPECIAL_G, specialColor.g); + EEPROM.write(ADDR_SPECIAL_B, specialColor.b); + EEPROM.write(ADDR_CONFIGURED, MAGIC_BYTE_INIT); + + EEPROM.write(ADDR_TIMESTAMP, (bootTime >> 24) & 0xFF); + EEPROM.write(ADDR_TIMESTAMP + 1, (bootTime >> 16) & 0xFF); + EEPROM.write(ADDR_TIMESTAMP + 2, (bootTime >> 8) & 0xFF); + EEPROM.write(ADDR_TIMESTAMP + 3, bootTime & 0xFF); + + EEPROM.commit(); +} + +void loadDriftRate() { + lastSyncTime = 0; + lastSyncTime |= ((unsigned long)EEPROM.read(ADDR_LAST_SYNC)) << 24; + lastSyncTime |= ((unsigned long)EEPROM.read(ADDR_LAST_SYNC + 1)) << 16; + lastSyncTime |= ((unsigned long)EEPROM.read(ADDR_LAST_SYNC + 2)) << 8; + lastSyncTime |= EEPROM.read(ADDR_LAST_SYNC + 3); + + byte driftBytes[4]; + for (int i = 0; i < 4; i++) { + driftBytes[i] = EEPROM.read(ADDR_DRIFT_RATE + i); + } + driftRate = *((float*)driftBytes); + + syncCount = 0; + syncCount |= EEPROM.read(ADDR_SYNC_COUNT) << 8; + syncCount |= EEPROM.read(ADDR_SYNC_COUNT + 1); + + if (lastSyncTime < 1735689600) lastSyncTime = 0; + if (isnan(driftRate) || driftRate < -10.0 || driftRate > 10.0) driftRate = 0.0; + if (syncCount < 0 || syncCount > 1000) syncCount = 0; +} + +void saveDriftRate() { + EEPROM.write(ADDR_LAST_SYNC, (lastSyncTime >> 24) & 0xFF); + EEPROM.write(ADDR_LAST_SYNC + 1, (lastSyncTime >> 16) & 0xFF); + EEPROM.write(ADDR_LAST_SYNC + 2, (lastSyncTime >> 8) & 0xFF); + EEPROM.write(ADDR_LAST_SYNC + 3, lastSyncTime & 0xFF); + + byte* driftBytes = (byte*)&driftRate; + for (int i = 0; i < 4; i++) { + EEPROM.write(ADDR_DRIFT_RATE + i, driftBytes[i]); + } + + EEPROM.write(ADDR_SYNC_COUNT, (syncCount >> 8) & 0xFF); + EEPROM.write(ADDR_SYNC_COUNT + 1, syncCount & 0xFF); + + EEPROM.commit(); +} + +// ════════════════════════════════════════════════════════════════ +// OTA VERSION MANAGEMENT +// ════════════════════════════════════════════════════════════════ +void saveOTAVersion() { + for (int i = 0; i < 32; i++) { + EEPROM.write(ADDR_OTA_VERSION + i, firmwareVersion[i]); + if (firmwareVersion[i] == '\0') break; + } + + const char* buildDate = BUILD_DATE; + const char* buildTime = BUILD_TIME; + int idx = 0; + for (uint8_t i = 0; i < strlen(buildDate) && idx < 11; i++, idx++) { + EEPROM.write(ADDR_OTA_BUILD_DATE + idx, buildDate[i]); + } + EEPROM.write(ADDR_OTA_BUILD_DATE + idx++, ' '); + for (uint8_t i = 0; i < strlen(buildTime) && idx < 20; i++, idx++) { + EEPROM.write(ADDR_OTA_BUILD_DATE + idx, buildTime[i]); + } + EEPROM.write(ADDR_OTA_BUILD_DATE + idx, '\0'); + + EEPROM.commit(); + DEBUG_PRINTLN("βœ“ OTA Version gespeichert: " + String(firmwareVersion)); +} + +void loadOTAVersion() { + for (int i = 0; i < 32; i++) { + firmwareVersion[i] = EEPROM.read(ADDR_OTA_VERSION + i); + if (firmwareVersion[i] == '\0' || firmwareVersion[i] == 0xFF) { + firmwareVersion[i] = '\0'; + break; + } + } + + if (firmwareVersion[0] == '\0' || firmwareVersion[0] == 0xFF) { + generateVersion(firmwareVersion, sizeof(firmwareVersion)); + saveOTAVersion(); + } + + DEBUG_PRINTLN("Firmware Version: " + String(firmwareVersion)); +} + +// ════════════════════════════════════════════════════════════════ +// WIFI STATION CONFIG MANAGEMENT +// ════════════════════════════════════════════════════════════════ +void loadWiFiStationConfig() { + // Station enabled flag + staEnabled = (EEPROM.read(ADDR_STA_ENABLED) == STA_ENABLED_MAGIC); + + if (!staEnabled) { + DEBUG_PRINTLN("WiFi Station nicht konfiguriert"); + return; + } + + // SSID laden + for (int i = 0; i < 32; i++) { + byte c = EEPROM.read(ADDR_STA_SSID + i); + if (c == 0 || c == 0xFF) { + staSsid[i] = '\0'; + break; + } + staSsid[i] = c; + } + staSsid[31] = '\0'; // Sicherheit + + // Password laden + for (int i = 0; i < 64; i++) { + byte c = EEPROM.read(ADDR_STA_PASSWORD + i); + if (c == 0 || c == 0xFF) { + staPassword[i] = '\0'; + break; + } + staPassword[i] = c; + } + staPassword[63] = '\0'; + + // DHCP flag + staDhcp = (EEPROM.read(ADDR_STA_DHCP) == 0x01); + + // Statische IP-Konfiguration (wenn nicht DHCP) + if (!staDhcp) { + staIP[0] = EEPROM.read(ADDR_STA_IP); + staIP[1] = EEPROM.read(ADDR_STA_IP + 1); + staIP[2] = EEPROM.read(ADDR_STA_IP + 2); + staIP[3] = EEPROM.read(ADDR_STA_IP + 3); + + staGateway[0] = EEPROM.read(ADDR_STA_GATEWAY); + staGateway[1] = EEPROM.read(ADDR_STA_GATEWAY + 1); + staGateway[2] = EEPROM.read(ADDR_STA_GATEWAY + 2); + staGateway[3] = EEPROM.read(ADDR_STA_GATEWAY + 3); + + staSubnet[0] = EEPROM.read(ADDR_STA_SUBNET); + staSubnet[1] = EEPROM.read(ADDR_STA_SUBNET + 1); + staSubnet[2] = EEPROM.read(ADDR_STA_SUBNET + 2); + staSubnet[3] = EEPROM.read(ADDR_STA_SUBNET + 3); + + staDNS[0] = EEPROM.read(ADDR_STA_DNS); + staDNS[1] = EEPROM.read(ADDR_STA_DNS + 1); + staDNS[2] = EEPROM.read(ADDR_STA_DNS + 2); + staDNS[3] = EEPROM.read(ADDR_STA_DNS + 3); + } + + DEBUG_PRINTF("βœ“ WiFi Station SSID: %s\n", staSsid); + DEBUG_PRINTF(" DHCP: %s\n", staDhcp ? "Ja" : "Nein"); + if (!staDhcp) { + DEBUG_PRINTF(" IP: %s\n", staIP.toString().c_str()); + DEBUG_PRINTF(" Gateway: %s\n", staGateway.toString().c_str()); + } +} + +void saveWiFiStationConfig() { + // Enabled flag + EEPROM.write(ADDR_STA_ENABLED, staEnabled ? STA_ENABLED_MAGIC : 0); + + // SSID speichern + for (int i = 0; i < 32; i++) { + EEPROM.write(ADDR_STA_SSID + i, staSsid[i]); + if (staSsid[i] == '\0') break; + } + + // Password speichern + for (int i = 0; i < 64; i++) { + EEPROM.write(ADDR_STA_PASSWORD + i, staPassword[i]); + if (staPassword[i] == '\0') break; + } + + // DHCP flag + EEPROM.write(ADDR_STA_DHCP, staDhcp ? 0x01 : 0x00); + + // Statische IP (immer speichern, auch wenn DHCP aktiv) + EEPROM.write(ADDR_STA_IP, staIP[0]); + EEPROM.write(ADDR_STA_IP + 1, staIP[1]); + EEPROM.write(ADDR_STA_IP + 2, staIP[2]); + EEPROM.write(ADDR_STA_IP + 3, staIP[3]); + + EEPROM.write(ADDR_STA_GATEWAY, staGateway[0]); + EEPROM.write(ADDR_STA_GATEWAY + 1, staGateway[1]); + EEPROM.write(ADDR_STA_GATEWAY + 2, staGateway[2]); + EEPROM.write(ADDR_STA_GATEWAY + 3, staGateway[3]); + + EEPROM.write(ADDR_STA_SUBNET, staSubnet[0]); + EEPROM.write(ADDR_STA_SUBNET + 1, staSubnet[1]); + EEPROM.write(ADDR_STA_SUBNET + 2, staSubnet[2]); + EEPROM.write(ADDR_STA_SUBNET + 3, staSubnet[3]); + + EEPROM.write(ADDR_STA_DNS, staDNS[0]); + EEPROM.write(ADDR_STA_DNS + 1, staDNS[1]); + EEPROM.write(ADDR_STA_DNS + 2, staDNS[2]); + EEPROM.write(ADDR_STA_DNS + 3, staDNS[3]); + + EEPROM.commit(); + DEBUG_PRINTLN("βœ“ WiFi Station Config gespeichert"); +} + +// ════════════════════════════════════════════════════════════════ +// NTP CONFIG MANAGEMENT +// ════════════════════════════════════════════════════════════════ +void loadNTPConfig() { + ntpEnabled = (EEPROM.read(ADDR_NTP_ENABLED) == NTP_ENABLED_MAGIC); + + if (!ntpEnabled) { + DEBUG_PRINTLN("NTP nicht aktiviert"); + return; + } + + // NTP Server laden + for (int i = 0; i < 32; i++) { + byte c = EEPROM.read(ADDR_NTP_SERVER + i); + if (c == 0 || c == 0xFF) { + ntpServer[i] = '\0'; + break; + } + ntpServer[i] = c; + } + ntpServer[31] = '\0'; + + // Letzter Sync-Timestamp + lastNtpSync = 0; + lastNtpSync |= ((unsigned long)EEPROM.read(ADDR_NTP_LAST_SYNC)) << 24; + lastNtpSync |= ((unsigned long)EEPROM.read(ADDR_NTP_LAST_SYNC + 1)) << 16; + lastNtpSync |= ((unsigned long)EEPROM.read(ADDR_NTP_LAST_SYNC + 2)) << 8; + lastNtpSync |= EEPROM.read(ADDR_NTP_LAST_SYNC + 3); + + DEBUG_PRINTF("βœ“ NTP Server: %s\n", ntpServer); + DEBUG_PRINTF(" Letzter Sync: %lu\n", lastNtpSync); +} + +void saveNTPConfig() { + EEPROM.write(ADDR_NTP_ENABLED, ntpEnabled ? NTP_ENABLED_MAGIC : 0); + + // NTP Server speichern + for (int i = 0; i < 32; i++) { + EEPROM.write(ADDR_NTP_SERVER + i, ntpServer[i]); + if (ntpServer[i] == '\0') break; + } + + // Letzter Sync + EEPROM.write(ADDR_NTP_LAST_SYNC, (lastNtpSync >> 24) & 0xFF); + EEPROM.write(ADDR_NTP_LAST_SYNC + 1, (lastNtpSync >> 16) & 0xFF); + EEPROM.write(ADDR_NTP_LAST_SYNC + 2, (lastNtpSync >> 8) & 0xFF); + EEPROM.write(ADDR_NTP_LAST_SYNC + 3, lastNtpSync & 0xFF); + + EEPROM.commit(); + DEBUG_PRINTLN("βœ“ NTP Config gespeichert"); +} + +// ════════════════════════════════════════════════════════════════ +// AUTO-BRIGHTNESS CONFIG MANAGEMENT +// ════════════════════════════════════════════════════════════════ +void loadAutoBrightnessConfig() { + autoBrightnessEnabled = (EEPROM.read(ADDR_AUTO_BRIGHTNESS_ENABLED) == AUTO_BRIGHTNESS_MAGIC); + + if (!autoBrightnessEnabled) { + DEBUG_PRINTLN("Auto-Brightness nicht aktiviert"); + return; + } + + // Min ADC-Wert laden (2 bytes) + autoBrightnessMinADC = (EEPROM.read(ADDR_AUTO_BRIGHTNESS_MIN_ADC) << 8) | + EEPROM.read(ADDR_AUTO_BRIGHTNESS_MIN_ADC + 1); + + // Max ADC-Wert laden (2 bytes) + autoBrightnessMaxADC = (EEPROM.read(ADDR_AUTO_BRIGHTNESS_MAX_ADC) << 8) | + EEPROM.read(ADDR_AUTO_BRIGHTNESS_MAX_ADC + 1); + + // Min/Max Helligkeit laden (1 byte each) + autoBrightnessMin = EEPROM.read(ADDR_AUTO_BRIGHTNESS_MIN); + autoBrightnessMax = EEPROM.read(ADDR_AUTO_BRIGHTNESS_MAX); + + // Validierung + if (autoBrightnessMin > 80) autoBrightnessMin = 0; + if (autoBrightnessMax > 80 || autoBrightnessMax == 0) autoBrightnessMax = 80; + if (autoBrightnessMin > autoBrightnessMax) autoBrightnessMin = 0; + + DEBUG_PRINTF("βœ“ Auto-Brightness: ADC=%d-%d, Brightness=%d-%d\n", + autoBrightnessMinADC, autoBrightnessMaxADC, + autoBrightnessMin, autoBrightnessMax); +} + +void saveAutoBrightnessConfig() { + EEPROM.write(ADDR_AUTO_BRIGHTNESS_ENABLED, autoBrightnessEnabled ? AUTO_BRIGHTNESS_MAGIC : 0); + + // Min ADC-Wert speichern (2 bytes) + EEPROM.write(ADDR_AUTO_BRIGHTNESS_MIN_ADC, (autoBrightnessMinADC >> 8) & 0xFF); + EEPROM.write(ADDR_AUTO_BRIGHTNESS_MIN_ADC + 1, autoBrightnessMinADC & 0xFF); + + // Max ADC-Wert speichern (2 bytes) + EEPROM.write(ADDR_AUTO_BRIGHTNESS_MAX_ADC, (autoBrightnessMaxADC >> 8) & 0xFF); + EEPROM.write(ADDR_AUTO_BRIGHTNESS_MAX_ADC + 1, autoBrightnessMaxADC & 0xFF); + + // Min/Max Helligkeit speichern (1 byte each) + EEPROM.write(ADDR_AUTO_BRIGHTNESS_MIN, autoBrightnessMin); + EEPROM.write(ADDR_AUTO_BRIGHTNESS_MAX, autoBrightnessMax); + + EEPROM.commit(); + DEBUG_PRINTF("βœ“ Auto-Brightness: ADC=%d-%d, Brightness=%d-%d gespeichert\n", + autoBrightnessMinADC, autoBrightnessMaxADC, + autoBrightnessMin, autoBrightnessMax); +} + +int setHour(int hour, CRGB color) { + // StundenwΓΆrter: Bei RΓΌckwΓ€rtssuche immer occurrence=0 (letztes Vorkommen im String) + // Dies ermΓΆglicht "X VOR X" Anzeigen (z.B. FÜNF VOR FÜNF, ZEHN VOR ZEHN) + switch(hour) { + case 1: return setWord("EINS" , color, 0, true); + case 2: return setWord("ZWEI" , color, 0, true); + case 3: return setWord("DREI" , color, 0, true); + case 4: return setWord("VIER" , color, 0, true); + case 5: return setWord("FuNF" , color, 0, true); // Nutzt "FuNF" in "ZWoLFuNF" fΓΌr Stunde + case 6: return setWord("SECHS" , color, 0, true); + case 7: return setWord("SIEBEN", color, 0, true); + case 8: return setWord("ACHT" , color, 0, true); + case 9: return setWord("NEUN" , color, 0, true); + case 10: return setWord("ZEHN" , color, 0, true); + case 11: return setWord("ELF" , color, 0, true); + case 12: return setWord("ZWoLF" , color, 0, true); + case 13: return setWord("EIN" , color, 0, true); + } +} + +String getHourName(int hour) +{ + const char* hourNames[] = { + "ZWΓ–LF", "EINS", "ZWEI", "DREI", "VIER", "FÜNF", + "SECHS", "SIEBEN", "ACHT", "NEUN", "ZEHN", "ELF", "ZWΓ–LF", "EIN" + }; + + if (hour == 0) hour = 12; + return String(hourNames[hour]); +} + +void displayTime(int hours, int minutes) +{ + DEBUG_PRINT("Zeit: '"); + if (hours < 10) DEBUG_PRINT("0"); + DEBUG_PRINT(hours); + DEBUG_PRINT(":"); + if (minutes < 10) DEBUG_PRINT("0"); + DEBUG_PRINT(minutes); + DEBUG_PRINT("' -> "); + + fadeOutAll(200, 15); + //FastLED.setBrightness(80); + //FastLED.clear(); + //showLEDs(); + //yield(); + + CharGraphTimeWords result; + int8_t resultval = getCharGraphWords(DEFAULT_CHARSOAP, hours, minutes, result); + + if (resultval == 0) + { + uint8_t hasUhr = 0; + char wordBuf[16]; // Buffer fΓΌr PROGMEM-String + + // Letztes Wort aus PROGMEM lesen + strcpy_P(wordBuf, (PGM_P)result.words[result.wordCount - 1]); + + if (wordBuf[0] == 'U') { // PrΓΌfe auf "UHR" + hasUhr = 1; + } + DEBUG_PRINT(result.text); // text[] liegt im RAM + + // WΓΆrter durchgehen + for (uint8_t i = 0; i < result.wordCount; i++) { + // Wort aus PROGMEM in Buffer kopieren + strcpy_P(wordBuf, (PGM_P)result.words[i]); + + // Hervorhebung fΓΌr Stundenwort (letztes vor UHR) + if (i == (result.wordCount - 1 - hasUhr)) + { + setWord(wordBuf, normalColor, 0, true); + } + else + { + setWord(wordBuf, normalColor, 0); + } + } + + //Minuten + for (int i = 0; i < 4; i++) + { + if(result.ledHex & (1 << i)) + { + leds[bridgeLED(MINUTE_LEDS[3-i])] = normalColor; + } + else + { + leds[bridgeLED(MINUTE_LEDS[3-i])] = CRGB::Black; + } + + if(result.ledHex & (1 << (3-i))) + { + + DEBUG_PRINT(" ●"); + } + else + { + DEBUG_PRINT(" β—‹"); + } + } + DEBUG_PRINT("\n"); + } + //yield(); + //noInterrupts(); + //showLEDs(); // finaler Frame + //interrupts(); + //yield(); + fadeInCurrentFrame(80,200,15); +} + +// ════════════════════════════════════════════════════════════════ +// ZEITZONE & OTA FUNKTIONEN +// ════════════════════════════════════════════════════════════════ +bool isDST(time_t utcTime) { + struct tm* timeinfo = gmtime(&utcTime); + int year = timeinfo->tm_year + 1900; + int month = timeinfo->tm_mon + 1; // 1-12 + int day = timeinfo->tm_mday; + int hour = timeinfo->tm_hour; + + // Berechne letzten Sonntag im MΓ€rz (effizient) + // Wochentag des 31. MΓ€rz berechnen, dann rΓΌckwΓ€rts zum Sonntag + struct tm march31 = {0}; + march31.tm_year = year - 1900; + march31.tm_mon = 2; // MΓ€rz = 2 + march31.tm_mday = 31; + mktime(&march31); + int marchLastSunday = 31 - march31.tm_wday; // Sonntag ist 0 + + // Berechne letzten Sonntag im Oktober (effizient) + struct tm oct31 = {0}; + oct31.tm_year = year - 1900; + oct31.tm_mon = 9; // Oktober = 9 + oct31.tm_mday = 31; + mktime(&oct31); + int octoberLastSunday = 31 - oct31.tm_wday; + + // PrΓΌfe ob Sommerzeit aktiv + if (month < 3 || month > 10) { + return false; // Januar, Februar, November, Dezember: Winterzeit + } + if (month > 3 && month < 10) { + return true; // April - September: Sommerzeit + } + + // MΓ€rz: Sommerzeit ab letztem Sonntag 01:00 UTC + if (month == 3) { + if (day < marchLastSunday) return false; + if (day > marchLastSunday) return true; + if (hour < 1) return false; // Vor 01:00 UTC + return true; // Ab 01:00 UTC + } + + // Oktober: Winterzeit ab letztem Sonntag 01:00 UTC + if (month == 10) { + if (day < octoberLastSunday) return true; + if (day > octoberLastSunday) return false; + if (hour < 1) return true; // Vor 01:00 UTC + return false; // Ab 01:00 UTC + } + + return false; +} + +void showOTAProgress(int progress, bool isError = false, bool isSuccess = false) { + int ledCount = map(progress, 0, 100, 0, 110); + + FastLED.clear(); + + CRGB color; + if (isSuccess) { + color = CRGB::Green; + ledCount = 110; + } else if (isError) { + color = CRGB::Red; + ledCount = 110; + } else { + color = CRGB::Blue; + } + + for (int i = 0; i < ledCount && i < NUM_LEDS; i++) { + int row = i / COLS; + int col = i % COLS; + int ledIndex; + + if (row % 2 == 0) { + ledIndex = row * COLS + col; + } else { + ledIndex = row * COLS + (COLS - 1 - col); + } + + leds[bridgeLED(ledIndex)] = color; + } + + int minuteLEDsToShow = progress / 25; + for (int i = 0; i < 4 && i < minuteLEDsToShow; i++) { + leds[bridgeLED(MINUTE_LEDS[i])] = color; + } + + FastLED.setBrightness(80); + showLEDs(); + yield(); +} + +void otaProgressCallback(size_t current, size_t total) { + if (total > 0) { + int progress = (current * 100) / total; + otaProgress = progress; + + static int lastProgress = -1; + if (progress != lastProgress) { + showOTAProgress(progress); + lastProgress = progress; + + DEBUG_PRINTF("OTA Progress: %d%% (%u/%u bytes)\n", + progress, current, total); + } + } + yield(); +} + +// ════════════════════════════════════════════════════════════════ +// FUNKTIONEN: ZEIT +// ════════════════════════════════════════════════════════════════ +void getCurrentTime(int &hours, int &minutes, int &seconds) { + static unsigned long lastMillis = 0; + static unsigned long millisOverflows = 0; + static unsigned long lastRTCSync = 0; + + unsigned long currentMillis = millis(); + + if (currentMillis < lastMillis) { + millisOverflows++; + DEBUG_PRINTLN("⚠ millis() Overflow!"); + } + lastMillis = currentMillis; + + unsigned long totalSeconds = (millisOverflows * 4294967UL) + (currentMillis / 1000); + unsigned long currentSeconds = bootTime + totalSeconds; + + if (currentMillis - lastRTCSync > 86400000 || lastRTCSync > currentMillis) { + if (rtc.begin() && rtc.isrunning()) { + DateTime rtcNow = rtc.now(); + unsigned long rtcTime = rtcNow.unixtime(); + long drift = currentSeconds - rtcTime; + + if (abs(drift) > 30) { + bootTime = rtcTime - totalSeconds; + currentSeconds = rtcTime; + DEBUG_PRINTF("RTC-Sync: %ld Sek\n", drift); + } + } + lastRTCSync = currentMillis; + } + + if (driftRate != 0.0 && lastSyncTime > 0) { + float daysSinceSync = (currentSeconds - lastSyncTime) / 86400.0; + long estimatedDrift = (long)(driftRate * daysSinceSync); + currentSeconds -= estimatedDrift; + } + + // ════════════════════════════════════════════════════════════════ + // ZEITZONE: UTC β†’ MEZ/MESZ (Deutschland) + // ════════════════════════════════════════════════════════════════ + // currentSeconds ist aktuell in UTC + // PrΓΌfe ob Sommerzeit (MESZ) oder Winterzeit (MEZ) + time_t utcTime = currentSeconds; + + //if(ntpEnabled == true) + { + if (isDST(utcTime)) { + // MESZ = UTC+2 + currentSeconds += 7200; // +2 Stunden + } else { + // MEZ = UTC+1 + currentSeconds += 3600; // +1 Stunde + } + } + seconds = currentSeconds % 60; + minutes = (currentSeconds / 60) % 60; + hours = (currentSeconds / 3600) % 24; +} + +// ════════════════════════════════════════════════════════════════ +// WEBSERVER HANDLER +// ════════════════════════════════════════════════════════════════ +void handleRoot() { + DEBUG_PRINTLN("β†’ handleRoot aufgerufen"); + + // GZIP-komprimierte HTML-Seite senden + server.sendHeader("Content-Encoding", "gzip"); + server.send_P(200, "text/html", (const char*)HTML_PAGE_GZIP, HTML_PAGE_GZIP_LEN); + + DEBUG_PRINTF(" HTML gesendet: %d Bytes (komprimiert)\n", HTML_PAGE_GZIP_LEN); +} + +void handleGetTime() { + DEBUG_PRINTLN("β†’ handleGetTime aufgerufen"); + int h, m, s; + getCurrentTime(h, m, s); + + // UTC-Zeit berechnen + unsigned long currentSecondsUTC = bootTime + (millis() / 1000); + + // Zeitzonenkorrektur: UTC β†’ MEZ/MESZ (wie in getCurrentTime) + time_t utcTime = currentSecondsUTC; + unsigned long currentSecondsLocal = currentSecondsUTC; + + if (isDST(utcTime)) { + // MESZ = UTC+2 + currentSecondsLocal += 7200; // +2 Stunden + } else { + // MEZ = UTC+1 + currentSecondsLocal += 3600; // +1 Stunde + } + + String json = "{"; + json += "\"time\":\"" + String(h) + ":" + String(m) + ":" + String(s) + "\","; + json += "\"timestamp\":" + String(currentSecondsLocal) + ","; // Jetzt lokale Zeit (MEZ/MESZ) + json += "\"lastSync\":" + String(lastSyncTime) + ","; + json += "\"driftRate\":" + String(driftRate, 3) + ","; + json += "\"syncCount\":" + String(syncCount) + ","; + json += "\"uptime\":" + String(millis() / 1000); + json += "}"; + + server.send(200, "application/json", json); +} + +// ════════════════════════════════════════════════════════════════ +// PATTERN TEST HANDLER +// ════════════════════════════════════════════════════════════════ +void handlePatternTest() { + DEBUG_PRINTLN("β†’ handlePatternTest aufgerufen"); + + if (patternTestRunning) { + server.send(409, "text/plain", "Pattern-Test lΓ€uft bereits!"); + return; + } + + server.send(200, "text/plain", "Pattern-Test gestartet - Nur kritische Zeiten werden getestet"); + + patternTestRunning = true; + + // Kritische Test-Zeiten: Nur Zeiten mit WortΓΌberlappungen + const int testTimes[][2] = { + // Stunde 0 und 1 KOMPLETT (EIN vs EINS, mit/ohne UHR) + {0, 0}, {0, 5}, {0, 10}, {0, 15}, {0, 20}, {0, 25}, {0, 30}, {0, 35}, {0, 40}, {0, 45}, {0, 50}, {0, 55}, + {1, 0}, {1, 5}, {1, 10}, {1, 15}, {1, 20}, {1, 25}, {1, 30}, {1, 35}, {1, 40}, {1, 45}, {1, 50}, {1, 55}, + + // Beispiel weitere Stunden + {12, 0}, {12, 25}, // ZWΓ–LF und HALB EINS + + // VIER als Stunde (weil "VIER" zweimal vorkommt: in VIERTEL + separat) + {3, 45}, // VIERTEL VOR VIER + {4, 0}, // VIER UHR + {4, 5}, {4, 10}, {4, 15}, {4, 20}, {4, 25}, {4, 30}, {4, 35}, {4, 40}, {4, 45}, {4, 50}, {4, 55}, + + // FÜNF als Stunde (weil "FuNF" zweimal vorkommt: separat + in ZWoLFuNF) + {4, 55}, // FÜNF VOR FÜNF + {5, 0}, // FÜNF UHR + {5, 5}, {5, 10}, {5, 15}, {5, 20}, {5, 25}, {5, 30}, {5, 35}, {5, 40}, {5, 45}, {5, 50}, {5, 55}, + + // ZEHN als Stunde (weil "ZEHN" zweimal vorkommt: oben + unten) + {9, 50}, // ZEHN VOR ZEHN + {10, 0}, // ZEHN UHR + {10, 5}, {10, 10}, {10, 15}, {10, 20}, {10, 25}, {10, 30}, {10, 35}, {10, 40}, {10, 45}, {10, 50}, {10, 55} + }; + + const int numTests = sizeof(testTimes) / sizeof(testTimes[0]); + + for (int i = 0; i < numTests && patternTestRunning; i++) { + int h = testTimes[i][0]; + int m = testTimes[i][1]; + + DEBUG_PRINTF("Pattern-Test: %02d:%02d (%d/%d)\n", h, m, i+1, numTests); + displayTime(h, m); + + // 2 Sekunden warten, dabei WiFi am Leben halten + for (int j = 0; j < 20 && patternTestRunning; j++) { + yield(); + server.handleClient(); + delay(100); + } + } + + patternTestRunning = false; + DEBUG_PRINTLN("βœ“ Pattern-Test abgeschlossen"); + + // ZurΓΌck zur aktuellen Zeit + int h, m, s; + getCurrentTime(h, m, s); + displayTime(h, m); +} + +void handleGetCharsoap() { + DEBUG_PRINTLN("β†’ handleGetCharsoap aufgerufen"); + server.send(200, "text/plain", charsoap); +} + +void handleResetCharsoap() { + DEBUG_PRINTLN("β†’ handleResetCharsoap aufgerufen"); + resetCharsoap(); + server.send(200, "text/plain", "OK"); +} + +// ════════════════════════════════════════════════════════════════ +// SPECIAL WORD HANDLER +// ════════════════════════════════════════════════════════════════ +void handleGetSpecialWords() { + DEBUG_PRINTLN("β†’ handleGetSpecialWords aufgerufen"); + + String json = "{\"words\":["; + for (int i = 0; i < MAXWORDS; i++) { + json += "\""; + json += SPECIAL_WORD[i]; + json += "\""; + if (i < MAXWORDS - 1) json += ","; + } + json += "],\"interval\":"; + json += String(specialWordInterval); + json += "}"; + + server.send(200, "application/json", json); +} + +void handleSaveSpecialWords() { + DEBUG_PRINTLN("β†’ handleSaveSpecialWords aufgerufen"); + + char newWords[MAXWORDS][12]; + + for (int i = 0; i < MAXWORDS; i++) { + String argName = "word" + String(i); + if (server.hasArg(argName)) { + String word = server.arg(argName); + if (word.length() > 11) { + server.send(400, "text/plain", "Wort " + String(i) + " zu lang (max 11 Zeichen)"); + return; + } + word.toCharArray(newWords[i], 12); + } else { + newWords[i][0] = '\0'; + } + } + + // Intervall speichern (falls angegeben) + if (server.hasArg("interval")) { + uint8_t interval = server.arg("interval").toInt(); + saveSpecialWordInterval(interval); + } + + saveSpecialWords(newWords); + server.send(200, "text/plain", "OK - SpezialwΓΆrter gespeichert!"); +} + +void handleResetSpecialWords() { + DEBUG_PRINTLN("β†’ handleResetSpecialWords aufgerufen"); + resetSpecialWords(); + resetSpecialWordInterval(); + server.send(200, "text/plain", "OK - SpezialwΓΆrter zurΓΌckgesetzt!"); +} + +// ════════════════════════════════════════════════════════════════ +// MINUTE LEDS HANDLER +// ════════════════════════════════════════════════════════════════ +void handleGetMinuteLeds() { + DEBUG_PRINTLN("β†’ handleGetMinuteLeds aufgerufen"); + + String json = "{\"leds\":["; + for (int i = 0; i < 4; i++) { + json += String(MINUTE_LEDS[i]); + if (i < 3) json += ","; + } + json += "]}"; + + server.send(200, "application/json", json); +} + +void handleSaveMinuteLeds() { + DEBUG_PRINTLN("β†’ handleSaveMinuteLeds aufgerufen"); + + uint8_t newLeds[4]; + + for (int i = 0; i < 4; i++) { + String argName = "led" + String(i); + if (!server.hasArg(argName)) { + server.send(400, "text/plain", "LED " + String(i) + " fehlt"); + return; + } + + int value = server.arg(argName).toInt(); + if (value < 0 || value >= NUM_LEDS) { + server.send(400, "text/plain", "LED " + String(i) + " ungΓΌltig (0-" + String(NUM_LEDS-1) + ")"); + return; + } + + newLeds[i] = value; + } + + saveMinuteLeds(newLeds); + server.send(200, "text/plain", "OK - Minuten-LEDs gespeichert!"); +} + +void handleResetMinuteLeds() { + DEBUG_PRINTLN("β†’ handleResetMinuteLeds aufgerufen"); + resetMinuteLeds(); + server.send(200, "text/plain", "OK - Minuten-LEDs zurΓΌckgesetzt!"); +} + +void handleSave() { + DEBUG_PRINTLN("β†’ handleSave aufgerufen"); + if (server.hasArg("timestamp")) { + DEBUG_PRINT("handleSave"); + unsigned long utcTimestamp = server.arg("timestamp").toInt(); + int timezoneOffset = server.hasArg("tzoffset") ? server.arg("tzoffset").toInt() : 0; + unsigned long clientTime = utcTimestamp - timezoneOffset; + + if (lastSyncTime > 0 && bootTime > 0) { + unsigned long espTime = bootTime + (millis() / 1000); + unsigned long timeSinceSync = clientTime - lastSyncTime; + + if (timeSinceSync > 3600) { + long drift = espTime - clientTime; + float daysElapsed = timeSinceSync / 86400.0; + float newDriftRate = drift / daysElapsed; + + if (syncCount > 0) { + driftRate = (driftRate * syncCount + newDriftRate) / (syncCount + 1); + } else { + driftRate = newDriftRate; + } + + syncCount++; + saveDriftRate(); + } + } + + bootTime = clientTime - (millis() / 1000); + lastSyncTime = clientTime; + + if (rtc.begin()) { + DateTime newTime(clientTime); + rtc.adjust(newTime); + } + + normalColor.r = server.arg("nr").toInt(); + normalColor.g = server.arg("ng").toInt(); + normalColor.b = server.arg("nb").toInt(); + + specialColor.r = server.arg("sr").toInt(); + specialColor.g = server.arg("sg").toInt(); + specialColor.b = server.arg("sb").toInt(); + + brightness = server.arg("brightness").toInt(); + + if (server.hasArg("charsoap")) { + String newCharsoap = server.arg("charsoap"); + newCharsoap.toUpperCase(); + if (newCharsoap.length() == (ROWS * COLS)) { + saveCharsoap(newCharsoap.c_str()); + } + } + + saveConfig(); + // User-Helligkeit (0-80) auf LED-Helligkeit (0-204) mappen + DEBUG_PRINTF("Brightness: %d",(int) map(brightness, 0, 80, 0, 204)); + FastLED.setBrightness(map(brightness, 0, 80, 0, 204)); + + server.send(200, "text/plain", "OK"); + int hours, minutes, seconds; + getCurrentTime(hours, minutes, seconds); + + displayTime(hours, minutes); + lastUpdateTime = millis(); + } else { + server.send(400, "text/plain", "Fehler"); + } +} +// ════════════════════════════════════════════════════════════════ +// LED TEST HANDLER +// ════════════════════════════════════════════════════════════════ +void handleLEDTest() { + Serial.println("β†’ handleLEDTest aufgerufen"); + + if (!server.hasArg("mode")) { + server.send(400, "text/plain", "Fehler: mode fehlt"); + return; + } + + String mode = server.arg("mode"); + Serial.printf(" Mode: %s\n", mode.c_str()); + + // Clear-Modus + if (mode == "clear") { + FastLED.clear(); + showLEDs(); + Serial.println(" β†’ Alle LEDs ausgeschaltet"); + server.send(200, "text/plain", "OK - LEDs ausgeschaltet"); + return; + } + + // Farbe und Helligkeit auslesen + uint8_t r = server.hasArg("r") ? server.arg("r").toInt() : 255; + uint8_t g = server.hasArg("g") ? server.arg("g").toInt() : 255; + uint8_t b = server.hasArg("b") ? server.arg("b").toInt() : 255; + uint8_t testBrightness = server.hasArg("brightness") ? server.arg("brightness").toInt() : 80; + + CRGB color = CRGB(r, g, b); + + // Aktuelle Helligkeit sichern und Test-Helligkeit setzen + uint8_t savedBrightness = brightness; + FastLED.setBrightness(testBrightness); + + FastLED.clear(); + + // Einzelne LED + if (mode == "single") { + if (!server.hasArg("led")) { + server.send(400, "text/plain", "Fehler: led fehlt"); + return; + } + + int ledNum = server.arg("led").toInt(); + + if (ledNum < 0 || ledNum >= NUM_LEDS) { + server.send(400, "text/plain", "Fehler: LED-Nummer ungΓΌltig"); + return; + } + + // LED Nr. 2 darf NIEMALS eingeschaltet werden (Fotowiderstand) + if (ledNum == 2) { + Serial.println(" ⚠ LED 2 blockiert (Fotowiderstand)"); + server.send(400, "text/plain", "Fehler: LED 2 ist gesperrt (Fotowiderstand)"); + return; + } + + leds[bridgeLED(ledNum)] = color; + showLEDs(); + + Serial.printf(" β†’ LED %d eingeschaltet (R:%d G:%d B:%d)\n", ledNum, r, g, b); + server.send(200, "text/plain", "OK - LED " + String(ledNum)); + } + + // LED-Bereich + else if (mode == "range") { + if (!server.hasArg("from") || !server.hasArg("to")) { + server.send(400, "text/plain", "Fehler: from/to fehlt"); + return; + } + + int from = server.arg("from").toInt(); + int to = server.arg("to").toInt(); + + if (from < 0 || to >= NUM_LEDS || from > to) { + server.send(400, "text/plain", "Fehler: Bereich ungΓΌltig"); + return; + } + + for (int i = from; i <= to; i++) { + // LED Nr. 2 muss IMMER aus sein (Fotowiderstand) + if (i == 2) { + leds[bridgeLED(i)] = CRGB::Black; + } else { + leds[bridgeLED(i)] = color; + } + } + showLEDs(); + + Serial.printf(" β†’ LEDs %d-%d eingeschaltet (LED 2 bleibt aus)\n", from, to); + server.send(200, "text/plain", "OK - LEDs " + String(from) + "-" + String(to)); + } + + // Alle LEDs (außer LED Nr. 2 - Fotowiderstand!) + else if (mode == "all") { + for (int i = 0; i < NUM_LEDS; i++) { + // LED Nr. 2 muss IMMER aus sein (Fotowiderstand) + if (i == 2) { + leds[bridgeLED(i)] = CRGB::Black; + } else { + leds[bridgeLED(i)] = color; + } + } + showLEDs(); + + Serial.println(" β†’ Alle LEDs eingeschaltet (außer LED 2)"); + server.send(200, "text/plain", "OK - Alle LEDs (außer LED 2)"); + } + + // Zeile + else if (mode == "row") { + if (!server.hasArg("row")) { + server.send(400, "text/plain", "Fehler: row fehlt"); + return; + } + + int row = server.arg("row").toInt(); + + if (row < 0 || row >= ROWS) { + server.send(400, "text/plain", "Fehler: Zeile ungΓΌltig"); + return; + } + + for (int col = 0; col < COLS; col++) { + int index; + if (row % 2 == 0) { + index = row * COLS + col; + } else { + index = row * COLS + (COLS - 1 - col); + } + // LED Nr. 2 muss IMMER aus sein (Fotowiderstand) + if (index == 2) { + leds[bridgeLED(index)] = CRGB::Black; + } else { + leds[bridgeLED(index)] = color; + } + } + showLEDs(); + + Serial.printf(" β†’ Zeile %d eingeschaltet (LED 2 bleibt aus)\n", row); + server.send(200, "text/plain", "OK - Zeile " + String(row)); + } + + // Spalte + else if (mode == "col") { + if (!server.hasArg("col")) { + server.send(400, "text/plain", "Fehler: col fehlt"); + return; + } + + int col = server.arg("col").toInt(); + + if (col < 0 || col >= COLS) { + server.send(400, "text/plain", "Fehler: Spalte ungΓΌltig"); + return; + } + + for (int row = 0; row < ROWS; row++) { + int index; + if (row % 2 == 0) { + index = row * COLS + col; + } else { + index = row * COLS + (COLS - 1 - col); + } + // LED Nr. 2 muss IMMER aus sein (Fotowiderstand) + if (index == 2) { + leds[bridgeLED(index)] = CRGB::Black; + } else { + leds[bridgeLED(index)] = color; + } + } + showLEDs(); + + Serial.printf(" β†’ Spalte %d eingeschaltet\n", col); + server.send(200, "text/plain", "OK - Spalte " + String(col)); + } + + else { + server.send(400, "text/plain", "Fehler: Unbekannter Modus"); + } + + // Helligkeit zurΓΌcksetzen + FastLED.setBrightness(savedBrightness); +} + +// ════════════════════════════════════════════════════════════════ +// OTA UPDATE HANDLER +// ════════════════════════════════════════════════════════════════ +void handleOTAInfo() { + DEBUG_PRINTLN("β†’ handleOTAInfo aufgerufen"); + + String json = "{"; + json += "\"version\":\"" + String(firmwareVersion) + "\","; + json += "\"buildDate\":\"" + String(BUILD_DATE) + "\","; + json += "\"buildTime\":\"" + String(BUILD_TIME) + "\","; + json += "\"freeSpace\":" + String(ESP.getFreeSketchSpace()) + ","; + json += "\"sketchSize\":" + String(ESP.getSketchSize()) + ","; + json += "\"chipId\":\"" + String(ESP.getChipId(), HEX) + "\","; + json += "\"flashSize\":" + String(ESP.getFlashChipRealSize()) + ","; + json += "\"otaReady\":" + String(otaInProgress ? "false" : "true"); + json += "}"; + + server.send(200, "application/json", json); +} + +void handleOTAUpload() { + HTTPUpload& upload = server.upload(); + + if (upload.status == UPLOAD_FILE_START) { + otaInProgress = true; + otaProgress = 0; + otaError = ""; + otaStartTime = millis(); + + DEBUG_PRINTF("OTA Upload Start: %s\n", upload.filename.c_str()); + + WiFi.setSleepMode(WIFI_NONE_SLEEP); + showOTAProgress(0); + + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + if (!Update.begin(maxSketchSpace)) { + Update.printError(Serial); + otaError = "Begin failed"; + } + } + else if (upload.status == UPLOAD_FILE_WRITE) { + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + Update.printError(Serial); + otaError = "Write failed"; + } else { + size_t progress = Update.progress(); + size_t total = Update.size(); + if (total > 0) { + otaProgressCallback(progress, total); + } + } + } + else if (upload.status == UPLOAD_FILE_END) { + if (Update.end(true)) { + DEBUG_PRINTF("OTA Success: %u bytes\n", upload.totalSize); + showOTAProgress(100, false, true); + + EEPROM.write(ADDR_OTA_FLAGS, OTA_FLAG_UPDATE_SUCCESS); + EEPROM.commit(); + + otaInProgress = false; + + delay(3000); + ESP.restart(); + } else { + Update.printError(Serial); + otaError = String(Update.getError()); + showOTAProgress(100, true, false); + otaInProgress = false; + } + } +} + +void handleOTAUploadDone() { + if (otaError.length() > 0) { + String json = "{\"success\":false,\"message\":\"" + otaError + "\"}"; + server.send(500, "application/json", json); + } else { + String json = "{\"success\":true,\"message\":\"Update erfolgreich\"}"; + server.send(200, "application/json", json); + } +} + +void handleOTAFromURL() { + DEBUG_PRINTLN("β†’ handleOTAFromURL aufgerufen"); + + if (!server.hasArg("url")) { + server.send(400, "application/json", + "{\"success\":false,\"message\":\"URL fehlt\"}"); + return; + } + + String firmwareURL = server.arg("url"); + + if (firmwareURL.length() == 0) { + server.send(400, "application/json", + "{\"success\":false,\"message\":\"URL leer\"}"); + return; + } + + server.send(200, "application/json", + "{\"success\":true,\"message\":\"Update gestartet\"}"); + + delay(500); + + otaInProgress = true; + otaProgress = 0; + otaError = ""; + otaStartTime = millis(); + + WiFi.setSleepMode(WIFI_NONE_SLEEP); + showOTAProgress(0); + + DEBUG_PRINTLN("Starting HTTP update from: " + firmwareURL); + + ESPhttpUpdate.onProgress([](int current, int total) { + otaProgressCallback(current, total); + }); + + ESPhttpUpdate.setLedPin(LED_BUILTIN, LOW); + + WiFiClient client; + t_httpUpdate_return ret = ESPhttpUpdate.update(client, firmwareURL); + + switch (ret) { + case HTTP_UPDATE_FAILED: + DEBUG_PRINTF("HTTP_UPDATE_FAILED Error (%d): %s\n", + ESPhttpUpdate.getLastError(), + ESPhttpUpdate.getLastErrorString().c_str()); + otaError = ESPhttpUpdate.getLastErrorString(); + showOTAProgress(100, true, false); + otaInProgress = false; + break; + + case HTTP_UPDATE_NO_UPDATES: + DEBUG_PRINTLN("HTTP_UPDATE_NO_UPDATES"); + otaError = "No updates available"; + showOTAProgress(100, true, false); + otaInProgress = false; + break; + + case HTTP_UPDATE_OK: + DEBUG_PRINTLN("HTTP_UPDATE_OK"); + showOTAProgress(100, false, true); + + EEPROM.write(ADDR_OTA_FLAGS, OTA_FLAG_UPDATE_SUCCESS); + EEPROM.commit(); + + delay(3000); + ESP.restart(); + break; + } +} + +void handleOTAStatus() { + DEBUG_PRINTLN("β†’ handleOTAStatus aufgerufen"); + + String json = "{"; + json += "\"inProgress\":" + String(otaInProgress ? "true" : "false") + ","; + json += "\"progress\":" + String(otaProgress) + ","; + json += "\"error\":\"" + otaError + "\","; + json += "\"elapsed\":" + String(millis() - otaStartTime); + json += "}"; + + server.send(200, "application/json", json); +} + +// ════════════════════════════════════════════════════════════════ +// WIFI/NTP CONFIG HANDLER +// ════════════════════════════════════════════════════════════════ +void handleGetWiFiConfig() { + DEBUG_PRINTLN("β†’ handleGetWiFiConfig aufgerufen"); + + String json = "{"; + + // WiFi Station Config + json += "\"staEnabled\":" + String(staEnabled ? "true" : "false") + ","; + json += "\"staSsid\":\"" + String(staSsid) + "\","; + json += "\"staPassword\":\"" + String(staPassword) + "\","; + json += "\"staDhcp\":" + String(staDhcp ? "true" : "false") + ","; + json += "\"staIP\":\"" + staIP.toString() + "\","; + json += "\"staGateway\":\"" + staGateway.toString() + "\","; + json += "\"staSubnet\":\"" + staSubnet.toString() + "\","; + json += "\"staDNS\":\"" + staDNS.toString() + "\","; + + // WiFi Status + json += "\"staConnected\":" + String(WiFi.status() == WL_CONNECTED ? "true" : "false") + ","; + json += "\"staLocalIP\":\"" + WiFi.localIP().toString() + "\","; + json += "\"staRSSI\":" + String(WiFi.RSSI()) + ","; + + // NTP Config + json += "\"ntpEnabled\":" + String(ntpEnabled ? "true" : "false") + ","; + json += "\"ntpServer\":\"" + String(ntpServer) + "\","; + json += "\"ntpLastSync\":" + String(lastNtpSync) + ","; + json += "\"ntpSyncOk\":" + String(ntpSyncSuccessful ? "true" : "false"); + + json += "}"; + + server.send(200, "application/json", json); +} + +void handleSaveWiFiConfig() { + DEBUG_PRINTLN("β†’ handleSaveWiFiConfig aufgerufen"); + + bool changed = false; + + // WiFi Station Enabled + if (server.hasArg("staEnabled")) { + staEnabled = (server.arg("staEnabled") == "true" || server.arg("staEnabled") == "1"); + changed = true; + } + + // SSID + if (server.hasArg("staSsid")) { + String newSsid = server.arg("staSsid"); + strncpy(staSsid, newSsid.c_str(), 31); + staSsid[31] = '\0'; + changed = true; + } + + // Password + if (server.hasArg("staPassword")) { + String newPassword = server.arg("staPassword"); + strncpy(staPassword, newPassword.c_str(), 63); + staPassword[63] = '\0'; + changed = true; + } + + // DHCP + if (server.hasArg("staDhcp")) { + staDhcp = (server.arg("staDhcp") == "true" || server.arg("staDhcp") == "1"); + changed = true; + } + + // Statische IP-Konfiguration + if (server.hasArg("staIP")) { + staIP.fromString(server.arg("staIP")); + changed = true; + } + if (server.hasArg("staGateway")) { + staGateway.fromString(server.arg("staGateway")); + changed = true; + } + if (server.hasArg("staSubnet")) { + staSubnet.fromString(server.arg("staSubnet")); + changed = true; + } + if (server.hasArg("staDNS")) { + staDNS.fromString(server.arg("staDNS")); + changed = true; + } + + // NTP Enabled + if (server.hasArg("ntpEnabled")) { + ntpEnabled = (server.arg("ntpEnabled") == "true" || server.arg("ntpEnabled") == "1"); + changed = true; + } + + // NTP Server (optional, Standard: ptbtime1.ptb.de) + if (server.hasArg("ntpServer")) { + String newNtpServer = server.arg("ntpServer"); + strncpy(ntpServer, newNtpServer.c_str(), 31); + ntpServer[31] = '\0'; + changed = true; + } + + if (changed) { + saveWiFiStationConfig(); + saveNTPConfig(); + + server.send(200, "text/plain", "OK - Neustart erforderlich!"); + + // Info: Neustart nΓΆtig fΓΌr WiFi-Γ„nderungen + DEBUG_PRINTLN("WiFi/NTP Config gespeichert - Neustart empfohlen"); + } else { + server.send(400, "text/plain", "Keine Γ„nderungen"); + } +} + +void handleRestart() { + DEBUG_PRINTLN("β†’ Neustart angefordert ΓΌber Web-Interface"); + + server.send(200, "text/plain", "Neustart wird durchgefΓΌhrt..."); + + delay(500); // Kurze Pause, damit Response gesendet wird + ESP.restart(); +} + +void handleWiFiScan() { + DEBUG_PRINTLN("β†’ handleWiFiScan aufgerufen"); + + // Starte WiFi-Scan (synchron - kann 2-5 Sek. dauern!) + int networksFound = WiFi.scanNetworks(false, false); + yield(); // Watchdog fΓΌttern nach Scan + + if (networksFound == 0) { + server.send(200, "application/json", "{\"networks\":[]}"); + return; + } + + // JSON-Array erstellen + String json = "{\"networks\":["; + + for (int i = 0; i < networksFound; i++) { + yield(); // Watchdog fΓΌttern in Schleife + if (i > 0) json += ","; + + json += "{"; + json += "\"ssid\":\"" + WiFi.SSID(i) + "\","; + json += "\"rssi\":" + String(WiFi.RSSI(i)) + ","; + json += "\"encryption\":" + String(WiFi.encryptionType(i)); + json += "}"; + } + + json += "]}"; + + // Scan-Daten lΓΆschen (Speicher freigeben) + WiFi.scanDelete(); + + server.send(200, "application/json", json); + DEBUG_PRINTF("βœ“ WiFi-Scan abgeschlossen: %d Netzwerke gefunden\n", networksFound); +} + +// ════════════════════════════════════════════════════════════════ +// AUTO-BRIGHTNESS CONFIG HANDLER +// ════════════════════════════════════════════════════════════════ +void handleGetAutoBrightness() { + //DEBUG_PRINTLN("β†’ handleGetAutoBrightness aufgerufen"); + + // Aktuellen ADC-Wert auslesen (Mittelwert ΓΌber 10 Messungen) + uint32_t adcSum = 0; + for (uint8_t i = 0; i < 10; i++) { + adcSum += analogRead(A0); + delay(1); + } + uint16_t currentADC = adcSum / 10; + + String json = "{"; + json += "\"enabled\":" + String(autoBrightnessEnabled ? "true" : "false") + ","; + json += "\"minADC\":" + String(autoBrightnessMinADC) + ","; + json += "\"maxADC\":" + String(autoBrightnessMaxADC) + ","; + json += "\"minBrightness\":" + String(autoBrightnessMin) + ","; + json += "\"maxBrightness\":" + String(autoBrightnessMax) + ","; + json += "\"currentADC\":" + String(currentADC); + json += "}"; + + server.send(200, "application/json", json); +} + +void handleSaveAutoBrightness() { + DEBUG_PRINTLN("β†’ handleSaveAutoBrightness aufgerufen"); + + bool changed = false; + + // Enabled Flag + if (server.hasArg("enabled")) { + autoBrightnessEnabled = (server.arg("enabled") == "true" || server.arg("enabled") == "1"); + changed = true; + } + + // Min ADC Wert + if (server.hasArg("minADC")) { + uint16_t newMinADC = server.arg("minADC").toInt(); + if (newMinADC >= 0 && newMinADC <= 1023) { + autoBrightnessMinADC = newMinADC; + // Auto-Korrektur: Max muss grâßer sein + if (autoBrightnessMaxADC <= autoBrightnessMinADC) { + autoBrightnessMaxADC = min(1023, autoBrightnessMinADC + 20); + } + changed = true; + } + } + + // Max ADC Wert + if (server.hasArg("maxADC")) { + uint16_t newMaxADC = server.arg("maxADC").toInt(); + if (newMaxADC >= 0 && newMaxADC <= 1023 && newMaxADC > autoBrightnessMinADC) { + autoBrightnessMaxADC = newMaxADC; + changed = true; + } + } + + // Min Helligkeit + if (server.hasArg("minBrightness")) { + uint8_t newMinBrightness = server.arg("minBrightness").toInt(); + if (newMinBrightness >= 0 && newMinBrightness <= 80) { + autoBrightnessMin = newMinBrightness; + // Auto-Korrektur: Max muss grâßer sein + if (autoBrightnessMax <= autoBrightnessMin) { + autoBrightnessMax = min(80, autoBrightnessMin + 5); + } + changed = true; + } + } + + // Max Helligkeit + if (server.hasArg("maxBrightness")) { + uint8_t newMaxBrightness = server.arg("maxBrightness").toInt(); + // Max darf nicht > 80 sein + if (newMaxBrightness >= 0 && newMaxBrightness <= 80) { + autoBrightnessMax = newMaxBrightness; + // Auto-Korrektur: Min muss kleiner sein + if (autoBrightnessMin >= autoBrightnessMax) { + autoBrightnessMin = max(0, autoBrightnessMax - 5); + } + changed = true; + } + } + + if (changed) { + saveAutoBrightnessConfig(); + server.send(200, "text/plain", "OK - Auto-Brightness konfiguriert!"); + DEBUG_PRINTLN("Auto-Brightness Config gespeichert"); + } else { + server.send(400, "text/plain", "Keine Γ„nderungen oder ungΓΌltige Werte"); + } +} + +void handleNotFound() { + DEBUG_PRINTLN("β†’ handleNotFound aufgerufen"); + server.sendHeader("Location", "/", true); + server.send(302, "text/plain", ""); +} +// ════════════════════════════════════════════════════════════════ +// STROMAUSFALL-ERKENNUNG ohne RTC +// ════════════════════════════════════════════════════════════════ + +// ════════════════════════════════════════════════════════════════ +// STROMAUSFALL-ERKENNUNG MIT RTC +// ════════════════════════════════════════════════════════════════ +bool detectPowerLossWithRTC() { + DateTime rtcNow = rtc.now(); + unsigned long rtcTime = rtcNow.unixtime(); + + // Hole letzte gespeicherte Zeit aus EEPROM + unsigned long lastKnownTime = 0; + lastKnownTime |= ((unsigned long)EEPROM.read(ADDR_TIMESTAMP)) << 24; + lastKnownTime |= ((unsigned long)EEPROM.read(ADDR_TIMESTAMP + 1)) << 16; + lastKnownTime |= ((unsigned long)EEPROM.read(ADDR_TIMESTAMP + 2)) << 8; + lastKnownTime |= EEPROM.read(ADDR_TIMESTAMP + 3); + + Serial.println("\n╔════════════════════════════════════════╗"); + Serial.println( "β•‘ STROMAUSFALL-PRÜFUNG (RTC) β•‘"); + Serial.println( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + + Serial.printf("RTC-Zeit: %lu\n", rtcTime); + Serial.printf("Letzte bekannte Zeit: %lu\n", lastKnownTime); + + // PrΓΌfe ob gΓΌltige Zeit gespeichert ist + if (lastKnownTime < 1735689600) { // Vor 2025-01-01 + Serial.println("β†’ Keine gΓΌltige Zeit gespeichert"); + Serial.println("βœ“ Erste Inbetriebnahme\n"); + + // Übernehme RTC-Zeit als Startpunkt + bootTime = rtcTime; + return false; + } + + // RTC liegt VOR letzter bekannter Zeit β†’ RTC-Batterie leer + if (rtcTime < lastKnownTime) { + long diff = lastKnownTime - rtcTime; + Serial.printf("⚠⚠⚠ RTC liegt %ld Sekunden HINTER!\n", diff); + Serial.println("⚠⚠⚠ STROMAUSFALL + RTC-BATTERIE LEER!"); + Serial.println("════════════════════════════════════════\n"); + return true; + } + + // PrΓΌfe Zeitdifferenz + long timeDiff = rtcTime - lastKnownTime; + Serial.printf("Zeitdifferenz: %ld Sek (%ld Tage)\n", timeDiff, timeDiff / 86400); + + // Wenn mehr als 7 Tage Unterschied β†’ wahrscheinlich Stromausfall + if (timeDiff > 604800) { // 7 Tage in Sekunden + Serial.println("⚠ Sehr lange Zeitdifferenz!"); + Serial.println("⚠ STROMAUSFALL erkannt"); + Serial.println(" (RTC lief weiter, ESP8266 war aus)"); + Serial.println("════════════════════════════════════════\n"); + + // Übernehme RTC-Zeit + bootTime = rtcTime; + return true; + } + + // Alles OK - normale Zeitdifferenz + Serial.println("βœ“ Normale Zeitdifferenz"); + Serial.println("βœ“ Kein Stromausfall\n"); + + // Übernehme RTC-Zeit als Basis + bootTime = rtcTime; + return false; +} + +void powerLossLoop() { + Serial.println("⚠ STROMAUSFALL-WARNUNG - Blinke SOS-Muster"); + + while (true) { + // SOS-Muster: ... --- ... + + // S (3Γ— kurz) + for (int i = 0; i < 3; i++) { + fill_solid(leds, NUM_LEDS, CRGB::Red); + FastLED.setBrightness(40); + showLEDs(); + delay(200); + FastLED.clear(); + showLEDs(); + delay(200); + } + + delay(400); + + // O (3Γ— lang) + for (int i = 0; i < 3; i++) { + fill_solid(leds, NUM_LEDS, CRGB::Red); + FastLED.setBrightness(40); + showLEDs(); + delay(600); + FastLED.clear(); + showLEDs(); + delay(200); + } + + delay(400); + + // S (3Γ— kurz) + for (int i = 0; i < 3; i++) { + fill_solid(leds, NUM_LEDS, CRGB::Red); + FastLED.setBrightness(40); + showLEDs(); + delay(200); + FastLED.clear(); + showLEDs(); + delay(200); + } + + delay(2000); // Lange Pause + yield(); + } +} + +bool detectPowerLoss() { + // ═══ PRÜFE OTA-UPDATE-FLAG ZUERST ═══ + uint8_t otaFlags = EEPROM.read(ADDR_OTA_FLAGS); + if (otaFlags == OTA_FLAG_UPDATE_SUCCESS) { + Serial.println("\n╔════════════════════════════════════════╗"); + Serial.println( "β•‘ OTA UPDATE ERFOLGREICH β•‘"); + Serial.println( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + Serial.println("βœ“ Neustart nach OTA-Update"); + Serial.println("βœ“ Kein Stromausfall\n"); + + // LΓΆsche OTA-Flag (einmalig) + EEPROM.write(ADDR_OTA_FLAGS, 0); + EEPROM.commit(); + + return false; // KEIN Stromausfall! + } + + bool rtcAvailable = rtc.begin() && rtc.isrunning(); + #if (defined(USE_RTC) && USE_RTC == false) + #warning "RTC not in use!" + rtcAvailable = false; + #endif + + if (rtcAvailable) + { + Serial.println("β†’ RTC verfΓΌgbar - nutze RTC-Methode"); + return detectPowerLossWithRTC(); // Die vorherige Methode + } else { + Serial.println("β†’ Keine RTC - nutze EEPROM-Methode"); + + // Kombiniere Reset-Reason + EEPROM-Flag + bool badResetReason = detectPowerLossFromResetReason(); + bool runningFlagSet = detectPowerLossWithoutRTC(); + + // Wenn BEIDES zutrifft β†’ definitiv Stromausfall + if (badResetReason && runningFlagSet) { + Serial.println("\n⚠⚠⚠ STROMAUSFALL BESTΓ„TIGT"); + Serial.println(" (Reset-Grund + Running-Flag)"); + #ifndef POWERLOSSDETECT + return false; + #endif + #ifdef POWERLOSSDETECT + return POWERLOSSDETECT; + #endif + } + + // Wenn nur Running-Flag β†’ wahrscheinlich Stromausfall + if (runningFlagSet) { + Serial.println("\n⚠ Wahrscheinlicher Stromausfall"); + #ifndef POWERLOSSDETECT + return false; + #endif + #ifdef POWERLOSSDETECT + return POWERLOSSDETECT; + #endif + } + + // Wenn nur Reset-Reason aber kein Flag β†’ erste Inbetriebnahme oder Reset-Taste + if (badResetReason && !runningFlagSet) { + Serial.println("\nβœ“ Erste Inbetriebnahme oder Reset-Taste"); + return false; + } + + return false; + } +} + +// ════════════════════════════════════════════════════════════════ +// WIFI STATION SETUP +// ════════════════════════════════════════════════════════════════ +void setupWiFiStation() { + if (!staEnabled || strlen(staSsid) == 0) { + DEBUG_PRINTLN("β†’ WiFi Station deaktiviert (keine Credentials)"); + return; + } + + DEBUG_PRINTLN("\n╔════════════════════════════════════════╗"); + DEBUG_PRINTLN( "β•‘ WIFI STATION VERBINDUNG β•‘"); + DEBUG_PRINTLN( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + DEBUG_PRINTF("SSID: %s\n", staSsid); + + // Statische IP konfigurieren (vor WiFi.begin!) + if (!staDhcp) { + DEBUG_PRINTLN("Verwende statische IP:"); + DEBUG_PRINTF(" IP: %s\n", staIP.toString().c_str()); + DEBUG_PRINTF(" Gateway: %s\n", staGateway.toString().c_str()); + DEBUG_PRINTF(" Subnet: %s\n", staSubnet.toString().c_str()); + DEBUG_PRINTF(" DNS: %s\n", staDNS.toString().c_str()); + + WiFi.config(staIP, staGateway, staSubnet, staDNS); + } else { + DEBUG_PRINTLN("Verwende DHCP"); + } + + // Auto-Reconnect aktivieren + WiFi.setAutoReconnect(true); + WiFi.persistent(false); // Kein Flash-Write bei jedem Connect + + // Verbindung starten + WiFi.begin(staSsid, staPassword); + + // Warte max. 10 Sekunden auf Verbindung + DEBUG_PRINT("Verbinde"); + int timeout = 0; + while (WiFi.status() != WL_CONNECTED && timeout < 20) { + delay(500); + DEBUG_PRINT("."); + timeout++; + } + DEBUG_PRINTLN(); + + if (WiFi.status() == WL_CONNECTED) { + DEBUG_PRINTLN("βœ“ WiFi verbunden!"); + DEBUG_PRINTF(" IP: %s\n", WiFi.localIP().toString().c_str()); + DEBUG_PRINTF(" Gateway: %s\n", WiFi.gatewayIP().toString().c_str()); + DEBUG_PRINTF(" DNS: %s\n", WiFi.dnsIP().toString().c_str()); + DEBUG_PRINTF(" Kanal: %d\n", WiFi.channel()); + } else { + DEBUG_PRINTLN("⚠ Verbindung fehlgeschlagen (Timeout)"); + DEBUG_PRINTF(" Status: %d\n", WiFi.status()); + + // WICHTIG: Auto-Reconnect deaktivieren, um AP-FunktionalitΓ€t nicht zu beeintrΓ€chtigen + WiFi.setAutoReconnect(false); + WiFi.disconnect(); + + // AP-Mode sicherstellen + WiFi.mode(WIFI_AP_STA); // Dual-Mode beibehalten + DEBUG_PRINTLN("β†’ Auto-Reconnect deaktiviert, AP-Mode bleibt aktiv"); + DEBUG_PRINTLN("β†’ Verbinden Sie sich mit dem AP fΓΌr neue WiFi-Einstellungen"); + } +} + +// ════════════════════════════════════════════════════════════════ +// NTP SETUP & SYNC +// ════════════════════════════════════════════════════════════════ +void setupNTP() +{ + if (!ntpEnabled || WiFi.status() != WL_CONNECTED) + { + DEBUG_PRINTLN("\n╔══════════════════════════════════════════════════╗"); + DEBUG_PRINTLN( "β•‘ β†’ NTP nicht mΓΆglich (deaktiviert oder kein WiFi) β•‘"); + DEBUG_PRINTLN( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + return; + } + + DEBUG_PRINTLN("\n╔════════════════════════════════════════╗"); + DEBUG_PRINTLN( "β•‘ NTP ZEITSERVER-SYNC β•‘"); + DEBUG_PRINTLN( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + DEBUG_PRINTF("NTP Server: %s\n", ntpServer); + + // Konfiguriere NTP (UTC speichern, Zeitzone wird beim Anzeigen umgerechnet) + // Syntax: configTime(timezone_sec, daylight_sec, server1, server2, server3) + configTime(0, 0, ntpServer, "pool.ntp.org", "time.nist.gov"); + + DEBUG_PRINTLN("β„Ή Zeitzone: UTC (Umrechnung auf MEZ/MESZ erfolgt automatisch)"); + + DEBUG_PRINTLN("Warte auf NTP-Sync..."); + + // Warte max. 5 Sekunden auf ersten Sync + time_t now = time(nullptr); + int timeout = 0; + while (now < 1000000000 && timeout < 10) { // Timestamp > ~2001 + delay(500); + now = time(nullptr); + DEBUG_PRINT("."); + timeout++; + } + DEBUG_PRINTLN(); + + if (now > 1000000000) { + DEBUG_PRINTLN("βœ“ NTP-Sync erfolgreich!"); + DEBUG_PRINTF(" Unix-Zeit: %lld\n", (long long)now); + + struct tm* timeinfo = localtime(&now); + DEBUG_PRINTF(" Datum: %04d-%02d-%02d %02d:%02d:%02d\n", + timeinfo->tm_year + 1900, + timeinfo->tm_mon + 1, + timeinfo->tm_mday, + timeinfo->tm_hour, + timeinfo->tm_min, + timeinfo->tm_sec); + + // Übernehme NTP-Zeit als bootTime + bootTime = now - (millis() / 1000); + lastNtpSync = now; + lastSyncTime = now; // WICHTIG: FΓΌr Drift-Korrektur + ntpSyncSuccessful = true; + saveNTPConfig(); // Speichere letzten Sync + saveDriftRate(); // Speichere auch lastSyncTime + + // NΓ€chster Check in 1 Stunde + nextNtpCheck = millis() + 3600000; + } else { + DEBUG_PRINTLN("⚠ NTP-Sync fehlgeschlagen (Timeout)"); + ntpSyncSuccessful = false; + // Retry in 5 Minuten + nextNtpCheck = millis() + 300000; + } +} + +void checkNTPSync() { + // Nur wenn NTP aktiviert und WiFi verbunden + if (!ntpEnabled || WiFi.status() != WL_CONNECTED) { + return; + } + + // Nur wenn Check-Zeit erreicht + if (millis() < nextNtpCheck) { + return; + } + + DEBUG_PRINTLN("β†’ StΓΌndlicher NTP-Sync..."); + + time_t now = time(nullptr); + if (now > 1000000000) { + // Sync erfolgreich + unsigned long oldBootTime = bootTime; + bootTime = now - (millis() / 1000); + + // Berechne Drift seit letztem Sync + if (lastNtpSync > 0) { + long drift = (long)bootTime - (long)oldBootTime; + DEBUG_PRINTF(" Drift seit letztem Sync: %ld Sekunden\n", drift); + + // Aktualisiere Drift-Rate (nutze bestehendes System!) + unsigned long timeSinceSync = now - lastNtpSync; + if (timeSinceSync > 3600) { // Min. 1 Stunde + float daysElapsed = timeSinceSync / 86400.0; + float newDriftRate = drift / daysElapsed; + + if (syncCount > 0) { + driftRate = (driftRate * syncCount + newDriftRate) / (syncCount + 1); + } else { + driftRate = newDriftRate; + } + + syncCount++; + saveDriftRate(); + } + } + + lastNtpSync = now; + lastSyncTime = now; // WICHTIG: FΓΌr Drift-Korrektur + ntpSyncSuccessful = true; + saveNTPConfig(); + saveDriftRate(); // Speichere auch lastSyncTime + + DEBUG_PRINTLN(" βœ“ NTP-Sync erfolgreich"); + } else { + DEBUG_PRINTLN(" ⚠ NTP-Sync fehlgeschlagen"); + ntpSyncSuccessful = false; + } + + // NΓ€chster Check in 1 Stunde (oder 5 Min bei Fehler) + nextNtpCheck = millis() + (ntpSyncSuccessful ? 3600000 : 300000); +} + +// ════════════════════════════════════════════════════════════════ +// WIFI VERBINDUNGS-MONITOR +// ════════════════════════════════════════════════════════════════ +unsigned long lastWiFiCheck = 0; +bool wasConnected = false; + +void checkWiFiConnection() { + // Nur alle 10 Sekunden prΓΌfen + if (millis() - lastWiFiCheck < 10000) { + return; + } + lastWiFiCheck = millis(); + + // Nur wenn Station aktiviert ist + if (!staEnabled || strlen(staSsid) == 0) { + return; + } + + bool isConnected = (WiFi.status() == WL_CONNECTED); + + // Verbindung verloren? + if (wasConnected && !isConnected) { + DEBUG_PRINTLN("⚠ WiFi-Verbindung verloren!"); + DEBUG_PRINTF(" Status: %d\n", WiFi.status()); + + // Auto-Reconnect ist aktiv, ESP verbindet automatisch neu + DEBUG_PRINTLN(" β†’ Auto-Reconnect aktiv..."); + } + + // Verbindung wiederhergestellt? + if (!wasConnected && isConnected) + { + DEBUG_PRINTLN("βœ“ WiFi-Verbindung wiederhergestellt!"); + DEBUG_PRINTF(" IP: %s\n", WiFi.localIP().toString().c_str()); + + // NTP neu konfigurieren + if (ntpEnabled) { + setupNTP(); + } + } + wasConnected = isConnected; +} + +// ════════════════════════════════════════════════════════════════ +// SETUP & LOOP +// ════════════════════════════════════════════════════════════════ +void setup() +{ + Serial.begin(115200); + while (!Serial) { } + Serial.setDebugOutput(true); + delay(500); + Serial.setDebugOutput(false); + + Serial.printf("Flash Chip ID: %08X\n", ESP.getFlashChipId()); + Serial.printf("Flash Chip real size: %u bytes\n", ESP.getFlashChipRealSize()); + Serial.printf("Flash Chip mode: %d\n", ESP.getFlashChipMode()); + Serial.printf("Flash Chip speed: %u Hz\n", ESP.getFlashChipSpeed()); + Serial.printf("SDK-Version: %s\n", ESP.getSdkVersion()); + Serial.printf("Sketch size: %u bytes (Max: %u bytes)\n", ESP.getSketchSize(), ESP.getFreeSketchSpace() + ESP.getSketchSize()); + Serial.printf("Free sketch space: %u bytes\n", ESP.getFreeSketchSpace()); + Serial.printf("Flash layout: %s\n", (ESP.getFreeSketchSpace() + ESP.getSketchSize()) > 1100000 ? "4m2m (2MB)" : "4m1m (1MB)"); + + DEBUG_PRINTLN("\n╔════════════════════════════════╗"); + DEBUG_PRINTLN( "β•‘ CharGraph BOOT β•‘"); + DEBUG_PRINTLN( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n"); + + // Verdrahtungshinweis anzeigen + showConnect(); + + FastLED.addLeds(leds, NUM_LEDS); + //Clear leds + //memset(leds, 0, (size_t) sizeof(leds)); + FastLED.clear(true); + FastLED.setBrightness(0); + // WICHTIG: Setze Correction fΓΌr ESP8266 mit WiFi + FastLED.setCorrection(TypicalLEDStrip); + FastLED.setMaxRefreshRate(60); + showLEDs(); + delay(100); + // RGB Test ausfΓΌhren + DEBUG_PRINT("\n╔════════════════════════════════╗\n"); + DEBUG_PRINT( "β•‘ FΓΌhre RGB-Test aus... β•‘\n"); + DEBUG_PRINT( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n"); + FastLED.setBrightness(80); + if(TEST_RGB) rgbTest(); + FastLED.setBrightness(0); + while(TEST_RGB_ONLY); + + Wire.begin(I2C_SDA, I2C_SCL); + + if (rtc.begin()) + { + if (!rtc.isrunning()) { + rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); + } + DEBUG_PRINTLN("βœ“ RTC initialisiert"); + } + else + { + DEBUG_PRINTLN("❌ Keine RTC erkannt"); + } + + EEPROM.begin(EEPROM_SIZE); + + // Zuerst Default charsoap initialisieren + initCharsoap(); + + loadCharsoap(); + loadSpecialWords(); + loadMinuteLeds(); + loadSpecialWordInterval(); + + DEBUG_PRINT("\n╔════════════════════════════════╗\n"); + DEBUG_PRINT( "β•‘ PrΓΌfe WΓΆrter in Liste... β•‘\n"); + DEBUG_PRINT( "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n") + FastLED.setBrightness(80); + checkPattern(); + + loadConfig(); + loadDriftRate(); + loadOTAVersion(); + loadWiFiStationConfig(); + loadNTPConfig(); + loadAutoBrightnessConfig(); + + // ═══ STROMAUSFALL-PRÜFUNG ═══ + powerLossDetected = detectPowerLoss(); + + if (powerLossDetected) + { + powerLossLoop(); // Blinkt ROT bis Neustart + } + + // Wenn wir hier ankommen: Kein Stromausfall + // SETZE RUNNING-FLAG (System lΓ€uft jetzt) + setRunningFlag(); + + // User-Helligkeit (0-80) auf LED-Helligkeit (0-204) mappen + FastLED.setBrightness(map(brightness, 0, 80, 0, 204)); + + WiFi.mode(WIFI_AP_STA); // Dual-Mode: AP + Station + WiFi.softAP(AP_SSID, AP_PASSWORD); + + DEBUG_PRINTF("SSID: %s\n", AP_SSID); + DEBUG_PRINTF("IP: %s\n\n", WiFi.softAPIP().toString().c_str()); + + dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); + + // WiFi Station verbinden (wenn konfiguriert) + setupWiFiStation(); + + // NTP konfigurieren (wenn WiFi verbunden) + if (WiFi.status() == WL_CONNECTED) { + setupNTP(); + } + + server.on("/", handleRoot); + server.on("/save", handleSave); + server.on("/gettime", handleGetTime); + server.on("/getcharsoap", handleGetCharsoap); + server.on("/resetcharsoap", handleResetCharsoap); + server.on("/ledtest", handleLEDTest); + server.on("/patterntest", handlePatternTest); + server.on("/ota/info", handleOTAInfo); + server.on("/ota/upload", HTTP_POST, handleOTAUploadDone, handleOTAUpload); + server.on("/ota/url", handleOTAFromURL); + server.on("/ota/status", handleOTAStatus); + server.on("/wifi/config", handleGetWiFiConfig); + server.on("/wifi/save", handleSaveWiFiConfig); + server.on("/wifi/scan", handleWiFiScan); + server.on("/restart", handleRestart); + server.on("/autobrightness/config", handleGetAutoBrightness); + server.on("/autobrightness/save", handleSaveAutoBrightness); + server.on("/specialwords/get", handleGetSpecialWords); + server.on("/specialwords/save", handleSaveSpecialWords); + server.on("/specialwords/reset", handleResetSpecialWords); + server.on("/minuteleds/get", handleGetMinuteLeds); + server.on("/minuteleds/save", handleSaveMinuteLeds); + server.on("/minuteleds/reset", handleResetMinuteLeds); + server.onNotFound(handleNotFound); + server.begin(); + + apActive = true; + apStartTime = millis(); + + DEBUG_PRINTLN("βœ“ System bereit!\n"); + FastLED.clear(true); + FastLED.setBrightness(0); + yield(); + delay(1000); +} + +// ════════════════════════════════════════════════════════════════ +// AUTO-BRIGHTNESS MIT ADC (A0) +// ════════════════════════════════════════════════════════════════ +unsigned long lastBrightnessUpdate = 0; +const unsigned long brightnessUpdateInterval = 10000; // 2 Sekunden + +void updateBrightness() { + yield(); + // Nur alle 2 Sekunden aktualisieren (verhindert Flackern und spart CPU) + if (millis() - lastBrightnessUpdate < brightnessUpdateInterval) { + yield(); + return; + } + + if (!autoBrightnessEnabled) + { + return; // Funktion deaktiviert + } + + lastBrightnessUpdate = millis(); + return; + // ADC mehrfach auslesen und Mittelwert bilden (reduziert Rauschen) + uint32_t adcSum = 0; + const uint8_t samples = 10; + for (uint8_t i = 0; i < samples; i++) { + adcSum += analogRead(A0); + delay(1); // Kurze Pause zwischen Messungen + } + uint16_t adcValue = adcSum / samples; + + // Auf kalibrierten Bereich begrenzen + if (adcValue < autoBrightnessMinADC) adcValue = autoBrightnessMinADC; + if (adcValue > autoBrightnessMaxADC) adcValue = autoBrightnessMaxADC; + + // Linear auf autoBrightnessMin-autoBrightnessMax mappen + uint8_t newBrightness = map(adcValue, + autoBrightnessMinADC, + autoBrightnessMaxADC, + autoBrightnessMin, + autoBrightnessMax); + + // User-Helligkeit (0-80) auf LED-Helligkeit (0-204) mappen + // 80 = 100% User-Helligkeit entspricht 204 = 80% LED-Helligkeit + newBrightness = map(newBrightness, 0, 80, 0, 204); + + // Helligkeit setzen + FastLED.setBrightness(newBrightness); + + DEBUG_PRINTF("Auto-Brightness: ADC=%d β†’ Brightness=%d (Range: %d-%d)\n", + adcValue, newBrightness, autoBrightnessMin, autoBrightnessMax); +} + +void loop() +{ + if (apActive) + { + dnsServer.processNextRequest(); + + // LOGGING: Vor handleClient + if (server.client() && server.client().available()) + { + DEBUG_PRINTLN("β†’ Eingehender Request!"); + } + + server.handleClient(); + + // WiFi Station ΓΌberwachen (Auto-Reconnect) + checkWiFiConnection(); + + // NTP-Sync prΓΌfen (stΓΌndlich) + checkNTPSync(); + + #if defined(DEBUG_MODE) && (DEBUG_MODE == false) + #warning "WiFi AP: Timeout nur wenn keine Station konfiguriert" + // AP-Timeout Strategie (Produktionsmodus): + // - AP bleibt PERMANENT aktiv wenn WiFi Station konfiguriert ist (egal ob verbunden!) + // β†’ ErmΓΆglicht Neukonfiguration bei falschen Credentials + // - AP schaltet nach 5 Min ab NUR wenn KEINE Station konfiguriert ist + // β†’ Stromsparen im reinen AP-Modus + // + // WICHTIG: Zugriff fΓΌr Konfiguration muss IMMER mΓΆglich sein! + // Wenn staEnabled=true β†’ Benutzer will Dual-Mode β†’ AP muss erreichbar bleiben + + if (!staEnabled && millis() - apStartTime > AP_TIMEOUT) + { + WiFi.softAPdisconnect(true); + WiFi.mode(WIFI_OFF); + apActive = false; + DEBUG_PRINTLN("⚠ AP nach Timeout deaktiviert (kein Dual-Mode)"); + } + #else + #warning "DEBUG_MODE is true, so WiFi AP bleibt immer aktiv" + #endif + } + + // Auto-Brightness aktualisieren (jede Sekunde) + //updateBrightness(); + + // Zeit seit der letzten Anzeige prΓΌfen + if (millis() - lastUpdateTime >= updateInterval) + { + int hours, minutes, seconds; + getCurrentTime(hours, minutes, seconds); + if (minutes != lastDisplayedMinute) + { + // PrΓΌfe ob Spezialwort angezeigt werden soll (basierend auf Intervall) + if (shouldShowSpecialWord(minutes)) + { + showSpecialWordThenTime(hours, minutes); + } + else + { + displayTime(hours, minutes); + } + lastDisplayedMinute = minutes; + } + lastUpdateTime = millis(); + } + yield(); +}