From 9972feb5bbfeceb4ac2d7b5a4d9f28efae160451 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 13 Jun 2026 12:06:06 -0500 Subject: [PATCH] Lora led rx (#10674) * add optional LED_LORA to indicate LoRa TX * Briefly flash LED_LORA on packet RX --------- Co-authored-by: Ben Meadors --- src/mesh/RadioInterface.cpp | 2 ++ src/mesh/RadioInterface.h | 3 ++ src/mesh/RadioLibInterface.cpp | 10 +++++++ src/modules/StatusLEDModule.cpp | 30 +++++++++++++++++++ src/modules/StatusLEDModule.h | 12 ++++++++ .../esp32s3/ELECROW-ThinkNode-M7/variant.cpp | 2 ++ 6 files changed, 59 insertions(+) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 6a8a0230a..2a5ae3222 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -32,6 +32,8 @@ #include "STM32WLE5JCInterface.h" #endif +Observable RadioInterface::loraRxPacketObservable; + #define RDEF(name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, frequency_switching, wide_lora) \ { \ meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, \ diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 55505fbe1..f0b005e3e 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -123,6 +123,9 @@ class RadioInterface virtual ~RadioInterface() {} + /// Fires once per valid received LoRa packet (arg = sender NodeNum). Used e.g. to flash LED_LORA. + static Observable loraRxPacketObservable; + /** * Coerce LoRa config fields (bandwidth/spread_factor) derived from presets. * This is used during early bootstrapping so UIs that display these fields directly remain consistent. diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index d255d8655..335e76a3b 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -509,6 +509,9 @@ void RadioLibInterface::completeSending() // that can take a long time auto p = sendingPacket; sendingPacket = NULL; +#ifdef LED_LORA + digitalWrite(LED_LORA, LED_STATE_OFF); +#endif if (p) { // Packet has been sent, count it toward our TX airtime utilization. @@ -611,6 +614,10 @@ void RadioLibInterface::handleReceiveInterrupt() printPacket("Lora RX", mp); +#ifdef LED_LORA + loraRxPacketObservable.notifyObservers(mp->from); +#endif + airTime->logAirtime(RX_LOG, rxMsec); deliverToReceiver(mp); @@ -686,6 +693,9 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp) enableInterrupt(isrTxLevel0); lastTxStart = millis(); printPacket("Started Tx", txp); +#ifdef LED_LORA + digitalWrite(LED_LORA, LED_STATE_ON); +#endif } return res == RADIOLIB_ERR_NONE; diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index f3a0e7a03..72aed21ff 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -1,6 +1,7 @@ #include "StatusLEDModule.h" #include "MeshService.h" #include "configuration.h" +#include "mesh/RadioInterface.h" #include /* @@ -17,6 +18,9 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule") if (inputBroker) inputObserver.observe(inputBroker); #endif +#ifdef LED_LORA + loraRxObserver.observe(&RadioInterface::loraRxPacketObservable); +#endif #ifdef NEOPIXEL_STATUS_POWER_PIN powerPixel.begin(); powerPixel.clear(); @@ -90,6 +94,18 @@ int StatusLEDModule::handleInputEvent(const InputEvent *event) return 0; } #endif +#ifdef LED_LORA +int StatusLEDModule::handleLoRaRx(uint32_t) +{ + // Briefly flash LED_LORA on each received packet. Turn it on now (we share the main thread with + // the radio's receive handler, so this is safe) and wake runOnce() at flash end to turn it off. + digitalWrite(LED_LORA, LED_STATE_ON); + LORA_LED_state = LED_STATE_ON; + LORA_LED_starttime = millis(); + setIntervalFromNow(LORA_RX_LED_FLASH_MS); + return 0; +} +#endif int32_t StatusLEDModule::runOnce() { @@ -227,6 +243,20 @@ int32_t StatusLEDModule::runOnce() digitalWrite(Battery_LED_4, chargeIndicatorLED4); #endif +#ifdef LED_LORA + // End the LoRa-RX flash once its duration has elapsed; otherwise make sure we come back + // exactly at flash end (only ever clamp my_interval down, so other LED timing is preserved). + if (LORA_LED_state == LED_STATE_ON) { + uint32_t elapsed = millis() - LORA_LED_starttime; + if (elapsed >= LORA_RX_LED_FLASH_MS) { + digitalWrite(LED_LORA, LED_STATE_OFF); + LORA_LED_state = LED_STATE_OFF; + } else if ((uint32_t)my_interval > LORA_RX_LED_FLASH_MS - elapsed) { + my_interval = LORA_RX_LED_FLASH_MS - elapsed; + } + } +#endif + return (my_interval); } diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h index f20198e39..793877400 100644 --- a/src/modules/StatusLEDModule.h +++ b/src/modules/StatusLEDModule.h @@ -43,6 +43,9 @@ class StatusLEDModule : private concurrency::OSThread #if !MESHTASTIC_EXCLUDE_INPUTBROKER int handleInputEvent(const InputEvent *arg); #endif +#ifdef LED_LORA + int handleLoRaRx(uint32_t sender); +#endif void setPowerLED(bool); @@ -65,6 +68,10 @@ class StatusLEDModule : private concurrency::OSThread CallbackObserver inputObserver = CallbackObserver(this, &StatusLEDModule::handleInputEvent); #endif +#ifdef LED_LORA + CallbackObserver loraRxObserver = + CallbackObserver(this, &StatusLEDModule::handleLoRaRx); +#endif private: bool CHARGE_LED_state = LED_STATE_OFF; @@ -77,6 +84,11 @@ class StatusLEDModule : private concurrency::OSThread uint32_t lastUserbuttonTime = 0; uint32_t POWER_LED_starttime = 0; bool doing_fast_blink = false; +#ifdef LED_LORA + static constexpr uint32_t LORA_RX_LED_FLASH_MS = 100; + bool LORA_LED_state = LED_STATE_OFF; + uint32_t LORA_LED_starttime = 0; +#endif enum PowerState { discharging, charging, charged, critical }; diff --git a/variants/esp32s3/ELECROW-ThinkNode-M7/variant.cpp b/variants/esp32s3/ELECROW-ThinkNode-M7/variant.cpp index 5adc241c8..9c190c6dc 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M7/variant.cpp +++ b/variants/esp32s3/ELECROW-ThinkNode-M7/variant.cpp @@ -9,4 +9,6 @@ void initVariant() { pinMode(LED_PAIRING, OUTPUT); digitalWrite(LED_PAIRING, !LED_STATE_ON); // Turn off the LED to start + pinMode(LED_LORA, OUTPUT); + digitalWrite(LED_LORA, !LED_STATE_ON); // Turn off the LED to start } \ No newline at end of file