Skip to content

Update LoRa firmware on Thinknode M7 - #11337

Merged
caveman99 merged 2 commits into
developfrom
m7-fw-fix
Aug 4, 2026
Merged

Update LoRa firmware on Thinknode M7#11337
caveman99 merged 2 commits into
developfrom
m7-fw-fix

Conversation

@jp-bennett

@jp-bennett jp-bennett commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Turns out the M7 ships with really old firmware on the Semtech chip itself, and that firmware refuses to RX our Turbo presets. This patch adds the firmware update, but also uses an extra 250k of flash to do so.
Flash: [==========] 97.8% (used 2372003 bytes from 2424832 bytes)

This is an 8 meg flash chip, and we could switch to the 8 meg partition, but that would result in a wipe.

Summary by CodeRabbit

  • New Features

    • Added optional LR1110 firmware recovery and update support.
    • Automatically recovers from certain startup failures and updates older firmware when configured.
    • Added support for firmware targets 0x0307, 0x0401, and 0x0402.
  • Bug Fixes

    • Improved initialization reliability for affected transceiver devices.
    • Failed updates or reinitialization now correctly report failure.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Note

Building this pull request… the flash button, badges and supported-board
list will appear here automatically once CI finishes.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds configurable LR1110 firmware embedding for supported targets. Initialization records device information, recovers from selected startup errors, updates older firmware once, and verifies reinitialization.

Changes

LR11x0 firmware lifecycle

Layer / File(s) Summary
Firmware target selection
src/mesh/LR11x0Interface.cpp, variants/esp32s3/ELECROW-ThinkNode-M7/variant.h
Supported firmware targets select embedded RadioLib images. The ThinkNode M7 variant targets firmware 0x0402.
Startup recovery and update
src/mesh/LR11x0Interface.h, src/mesh/LR11x0Interface.cpp
Initialization stores firmware and device identifiers, recovers from selected startup errors, updates older LR1110 firmware, reinitializes the radio, and refreshes version state.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LR11x0Interface
  participant LR1110
  participant EmbeddedFirmware
  LR11x0Interface->>LR1110: initialize
  LR1110-->>LR11x0Interface: startup status and version
  LR11x0Interface->>EmbeddedFirmware: select target image
  LR11x0Interface->>LR1110: recover or update firmware
  LR1110-->>LR11x0Interface: update result
  LR11x0Interface->>LR1110: reinitialize and query version
Loading

Possibly related PRs

Suggested labels: hardware-support, bugfix

Suggested reviewers: caveman99

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: updating LoRa firmware on the Thinknode M7.
Description check ✅ Passed The description explains the firmware issue, flash impact, and partition constraint, but it omits testing attestations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch m7-fw-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
variants/esp32s3/ELECROW-ThinkNode-M7/variant.h (1)

36-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reduce this comment to two lines.

Keep the update reason and removal condition. Move detailed release-history information to PR documentation or an issue.

As per coding guidelines, keep comments minimal—normally one or two lines.

Proposed reduction
-// 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.
+// Temporarily update LR1110 0x0303 units because Turbo RX and GetLoRaRxHeaderInfos require newer firmware.
+// Target 0x0402 includes emission and security fixes. Remove after affected units update.
🤖 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 `@variants/esp32s3/ELECROW-ThinkNode-M7/variant.h` around lines 36 - 41, Reduce
the comment above the LR1110 firmware requirement to two lines, retaining only
the update reason and the condition that it should be removed after affected
units are updated. Remove detailed firmware release history, failure behavior,
fix details, and flash-size information from the comment.

Source: Coding guidelines

🤖 Prompt for all review comments with 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.

Inline comments:
In `@src/mesh/LR11x0Interface.cpp`:
- Around line 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.

---

Nitpick comments:
In `@variants/esp32s3/ELECROW-ThinkNode-M7/variant.h`:
- Around line 36-41: Reduce the comment above the LR1110 firmware requirement to
two lines, retaining only the update reason and the condition that it should be
removed after affected units are updated. Remove detailed firmware release
history, failure behavior, fix details, and flash-size information from the
comment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d21baf69-3572-4b66-8767-4aa233967dc6

📥 Commits

Reviewing files that changed from the base of the PR and between 8d3ad2a and 7d8b356.

📒 Files selected for processing (3)
  • src/mesh/LR11x0Interface.cpp
  • src/mesh/LR11x0Interface.h
  • variants/esp32s3/ELECROW-ThinkNode-M7/variant.h

Comment on lines 153 to +189
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);
}

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.

@vidplace7 vidplace7 added the hardware-support Hardware related: new devices or modules, problems specific to hardware label Aug 2, 2026
@caveman99
caveman99 added this pull request to the merge queue Aug 4, 2026
Merged via the queue into develop with commit 7302db1 Aug 4, 2026
61 of 63 checks passed
@caveman99
caveman99 deleted the m7-fw-fix branch August 26, 2026 19:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hardware-support Hardware related: new devices or modules, problems specific to hardware

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants