Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 67 additions & 3 deletions src/mesh/LR11x0Interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@
#include "configuration.h"
#include "error.h"
#include "mesh/NodeDB.h"

// A variant may define LR11X0_UPDATE_FIRMWARE_TO to a Semtech transceiver firmware version (e.g. 0x0402) to
// bake that image in and update the radio on first boot. Every supported image is 61320 words, so this costs
// ~240 kB of flash regardless of the version chosen - only enable it on a variant with the headroom, and
// only for as long as it takes to update the affected units.
#ifdef LR11X0_UPDATE_FIRMWARE_TO
#if LR11X0_UPDATE_FIRMWARE_TO == 0x0402
#define RADIOLIB_LR1110_FIRMWARE_0402
#elif LR11X0_UPDATE_FIRMWARE_TO == 0x0401
#define RADIOLIB_LR1110_FIRMWARE_0401
#elif LR11X0_UPDATE_FIRMWARE_TO == 0x0307
#define RADIOLIB_LR1110_FIRMWARE_0307
#else
// Note: RadioLib ships lr1110_transceiver_0308.h but has no selector for it in LR11x0_firmware.h.
#error "LR11X0_UPDATE_FIRMWARE_TO must be one of 0x0307, 0x0401, 0x0402"
#endif
#include <modules/LR11x0/LR11x0_firmware.h>
#endif

#ifdef LR11X0_DIO_AS_RF_SWITCH
#include "rfswitch.h"
#elif ARCH_PORTDUINO
Expand Down Expand Up @@ -117,14 +136,59 @@ template <typename T> bool LR11x0Interface<T>::init()

// \todo Display actual typename of the adapter, not just `LR11x0`
LOG_INFO("LR11x0 init result %d", res);
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
return false;
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) {
#ifdef LR11X0_UPDATE_FIRMWARE_TO
// An interrupted update leaves the radio sitting in bootloader mode, where begin() fails. Retry the
// flash from here rather than giving up, otherwise the device could never recover on its own.
LOG_WARN("LR11x0 did not start; attempting firmware recovery in case an update was interrupted");
if (lora.updateFirmware(lr11xx_firmware_image, LR11XX_FIRMWARE_IMAGE_SIZE, true) == RADIOLIB_ERR_NONE) {
LOG_INFO("LR1110 firmware recovery succeeded, re-initializing radio");
res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
}
#endif
if (res != RADIOLIB_ERR_NONE)
return false;
}

LR11x0VersionInfo_t version;
res = lora.getVersionInfo(&version);
if (res == RADIOLIB_ERR_NONE)
if (res == RADIOLIB_ERR_NONE) {
LOG_DEBUG("LR11x0 Device %d, HW %d, FW %d.%d, WiFi %d.%d, GNSS %d.%d", version.device, version.hardware, version.fwMajor,
version.fwMinor, version.fwMajorWiFi, version.fwMinorWiFi, version.fwGNSS, version.almanacGNSS);
transceiverFw = ((uint16_t)version.fwMajor << 8) | version.fwMinor;
transceiverDevice = version.device;
}

