Refactor/lora cli #5

Zusammengeführt
tom hat 5 Commits von refactor/lora-cli nach main 2026-10-21 00:29:15 +02:00 zusammengeführt
6 geänderte Dateien mit 230 neuen und 50 gelöschten Zeilen
Nur Änderungen aus Commit 618ff87d5c werden angezeigt - Alle Commits anzeigen

Keine Dateien entsprechen Ihrer Suche

-6
Datei anzeigen
@@ -72,12 +72,6 @@ namespace Config {
constexpr uint8_t LORA_PREAMBLE_LENGTH_MSB = 0x00; 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 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 --- // --- DYNAMIC COMPILE-TIME BIT-SHIFTING EXPRESSIONS ---
// Helper to map bandwidth kHz to Semtech Register Bits (7-4 of RegModemConfig1) // Helper to map bandwidth kHz to Semtech Register Bits (7-4 of RegModemConfig1)
+2 -2
Datei anzeigen
@@ -2,7 +2,7 @@
* @file i2c.hpp * @file i2c.hpp
* @brief Thread-safe I2C Master Bus Wrapper for FoxMesh * @brief Thread-safe I2C Master Bus Wrapper for FoxMesh
* @author OM Tom (OE3TKT) * @author OM Tom (OE3TKT)
* *
* @license SPDX-License-Identifier: MIT * @license SPDX-License-Identifier: MIT
* @copyright Copyright (c) 2026 OM Tom (OE3TKT) * @copyright Copyright (c) 2026 OM Tom (OE3TKT)
* For the full license terms, see the LICENSE file in the project root. * 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. * @brief Scans the network space for active I2C addresses.
* *
* Probes the hardware address space from 0x03 to 0x77 using low-overhead * Probes the hardware address space from 0x03 to 0x77 using low-overhead
* commands and prints a formatted matrix directly to stdout. * commands and prints a formatted matrix directly to stdout.
*/ */
+2 -2
Datei anzeigen
@@ -2,7 +2,7 @@
* @file led.hpp * @file led.hpp
* @brief Thread-safe hardware LED controller with dynamic profile configuration * @brief Thread-safe hardware LED controller with dynamic profile configuration
* @author OM Tom (OE3TKT) * @author OM Tom (OE3TKT)
* *
* @license SPDX-License-Identifier: MIT * @license SPDX-License-Identifier: MIT
* @copyright Copyright (c) 2026 OM Tom (OE3TKT) * @copyright Copyright (c) 2026 OM Tom (OE3TKT)
* For the full license terms, see the LICENSE file in the project root. * For the full license terms, see the LICENSE file in the project root.
@@ -46,7 +46,7 @@ public:
private: private:
const gpio_num_t m_gpioPin; const gpio_num_t m_gpioPin;
static const char* TAG; static const char* TAG;
// Track the internal task handle to allow cross-thread signaling via notifications // Track the internal task handle to allow cross-thread signaling via notifications
TaskHandle_t m_taskHandle = nullptr; TaskHandle_t m_taskHandle = nullptr;
+191 -40
Datei anzeigen
@@ -141,31 +141,35 @@ esp_err_t LoraController::initializeHardware() {
return err; return err;
} }
// Configure interrupt triggering on the rising edge (PosEdge)
err = gpio_set_intr_type(Config::LORA_IRQ_PIN, GPIO_INTR_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); 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() { void LoraController::setModeReceiver() {
// DIO0 auf RxDone mappen (Bit 7-6 auf 00) // DIO0 auf RxDone mappen (Bit 7-6 auf 00)
writeRegister(0x40, 0x00U); writeRegister(0x40, 0x00U);
@@ -185,14 +189,18 @@ void LoraController::applyModemConfig() {
writeRegister(0x01, 0x80U); writeRegister(0x01, 0x80U);
vTaskDelay(pdMS_TO_TICKS(10)); vTaskDelay(pdMS_TO_TICKS(10));
// Apply RF carrier center frequency (e.g., MeshCom 433.175 MHz) // Apply RF carrier center frequency (e.g., MeshCom 433.175 MHz)
writeRegister(0x06, Config::LORA_FRF_MSB); writeRegister(0x06, Config::LORA_FRF_MSB);
writeRegister(0x07, Config::LORA_FRF_MID); writeRegister(0x07, Config::LORA_FRF_MID);
writeRegister(0x08, Config::LORA_FRF_LSB); writeRegister(0x08, Config::LORA_FRF_LSB);
// just write the m_state frequency.... // 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 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 // Configure modem bandwidth, spreading factor, and coding rate
writeRegister(0x1D, Config::LORA_MODEM_CONFIG_1); writeRegister(0x1D, Config::LORA_MODEM_CONFIG_1);
writeRegister(0x1E, Config::LORA_MODEM_CONFIG_2); 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"); 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 #ifndef MATH_COMPARE
writeRegister(0x01, 0x81U); #define MATH_COMPARE 0
vTaskDelay(pdMS_TO_TICKS(5)); #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) { 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) { void LoraController::setBandwidth(uint32_t bw_khz) {
if (m_spiHandle == nullptr) return; if (m_spiHandle == nullptr) return;
@@ -316,6 +373,18 @@ void LoraController::calculateTelemetry(uint8_t rawRssi, uint8_t rawSnr, float&
} }
void LoraController::run() { 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) { if (initializeHardware() != ESP_OK) {
ESP_LOGE(TAG, "Critical: LoRa init failed. Thread aborted."); ESP_LOGE(TAG, "Critical: LoRa init failed. Thread aborted.");
return; return;
@@ -328,7 +397,9 @@ void LoraController::run() {
ESP_LOGI(TAG, "LoRa RX was initialized and is running"); ESP_LOGI(TAG, "LoRa RX was initialized and is running");
while (true) { 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); uint32_t irqFlags = readRegister(0x12);
writeRegister(0x12, static_cast<uint8_t>(irqFlags)); writeRegister(0x12, static_cast<uint8_t>(irqFlags));
@@ -368,7 +439,6 @@ void LoraController::run() {
printf("%c", c); printf("%c", c);
else else
printf("~"); // not printable chars printf("~"); // not printable chars
} }
printf("\"\r\n Hex : "); printf("\"\r\n Hex : ");
for (uint8_t i = 0; i < packet.length; i++) 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) // FIXED: Uses the new parameter order (line first, text second)
if (display != nullptr) { if (display != nullptr) {
char displayBuffer[16]; char displayBuffer[85]; // worst case 21 chars with each 4 bytes + Nullbyte.
snprintf(displayBuffer, sizeof(displayBuffer), "LoRa rx #%03u", (unsigned int)(packetCounter % 1000));
snprintf(displayBuffer, sizeof(displayBuffer), "#%03u %4.1fdBm %2.1fdB",
(unsigned int)(packetCounter % 1000),
packet.rssi,
packet.snr);
display->printMessage(2, displayBuffer); 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 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
Datei anzeigen
@@ -16,6 +16,9 @@
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "lora_types.hpp" #include "lora_types.hpp"
#include <iostream>
#include <string>
#include <cstring>
/** /**
@@ -76,6 +79,17 @@ public:
*/ */
void setBandwidth(uint32_t bw_khz); 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: private:
static const char* TAG; static const char* TAG;
@@ -110,4 +124,6 @@ private:
*/ */
void calculateTelemetry(uint8_t rawRssi, uint8_t rawSnr, float& outRssi, float& outSnr); 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);
}; };
+19
Datei anzeigen
@@ -20,9 +20,28 @@ struct LoraStateTable {
uint32_t totalPacketsTransmitted = 0; ///< Accumulator metric track-counting egress frames transmitted. (4 Bytes) 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 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) 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 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) 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) 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. // 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.
};