Refactor/lora cli #5
Keine Dateien entsprechen Ihrer Suche
@@ -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
|
||||
@@ -43,3 +48,4 @@ build_src_flags =
|
||||
-Wno-include-next
|
||||
; Promotes all warnings to hard compilation errors to guarantee clean code
|
||||
;-Werror
|
||||
;-D MATH_COMPARE=1
|
||||
+26
-1
@@ -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,9 +92,11 @@ void process_command(const char* cmd) {
|
||||
printf(" freq <hz> - 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);
|
||||
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);
|
||||
@@ -101,6 +104,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 +240,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);
|
||||
}
|
||||
|
||||
+1
-7
@@ -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 = "0.1";
|
||||
constexpr const char* PROJECT_AUTHORS = "Tom OE3TKT";
|
||||
|
||||
// CLI Mute Configuration
|
||||
@@ -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)
|
||||
|
||||
+193
-52
@@ -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<uint8_t>(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
|
||||
// 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<size_t>(iso_char - 32);
|
||||
validCharacter = true;
|
||||
}
|
||||
} else if (c_primary >= 32 && c_primary <= 126) {
|
||||
// Handle standard printable 7-bit ASCII characters
|
||||
else if (c_primary >= 32 && c_primary <= 126) {
|
||||
fontIndex = static_cast<size_t>(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<char>(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<char>(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<char>(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<char>(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);
|
||||
// }
|
||||
}
|
||||
@@ -48,6 +48,7 @@ public:
|
||||
printMessage(static_cast<uint8_t>(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);
|
||||
+192
-41
@@ -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);
|
||||
err = gpio_isr_handler_add(Config::LORA_IRQ_PIN, LoraController::gpioIsrHandler, m_isrSemaphore);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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<uint8_t>((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<uint32_t>((static_cast<uint64_t>(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<uint32_t>((static_cast<uint64_t>(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<uint32_t>((static_cast<uint64_t>(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<uint32_t>((static_cast<uint64_t>(freq_hz) * 1125899907ULL) >> 36);
|
||||
#endif
|
||||
|
||||
|
||||
// 3. Shift the 24-bit frequency tracking token across the three consecutive hardware registers
|
||||
writeRegister(0x06, static_cast<uint8_t>((frf >> 16) & 0xFFU)); // RegFrfMsb
|
||||
writeRegister(0x07, static_cast<uint8_t>((frf >> 8) & 0xFFU)); // RegFrfMid
|
||||
writeRegister(0x08, static_cast<uint8_t>(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<uint8_t>(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,12 +450,40 @@ 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<const char*>(packet.payload), static_cast<size_t>(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;
|
||||
}
|
||||
@@ -16,6 +16,9 @@
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "lora_types.hpp"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
/**
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
@@ -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.
|
||||
};
|
||||
+7
-4
@@ -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 <stdio.h>
|
||||
#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));
|
||||
}
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren