From 4823f519b28c2c9f12ad168f76e19ab5b51de48a Mon Sep 17 00:00:00 2001 From: Thomas Kuschel Date: Tue, 20 Oct 2026 22:10:29 +0200 Subject: [PATCH 1/5] cli: add LoRa snapshot status, test-display command --- platformio.ini | 1 + src/cli.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/platformio.ini b/platformio.ini index 36486b9..03b76d2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -43,3 +43,4 @@ build_src_flags = -Wno-include-next ; Promotes all warnings to hard compilation errors to guarantee clean code ;-Werror + ;-D MATH_COMPARE=1 diff --git a/src/cli.cpp b/src/cli.cpp index 45189ff..1b52cc5 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -19,6 +19,7 @@ #include "esp_log.h" #include "config.hpp" #include "lora.hpp" +#include "display.hpp" static const char* TAG = "CLI"; static i2c* i2c_bus = nullptr; @@ -91,6 +92,7 @@ void process_command(const char* cmd) { printf(" freq - Set dynamic radio frequency (433 / 868 MHz bounds)\r\n"); printf(" bw <62|125|250|500>- Set dynamic LoRa bandwidth in kHz\r\n"); printf(" i2c - Scan the configured I2C bus infrastructure\r\n"); + printf(" test-display - Run a comprehensive visual test suite\r\n"); } else if (strcmp(cmd, "version") == 0) { printf("%s System - Running Version: %s\r\n", Config::PROJECT_TITLE, Config::PROJECT_VERSION); @@ -101,6 +103,23 @@ void process_command(const char* cmd) { printf("Mute Timer Period : %lu seconds\r\n", (unsigned long)current_mute_time_sec); printf("Target CPU Board : ESP32\r\n"); printf("ESP-IDF Version : %s\r\n", esp_get_idf_version()); + + if (lora_system != nullptr) { + // atomar, thread-safe copy of telemtry data (stack allocation) + LoraStateSnapshot snapshot = lora_system->getSnapshot(); + + printf("LoRa Hardware : %s\r\n", snapshot.isInitialized ? "INITIALIZED" : "FAILED / UNINITIALIZED"); + printf("LoRa Transceiver : Mode [%s]\r\n", snapshot.currentModeStr); + printf("LoRa Frequency : %lu Hz\r\n", (unsigned long)snapshot.frequencyHz); + printf("LoRa Bandwidth : %lu kHz\r\n", (unsigned long)snapshot.bandwidthKhz); + printf("LoRa SpreadingFact: SF%u\r\n", snapshot.spreadingFactor); + printf("LoRa Coding Rate : 4/%u\r\n", snapshot.codingRate); + printf("Packets Received : %lu\r\n", (unsigned long)snapshot.totalPacketsReceived); + printf("Packets Transmit : %lu\r\n", (unsigned long)snapshot.totalPacketsTransmitted); + printf("History In Memory : %zu Packets\r\n", snapshot.currentHistoryCount); + } else { + printf("LoRa Snapshot : Error (LoraController Handle uninitialized)\r\n"); + } } else if (strncmp(cmd, "mute ", 5) == 0) { int parsed_time = atoi(cmd + 5); @@ -220,6 +239,11 @@ void process_command(const char* cmd) { printf("Error: I2C Bus not initialized in CLI!\r\n"); } } + else if (strcmp(cmd, "test-display") == 0) { + printf("[CLI] Triggering asynchronous OLED font verification...\n"); + run_display_font_test(oled_display); + printf("[CLI] Test completed.\n"); + } else { printf("Unknown command: '%s'. Type 'help' for options.\r\n", cmd); } From 256fb9cdac307ff60213f40f8f112e7ac263388c Mon Sep 17 00:00:00 2001 From: Thomas Kuschel Date: Tue, 20 Oct 2026 22:11:59 +0200 Subject: [PATCH 2/5] display: utf8 table for 0xc3 and 0xc2, umlauts, etc. --- src/display.cpp | 247 +++++++++++++++++++++++++++++++++++++----------- src/display.hpp | 8 ++ 2 files changed, 202 insertions(+), 53 deletions(-) diff --git a/src/display.cpp b/src/display.cpp index c41aec4..5f71c64 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -116,26 +116,139 @@ static const uint8_t font_5x7[][5] = { {0x00, 0x00, 0x77, 0x00, 0x00}, // 124 | {0x00, 0x41, 0x36, 0x08, 0x00}, // 125 } {0x08, 0x08, 0x2a, 0x1c, 0x08}, // 126 ~ - // German Umlaute mappings -// {0x7d, 0x11, 0x11, 0x11, 0x7d}, // 127 Ä -// {0x3c, 0x41, 0x41, 0x41, 0x3c}, // 128 Ö -// {0x3d, 0x40, 0x40, 0x40, 0x3d}, // 129 Ü -// {0x20, 0x55, 0x54, 0x54, 0x78}, // 130 ä -// {0x38, 0x54, 0x54, 0x54, 0x38}, // 131 ö -// {0x3c, 0x40, 0x40, 0x20, 0x7c}, // 132 ü -// {0x7f, 0x49, 0x49, 0x36, 0x20} // 133 ß - // new German Umlaute mapping (5x7/5x8 Font Matrix) - {0,0,0,0,0}, //127 not allowed - {0x7d, 0x13, 0x13, 0x13, 0x7d}, // 128 Ä - {0x3c, 0x43, 0x43, 0x43, 0x3c}, // 129 Ö - {0x3d, 0x42, 0x42, 0x42, 0x3d}, // 130 Ü - {0x20, 0x55, 0x54, 0x54, 0x78}, // 131 ä - {0x38, 0x55, 0x55, 0x55, 0x38}, // 132 ö - {0x3c, 0x42, 0x42, 0x22, 0x7c}, // 133 ü - {0x7f, 0x49, 0x49, 0x36, 0x00}, // 134 ß (Letzte Spalte auf 0x00 bereinigt) -// {0x3c, 0x42, 0x51, 0x4a, 0x34} // 134 © Copyright -// {0x3c, 0x42, 0x4d, 0x4d, 0x32} // 135 - {0x3c, 0x42, 0x95, 0x95, 0x52} + {0x00, 0x00, 0x00, 0x00, 0x00}, // 127 DEL (Control character) + + // --- UTF-8 0xC2 RANGE (ISO-8859-1 Indizes 128 to 191) --- + {0x00, 0x00, 0x00, 0x00, 0x00}, // 128 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 129 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 130 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 131 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 132 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 133 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 134 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 135 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 136 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 137 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 138 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 139 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 140 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 141 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 142 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 143 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 144 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 145 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 146 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 147 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 148 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 149 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 150 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 151 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 152 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 153 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 154 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 155 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 156 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 157 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 158 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 159 Padding (Control character) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 160 Non-breaking space + {0x00, 0x00, 0x2f, 0x00, 0x00}, // 161 Inverted exclamation mark (¡) + {0x43, 0x4c, 0x78, 0x4c, 0x43}, // 162 Cent sign (¢) + {0x18, 0x7c, 0x14, 0x3c, 0x54}, // 163 Pound sign (£) + {0x6c, 0x6c, 0x6c, 0x6c, 0x6c}, // 164 Currency sign (¤) + {0x30, 0x78, 0x4d, 0x78, 0x30}, // 165 Yen sign (¥) + {0x00, 0x00, 0x77, 0x00, 0x00}, // 166 Broken bar (¦) + {0x00, 0x4d, 0x4d, 0x6c, 0x00}, // 167 Section sign / Paragraph (§) + {0x00, 0x00, 0x01, 0x00, 0x00}, // 168 Diaeresis (¨) + {0x00, 0x15, 0x16, 0x7c, 0x14}, // 169 Copyright sign (©) + {0x00, 0x42, 0x7b, 0x42, 0x42}, // 170 Feminine ordinal indicator (ª) + {0x08, 0x14, 0x2a, 0x14, 0x22}, // 171 Left-pointing double-angle quotation mark («) + {0x04, 0x04, 0x04, 0x04, 0x1c}, // 172 Not sign (¬) + {0x00, 0x08, 0x08, 0x08, 0x00}, // 173 Soft hyphen + {0x00, 0x14, 0x14, 0x14, 0x14}, // 174 Registered sign (®) + {0x00, 0x02, 0x02, 0x02, 0x02}, // 175 Macron (¯) + {0x00, 0x03, 0x03, 0x00, 0x00}, // 176 Degree sign (°) + {0x44, 0x44, 0x5f, 0x44, 0x44}, // 177 Plus-minus sign (±) + {0x00, 0x00, 0x07, 0x04, 0x04}, // 178 Superscript two (²) + {0x00, 0x00, 0x05, 0x05, 0x07}, // 179 Superscript three (³) + {0x02, 0x01, 0x02, 0x04, 0x00}, // 180 Acute accent (´) + {0x30, 0x48, 0x30, 0x08, 0x06}, // 181 Micro sign (µ) + {0x00, 0x6e, 0x6e, 0x6e, 0x00}, // 182 Pilcrow sign / Paragraph sign (¶) + {0x00, 0x00, 0x04, 0x00, 0x00}, // 183 Middle dot (·) + {0x00, 0x40, 0x44, 0x40, 0x40}, // 184 Cedilla (¸) + {0x00, 0x00, 0x01, 0x00, 0x00}, // 185 Superscript one (¹) + {0x00, 0x44, 0x7d, 0x40, 0x00}, // 186 Masculine ordinal indicator (º) + {0x22, 0x14, 0x2a, 0x14, 0x08}, // 187 Right-pointing double-angle quotation mark (») + {0x1c, 0x22, 0x49, 0x49, 0x30}, // 188 Vulgar fraction one quarter (¼) + {0x38, 0x44, 0x54, 0x24, 0x18}, // 189 Vulgar fraction one half (½) + {0x00, 0x00, 0x00, 0x00, 0x00}, // 190 Vulgar fraction three quarters (¾) + {0x40, 0x00, 0x45, 0x06, 0x00}, // 191 Inverted question mark (¿) + + // --- UTF-8 0xC3 RANGE (ISO-8859-1 Indizes 192 to 255) --- + {0x7c, 0x12, 0x11, 0x12, 0x7c}, // 192 Capital A with grave (À) + {0x7c, 0x11, 0x12, 0x14, 0x78}, // 193 Capital A with acute (Á) + {0x7c, 0x15, 0x16, 0x14, 0x7c}, // 194 Capital A with circumflex (Â) + {0x7c, 0x16, 0x19, 0x16, 0x7c}, // 195 Capital A with tilde (Ã) + {0x7c, 0x11, 0x13, 0x11, 0x7c}, // 196 Capital A with umlaut (Ä) + {0x7c, 0x11, 0x15, 0x11, 0x7c}, // 197 Capital A with ring above (Å) + {0x7e, 0x51, 0x7f, 0x41, 0x73}, // 198 Capital AE ligature (Æ) + {0x3e, 0x41, 0x49, 0x22, 0x10}, // 199 Capital C with cedilla (Ç) + {0x7f, 0x4a, 0x49, 0x4a, 0x41}, // 200 Capital E with grave (È) + {0x7f, 0x49, 0x4a, 0x4c, 0x40}, // 201 Capital E with acute (É) + {0x7f, 0x4d, 0x4e, 0x4d, 0x41}, // 202 Capital E with circumflex (Ê) + {0x7f, 0x49, 0x4b, 0x49, 0x41}, // 203 Capital E with umlaut (Ë) + {0x00, 0x42, 0x7d, 0x40, 0x00}, // 204 Capital I with grave (Ì) + {0x00, 0x40, 0x7d, 0x42, 0x00}, // 205 Capital I with acute (Í) + {0x00, 0x46, 0x7d, 0x46, 0x00}, // 206 Capital I with circumflex (Î) + {0x00, 0x45, 0x7d, 0x45, 0x00}, // 207 Capital I with umlaut (Ï) + {0x7f, 0x41, 0x41, 0x22, 0x1c}, // 208 Capital Eth (Ð) + {0x7f, 0x05, 0x06, 0x0c, 0x7f}, // 209 Capital N with tilde (Ñ) + {0x3e, 0x42, 0x41, 0x42, 0x3e}, // 210 Capital O with grave (Ò) + {0x3e, 0x40, 0x41, 0x42, 0x3c}, // 211 Capital O with acute (Ó) + {0x3e, 0x44, 0x45, 0x44, 0x3e}, // 212 Capital O with circumflex (Ô) + {0x3e, 0x45, 0x46, 0x45, 0x3e}, // 213 Capital O with tilde (Õ) + {0x3e, 0x41, 0x43, 0x41, 0x3e}, // 214 Capital O with umlaut (Ö) + {0x22, 0x14, 0x08, 0x14, 0x22}, // 215 Multiplication sign (×) + {0x2e, 0x41, 0x41, 0x41, 0x3a}, // 216 Capital O with stroke (Ø) + {0x3f, 0x42, 0x41, 0x40, 0x3f}, // 217 Capital U with grave (Ù) + {0x3f, 0x40, 0x41, 0x42, 0x3e}, // 218 Capital U with acute (Ú) + {0x3f, 0x44, 0x45, 0x44, 0x3f}, // 219 Capital U with circumflex (Û) + {0x3f, 0x41, 0x43, 0x41, 0x3f}, // 220 Capital U with umlaut (Ü) + {0x03, 0x08, 0x70, 0x0c, 0x03}, // 221 Capital Y with acute (Ý) + {0x7f, 0x49, 0x09, 0x09, 0x06}, // 222 Capital Thorn (Þ) + {0x7f, 0x49, 0x49, 0x36, 0x00}, // 223 Small sharp s / Eszett (ß) + {0x20, 0x56, 0x55, 0x56, 0x78}, // 224 Small a with grave (à) + {0x20, 0x55, 0x56, 0x58, 0x70}, // 225 Small a with acute (á) + {0x20, 0x59, 0x5a, 0x58, 0x78}, // 226 Small a with circumflex (â) + {0x20, 0x5a, 0x5d, 0x5a, 0x78}, // 227 Small a with tilde (ã) + {0x20, 0x55, 0x57, 0x55, 0x78}, // 228 Small a with umlaut (ä) + {0x20, 0x55, 0x59, 0x55, 0x78}, // 229 Small a with ring above (å) + {0x24, 0x54, 0x78, 0x54, 0x5c}, // 230 Small ae ligature (æ) + {0x38, 0x44, 0x4c, 0x54, 0x24}, // 231 Small c with cedilla (ç) + {0x38, 0x56, 0x55, 0x56, 0x18}, // 232 Small e with grave (è) + {0x38, 0x55, 0x56, 0x58, 0x10}, // 233 Small e with acute (é) + {0x38, 0x59, 0x5a, 0x58, 0x18}, // 234 Small e with circumflex (ê) + {0x38, 0x55, 0x57, 0x55, 0x18}, // 235 Small e with umlaut (ë) + {0x00, 0x45, 0x7e, 0x40, 0x00}, // 236 Small i with grave (ì) + {0x00, 0x40, 0x7e, 0x45, 0x00}, // 237 Small i with acute (í) + {0x00, 0x49, 0x7e, 0x49, 0x00}, // 238 Small i with circumflex (î) + {0x00, 0x46, 0x7e, 0x46, 0x00}, // 239 Small i with umlaut (ï) + {0x38, 0x44, 0x44, 0x48, 0x7f}, // 240 Small eth (ð) + {0x7c, 0x09, 0x0a, 0x04, 0x78}, // 241 Small n with tilde (ñ) + {0x38, 0x46, 0x45, 0x46, 0x38}, // 242 Small o with grave (ò) + {0x38, 0x45, 0x46, 0x48, 0x30}, // 243 Small o with acute (ó) + {0x38, 0x4a, 0x4b, 0x4a, 0x38}, // 244 Small o with circumflex (ô) + {0x38, 0x4b, 0x4c, 0x4b, 0x38}, // 245 Small o with tilde (õ) + {0x38, 0x45, 0x47, 0x45, 0x38}, // 246 Small o with umlaut (ö) + {0x08, 0x08, 0x2a, 0x08, 0x08}, // 247 Division sign (÷) + {0x38, 0x44, 0x44, 0x44, 0x38}, // 248 Small o with stroke (ø) + {0x3c, 0x42, 0x41, 0x20, 0x7c}, // 249 Small u with grave (ù) + {0x3c, 0x40, 0x41, 0x22, 0x78}, // 250 Small u with acute (ú) + {0x3c, 0x44, 0x45, 0x24, 0x7c}, // 251 Small u with circumflex (û) + {0x3c, 0x41, 0x43, 0x21, 0x7c}, // 252 Small u with umlaut (ü) + {0x0c, 0x50, 0x52, 0x50, 0x3c}, // 253 Small y with acute (ý) + {0x7f, 0x14, 0x14, 0x08, 0x04}, // 254 Small thorn (þ) + {0x0c, 0x51, 0x53, 0x51, 0x3c} // 255 Small y with umlaut (ÿ) }; /** @@ -335,56 +448,48 @@ void DisplayController::run() { bool validCharacter = false; const size_t current_char_idx = i; // Store original index for connection logic - // Basic UTF-8 multi-byte decoder for native German Umlaute translation + // Multi-byte UTF-8 decoder targeting the complete ISO-8859-1 (Latin-1) spectrum if ((c_primary == 0xC3 || c_primary == 0xC2) && (i + 1) < textLength) { const uint8_t c_secondary = static_cast(incoming.content[++i]); - switch (c_secondary) { - // 0xC3 Multi-byte compound tokens (fontIndex 128 - 133) - case 0x84: fontIndex = 128; validCharacter = true; break; // Ä - case 0x96: fontIndex = 129; validCharacter = true; break; // Ö - case 0x9C: fontIndex = 130; validCharacter = true; break; // Ü - case 0xA4: fontIndex = 131; validCharacter = true; break; // ä - case 0xB6: fontIndex = 132; validCharacter = true; break; // ö - case 0xBC: fontIndex = 133; validCharacter = true; break; // ü - // 0xC2 Multi-byte compound tokens (fontIndex 134 - 135) - case 0x9F: fontIndex = 134; validCharacter = true; break; // ß - case 0xA9: fontIndex = 135; validCharacter = true; break; // © - default: i--; // Rollback pointer if multi-byte token was invalid - } - } else if (c_primary >= 32 && c_primary <= 126) { + // Mathematical reconstruction of the raw Latin-1 byte position + // 0xC3 sequence maps to 0xC0-0xFF (c_secondary + 0x40) + // 0xC2 sequence maps directly to 0x80-0xBF + uint8_t iso_char = (c_primary == 0xC3) ? (c_secondary + 0x40) : c_secondary; + + // Subtract table offset since font array starts at index 32 (Space) + fontIndex = static_cast(iso_char - 32); + validCharacter = true; + } + // Handle standard printable 7-bit ASCII characters + else if (c_primary >= 32 && c_primary <= 126) { fontIndex = static_cast(c_primary - 32); validCharacter = true; } if (validCharacter && totalBytesWritten < 122) { uint8_t txBuffer[7]; + // copy all 5 font columns in a single, hardware-accelerated burst txBuffer[0] = 0x40; // Data identifier - txBuffer[1] = font_5x7[fontIndex][0]; - txBuffer[2] = font_5x7[fontIndex][1]; - txBuffer[3] = font_5x7[fontIndex][2]; - txBuffer[4] = font_5x7[fontIndex][3]; - txBuffer[5] = font_5x7[fontIndex][4]; + // txBuffer[1] = font_5x7[fontIndex][0]; + // txBuffer[2] = font_5x7[fontIndex][1]; + // txBuffer[3] = font_5x7[fontIndex][2]; + // txBuffer[4] = font_5x7[fontIndex][3]; + // txBuffer[5] = font_5x7[fontIndex][4]; + std::memcpy(&txBuffer[1], font_5x7[fontIndex], 5); // Check if current character is a potential line segment if (c_primary == '-' || c_primary == '_' || c_primary == '=') { - bool connect = false; + // Look behind: Direct boolean evaluation in a single line + bool connect = (current_char_idx > 0 && incoming.content[current_char_idx - 1] == static_cast(c_primary)); - // Look behind: Check if preceding byte was an identical line segment - if (current_char_idx > 0) { - const char prev = incoming.content[current_char_idx - 1]; - if (prev == static_cast(c_primary)) connect = true; - } - - // Look ahead: Check if succeeding byte is an identical line segment - // Since 'i' was updated by multi-byte parsing, we look ahead from the current 'i' - if (!connect && (i + 1) < textLength) { - const char next = incoming.content[i + 1]; - if (next == static_cast(c_primary)) connect = true; + // Look ahead: Only checks if the look-behind didn't already establish a connection + if (!connect && (i + 1) < textLength && incoming.content[i + 1] == static_cast(c_primary)) { + connect = true; } // Seamlessly bridge the 1-pixel gap if part of a multi-character line sequence - txBuffer[6] = connect ? font_5x7[fontIndex][4] : 0x00; + txBuffer[6] = connect ? txBuffer[5] : 0x00; } else { txBuffer[6] = 0x00; // Standard 1-pixel spacing for regular text elements } @@ -411,3 +516,39 @@ void DisplayController::run() { } } } + +/** + * @brief Run a comprehensive visual test suite and clear the screen after 5 seconds. + * @param display Pointer to the active DisplayController instance. + */ +void run_display_font_test(DisplayController* display) { + if (display == nullptr) return; + + // Test 1: Standard printable ASCII characters (Lines 0 and 1) + display->printMessage(0, "=== FONT TEST ===", 12); + display->printMessage(1, "ASCII: abcXYZ 123!?", 0); + + // Test 2: Native German Umlaute via multi-byte UTF-8 (Lines 2, 3 and 4) + display->printMessage(2, "Umlaute: AE-OE-UE-ss", 0); + display->printMessage(3, "Outputs: Ä Ö Ü ß", 0); + display->printMessage(4, "Outputs: ä ö ü", 0); + + // Test 3: Newly supported Latin-1 symbols (Line 5) + display->printMessage(5, "Symbols: 23°C © 2026", 0); + + // Test 4: Dynamic line-segment connection logic (Lines 6 and 7) + display->printMessage(6, "--------------------", 0); + display->printMessage(7, "Line test: ___ === _", 0); + + // Let the user look at the test pattern for 60 seconds + vTaskDelay(pdMS_TO_TICKS(60000)); + // Print a quick shutdown notice to the CLI console before restarting + printf("\r\n[SYSTEM] Visual test completed. Triggering hardware reset now...\n"); + vTaskDelay(pdMS_TO_TICKS(1000)); + // Perform a clean software-controlled hardware restart of the ESP32 + esp_restart(); + // Clear all lines by printing empty strings (triggers Auto-Clear) + // for (uint8_t line = 0; line < 8; line++) { + // display->printMessage(line, "", 0); + // } +} diff --git a/src/display.hpp b/src/display.hpp index 8384e86..d65e43a 100644 --- a/src/display.hpp +++ b/src/display.hpp @@ -48,6 +48,7 @@ public: printMessage(static_cast(line | 0x80), text, startX); } + private: // Internal structure mapping the safe asynchronous data pipeline struct DisplayMessage { @@ -67,3 +68,10 @@ private: esp_err_t sendCommand(uint8_t cmd) const; esp_err_t initializeHardware(); }; + +/** + * @brief Run a comprehensive visual test suite and clear the screen after 5 seconds. + * Note: This is a global helper function decoupled from the main class interface. + * @param display Pointer to the active DisplayController instance. + */ +void run_display_font_test(DisplayController* display); From 618ff87d5ce246ffb20bf0f054c59cf3ebb2ec40 Mon Sep 17 00:00:00 2001 From: Thomas Kuschel Date: Tue, 20 Oct 2026 22:13:38 +0200 Subject: [PATCH 3/5] lora: race condition removed, preparation for rx, getSnapshot, beautify led and i2c files --- src/config.hpp | 6 -- src/i2c.hpp | 4 +- src/led.hpp | 4 +- src/lora.cpp | 231 +++++++++++++++++++++++++++++++++++++-------- src/lora.hpp | 16 ++++ src/lora_types.hpp | 19 ++++ 6 files changed, 230 insertions(+), 50 deletions(-) diff --git a/src/config.hpp b/src/config.hpp index 693cd26..ed9482a 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -72,12 +72,6 @@ namespace Config { constexpr uint8_t LORA_PREAMBLE_LENGTH_MSB = 0x00; constexpr uint8_t LORA_PREAMBLE_LENGTH_LSB = 0x0C; // Semtech Factor Default is 0x08, MeshCom with 0x0C, i.e. 12 symbols - // Modem Config 1: BW 250kHz (0x80), CR 4/6 (0x04), Explicit Header (0x00) -> 0x84 - // constexpr uint8_t LORA_MODEM_CONFIG_1 = 0x84; // @TODO: will be removed in v1.1 - - // Modem Config 2: SF11 (0xB0), TX Continuous off (0x00), CRC On (0x04) -> 0xB4 - // constexpr uint8_t LORA_MODEM_CONFIG_2 = 0xB4; // @TODO: will be removed in v1.1 - // --- DYNAMIC COMPILE-TIME BIT-SHIFTING EXPRESSIONS --- // Helper to map bandwidth kHz to Semtech Register Bits (7-4 of RegModemConfig1) diff --git a/src/i2c.hpp b/src/i2c.hpp index fafd0ae..24abe05 100644 --- a/src/i2c.hpp +++ b/src/i2c.hpp @@ -2,7 +2,7 @@ * @file i2c.hpp * @brief Thread-safe I2C Master Bus Wrapper for FoxMesh * @author OM Tom (OE3TKT) - * + * * @license SPDX-License-Identifier: MIT * @copyright Copyright (c) 2026 OM Tom (OE3TKT) * For the full license terms, see the LICENSE file in the project root. @@ -25,7 +25,7 @@ public: /** * @brief Scans the network space for active I2C addresses. - * + * * Probes the hardware address space from 0x03 to 0x77 using low-overhead * commands and prints a formatted matrix directly to stdout. */ diff --git a/src/led.hpp b/src/led.hpp index 352557f..c03f198 100644 --- a/src/led.hpp +++ b/src/led.hpp @@ -2,7 +2,7 @@ * @file led.hpp * @brief Thread-safe hardware LED controller with dynamic profile configuration * @author OM Tom (OE3TKT) - * + * * @license SPDX-License-Identifier: MIT * @copyright Copyright (c) 2026 OM Tom (OE3TKT) * For the full license terms, see the LICENSE file in the project root. @@ -46,7 +46,7 @@ public: private: const gpio_num_t m_gpioPin; static const char* TAG; - + // Track the internal task handle to allow cross-thread signaling via notifications TaskHandle_t m_taskHandle = nullptr; diff --git a/src/lora.cpp b/src/lora.cpp index ebe481c..0189354 100644 --- a/src/lora.cpp +++ b/src/lora.cpp @@ -141,31 +141,35 @@ esp_err_t LoraController::initializeHardware() { return err; } + // Configure interrupt triggering on the rising edge (PosEdge) err = gpio_set_intr_type(Config::LORA_IRQ_PIN, GPIO_INTR_POSEDGE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set GPIO interrupt type: %s", esp_err_to_name(err)); + return err; + } + // Bind the static ISR handler to the physical pin context err = gpio_isr_handler_add(Config::LORA_IRQ_PIN, LoraController::gpioIsrHandler, m_isrSemaphore); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register GPIO ISR handler: %s", esp_err_to_name(err)); + return err; + } - return err; + // MARK STATE MATRIX READY (Thread-Safe Status Update) + // We use a safe timeout here. If the mutex is permanently blocked during boot, + // we must escalate this as a system fault instead of returning ESP_OK. + if (xSemaphoreTake(m_stateMutex, pdMS_TO_TICKS(100)) == pdTRUE) { + m_state.isInitialized = true; + m_state.frequencyHz = Config::LORA_DEFAULT_FREQ_HZ; + xSemaphoreGive(m_stateMutex); + } else { + ESP_LOGE(TAG, "Fatal: State mutex acquisition timed out during hardware registration."); + return ESP_ERR_TIMEOUT; // Escalate the mutex block as a hard boot failure + } + + ESP_LOGI(TAG, "SX1276 LoRa transceiver attached successfully via SPI (Version: 0x%02X).", version); + return ESP_OK; } -/* -void LoraController::setModeReceiver() { - writeRegister(0x01, 0x80U); // Sleep + LoRa Mode - vTaskDelay(pdMS_TO_TICKS(10)); - - // Apply MeshCom network profile: 433.175 MHz, BW 250kHz, SF11, CR 4/6 - writeRegister(0x06, Config::LORA_FRF_MSB); - writeRegister(0x07, Config::LORA_FRF_MID); - writeRegister(0x08, Config::LORA_FRF_LSB); - - writeRegister(0x1D, Config::LORA_MODEM_CONFIG_1); // RegModemConfig1 - writeRegister(0x1E, Config::LORA_MODEM_CONFIG_2); // RegModemConfig2 - - writeRegister(0x40, 0x00U); // DIO0 map onto RxDone - writeRegister(0x01, 0x85U); // RXCONTINUOUS - ESP_LOGI(TAG, "Radio state changed to RXCONTINUOUS"); -} -*/ - void LoraController::setModeReceiver() { // DIO0 auf RxDone mappen (Bit 7-6 auf 00) writeRegister(0x40, 0x00U); @@ -185,14 +189,18 @@ void LoraController::applyModemConfig() { writeRegister(0x01, 0x80U); vTaskDelay(pdMS_TO_TICKS(10)); + + // Apply RF carrier center frequency (e.g., MeshCom 433.175 MHz) writeRegister(0x06, Config::LORA_FRF_MSB); writeRegister(0x07, Config::LORA_FRF_MID); writeRegister(0x08, Config::LORA_FRF_LSB); + // just write the m_state frequency.... m_state.frequencyHz = Config::LORA_DEFAULT_FREQ_HZ; // @TODO: remove this line after implementation of the doubly linked list + // Configure modem bandwidth, spreading factor, and coding rate writeRegister(0x1D, Config::LORA_MODEM_CONFIG_1); writeRegister(0x1E, Config::LORA_MODEM_CONFIG_2); @@ -211,27 +219,76 @@ void LoraController::applyModemConfig() { ESP_LOGI(TAG, "Radio profile applied: 433.175 MHz | SF11 | BW 250kHz | SyncWord 0x34"); } -/* -void LoraController::setFrequency(uint8_t msb, uint8_t mid, uint8_t lsb) { - if (m_spiHandle == nullptr) return; - // Safely force transceiver to Standby to update hardware registers safely - writeRegister(0x01, 0x81U); - vTaskDelay(pdMS_TO_TICKS(5)); +#ifndef MATH_COMPARE + #define MATH_COMPARE 0 +#endif - writeRegister(0x06, msb); - writeRegister(0x07, mid); - writeRegister(0x08, lsb); - - // Re-engage Continous Reception Mode - writeRegister(0x01, 0x85U); - ESP_LOGI(TAG, "Frequency dynamically shifted to: 0x%02X%02X%02X", msb, mid, lsb); -} - */ void LoraController::setFrequency(uint32_t freq_hz) { - // @TODO + if (m_spiHandle == nullptr) return; + + if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) { + m_state.frequencyHz = freq_hz; + + // 1. Fetch current mode and shift transceiver into Standby (0x01) + // Changing RF carrier registers is strictly prohibited while actively radiating or sampling + const uint8_t previousMode = readRegister(0x01); // RegOpMode + writeRegister(0x01, static_cast((previousMode & 0xF8U) | 0x01U)); + vTaskDelay(pdMS_TO_TICKS(2)); + + // 2. High-speed fractional frequency calculation + // Formula optimized from: (freq_hz * 2^19) / 32,000,000 b/c 32,000,000 can be seen as 2^11 * 5^6: + // we see freq_hz * 2^(19-11)/5^6 which is: freq_hz * 2^8 / 15625, i.e. + // reduced to exact fraction: 256 / 15,625 + // or even more using shift operation, we can switch on a compare of all three methods with #MATH_COMPARE 1 + + #if (MATH_COMPARE == 1) + // ================================================================= + // BENCHMARK MODE (Runs all 3 variants and measures CPU cycles) + // ================================================================= + uint32_t start_cycles = esp_cpu_get_cycle_count(); + [[maybe_unused]] volatile uint32_t frf_long = static_cast((static_cast(freq_hz) << 19) / 32000000ULL); + uint32_t cycles_long = esp_cpu_get_cycle_count() - start_cycles; + + start_cycles = esp_cpu_get_cycle_count(); + [[maybe_unused]] volatile uint32_t frf_64bit = static_cast((static_cast(freq_hz) * 256ULL) / 15625ULL); + uint32_t cycles_64bit = esp_cpu_get_cycle_count() - start_cycles; + + start_cycles = esp_cpu_get_cycle_count(); + volatile uint32_t frf_shift = static_cast((static_cast(freq_hz) * 1125899907ULL) >> 36); + uint32_t cycles_shift = esp_cpu_get_cycle_count() - start_cycles; + + printf("[MATH_COMPARE] Freq: %lu Hz -> long 64bit: %lu cycles | 64bit: %lu cycles | Shift: %lu cycles\n", + freq_hz, cycles_long, cycles_64bit, cycles_shift); + + const uint32_t frf = frf_shift; // Fallback to the safe bit-shift variant + #else + // ================================================================= + // PRODUCTION MODE (Highly optimized, zero benchmark overhead) + // ================================================================= + const uint32_t frf = static_cast((static_cast(freq_hz) * 1125899907ULL) >> 36); + #endif + + + // 3. Shift the 24-bit frequency tracking token across the three consecutive hardware registers + writeRegister(0x06, static_cast((frf >> 16) & 0xFFU)); // RegFrfMsb + writeRegister(0x07, static_cast((frf >> 8) & 0xFFU)); // RegFrfMid + writeRegister(0x08, static_cast(frf & 0xFFU)); // RegFrfLsb + + // 4. Restore previous operational transceiver path configuration + writeRegister(0x01, previousMode); + xSemaphoreGive(m_stateMutex); + + // 5. Force state synchronization and PLL locking verification + setModeReceiver(); + + // High-priority diagnosis logging bypasses CLI lock buffers uncensored + printf("\r\n[LoRa HW] Frequency successfully shifted to %lu Hz (FRF Word: 0x%06X)\r\n", + (unsigned long)freq_hz, (unsigned int)(frf & 0xFFFFFFU)); + fflush(stdout); } +} void LoraController::setBandwidth(uint32_t bw_khz) { if (m_spiHandle == nullptr) return; @@ -316,6 +373,18 @@ void LoraController::calculateTelemetry(uint8_t rawRssi, uint8_t rawSnr, float& } void LoraController::run() { + // Lambda Object: + auto utf8_advance = [](const std::string& str, size_t start_byte, size_t num_chars) -> size_t { + size_t i = start_byte; + size_t chars_counted = 0; + while (i < str.length() && chars_counted < num_chars) { + if (((unsigned char)str[i] & 0xC0) != 0x80) chars_counted++; + i++; + } + while (i < str.length() && ((unsigned char)str[i] & 0xC0) == 0x80) i++; + return i; + }; + if (initializeHardware() != ESP_OK) { ESP_LOGE(TAG, "Critical: LoRa init failed. Thread aborted."); return; @@ -328,7 +397,9 @@ void LoraController::run() { ESP_LOGI(TAG, "LoRa RX was initialized and is running"); while (true) { - if (xSemaphoreTake(m_isrSemaphore, portMAX_DELAY) == pdTRUE) { + // CHANGE: wait a maximum of 500 ms instead of infinity long + if (xSemaphoreTake(m_isrSemaphore, pdMS_TO_TICKS(500)) == pdTRUE) { + // if (xSemaphoreTake(m_isrSemaphore, portMAX_DELAY) == pdTRUE) { uint32_t irqFlags = readRegister(0x12); writeRegister(0x12, static_cast(irqFlags)); @@ -368,7 +439,6 @@ void LoraController::run() { printf("%c", c); else printf("~"); // not printable chars - } printf("\"\r\n Hex : "); for (uint8_t i = 0; i < packet.length; i++) @@ -380,11 +450,39 @@ void LoraController::run() { // FIXED: Uses the new parameter order (line first, text second) if (display != nullptr) { - char displayBuffer[16]; - snprintf(displayBuffer, sizeof(displayBuffer), "LoRa rx #%03u", (unsigned int)(packetCounter % 1000)); + char displayBuffer[85]; // worst case 21 chars with each 4 bytes + Nullbyte. + + snprintf(displayBuffer, sizeof(displayBuffer), "#%03u %4.1fdBm %2.1fdB", + (unsigned int)(packetCounter % 1000), + packet.rssi, + packet.snr); display->printMessage(2, displayBuffer); + // we don't care about UTF-8 for now + std::string text(reinterpret_cast(packet.payload), static_cast(packet.length)); + + size_t byte_pos = 0; + uint8_t linenr = 3; // start at line 3 + + while ( byte_pos < text.length() && linenr < 8) { + // The '::' ensures that the global/local function is called. + size_t next_byte_pos = utf8_advance(text, byte_pos, 21); + std::string line = text.substr(byte_pos, next_byte_pos - byte_pos); + + std::strncpy(displayBuffer, line.c_str(), sizeof(displayBuffer ) - 1); + displayBuffer[sizeof(displayBuffer) - 1] ='\0'; + + display->printMessage(linenr++, displayBuffer); + byte_pos = next_byte_pos; + } } } + + // New: after work or after 500ms delay, the chip has usually changed its mode, we update the state for the m_state: + uint8_t currentHardwareMode = readRegister(0x01); + if (xSemaphoreTake(m_stateMutex, pdMS_TO_TICKS(10)) == pdTRUE) { + m_state.currentMode = currentHardwareMode; + xSemaphoreGive(m_stateMutex); + } } } } @@ -400,3 +498,56 @@ void LoraController::startTask(uint32_t priority) { 1 ); } + +/** + * @brief Generates an atomic, high-performance runtime snapshot of configuration parameters and metrics. + */ +LoraStateSnapshot LoraController::getSnapshot() { + LoraStateSnapshot snapshot = {}; + + // Sichere die thread-übergreifende Konsistenz mittels Mutex ab + if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) { + + // Kopiere die primitiven Werte aus dem internen Zustandstabellen-Struct (m_state) + snapshot.frequencyHz = m_state.frequencyHz; + snapshot.bandwidthKhz = m_state.bandwidthKhz; + snapshot.totalPacketsReceived = m_state.totalPacketsReceived; + snapshot.totalPacketsTransmitted = m_state.totalPacketsTransmitted; + snapshot.spreadingFactor = m_state.spreadingFactor; + snapshot.codingRate = m_state.codingRate; + snapshot.isInitialized = m_state.isInitialized; + + // Zähle die Elemente in der Liste, ohne die schweren Payload-Vektoren zu kopieren + // TODO install the package list: + // snapshot.currentHistoryCount = m_state.packetList.size(); + + // Read Register 0x01 ONLY ONCE to avoid SPI race conditions + // uint8_t rohofmode = readRegister(0x01); + // uint8_t rawMode = rohofmode & 0x07U; + // use the cached mode from RAM instead live register read + uint8_t rawMode = m_state.currentMode & 0x07U; + + // Print the real, unmanipulated hex value safely + //printf("Readregister actual raw hex: 0x%02X\r\n", rohofmode); + //fflush(stdout); + + static const char* const MODE_STRINGS[] = { + "SLEEP", // 0x00 + "STANDBY", // 0x01 + "FSTX", // 0x02 + "TX", // 0x03 + "FSRX", // 0x04 + "RXCONTINUOUS", // 0x05 + "RXSINGLE", // 0x06 + "CAD" // 0x07 + }; + + strncpy(snapshot.currentModeStr, MODE_STRINGS[rawMode], sizeof(snapshot.currentModeStr) - 1); + snapshot.currentModeStr[sizeof(snapshot.currentModeStr) - 1] = '\0'; // guarantee NULL termination + + // Release mutex immediately + xSemaphoreGive(m_stateMutex); + } + + return snapshot; +} diff --git a/src/lora.hpp b/src/lora.hpp index 2fdeada..1f03ca0 100644 --- a/src/lora.hpp +++ b/src/lora.hpp @@ -16,6 +16,9 @@ #include "freertos/semphr.h" #include "freertos/task.h" #include "lora_types.hpp" +#include +#include +#include /** @@ -76,6 +79,17 @@ public: */ void setBandwidth(uint32_t bw_khz); + /** + * @brief Generates an atomic, high-performance runtime snapshot of configuration parameters and metrics. + * @details This routine performs a rapid, thread-safe memory copy of the volatile tracking state variables. + * Unlike getPacketListCopy(), this method explicitly excludes the heavy heap-allocated packet list data, + * making it exceptionally lightweight and highly suitable for high-frequency UI display refreshes, + * CLI updates, or periodic remote health diagnostics. + * @note Acquires the internal synchronization mutex lock momentarily to guarantee structural telemetry alignment. + * @return A self-contained snapshot structure mapping the current system hardware configurations and operational counts. + */ + LoraStateSnapshot getSnapshot(); + private: static const char* TAG; @@ -110,4 +124,6 @@ private: */ void calculateTelemetry(uint8_t rawRssi, uint8_t rawSnr, float& outRssi, float& outSnr); + size_t utf8_advance(const std::string& str, size_t start_byte, size_t num_chars); + }; diff --git a/src/lora_types.hpp b/src/lora_types.hpp index 75ea56b..25afe0a 100644 --- a/src/lora_types.hpp +++ b/src/lora_types.hpp @@ -20,9 +20,28 @@ struct LoraStateTable { uint32_t totalPacketsTransmitted = 0; ///< Accumulator metric track-counting egress frames transmitted. (4 Bytes) uint32_t nextDatabaseId = 1; ///< Identity sequencer pointer controlling the next node's databaseId field. (4 Bytes) uint32_t bandwidthKhz = 250; ///< Modem bandwidth performance target configured in kHz. (4 Bytes) + uint8_t currentMode = 0; ///< Current operationl modem mode (1 Byte) uint8_t spreadingFactor = 11; ///< LoRa Spreading Factor configuration (typically ranges between 6 and 12). (1 Byte) uint8_t codingRate = 6; ///< Coding Rate denominator setting (e.g., value 6 yields a 4/6 coding protection overhead). (1 Byte) bool isInitialized = false; ///< True if hardware was successfully validated and attached during initialization. (1 Byte) // The three 1-byte variables at the bottom align perfectly into a 32-bit slot, leaving minimal trailing padding. }; + +/** + * @brief Thread-isolated telemetry snapshot for UI displays and remote diagnostics. + * @note Optimized using "largest-to-smallest" data type ordering to minimize memory padding. + */ +struct LoraStateSnapshot { + size_t currentHistoryCount; ///< Soft-limit threshold capping the maximum number of history records stored before auto-purge. (4 or 8 Bytes) + uint32_t frequencyHz; ///< RF carrier operating frequency configured in Hertz. (4 Bytes) + uint32_t bandwidthKhz; ///< Modem bandwidth performance target configured in kHz. (4 Bytes) + uint32_t totalPacketsReceived; ///< Accumulator metric track-counting successful ingress frames. (4 Bytes) + uint32_t totalPacketsTransmitted; ///< Accumulator metric track-counting egress frames transmitted. (4 Bytes) + char currentModeStr[16]; ///< Human-readable string representation of the current operational modem mode. (16 Bytes) + uint8_t spreadingFactor; ///< LoRa Spreading Factor configuration (typically ranges between 6 and 12). (1 Byte) + uint8_t codingRate; ///< Coding Rate denominator setting (e.g., value 6 yields a 4/6 coding protection overhead). (1 Byte) + bool isInitialized; ///< True if hardware was successfully validated and attached during initialization. (1 Byte) + + // Elements are strictly organized by size alignment requirements to completely prevent intermittent alignment gaps. +}; From fb0e096e4e73fbbc41f2044f89bf18abe87db7ee Mon Sep 17 00:00:00 2001 From: Thomas Kuschel Date: Wed, 21 Oct 2026 00:13:59 +0200 Subject: [PATCH 4/5] refactor Build Date, GIT_TAG, and GIT_HASH support --- platformio.ini | 5 +++++ src/cli.cpp | 3 ++- src/config.hpp | 2 +- src/main.cpp | 11 +++++++---- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index 03b76d2..6a6bcff 100644 --- a/platformio.ini +++ b/platformio.ini @@ -25,6 +25,11 @@ build_flags = ; -isystem /home/tom/.platformio/packages/framework-espidf/components/esp_libc/platform_include -Wno-include-next ; --- STRICT MODE FOR PROJECT APPLICATION CODE ONLY --- + + #-DGIT_HASH=\"\"${sysenv.GIT_HASH}\"\" + !echo -DGIT_HASH=\\\"$(git rev-parse --short HEAD)\\\" + !echo -DGIT_TAG=\\\"$(git describe --tags --always)\\\" + ; These flags are strictly applied ONLY to C++ files inside the src/ directory. build_src_flags = ; Enables all standard compiler warning flags diff --git a/src/cli.cpp b/src/cli.cpp index 1b52cc5..eefe133 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -95,7 +95,8 @@ void process_command(const char* cmd) { printf(" test-display - Run a comprehensive visual test suite\r\n"); } else if (strcmp(cmd, "version") == 0) { - printf("%s System - Running Version: %s\r\n", Config::PROJECT_TITLE, Config::PROJECT_VERSION); + printf("%s System - Running Version: %s Tag: " GIT_TAG "\r\n", Config::PROJECT_TITLE, Config::PROJECT_VERSION); + printf("Build Date: " __DATE__ ", Git-Hash: " GIT_HASH "\r\n"); } else if (strcmp(cmd, "status") == 0) { printf("--- %s Status ---\r\n", Config::PROJECT_TITLE); diff --git a/src/config.hpp b/src/config.hpp index ed9482a..89eda1d 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -22,7 +22,7 @@ namespace Config { // Compile-time constants with strict type enforcement constexpr const char* PROJECT_TITLE = "FoxMesh"; - constexpr const char* PROJECT_VERSION = "1.0.3"; + constexpr const char* PROJECT_VERSION = "1.0"; constexpr const char* PROJECT_AUTHORS = "Tom OE3TKT"; // CLI Mute Configuration diff --git a/src/main.cpp b/src/main.cpp index c508661..deb2991 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,10 @@ * For the full license terms, see the LICENSE file in the project root. */ +#ifndef GIT_TAG + #define GIT_TAG "unknown" +#endif + #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -127,12 +131,10 @@ extern "C" { // Trigger synchronous welcome splash screen frame if (display != nullptr) { char msg[32]; - //display->printMessage(0, "FoxMesh " Config::PROJECT_VERSION " \x86" "OE3TKT"); - snprintf(msg, sizeof(msg), "FoxMesh %s", Config::PROJECT_VERSION); + snprintf(msg, sizeof(msg), "FoxMesh " GIT_TAG); display->printMessage(0, msg); - display->appendMessage(0, "OE3TKT", 15*6); + // display->appendMessage(0, GIT_TAG, 12*6); display->printMessage(1, "----------------------"); - //display->printMessage(2, "Bärbel frühstückt ©"); } // Main system watchdog and status monitor loop while (true) { @@ -140,6 +142,7 @@ extern "C" { Config::PROJECT_TITLE, Config::PROJECT_VERSION, (unsigned long)esp_get_free_heap_size()); + // ESP_LOGI(TAG, "Git-Hash: %s", GIT_HASH); vTaskDelay(pdMS_TO_TICKS(5000)); } From 1a56c456f60beffcaefd3dd7cc29851cf69cd995 Mon Sep 17 00:00:00 2001 From: Thomas Kuschel Date: Wed, 21 Oct 2026 00:23:54 +0200 Subject: [PATCH 5/5] version 0.1 --- src/config.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.hpp b/src/config.hpp index 89eda1d..e16a7a4 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -22,7 +22,7 @@ namespace Config { // Compile-time constants with strict type enforcement constexpr const char* PROJECT_TITLE = "FoxMesh"; - constexpr const char* PROJECT_VERSION = "1.0"; + constexpr const char* PROJECT_VERSION = "0.1"; constexpr const char* PROJECT_AUTHORS = "Tom OE3TKT"; // CLI Mute Configuration