10#ifdef IOHOME_LR1121_FIRMWARE_UPDATE
14#include "lr1121_firmware_update_image.h"
16#ifdef IOHOME_LR1121_BOOTLOADER_UPDATE
19#include "lr1121_bootloader_loader_image.h"
22#include "esphome/core/application.h"
57constexpr uint32_t LR1121_FLASH_CONFIRM_WINDOW_MS = 60 * 1000;
59std::string format_lr1121_fw_version(uint16_t version) {
63 snprintf(buf,
sizeof(buf),
"%u.%u",
static_cast<unsigned>(version >> 8),
static_cast<unsigned>(version & 0xFF));
67std::string format_hex16(uint16_t value) {
69 snprintf(buf,
sizeof(buf),
"0x%04X", value);
73std::string format_hex8(uint8_t value) {
75 snprintf(buf,
sizeof(buf),
"0x%02X", value);
81std::string format_lr1121_bootloader_version(uint16_t version) {
82 return version == 0 ?
"unknown" : format_hex16(version);
87enum class Lr1121SanityResult {
105Lr1121SanityResult lr1121_check_bootloader_sanity(
bool known, uint16_t known_bootloader_version, uint8_t sanity_type,
106 uint16_t sanity_bootloader_version) {
107 if (sanity_type != LR1121_UPDATER_BOOTLOADER_TYPE)
108 return Lr1121SanityResult::WRONG_TYPE;
110 if (sanity_bootloader_version != known_bootloader_version)
111 return Lr1121SanityResult::VERSION_MISMATCH;
112 return Lr1121SanityResult::OK;
115 return Lr1121SanityResult::WRONG_CHIP_FAMILY;
116 return Lr1121SanityResult::OK;
122std::string lr1121_sanity_failure_reason(Lr1121SanityResult sanity, uint16_t sanity_bootloader_version) {
124 case Lr1121SanityResult::WRONG_TYPE:
126 case Lr1121SanityResult::WRONG_CHIP_FAMILY:
127 return "bootloader version " + format_hex16(sanity_bootloader_version) +
" identifies " +
129 case Lr1121SanityResult::VERSION_MISMATCH:
131 return "bootloader version changed since boot";
139void lr1121_log_post_flash_verify_result(uint16_t new_fw, uint16_t target_fw) {
140 if (target_fw == 0) {
142 "LR1121 firmware update: now running %s; this build had no expected version to compare against",
143 format_lr1121_fw_version(new_fw).c_str());
144 }
else if (new_fw == target_fw) {
145 ESP_LOGI(
detail::TAG,
"LR1121 firmware update: success -- now running %s",
146 format_lr1121_fw_version(new_fw).c_str());
148 ESP_LOGW(
detail::TAG,
"LR1121 firmware update: post-flash version is %s, expected %s",
149 format_lr1121_fw_version(new_fw).c_str(), format_lr1121_fw_version(target_fw).c_str());
184void lr1121_log_post_write_hash(Lr1121FirmwareUpdater &updater) {
185 uint8_t hash[LR1121_UPDATER_HASH_LENGTH] = {0};
186 if (!updater.read_hash(hash,
sizeof(hash))) {
188 "LR1121 firmware update: could not read the flash fingerprint (BUSY timeout). Harmless -- it is "
189 "only an identifier, not a correctness check; the version check below is what confirms the flash.");
198 const bool degenerate = std::all_of(hash + 1, hash + LR1121_UPDATER_HASH_LENGTH, [](uint8_t b) {
return b == 0; });
201 "LR1121 firmware update: no flash fingerprint available on this bootloader (GetHash returned a "
202 "fixed non-value). Harmless -- it was only ever an identifier, never a correctness check; the "
203 "firmware version reported below is what confirms the flash worked.");
206 char hex[LR1121_UPDATER_HASH_LENGTH * 2 + 1];
207 for (
size_t i = 0; i < LR1121_UPDATER_HASH_LENGTH; i++)
208 snprintf(hex + i * 2, 3,
"%02x", hash[i]);
210 "LR1121 firmware update: flash fingerprint %s -- an identifier for the image now on the chip, useful "
211 "for comparing two boards. It is not the image's MD5 and cannot be checked against anything; the "
212 "firmware version reported below is what confirms the flash worked.",
223bool lr1121_erase_and_write_image_(Lr1121FirmwareUpdater &updater,
const char *stage_label,
const uint32_t *image,
224 size_t word_count, uint32_t &erase_elapsed_ms, uint32_t &write_elapsed_ms) {
225 ESP_LOGI(
detail::TAG,
"%s: erasing radio flash, this takes a few seconds...", stage_label);
226 const uint32_t erase_start_ms = millis();
227 if (!updater.erase_flash()) {
228 ESP_LOGE(
detail::TAG,
"%s: erase failed (BUSY timeout)", stage_label);
231 erase_elapsed_ms = millis() - erase_start_ms;
233 size_t last_logged_words = 0;
234 const size_t log_step = std::max<size_t>(word_count / 10, 1);
235 const uint32_t write_start_ms = millis();
236 const bool write_ok = updater.write_image(image, word_count, [&](
size_t done,
size_t total) {
240 if (done - last_logged_words < log_step && done != total)
242 last_logged_words = done;
243 ESP_LOGI(
detail::TAG,
"%s: flashing %zu/%zu words (%u%%)", stage_label, done, total,
244 static_cast<unsigned>((done * 100) / total));
246 write_elapsed_ms = millis() - write_start_ms;
248 ESP_LOGE(
detail::TAG,
"%s: write failed (BUSY timeout) after %" PRIu32
" ms", stage_label, write_elapsed_ms);
251 ESP_LOGI(
detail::TAG,
"%s: erase took %" PRIu32
" ms, write took %" PRIu32
" ms", stage_label, erase_elapsed_ms,
267std::string lr1121_needs_confirmation_reason(uint8_t device_type, uint16_t bootloader_version, uint16_t installed_fw,
268 uint16_t target_fw) {
269 if (bootloader_version == 0)
270 return "the bootloader version could not be read at boot, so chip identity and bootloader compatibility "
271 "cannot be verified";
272 if (device_type == 0)
273 return "the installed firmware version could not be read (radio failed to initialize, or the read itself "
276 return "no target firmware version could be determined for the configured image";
278 return "target firmware " + format_lr1121_fw_version(target_fw) +
279 " is not in this build's known bootloader-compatibility table (unverified, not refused)";
281 if (installed_fw == 0)
282 return "the installed firmware version is unknown";
283 return "target firmware " + format_lr1121_fw_version(target_fw) +
" is not newer than the installed " +
284 format_lr1121_fw_version(installed_fw);
289Lr1121FirmwareUpdateController::Lr1121FirmwareUpdateController(
RadioDriver **radio,
SpiAccess *spi,
290 InternalGPIOPin **rst_pin, InternalGPIOPin **busy_pin,
300 begin_blocking_excursion_(std::move(begin_blocking_excursion)),
303void Lr1121FirmwareUpdateController::run_boot_time_bootloader_read() {
304 this->lr1121_firmware_updater_ =
305 new (std::nothrow) Lr1121FirmwareUpdater(this->spi_, *this->rst_pin_, *this->busy_pin_);
306 if (this->lr1121_firmware_updater_ ==
nullptr) {
307 ESP_LOGE(detail::TAG,
"LR1121 firmware update: failed to allocate the updater; bootloader version unknown");
312 uint16_t bootloader_version = 0;
313 if (!this->lr1121_firmware_updater_->enter_bootloader(type, bootloader_version)) {
314 ESP_LOGW(detail::TAG,
"LR1121 firmware update: could not read the bootloader version at boot (BUSY timeout) -- "
315 "bootloader version stays unknown until the next boot");
318 this->lr1121_bootloader_chip_type_ = type;
319 this->lr1121_bootloader_version_ = bootloader_version;
320 this->lr1121_bootloader_version_known_ =
true;
326 if (!this->lr1121_firmware_updater_->reboot(
false)) {
327 ESP_LOGW(detail::TAG,
"LR1121 firmware update: reboot-out-of-bootloader command failed to send (BUSY timeout); the "
328 "upcoming radio init's own hardware reset will recover it");
332void Lr1121FirmwareUpdateController::cache_flash_verdict() {
333 uint8_t device_type = 0;
334 uint16_t installed_fw = 0;
335 if (*this->radio_ !=
nullptr && this->lr1121_firmware_updater_ !=
nullptr) {
340 uint8_t fw_major = 0, fw_minor = 0;
343 if (this->lr1121_firmware_updater_->read_normal_version(device_type, fw_major, fw_minor))
344 installed_fw = (
static_cast<uint16_t
>(fw_major) << 8) | fw_minor;
346 this->lr1121_installed_device_type_ = device_type;
347 this->lr1121_installed_fw_ = installed_fw;
351 this->lr1121_flash_verdict_ =
352 lr1121_flash_decision(device_type, this->lr1121_bootloader_chip_type_, this->lr1121_bootloader_version_,
353 installed_fw, LR1121_FIRMWARE_UPDATE_TARGET_VERSION,
false);
354 this->lr1121_flash_verdict_known_ =
true;
361std::string Lr1121FirmwareUpdateController::describe_flash_verdict()
const {
362 const uint16_t target = LR1121_FIRMWARE_UPDATE_TARGET_VERSION;
363 const std::string prefix =
"Firmware update target: " + format_lr1121_fw_version(target) +
" -- ";
365 switch (this->lr1121_flash_verdict_) {
366 case FlashDecision::REJECT_WRONG_CHIP: {
370 if (this->lr1121_bootloader_chip_type_ != LR1121_BOOTLOADER_TYPE_FOR_FIRMWARE_DECISIONS ||
372 return prefix +
"CANNOT PROCEED: bootloader version " + format_hex16(this->lr1121_bootloader_version_) +
375 return prefix +
"CANNOT PROCEED: normal-mode chip identity byte " +
376 format_hex8(this->lr1121_installed_device_type_) +
" identifies " +
379 case FlashDecision::REJECT_BOOTLOADER_TOO_OLD: {
386 BootloaderMismatch::TARGET_NEEDS_OLDER) {
387 return prefix +
"CANNOT PROCEED: this chip's bootloader " + format_hex16(this->lr1121_bootloader_version_) +
388 " is newer than this firmware supports (needs " + format_hex16(required) +
389 ") -- there is no downgrade path";
391 std::string message = prefix +
"CANNOT PROCEED: needs bootloader " + format_hex16(required) +
", this chip has " +
392 format_hex16(this->lr1121_bootloader_version_);
393#ifndef IOHOME_LR1121_BOOTLOADER_UPDATE
399 message +=
" -- add a bootloader: sub-block to lr1121_firmware_update: to enable the (irreversible) upgrade "
404 case FlashDecision::ALREADY_INSTALLED:
405 return prefix +
"already running the configured firmware, nothing to do";
406 case FlashDecision::NEEDS_CONFIRMATION:
408 lr1121_needs_confirmation_reason(this->lr1121_installed_device_type_, this->lr1121_bootloader_version_,
409 this->lr1121_installed_fw_, target) +
410 " (bootloader version " + format_lr1121_bootloader_version(this->lr1121_bootloader_version_) +
")";
411 case FlashDecision::PROCEED:
413 return prefix +
"ready to flash (press \"Flash LR1121 Radio Firmware\")";
421#ifdef IOHOME_LR1121_BOOTLOADER_UPDATE
422std::string Lr1121FirmwareUpdateController::describe_bootloader_refusal(BootloaderUpgradePath path)
const {
428 const std::string target_text = format_lr1121_fw_version(LR1121_FIRMWARE_UPDATE_TARGET_VERSION);
429 const std::string chip_text = format_hex16(this->lr1121_bootloader_version_);
431 const std::string prefix =
"LR1121 firmware update: nothing was done, the radio was not touched. ";
434 case BootloaderUpgradePath::AVAILABLE:
435 return prefix +
"Firmware " + target_text +
" needs bootloader " + required_text +
" and this chip has " +
437 ", so the bootloader has to be rewritten first. To do that, turn on the \"Allow LR1121 Bootloader "
438 "Rewrite (Irreversible)\" switch and press this button again. A bootloader rewrite cannot be undone.";
439 case BootloaderUpgradePath::BLOCKED_UNKNOWN_TARGET:
440 return prefix +
"This build does not recognise firmware " + target_text +
441 ", so it cannot tell which bootloader that image needs. The bootloader rewrite stays disabled rather "
442 "than risk an irreversible write on a guess.";
443 case BootloaderUpgradePath::BLOCKED_BOOTLOADER_NEWER:
444 return prefix +
"This chip's bootloader " + chip_text +
" is already newer than firmware " + target_text +
445 " supports (that image needs " + required_text +
"), and there is no way back to an older bootloader.";
446 case BootloaderUpgradePath::NOT_APPLICABLE:
450 return this->describe_flash_verdict();
455std::vector<std::string> Lr1121FirmwareUpdateController::debug_lines()
const {
456 std::vector<std::string> lines;
457 if (this->lr1121_bootloader_version_known_) {
458 lines.push_back(
"LR1121 bootloader version: " + format_hex16(this->lr1121_bootloader_version_));
460 lines.push_back(
"LR1121 firmware update: bootloader version could not be read at boot");
462 if (this->lr1121_flash_verdict_known_)
463 lines.push_back(this->describe_flash_verdict());
464#ifdef IOHOME_LR1121_BOOTLOADER_UPDATE
469 true, this->lr1121_bootloader_version_known_, this->lr1121_bootloader_version_,
470 LR1121_BOOTLOADER_LOADER_FW, LR1121_FIRMWARE_UPDATE_TARGET_VERSION);
471 if (upgrade_path == BootloaderUpgradePath::AVAILABLE) {
476 lines.push_back(
"LR1121 bootloader rewrite: AVAILABLE -- needs bootloader " +
478 ", this chip has " + format_hex16(this->lr1121_bootloader_version_) +
479 ". A bootloader rewrite cannot be undone.");
480 }
else if (upgrade_path == BootloaderUpgradePath::BLOCKED_UNKNOWN_TARGET) {
481 lines.push_back(
"LR1121 bootloader rewrite: configured, but inert -- this build does not know what bootloader the "
482 "configured target requires, so it will not gamble an irreversible write on it.");
483 }
else if (upgrade_path == BootloaderUpgradePath::BLOCKED_BOOTLOADER_NEWER) {
484 lines.push_back(
"LR1121 bootloader rewrite: configured, but inert -- this chip's bootloader is already newer than "
485 "the configured target needs; there is no downgrade path.");
491void Lr1121FirmwareUpdateController::dump_debug()
const {
492 for (
const auto &line : this->debug_lines())
493 ESP_LOGCONFIG(detail::TAG,
" %s", line.c_str());
496void Lr1121FirmwareUpdateController::arm_flash_confirmation_() {
497 this->lr1121_flash_confirmation_armed_ =
true;
508 App.scheduler.set_timeout(
static_cast<const void *
>(this->hub_), LR1121_FLASH_CONFIRM_WINDOW_MS, [
this]() {
511 if (!this->lr1121_flash_confirmation_armed_)
513 this->lr1121_flash_confirmation_armed_ =
false;
514 ESP_LOGI(detail::TAG,
"LR1121 firmware update: confirmation window expired without a second press");
518void Lr1121FirmwareUpdateController::trigger() {
523 if (*this->radio_ ==
nullptr)
524 ESP_LOGW(detail::TAG,
"LR1121 firmware update: radio_ is null (failed init); proceeding without standby");
531 ESP_LOGW(detail::TAG,
"LR1121 firmware update: radio busy with another operation, ignoring press");
535 if (this->lr1121_firmware_updater_ ==
nullptr || !this->lr1121_flash_verdict_known_) {
536 ESP_LOGE(detail::TAG,
"LR1121 firmware update: no cached verdict available (setup() may have failed early)");
545 if (verdict == FlashDecision::REJECT_WRONG_CHIP) {
546 ESP_LOGE(detail::TAG,
"%s", this->describe_flash_verdict().c_str());
550 if (verdict == FlashDecision::REJECT_BOOTLOADER_TOO_OLD) {
551#ifdef IOHOME_LR1121_BOOTLOADER_UPDATE
556 true, this->lr1121_bootloader_version_known_, this->lr1121_bootloader_version_,
557 LR1121_BOOTLOADER_LOADER_FW, LR1121_FIRMWARE_UPDATE_TARGET_VERSION);
558 if (upgrade_path == BootloaderUpgradePath::AVAILABLE) {
559 if (!this->bootloader_rewrite_allowed_) {
560 ESP_LOGE(detail::TAG,
"%s", this->describe_bootloader_refusal(upgrade_path).c_str());
566 ESP_LOGW(detail::TAG,
"LR1121 bootloader rewrite: arming switch is on -- running the three-stage sequence now.");
567 this->run_bootloader_upgrade_sequence_();
570 if (upgrade_path == BootloaderUpgradePath::BLOCKED_UNKNOWN_TARGET ||
571 upgrade_path == BootloaderUpgradePath::BLOCKED_BOOTLOADER_NEWER) {
572 ESP_LOGE(detail::TAG,
"%s", this->describe_bootloader_refusal(upgrade_path).c_str());
579 ESP_LOGE(detail::TAG,
"%s", this->describe_flash_verdict().c_str());
583 const bool proceeding = (verdict == FlashDecision::PROCEED) || this->lr1121_flash_confirmation_armed_;
590 const bool already_installed = (verdict == FlashDecision::ALREADY_INSTALLED);
591 const std::string confirm_suffix =
" -- press \"Flash LR1121 Radio Firmware\" again within " +
592 std::to_string(LR1121_FLASH_CONFIRM_WINDOW_MS / 1000) +
"s to " +
593 (already_installed ?
"re-flash anyway" :
"proceed anyway");
594 const std::string message = this->describe_flash_verdict() + confirm_suffix;
595 if (already_installed) {
596 ESP_LOGI(detail::TAG,
"%s", message.c_str());
598 ESP_LOGW(detail::TAG,
"%s", message.c_str());
600 this->arm_flash_confirmation_();
604 this->lr1121_flash_confirmation_armed_ =
false;
605 this->run_flash_sequence_();
608void Lr1121FirmwareUpdateController::run_flash_sequence_() {
616 this->begin_blocking_excursion_();
617 if (*this->radio_ !=
nullptr)
618 (*this->radio_)->set_mode_standby();
623 uint8_t sanity_type = 0;
624 uint16_t sanity_bootloader_version = 0;
625 if (!this->lr1121_firmware_updater_->enter_bootloader(sanity_type, sanity_bootloader_version)) {
626 ESP_LOGE(detail::TAG,
627 "LR1121 firmware update: bootloader entry could not be confirmed (BUSY timeout on the verification "
628 "read) -- the entry sequence itself already ran, so the chip may be unconfigured; rebooting to "
629 "recover it rather than risking a silently dead radio");
634 const Lr1121SanityResult sanity = lr1121_check_bootloader_sanity(
635 this->lr1121_bootloader_version_known_, this->lr1121_bootloader_version_, sanity_type, sanity_bootloader_version);
636 if (sanity != Lr1121SanityResult::OK) {
637 const std::string sanity_reason = lr1121_sanity_failure_reason(sanity, sanity_bootloader_version);
640 "LR1121 firmware update: bootloader-entry sanity check failed (%s; read type=0x%02X bootloader=%s, "
641 "boot-time bootloader was %s) -- aborting before erasing anything",
642 sanity_reason.c_str(), sanity_type, format_hex16(sanity_bootloader_version).c_str(),
643 this->lr1121_bootloader_version_known_ ? format_hex16(this->lr1121_bootloader_version_).c_str() :
"unknown");
644 this->lr1121_firmware_updater_->reboot(
false);
648 if (!this->lr1121_bootloader_version_known_) {
653 ESP_LOGI(detail::TAG,
654 "LR1121 firmware update: boot-time bootloader version was unknown; type check passed and bootloader "
656 format_hex16(sanity_bootloader_version).c_str());
657 this->lr1121_bootloader_chip_type_ = sanity_type;
658 this->lr1121_bootloader_version_ = sanity_bootloader_version;
659 this->lr1121_bootloader_version_known_ =
true;
662 uint32_t erase_elapsed_ms = 0, write_elapsed_ms = 0;
663 if (!lr1121_erase_and_write_image_(*this->lr1121_firmware_updater_,
"LR1121 firmware update",
664 LR1121_FIRMWARE_UPDATE_IMAGE, LR1121_FIRMWARE_UPDATE_IMAGE_WORDS, erase_elapsed_ms,
666 ESP_LOGE(detail::TAG,
667 "LR1121 firmware update: the radio firmware is now incomplete. This is recoverable: after this "
668 "reboot, press the button again to re-flash.");
675 lr1121_log_post_write_hash(*this->lr1121_firmware_updater_);
677 if (!this->lr1121_firmware_updater_->reboot(
false)) {
678 ESP_LOGW(detail::TAG,
"LR1121 firmware update: reboot-to-image command failed to send (BUSY timeout)");
680 uint8_t device_type = 0, fw_major = 0, fw_minor = 0;
681 if (this->lr1121_firmware_updater_->read_normal_version(device_type, fw_major, fw_minor)) {
682 const uint16_t new_fw = (
static_cast<uint16_t
>(fw_major) << 8) | fw_minor;
683 lr1121_log_post_flash_verify_result(new_fw, LR1121_FIRMWARE_UPDATE_TARGET_VERSION);
685 ESP_LOGW(detail::TAG,
"LR1121 firmware update: could not read back the post-flash version (BUSY timeout)");
695#ifdef IOHOME_LR1121_BOOTLOADER_UPDATE
697void Lr1121FirmwareUpdateController::run_bootloader_upgrade_sequence_() {
699 this->begin_blocking_excursion_();
700 if (*this->radio_ !=
nullptr)
701 (*this->radio_)->set_mode_standby();
703 ESP_LOGW(detail::TAG,
704 "LR1121 bootloader rewrite: starting the three-stage sequence, ~10s total. Mains power, not "
705 "battery -- do not interrupt power. Stage 2 has no recovery path in this project if power is lost.");
709 uint8_t sanity_type = 0;
710 uint16_t sanity_bootloader_version = 0;
711 if (!this->lr1121_firmware_updater_->enter_bootloader(sanity_type, sanity_bootloader_version)) {
712 ESP_LOGE(detail::TAG,
713 "LR1121 bootloader rewrite: Stage 1a bootloader entry could not be confirmed (BUSY timeout) -- "
714 "the bootloader itself is untouched, this is recoverable: press the button again to retry.");
719 const Lr1121SanityResult sanity = lr1121_check_bootloader_sanity(
720 this->lr1121_bootloader_version_known_, this->lr1121_bootloader_version_, sanity_type, sanity_bootloader_version);
721 if (sanity != Lr1121SanityResult::OK) {
722 const std::string sanity_reason = lr1121_sanity_failure_reason(sanity, sanity_bootloader_version);
723 ESP_LOGE(detail::TAG,
724 "LR1121 bootloader rewrite: Stage 1a sanity check failed (%s) -- aborting before erasing anything; "
725 "the bootloader is untouched, this is recoverable: press the button again to retry.",
726 sanity_reason.c_str());
727 this->lr1121_firmware_updater_->reboot(
false);
738 uint32_t erase_elapsed_ms = 0, write_elapsed_ms = 0;
739 if (!lr1121_erase_and_write_image_(
740 *this->lr1121_firmware_updater_,
"LR1121 bootloader rewrite: Stage 1a (loader write)",
741 LR1121_BOOTLOADER_LOADER_IMAGE, LR1121_BOOTLOADER_LOADER_IMAGE_WORDS, erase_elapsed_ms, write_elapsed_ms)) {
742 ESP_LOGE(detail::TAG,
743 "LR1121 bootloader rewrite: Stage 1a failed -- the bootloader is untouched, this is recoverable: "
744 "press the button again to retry.");
751 if (!this->lr1121_firmware_updater_->reboot(
false)) {
752 ESP_LOGE(detail::TAG,
753 "LR1121 bootloader rewrite: Stage 1b reboot-into-loader command failed to send (BUSY timeout) -- "
754 "the bootloader is untouched, this is recoverable: press the button again to retry.");
758 uint8_t loader_device_type = 0, loader_fw_major = 0, loader_fw_minor = 0;
762 if (!this->lr1121_firmware_updater_->read_normal_version(loader_device_type, loader_fw_major, loader_fw_minor)) {
763 ESP_LOGE(detail::TAG,
764 "LR1121 bootloader rewrite: Stage 1b checkpoint failed -- could not read the chip's firmware version "
765 "after the reboot (BUSY timeout). Aborting before the irreversible write; the bootloader is "
766 "untouched, this is recoverable: press the button again to retry.");
779 if (loader_device_type != LR1121_UPDATER_LOADER_DEVICE_TYPE) {
780 const bool still_in_bootloader = loader_device_type == LR1121_UPDATER_BOOTLOADER_TYPE;
781 ESP_LOGE(detail::TAG,
782 "LR1121 bootloader rewrite: Stage 1b checkpoint failed -- chip reports type=0x%02X, expected the "
783 "loader's 0x%02X%s. The loader is not confirmed to be running, so 0x8100 must not be sent. "
784 "Aborting before the irreversible write; the bootloader is untouched, this is recoverable: press "
785 "the button again to retry.",
786 loader_device_type, LR1121_UPDATER_LOADER_DEVICE_TYPE,
787 still_in_bootloader ?
" (0xDF means the chip never left bootloader mode)" :
"");
791 const uint16_t loader_running_fw = (
static_cast<uint16_t
>(loader_fw_major) << 8) | loader_fw_minor;
792 if (loader_running_fw != LR1121_LOADER_2100) {
793 ESP_LOGE(detail::TAG,
794 "LR1121 bootloader rewrite: Stage 1b checkpoint failed -- chip reports firmware %s after the "
795 "reboot, expected the loader's %s. Aborting before the irreversible write; the bootloader is "
796 "untouched, this is recoverable: press the button again to retry.",
797 format_lr1121_fw_version(loader_running_fw).c_str(), format_lr1121_fw_version(LR1121_LOADER_2100).c_str());
801 ESP_LOGI(detail::TAG,
802 "LR1121 bootloader rewrite: Stage 1b checkpoint passed -- chip in transceiver mode: type=0x%02X fw=%s",
803 loader_device_type, format_lr1121_fw_version(loader_running_fw).c_str());
806 ESP_LOGW(detail::TAG,
807 "LR1121 bootloader rewrite: Stage 2 -- rewriting the bootloader now. This step cannot be undone. Do "
808 "not interrupt power.");
809 if (!this->lr1121_firmware_updater_->update_bootloader()) {
810 ESP_LOGE(detail::TAG,
811 "LR1121 bootloader rewrite: Stage 2 UpdateBootloader timed out waiting for BUSY -- outcome "
812 "unknown, the bootloader may be mid-write. There is no recovery path in this project for this "
813 "failure. Rebooting.");
824 Lr1121UpdaterStatus updater_status;
825 if (!this->lr1121_firmware_updater_->read_updater_status(updater_status)) {
826 ESP_LOGW(detail::TAG,
827 "LR1121 bootloader rewrite: Stage 2 status read timed out (BUSY) -- continuing to the verification "
828 "read, which is what actually decides the outcome");
829 }
else if (updater_status.command_status != Lr1121UpdaterCommandStatus::OK &&
830 updater_status.command_status != Lr1121UpdaterCommandStatus::DATA) {
831 ESP_LOGE(detail::TAG,
832 "LR1121 bootloader rewrite: Stage 2 chip reports command_status=%u after UpdateBootloader (0=FAIL, "
833 "1=PERR) -- the chip did not accept 0x8100, which most likely means the bootloader was NOT "
834 "rewritten. The verification below decides; report this line if it appears.",
835 static_cast<unsigned>(updater_status.command_status));
837 ESP_LOGI(detail::TAG,
"LR1121 bootloader rewrite: Stage 2 chip accepted UpdateBootloader (command_status=%u)",
838 static_cast<unsigned>(updater_status.command_status));
841 Lr1121BootloaderVerification verification;
842 if (!this->lr1121_firmware_updater_->verify_bootloader(verification)) {
843 ESP_LOGE(detail::TAG,
844 "LR1121 bootloader rewrite: Stage 2 VerifyBootloader read timed out (BUSY) after the write already "
845 "ran -- outcome unknown. There is no recovery path in this project for this failure. Rebooting.");
849 if (!verification.all_checks_passed()) {
850 ESP_LOGE(detail::TAG,
851 "LR1121 bootloader rewrite: Stage 2 verification failed after the write already ran (signature=%d "
852 "version=%d use_case=%d version_major=%d version_minor=%d anti_rollback=%d) -- the write already "
853 "happened; do NOT retry Stage 2. There is no recovery path in this project for this failure. "
855 verification.signature_verified, verification.version_verified, verification.use_case_verified,
856 verification.version_major_verified, verification.version_minor_verified,
857 verification.anti_rollback_verified);
862 if (!this->lr1121_firmware_updater_->updater_reboot(
false)) {
863 ESP_LOGE(detail::TAG,
864 "LR1121 bootloader rewrite: Stage 2 post-verify reboot command failed to send (BUSY timeout) -- "
865 "the write and verification both succeeded, but the chip's resulting state cannot be confirmed. "
866 "Rebooting the ESP32.");
873 uint8_t post_update_type = 0;
874 uint16_t post_update_bootloader_version = 0;
875 const bool post_update_read_ok =
876 this->lr1121_firmware_updater_->read_bootloader_version(post_update_type, post_update_bootloader_version);
877 if (!post_update_read_ok || post_update_type != LR1121_UPDATER_BOOTLOADER_TYPE ||
878 post_update_bootloader_version != LR1121_BOOTLOADER_2101) {
879 ESP_LOGE(detail::TAG,
880 "LR1121 bootloader rewrite: Stage 2 succeeded but the chip is not behaving as expected afterward "
881 "(read_ok=%d type=0x%02X bootloader=%s; expected to stay in the bootloader reporting 0x2101) -- "
882 "the write already happened; this is NOT the recoverable kind of failure. If the chip still "
883 "answers a bootloader-mode GetVersion with a sane version, the strap works and a transceiver "
884 "image can be written for whichever bootloader it reports -- but do not auto-retry Stage 2. "
886 post_update_read_ok, post_update_type, format_hex16(post_update_bootloader_version).c_str());
890 this->lr1121_bootloader_chip_type_ = post_update_type;
891 this->lr1121_bootloader_version_ = post_update_bootloader_version;
892 ESP_LOGI(detail::TAG,
"LR1121 bootloader rewrite: Stage 2 complete -- bootloader is now 0x2101.");
897 uint8_t stage3_type = 0;
898 uint16_t stage3_bootloader_version = 0;
899 if (!this->lr1121_firmware_updater_->enter_bootloader(stage3_type, stage3_bootloader_version)) {
900 ESP_LOGE(detail::TAG,
901 "LR1121 bootloader rewrite: Stage 3 bootloader entry could not be confirmed (BUSY timeout) -- the "
902 "bootloader was already rewritten successfully in Stage 2, so this is recoverable: press the "
903 "ordinary flash button again (no switch needed) once power is stable.");
907 if (stage3_type != LR1121_UPDATER_BOOTLOADER_TYPE || stage3_bootloader_version != LR1121_BOOTLOADER_2101) {
908 ESP_LOGE(detail::TAG,
909 "LR1121 bootloader rewrite: Stage 3 sanity check failed (type=0x%02X bootloader=%s, expected "
910 "0x2101) -- aborting before erasing the transceiver region. The bootloader was already rewritten "
911 "successfully in Stage 2; this is recoverable: press the ordinary flash button again.",
912 stage3_type, format_hex16(stage3_bootloader_version).c_str());
916 this->lr1121_bootloader_chip_type_ = stage3_type;
917 this->lr1121_bootloader_version_ = stage3_bootloader_version;
919 if (!lr1121_erase_and_write_image_(
920 *this->lr1121_firmware_updater_,
"LR1121 bootloader rewrite: Stage 3 (transceiver write)",
921 LR1121_FIRMWARE_UPDATE_IMAGE, LR1121_FIRMWARE_UPDATE_IMAGE_WORDS, erase_elapsed_ms, write_elapsed_ms)) {
922 ESP_LOGE(detail::TAG,
923 "LR1121 bootloader rewrite: Stage 3 failed -- the bootloader is already on 0x2101 (that part is "
924 "done and does not need to be repeated); this is recoverable: after this reboot, the ordinary "
925 "flash button (no switch needed) can retry the transceiver write.");
930 lr1121_log_post_write_hash(*this->lr1121_firmware_updater_);
932 if (!this->lr1121_firmware_updater_->reboot(
false)) {
933 ESP_LOGW(detail::TAG,
"LR1121 bootloader rewrite: Stage 3 reboot-to-image command failed to send (BUSY timeout)");
935 uint8_t device_type = 0, fw_major = 0, fw_minor = 0;
936 if (this->lr1121_firmware_updater_->read_normal_version(device_type, fw_major, fw_minor)) {
937 const uint16_t new_fw = (
static_cast<uint16_t
>(fw_major) << 8) | fw_minor;
938 lr1121_log_post_flash_verify_result(new_fw, LR1121_FIRMWARE_UPDATE_TARGET_VERSION);
940 ESP_LOGW(detail::TAG,
"LR1121 bootloader rewrite: could not read back the post-flash version (BUSY timeout)");
The main IO-Homecontrol component.
Abstract radio driver for IO-Homecontrol.
Interface for SPI bus access.
Hub-layer log tag and log/format helpers shared by the hub and its collaborators.
Pure decision logic for the LR1121 transceiver-firmware-update feature.
LR1121 transceiver-firmware-update feature — orchestration collaborator.
constexpr const char * TAG
Shared log tag for hub-level messages.
constexpr uint16_t lr1121_required_bootloader_for(uint16_t target_fw)
Required bootloader for a known target firmware version.
constexpr BootloaderSupport lr1121_bootloader_supports_target(uint16_t target_fw, uint16_t bootloader_version)
Look up whether target_fw is known to require bootloader_version.
constexpr BootloaderUpgradePath lr1121_bootloader_upgrade_path(bool block_present, bool bootloader_version_known, uint16_t bootloader_version, uint16_t loader_fw, uint16_t target_fw)
Whether the three-stage bootloader upgrade is applicable for the current cached state.
BootloaderUpgradePath
Whether the three-stage bootloader-rewrite sequence (ADR 0021) is applicable, and if not,...
@ OK
Request built and transmitted (with or without replies).
constexpr const char * lr1121_chip_family_for_bootloader(uint16_t bootloader_version)
Human-readable chip family for a bootloader version that is not one of the two LR1121 values above,...
constexpr bool lr1121_bootloader_is_lr1121(uint16_t bootloader_version)
@ UNKNOWN_TARGET
target_fw does not appear in LR1121_KNOWN_BOOTLOADER_REQUIREMENTS at all.
constexpr FlashDecision lr1121_flash_decision(uint8_t device_type, uint8_t bootloader_chip_type, uint16_t bootloader_version, uint16_t installed_fw, uint16_t target_fw, bool already_confirmed)
The single decision point for whether/how to flash target_fw.
FlashDecision
Outcome of lr1121_flash_decision().
@ NEEDS_CONFIRMATION
Not unsafe, but not an unambiguous "yes" either — needs a second press.
std::function< void()> BeginBlockingExcursionFn
Raises the hub's "operation took a long time" warning threshold for a blocking radio excursion — writ...
constexpr BootloaderMismatch lr1121_bootloader_mismatch_kind(uint16_t target_fw, uint16_t bootloader_version)
Classify a bootloader/target mismatch by direction; see BootloaderMismatch.
constexpr const char * lr1121_chip_family_for_device_type(uint8_t device_type)
Human-readable chip family for a normal-mode device_type that is not the LR1121 value above,...
LR1121 bootloader-mode-*and*-loader-mode SPI transport, standalone from the running RadioDriver.