diff --git a/lib/tdeck_ui/UI/LXMF/SettingsScreen.cpp b/lib/tdeck_ui/UI/LXMF/SettingsScreen.cpp index 19a0791f..19819f68 100644 --- a/lib/tdeck_ui/UI/LXMF/SettingsScreen.cpp +++ b/lib/tdeck_ui/UI/LXMF/SettingsScreen.cpp @@ -1312,6 +1312,13 @@ void SettingsScreen::set_lxmf_address(const Bytes& hash) { _lxmf_address = hash; } +void SettingsScreen::set_firmware_version(const String& version) { + if (_label_firmware) { + String text = "Firmware: " + version; + lv_label_set_text(_label_firmware, text.c_str()); + } +} + void SettingsScreen::set_gps(TinyGPSPlus* gps) { _gps = gps; } diff --git a/lib/tdeck_ui/UI/LXMF/SettingsScreen.h b/lib/tdeck_ui/UI/LXMF/SettingsScreen.h index 1cbca224..df66764b 100644 --- a/lib/tdeck_ui/UI/LXMF/SettingsScreen.h +++ b/lib/tdeck_ui/UI/LXMF/SettingsScreen.h @@ -176,6 +176,9 @@ public: */ void set_lxmf_address(const RNS::Bytes& hash); + /** Set the exact running firmware build for post-reboot verification. */ + void set_firmware_version(const String& version); + /** * Set GPS pointer for status display */ diff --git a/src/main.cpp b/src/main.cpp index 5ae8059b..4bc2227e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include // placement new #include @@ -956,6 +957,30 @@ void setup_hardware() { INFO("Power enabled (early init)"); } +void confirm_running_firmware() { + const esp_partition_t* running = esp_ota_get_running_partition(); + if (!running) { + ERROR("Unable to identify running OTA partition"); + return; + } + + esp_ota_img_states_t state = ESP_OTA_IMG_UNDEFINED; + esp_err_t state_result = esp_ota_get_state_partition(running, &state); + INFOF("Running firmware: version=%s partition=%s subtype=%d address=0x%lx state=%d", + FIRMWARE_VERSION, running->label, static_cast(running->subtype), + static_cast(running->address), static_cast(state)); + + if (state_result == ESP_OK && + (state == ESP_OTA_IMG_NEW || state == ESP_OTA_IMG_PENDING_VERIFY)) { + esp_err_t result = esp_ota_mark_app_valid_cancel_rollback(); + if (result == ESP_OK) { + INFO("Running firmware marked valid; OTA rollback cancelled"); + } else { + ERRORF("Failed to mark running firmware valid: %s", esp_err_to_name(result)); + } + } +} + void setup_lvgl_and_ui() { INFO("\n=== LVGL & UI Initialization ==="); @@ -1275,6 +1300,7 @@ void setup_ui_manager() { // Configure settings screen UI::LXMF::SettingsScreen* settings = ui_manager->get_settings_screen(); if (settings) { + settings->set_firmware_version(FIRMWARE_VERSION); // Pass GPS for status display settings->set_gps(&gps); @@ -1789,6 +1815,11 @@ void setup() { Serial.flush(); }); + // Columba writes app0 plus fresh OTA-selection data. This framework has + // bootloader rollback enabled, so confirm the app only after filesystem, + // LXMF, and UI initialization have all completed successfully. + confirm_running_firmware(); + // Boot profiling complete BOOT_PROFILE_COMPLETE(); #ifdef BOOT_PROFILING_ENABLED diff --git a/tests/build_scripts/test_message_persistence_contract.py b/tests/build_scripts/test_message_persistence_contract.py index 4e7b88f7..eafcae6a 100644 --- a/tests/build_scripts/test_message_persistence_contract.py +++ b/tests/build_scripts/test_message_persistence_contract.py @@ -7,6 +7,7 @@ REPO_ROOT = Path(__file__).resolve().parents[2] UI_MANAGER = REPO_ROOT / "lib/tdeck_ui/UI/LXMF/UIManager.cpp" CHAT_SCREEN = REPO_ROOT / "lib/tdeck_ui/UI/LXMF/ChatScreen.cpp" COMPOSE_SCREEN = REPO_ROOT / "lib/tdeck_ui/UI/LXMF/ComposeScreen.cpp" +MAIN = REPO_ROOT / "src/main.cpp" def function_body(source: str, signature: str, next_signature: str) -> str: @@ -84,3 +85,17 @@ def test_storage_error_dialogs_are_coalesced(): source = UI_MANAGER.read_text() assert "if (storage_error_dialog) return;" in source assert "storage_error_dialog = nullptr;" in source + + +def test_successful_boot_cancels_ota_rollback_after_subsystems_initialize(): + source = MAIN.read_text() + confirm = function_body( + source, + "void confirm_running_firmware()", + "void setup_lvgl_and_ui()", + ) + assert "ESP_OTA_IMG_PENDING_VERIFY" in confirm + assert "esp_ota_mark_app_valid_cancel_rollback()" in confirm + setup = function_body(source, "void setup()", "void loop()") + assert setup.index("setup_lxmf();") < setup.index("setup_ui_manager();") + assert setup.index("setup_ui_manager();") < setup.index("confirm_running_firmware();")