#ifdef LR11X0_UPDATE_FIRMWARE_TO
// One-shot transceiver firmware update, opt-in per variant. Only runs when the part is an LR1110 running
// older firmware than the baked-in image, so once it has succeeded it is a no-op on subsequent boots.
if (transceiverDevice == RADIOLIB_LR11X0_DEVICE_LR1110 && transceiverFw != 0 && transceiverFw < LR11X0_UPDATE_FIRMWARE_TO) {
LOG_WARN("LR1110 transceiver FW %d.%d is older than %d.%d - updating now. DO NOT POWER OFF: this "
"erases and rewrites the radio's own flash.",
transceiverFw >> 8, transceiverFw & 0xFF, LR11X0_UPDATE_FIRMWARE_TO >> 8, LR11X0_UPDATE_FIRMWARE_TO & 0xFF);

int upd = lora.updateFirmware(lr11xx_firmware_image, LR11XX_FIRMWARE_IMAGE_SIZE, true);
if (upd != RADIOLIB_ERR_NONE) {
// The radio is likely sitting in bootloader mode. It is not bricked - the update is retried on
// the next boot because the version check above will still see old (or unreadable) firmware.
LOG_ERROR("LR1110 firmware update FAILED %s%d - power-cycle to retry", radioLibErr, upd);
return false;
}

LOG_INFO("LR1110 firmware update complete, re-initializing radio");
res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
if (res != RADIOLIB_ERR_NONE) {
LOG_ERROR("LR11x0 re-init after firmware update failed %s%d", radioLibErr, res);
return false;
}

if (lora.getVersionInfo(&version) == RADIOLIB_ERR_NONE) {
transceiverFw = ((uint16_t)version.fwMajor << 8) | version.fwMinor;
transceiverDevice = version.device;
LOG_INFO("LR1110 now running transceiver FW %d.%d", version.fwMajor, version.fwMinor);
}
Comment on lines 153 to +189

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reset and verify the transceiver version state.

A failed getVersionInfo() call leaves the previous transceiverFw and transceiverDevice values intact. A later init() can then start an erase-and-reflash operation from stale version data during a communication failure.

After the update, a failed version query also returns success without verifying that the radio runs at least LR11X0_UPDATE_FIRMWARE_TO. Clear both fields before the first query. After the update, require a successful query and validate the resulting version.

Proposed fix
+    transceiverFw = 0;
+    transceiverDevice = 0;
     LR11x0VersionInfo_t version;
     res = lora.getVersionInfo(&version);
     if (res == RADIOLIB_ERR_NONE) {
         LOG_DEBUG("LR11x0 Device %d, HW %d, FW %d.%d, WiFi %d.%d, GNSS %d.%d", version.device, version.hardware, version.fwMajor,
                   version.fwMinor, version.fwMajorWiFi, version.fwMinorWiFi, version.fwGNSS, version.almanacGNSS);
         transceiverFw = ((uint16_t)version.fwMajor << 8) | version.fwMinor;
         transceiverDevice = version.device;
     }
...
-        if (lora.getVersionInfo(&version) == RADIOLIB_ERR_NONE) {
-            transceiverFw = ((uint16_t)version.fwMajor << 8) | version.fwMinor;
-            transceiverDevice = version.device;
-            LOG_INFO("LR1110 now running transceiver FW %d.%d", version.fwMajor, version.fwMinor);
-        }
+        res = lora.getVersionInfo(&version);
+        if (res != RADIOLIB_ERR_NONE) {
+            LOG_ERROR("LR1110 firmware update could not be verified %s%d", radioLibErr, res);
+            return false;
+        }
+
+        transceiverFw = ((uint16_t)version.fwMajor << 8) | version.fwMinor;
+        transceiverDevice = version.device;
+        if (transceiverFw < LR11X0_UPDATE_FIRMWARE_TO) {
+            LOG_ERROR("LR1110 firmware update did not reach target version");
+            return false;
+        }
+        LOG_INFO("LR1110 now running transceiver FW %d.%d", version.fwMajor, version.fwMinor);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
LR11x0VersionInfo_t version;
res = lora.getVersionInfo(&version);
if (res == RADIOLIB_ERR_NONE)
if (res == RADIOLIB_ERR_NONE) {
LOG_DEBUG("LR11x0 Device %d, HW %d, FW %d.%d, WiFi %d.%d, GNSS %d.%d", version.device, version.hardware, version.fwMajor,
version.fwMinor, version.fwMajorWiFi, version.fwMinorWiFi, version.fwGNSS, version.almanacGNSS);
transceiverFw = ((uint16_t)version.fwMajor << 8) | version.fwMinor;
transceiverDevice = version.device;
}
#ifdef LR11X0_UPDATE_FIRMWARE_TO
// One-shot transceiver firmware update, opt-in per variant. Only runs when the part is an LR1110 running
// older firmware than the baked-in image, so once it has succeeded it is a no-op on subsequent boots.
if (transceiverDevice == RADIOLIB_LR11X0_DEVICE_LR1110 && transceiverFw != 0 && transceiverFw < LR11X0_UPDATE_FIRMWARE_TO) {
LOG_WARN("LR1110 transceiver FW %d.%d is older than %d.%d - updating now. DO NOT POWER OFF: this "
"erases and rewrites the radio's own flash.",
transceiverFw >> 8, transceiverFw & 0xFF, LR11X0_UPDATE_FIRMWARE_TO >> 8, LR11X0_UPDATE_FIRMWARE_TO & 0xFF);
int upd = lora.updateFirmware(lr11xx_firmware_image, LR11XX_FIRMWARE_IMAGE_SIZE, true);
if (upd != RADIOLIB_ERR_NONE) {
// The radio is likely sitting in bootloader mode. It is not bricked - the update is retried on
// the next boot because the version check above will still see old (or unreadable) firmware.
LOG_ERROR("LR1110 firmware update FAILED %s%d - power-cycle to retry", radioLibErr, upd);
return false;
}
LOG_INFO("LR1110 firmware update complete, re-initializing radio");
res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
if (res != RADIOLIB_ERR_NONE) {
LOG_ERROR("LR11x0 re-init after firmware update failed %s%d", radioLibErr, res);
return false;
}
if (lora.getVersionInfo(&version) == RADIOLIB_ERR_NONE) {
transceiverFw = ((uint16_t)version.fwMajor << 8) | version.fwMinor;
transceiverDevice = version.device;
LOG_INFO("LR1110 now running transceiver FW %d.%d", version.fwMajor, version.fwMinor);
}
transceiverFw = 0;
transceiverDevice = 0;
LR11x0VersionInfo_t version;
res = lora.getVersionInfo(&version);
if (res == RADIOLIB_ERR_NONE) {
LOG_DEBUG("LR11x0 Device %d, HW %d, FW %d.%d, WiFi %d.%d, GNSS %d.%d", version.device, version.hardware, version.fwMajor,
version.fwMinor, version.fwMajorWiFi, version.fwMinorWiFi, version.fwGNSS, version.almanacGNSS);
transceiverFw = ((uint16_t)version.fwMajor << 8) | version.fwMinor;
transceiverDevice = version.device;
}
`#ifdef` LR11X0_UPDATE_FIRMWARE_TO
// One-shot transceiver firmware update, opt-in per variant. Only runs when the part is an LR1110 running
// older firmware than the baked-in image, so once it has succeeded it is a no-op on subsequent boots.
if (transceiverDevice == RADIOLIB_LR11X0_DEVICE_LR1110 && transceiverFw != 0 && transceiverFw < LR11X0_UPDATE_FIRMWARE_TO) {
LOG_WARN("LR1110 transceiver FW %d.%d is older than %d.%d - updating now. DO NOT POWER OFF: this "
"erases and rewrites the radio's own flash.",
transceiverFw >> 8, transceiverFw & 0xFF, LR11X0_UPDATE_FIRMWARE_TO >> 8, LR11X0_UPDATE_FIRMWARE_TO & 0xFF);
int upd = lora.updateFirmware(lr11xx_firmware_image, LR11XX_FIRMWARE_IMAGE_SIZE, true);
if (upd != RADIOLIB_ERR_NONE) {
// The radio is likely sitting in bootloader mode. It is not bricked - the update is retried on
// the next boot because the version check above will still see old (or unreadable) firmware.
LOG_ERROR("LR1110 firmware update FAILED %s%d - power-cycle to retry", radioLibErr, upd);
return false;
}
LOG_INFO("LR1110 firmware update complete, re-initializing radio");
res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
if (res != RADIOLIB_ERR_NONE) {
LOG_ERROR("LR11x0 re-init after firmware update failed %s%d", radioLibErr, res);
return false;
}
res = lora.getVersionInfo(&version);
if (res != RADIOLIB_ERR_NONE) {
LOG_ERROR("LR1110 firmware update could not be verified %s%d", radioLibErr, res);
return false;
}
transceiverFw = ((uint16_t)version.fwMajor << 8) | version.fwMinor;
transceiverDevice = version.device;
if (transceiverFw < LR11X0_UPDATE_FIRMWARE_TO) {
LOG_ERROR("LR1110 firmware update did not reach target version");
return false;
}
LOG_INFO("LR1110 now running transceiver FW %d.%d", version.fwMajor, version.fwMinor);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mesh/LR11x0Interface.cpp` around lines 153 - 189, Reset transceiverFw and
transceiverDevice before the initial getVersionInfo call so a failed query
cannot reuse stale state. After the firmware update and re-initialization,
require getVersionInfo to succeed, refresh both fields, and verify the resulting
firmware is at least LR11X0_UPDATE_FIRMWARE_TO before reporting success;
otherwise log the failure or version mismatch and return false.

}
#endif

LOG_INFO("Frequency set to %f", getFreq());
LOG_INFO("Bandwidth set to %f", bw);
Expand Down
5 changes: 5 additions & 0 deletions src/mesh/LR11x0Interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ template <class T> class LR11x0Interface : public RadioLibInterface

int16_t getCurrentRSSI() override;

/// Transceiver firmware version as (major << 8 | minor), and which LR11x0 part this is. Captured at
/// init() from getVersionInfo(); 0 if the query failed.
uint16_t transceiverFw = 0;
uint8_t transceiverDevice = 0;

/**
* Glue functions called from ISR land
*/
Expand Down
8 changes: 8 additions & 0 deletions variants/esp32s3/ELECROW-ThinkNode-M7/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
#define LR11X0_DIO3_TCXO_VOLTAGE 1.8
#define LR11X0_DIO_AS_RF_SWITCH

// TEMPORARY: units shipped with LR1110 transceiver FW 0x0303 (the original 2020 release), which cannot
// reliably demodulate 500 kHz LoRa - Turbo presets fail RX with ~41% byte errors while TX and narrower
// bandwidths are fine. 0x0303 also predates GetLoRaRxHeaderInfos, which computePacketTime() calls on every
// received packet. Confirmed fixed by updating to 0x0307; targeting 0x0402 additionally picks up the
// out-of-band emission fix for consecutive LoRa transmissions (0x0401) and three CVE fixes (0x0402).
// Costs ~240 kB of flash. Remove once the affected units are updated.
#define LR11X0_UPDATE_FIRMWARE_TO 0x0402

#define HAS_ETHERNET 1
#define USE_CH390D 1

Expand Down
Loading