From e93abf85c09b1cbb38ba8641d6586df4bd20ed19 Mon Sep 17 00:00:00 2001 From: Jake-B Date: Sun, 21 Dec 2025 20:38:44 -0500 Subject: [PATCH 1/9] Initial commit of combined BLE and WiFi OTA --- src/mesh/NodeDB.cpp | 6 +-- src/modules/AdminModule.cpp | 23 +++------- src/platform/esp32/BleOta.cpp | 46 ------------------- src/platform/esp32/BleOta.h | 20 -------- .../esp32/{WiFiOTA.cpp => MeshtasticOTA.cpp} | 21 +++++---- .../esp32/{WiFiOTA.h => MeshtasticOTA.h} | 12 ++--- src/platform/esp32/main-esp32.cpp | 19 ++------ 7 files changed, 34 insertions(+), 113 deletions(-) delete mode 100644 src/platform/esp32/BleOta.cpp delete mode 100644 src/platform/esp32/BleOta.h rename src/platform/esp32/{WiFiOTA.cpp => MeshtasticOTA.cpp} (80%) rename src/platform/esp32/{WiFiOTA.h => MeshtasticOTA.h} (50%) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 2d4bad85421..9f9ba2c509f 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -53,7 +53,7 @@ #endif #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI -#include +#include #endif NodeDB *nodeDB = nullptr; @@ -739,8 +739,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.display.compass_orientation = COMPASS_ORIENTATION; #endif #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI - if (WiFiOTA::isUpdated()) { - WiFiOTA::recoverConfig(&config.network); + if (MeshtasticOTA::isUpdated()) { + MeshtasticOTA::recoverConfig(&config.network); } #endif diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 5f0c27fffa2..6d17cdd1433 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -9,11 +9,8 @@ #include "meshUtils.h" #include #include // for better whitespace handling -#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH -#include "BleOta.h" -#endif #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI -#include "WiFiOTA.h" +#include "MeshtasticOTA.h" #endif #include "Router.h" #include "configuration.h" @@ -237,24 +234,18 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta break; } case meshtastic_AdminMessage_reboot_ota_seconds_tag: { + // TODO: Update to new protobufs int32_t s = r->reboot_ota_seconds; #if defined(ARCH_ESP32) -#if !MESHTASTIC_EXCLUDE_BLUETOOTH - if (!BleOta::getOtaAppVersion().isEmpty()) { + if (MeshtasticOTA::trySwitchToOTA()) { + LOG_INFO("OTA Requested"); if (screen) screen->startFirmwareUpdateScreen(); - BleOta::switchToOtaApp(); - LOG_INFO("Rebooting to BLE OTA"); - } -#endif -#if !MESHTASTIC_EXCLUDE_WIFI - if (WiFiOTA::trySwitchToOTA()) { - if (screen) - screen->startFirmwareUpdateScreen(); - WiFiOTA::saveConfig(&config.network); + MeshtasticOTA::saveConfig(&config.network, (s % 2) & 0b1); LOG_INFO("Rebooting to WiFi OTA"); + } else { + LOG_INFO("WIFI OTA Failed"); } -#endif #endif LOG_INFO("Reboot in %d seconds", s); rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000); diff --git a/src/platform/esp32/BleOta.cpp b/src/platform/esp32/BleOta.cpp deleted file mode 100644 index 698336f69d7..00000000000 --- a/src/platform/esp32/BleOta.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "BleOta.h" -#include "Arduino.h" -#include - -static const String MESHTASTIC_OTA_APP_PROJECT_NAME("Meshtastic-OTA"); - -const esp_partition_t *BleOta::findEspOtaAppPartition() -{ - const esp_partition_t *part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, nullptr); - - esp_app_desc_t app_desc; - esp_err_t ret = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_get_partition_description(part, &app_desc)); - - if (ret != ESP_OK || MESHTASTIC_OTA_APP_PROJECT_NAME != app_desc.project_name) { - part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, nullptr); - ret = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_get_partition_description(part, &app_desc)); - } - - if (ret == ESP_OK && MESHTASTIC_OTA_APP_PROJECT_NAME == app_desc.project_name) { - return part; - } else { - return nullptr; - } -} - -String BleOta::getOtaAppVersion() -{ - const esp_partition_t *part = findEspOtaAppPartition(); - esp_app_desc_t app_desc; - esp_err_t ret = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_get_partition_description(part, &app_desc)); - String version; - if (ret == ESP_OK) { - version = app_desc.version; - } - return version; -} - -bool BleOta::switchToOtaApp() -{ - bool success = false; - const esp_partition_t *part = findEspOtaAppPartition(); - if (part) { - success = (ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_set_boot_partition(part)) == ESP_OK); - } - return success; -} \ No newline at end of file diff --git a/src/platform/esp32/BleOta.h b/src/platform/esp32/BleOta.h deleted file mode 100644 index f4c51092098..00000000000 --- a/src/platform/esp32/BleOta.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef BLEOTA_H -#define BLEOTA_H - -#include -#include - -class BleOta -{ - public: - explicit BleOta(){}; - - static String getOtaAppVersion(); - static bool switchToOtaApp(); - - private: - String mUserAgent; - static const esp_partition_t *findEspOtaAppPartition(); -}; - -#endif // BLEOTA_H \ No newline at end of file diff --git a/src/platform/esp32/WiFiOTA.cpp b/src/platform/esp32/MeshtasticOTA.cpp similarity index 80% rename from src/platform/esp32/WiFiOTA.cpp rename to src/platform/esp32/MeshtasticOTA.cpp index 4cf157b4c81..62bd2e327b0 100644 --- a/src/platform/esp32/WiFiOTA.cpp +++ b/src/platform/esp32/MeshtasticOTA.cpp @@ -1,13 +1,13 @@ -#include "WiFiOTA.h" +#include "MeshtasticOTA.h" #include "configuration.h" #include #include -namespace WiFiOTA +namespace MeshtasticOTA { -static const char *nvsNamespace = "ota-wifi"; -static const char *appProjectName = "OTA-WiFi"; +static const char *nvsNamespace = "MeshtasticOTA"; +static const char *appProjectName = "MeshtasticOTA"; static bool updated = false; @@ -43,12 +43,13 @@ void recoverConfig(meshtastic_Config_NetworkConfig *network) strncpy(network->wifi_psk, psk.c_str(), sizeof(network->wifi_psk)); } -void saveConfig(meshtastic_Config_NetworkConfig *network) +void saveConfig(meshtastic_Config_NetworkConfig *network, bool method) { LOG_INFO("Saving WiFi settings for upcoming OTA update"); Preferences prefs; prefs.begin(nvsNamespace); + prefs.putUChar("method", method); prefs.putString("ssid", network->wifi_ssid); prefs.putString("psk", network->wifi_psk); prefs.putBool("updated", false); @@ -62,10 +63,14 @@ const esp_partition_t *getAppPartition() bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc) { - if (esp_ota_get_partition_description(part, app_desc) != ESP_OK) + if (esp_ota_get_partition_description(part, app_desc) != ESP_OK) { + LOG_INFO("esp_ota_get_partition_description failed"); return false; - if (strcmp(app_desc->project_name, appProjectName) != 0) + } + if (strcmp(app_desc->project_name, appProjectName) != 0) { + LOG_INFO("app_desc->project_name == 0"); return false; + } return true; } @@ -89,4 +94,4 @@ const char *getVersion() return app_desc.version; } -} // namespace WiFiOTA +} // namespace MeshtasticOTA diff --git a/src/platform/esp32/WiFiOTA.h b/src/platform/esp32/MeshtasticOTA.h similarity index 50% rename from src/platform/esp32/WiFiOTA.h rename to src/platform/esp32/MeshtasticOTA.h index 5a7ee348a53..eac39fcb40e 100644 --- a/src/platform/esp32/WiFiOTA.h +++ b/src/platform/esp32/MeshtasticOTA.h @@ -1,18 +1,18 @@ -#ifndef WIFIOTA_H -#define WIFIOTA_H +#ifndef MESHTASTICOTA_H +#define MESHTASTICOTA_H #include "mesh-pb-constants.h" #include -namespace WiFiOTA +namespace MeshtasticOTA { void initialize(); bool isUpdated(); void recoverConfig(meshtastic_Config_NetworkConfig *network); -void saveConfig(meshtastic_Config_NetworkConfig *network); +void saveConfig(meshtastic_Config_NetworkConfig *network, bool method); bool trySwitchToOTA(); const char *getVersion(); -} // namespace WiFiOTA +} // namespace MeshtasticOTA -#endif // WIFIOTA_H +#endif // MESHTASTICOTA_H diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 760964119c2..6667acf5cb9 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -5,11 +5,10 @@ #include "main.h" #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH -#include "BleOta.h" #include "nimble/NimbleBluetooth.h" #endif -#include +#include #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" @@ -144,22 +143,14 @@ void esp32Setup() preferences.putUInt("hwVendor", HW_VENDOR); preferences.end(); LOG_DEBUG("Number of Device Reboots: %d", rebootCounter); -#if !MESHTASTIC_EXCLUDE_BLUETOOTH - String BLEOTA = BleOta::getOtaAppVersion(); - if (BLEOTA.isEmpty()) { - LOG_INFO("No BLE OTA firmware available"); - } else { - LOG_INFO("BLE OTA firmware version %s", BLEOTA.c_str()); - } -#endif #if !MESHTASTIC_EXCLUDE_WIFI - String version = WiFiOTA::getVersion(); + String version = MeshtasticOTA::getVersion(); if (version.isEmpty()) { - LOG_INFO("No WiFi OTA firmware available"); + LOG_INFO("MeshtasticOTA firmware not available"); } else { - LOG_INFO("WiFi OTA firmware version %s", version.c_str()); + LOG_INFO("MeshtasticOTA firmware version %s", version.c_str()); } - WiFiOTA::initialize(); + MeshtasticOTA::initialize(); #endif // enableModemSleep(); From 7a5d4ad43cee948243b0474412ad552f4022254b Mon Sep 17 00:00:00 2001 From: Jake-B Date: Thu, 25 Dec 2025 12:03:25 -0500 Subject: [PATCH 2/9] Incorporate ota_hash in AdminMessage protobuf --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.cpp | 2 + src/mesh/generated/meshtastic/admin.pb.h | 30 ++++++++- src/mesh/generated/meshtastic/mesh.pb.cpp | 5 ++ src/mesh/generated/meshtastic/mesh.pb.h | 75 +++++++++++++++++++++ src/mesh/generated/meshtastic/portnums.pb.h | 5 ++ src/modules/AdminModule.cpp | 11 ++- src/platform/esp32/MeshtasticOTA.cpp | 3 +- src/platform/esp32/MeshtasticOTA.h | 2 +- 9 files changed, 127 insertions(+), 8 deletions(-) diff --git a/protobufs b/protobufs index 9beb80f1d30..c39b564ca64 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 9beb80f1d302f70d05f9c4bc9dd543b8f7bc8796 +Subproject commit c39b564ca6498d23a3002b6eaade67a69194b459 diff --git a/src/mesh/generated/meshtastic/admin.pb.cpp b/src/mesh/generated/meshtastic/admin.pb.cpp index 4c4d0e3d1d4..ccece50d374 100644 --- a/src/mesh/generated/meshtastic/admin.pb.cpp +++ b/src/mesh/generated/meshtastic/admin.pb.cpp @@ -33,3 +33,5 @@ PB_BIND(meshtastic_KeyVerificationAdmin, meshtastic_KeyVerificationAdmin, AUTO) + + diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index a542cf29c89..b02c6e331fd 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -16,6 +16,16 @@ #endif /* Enum definitions */ +/* Firmware update mode for OTA updates */ +typedef enum _meshtastic_OTAMode { + /* Do not reboot into OTA mode */ + meshtastic_OTAMode_NO_REBOOT_OTA = 0, + /* Reboot into OTA mode for BLE firmware update */ + meshtastic_OTAMode_OTA_BLE = 1, + /* Reboot into OTA mode for WiFi firmware update */ + meshtastic_OTAMode_OTA_WIFI = 2 +} meshtastic_OTAMode; + /* TODO: REPLACE */ typedef enum _meshtastic_AdminMessage_ConfigType { /* TODO: REPLACE */ @@ -148,6 +158,7 @@ typedef struct _meshtastic_KeyVerificationAdmin { uint32_t security_number; } meshtastic_KeyVerificationAdmin; +typedef PB_BYTES_ARRAY_T(32) meshtastic_AdminMessage_ota_hash_t; typedef PB_BYTES_ARRAY_T(8) meshtastic_AdminMessage_session_passkey_t; /* This message is handled by the Admin module and is responsible for all settings/channel read/write operations. This message is used to do settings operations to both remote AND local nodes. @@ -258,10 +269,13 @@ typedef struct _meshtastic_AdminMessage { meshtastic_SharedContact add_contact; /* Initiate or respond to a key verification request */ meshtastic_KeyVerificationAdmin key_verification; + /* Tell the node to reboot into OTA mode for firmware update via BLE or WiFi (ESP32 only for now) */ + meshtastic_OTAMode reboot_ota_mode; /* Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. */ int32_t factory_reset_device; /* Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot) - Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth. */ + Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth. + Deprecated in favor of reboot_ota_mode in 2.7.17 */ int32_t reboot_ota_seconds; /* This message is only supported for the simulator Portduino build. If received the simulator will exit successfully. */ @@ -275,6 +289,9 @@ typedef struct _meshtastic_AdminMessage { /* Tell the node to reset the nodedb. When true, favorites are preserved through reset. */ bool nodedb_reset; + /* A 32 byte hash of the OTA firmware. + Used to verify the integrity of the firmware before applying an update. */ + meshtastic_AdminMessage_ota_hash_t ota_hash; }; /* The node generates this key and sends it with any get_x_response packets. The client MUST include the same key with any set_x commands. Key expires after 300 seconds. @@ -288,6 +305,10 @@ extern "C" { #endif /* Helper constants for enums */ +#define _meshtastic_OTAMode_MIN meshtastic_OTAMode_NO_REBOOT_OTA +#define _meshtastic_OTAMode_MAX meshtastic_OTAMode_OTA_WIFI +#define _meshtastic_OTAMode_ARRAYSIZE ((meshtastic_OTAMode)(meshtastic_OTAMode_OTA_WIFI+1)) + #define _meshtastic_AdminMessage_ConfigType_MIN meshtastic_AdminMessage_ConfigType_DEVICE_CONFIG #define _meshtastic_AdminMessage_ConfigType_MAX meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG #define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1)) @@ -309,6 +330,7 @@ extern "C" { #define meshtastic_AdminMessage_payload_variant_backup_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation #define meshtastic_AdminMessage_payload_variant_restore_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation #define meshtastic_AdminMessage_payload_variant_remove_backup_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation +#define meshtastic_AdminMessage_payload_variant_reboot_ota_mode_ENUMTYPE meshtastic_OTAMode @@ -396,6 +418,7 @@ extern "C" { #define meshtastic_AdminMessage_commit_edit_settings_tag 65 #define meshtastic_AdminMessage_add_contact_tag 66 #define meshtastic_AdminMessage_key_verification_tag 67 +#define meshtastic_AdminMessage_reboot_ota_mode_tag 68 #define meshtastic_AdminMessage_factory_reset_device_tag 94 #define meshtastic_AdminMessage_reboot_ota_seconds_tag 95 #define meshtastic_AdminMessage_exit_simulator_tag 96 @@ -403,6 +426,7 @@ extern "C" { #define meshtastic_AdminMessage_shutdown_seconds_tag 98 #define meshtastic_AdminMessage_factory_reset_config_tag 99 #define meshtastic_AdminMessage_nodedb_reset_tag 100 +#define meshtastic_AdminMessage_ota_hash_tag 102 #define meshtastic_AdminMessage_session_passkey_tag 101 /* Struct field encoding specification for nanopb */ @@ -454,6 +478,7 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_ed X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,add_contact,add_contact), 66) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,key_verification,key_verification), 67) \ +X(a, STATIC, ONEOF, UENUM, (payload_variant,reboot_ota_mode,reboot_ota_mode), 68) \ X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_device,factory_reset_device), 94) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulator), 96) \ @@ -461,7 +486,8 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_second X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \ X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \ -X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) +X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) \ +X(a, STATIC, ONEOF, BYTES, (payload_variant,ota_hash,ota_hash), 102) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL #define meshtastic_AdminMessage_payload_variant_get_channel_response_MSGTYPE meshtastic_Channel diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 9966e52f8da..d8eee120336 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -24,6 +24,9 @@ PB_BIND(meshtastic_Data, meshtastic_Data, 2) PB_BIND(meshtastic_KeyVerification, meshtastic_KeyVerification, AUTO) +PB_BIND(meshtastic_StoreForwardPlusPlus, meshtastic_StoreForwardPlusPlus, 2) + + PB_BIND(meshtastic_Waypoint, meshtastic_Waypoint, AUTO) @@ -121,6 +124,8 @@ PB_BIND(meshtastic_ChunkedPayloadResponse, meshtastic_ChunkedPayloadResponse, AU + + diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 0c48a789128..6f2c755bed2 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -478,6 +478,22 @@ typedef enum _meshtastic_Routing_Error { meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED = 38 } meshtastic_Routing_Error; +/* Enum of message types */ +typedef enum _meshtastic_StoreForwardPlusPlus_SFPP_message_type { + /* Send an announcement of the canonical tip of a chain */ + meshtastic_StoreForwardPlusPlus_SFPP_message_type_CANON_ANNOUNCE = 0, + /* Query whether a specific link is on the chain */ + meshtastic_StoreForwardPlusPlus_SFPP_message_type_CHAIN_QUERY = 1, + /* Request the next link in the chain */ + meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_REQUEST = 3, + /* Provide a link to add to the chain */ + meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE = 4, + /* If we must fragment, send the first half */ + meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_FIRSTHALF = 5, + /* If we must fragment, send the second half */ + meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF = 6 +} meshtastic_StoreForwardPlusPlus_SFPP_message_type; + /* The priority of this message for sending. Higher priorities are sent first (when managing the transmit queue). This field is never sent over the air, it is only used internally inside of a local device node. @@ -782,6 +798,32 @@ typedef struct _meshtastic_KeyVerification { meshtastic_KeyVerification_hash2_t hash2; } meshtastic_KeyVerification; +typedef PB_BYTES_ARRAY_T(32) meshtastic_StoreForwardPlusPlus_message_hash_t; +typedef PB_BYTES_ARRAY_T(32) meshtastic_StoreForwardPlusPlus_commit_hash_t; +typedef PB_BYTES_ARRAY_T(32) meshtastic_StoreForwardPlusPlus_root_hash_t; +typedef PB_BYTES_ARRAY_T(240) meshtastic_StoreForwardPlusPlus_message_t; +/* The actual over-the-mesh message doing store and forward++ */ +typedef struct _meshtastic_StoreForwardPlusPlus { + /* Which message type is this */ + meshtastic_StoreForwardPlusPlus_SFPP_message_type sfpp_message_type; + /* The hash of the specific message */ + meshtastic_StoreForwardPlusPlus_message_hash_t message_hash; + /* The hash of a link on a chain */ + meshtastic_StoreForwardPlusPlus_commit_hash_t commit_hash; + /* the root hash of a chain */ + meshtastic_StoreForwardPlusPlus_root_hash_t root_hash; + /* The encrypted bytes from a message */ + meshtastic_StoreForwardPlusPlus_message_t message; + /* Message ID of the contained message */ + uint32_t encapsulated_id; + /* Destination of the contained message */ + uint32_t encapsulated_to; + /* Sender of the contained message */ + uint32_t encapsulated_from; + /* The receive time of the message in question */ + uint32_t encapsulated_rxtime; +} meshtastic_StoreForwardPlusPlus; + /* Waypoint message, used to share arbitrary locations across the mesh */ typedef struct _meshtastic_Waypoint { /* Id of the waypoint */ @@ -1310,6 +1352,10 @@ extern "C" { #define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED #define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED+1)) +#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN meshtastic_StoreForwardPlusPlus_SFPP_message_type_CANON_ANNOUNCE +#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MAX meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF +#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_ARRAYSIZE ((meshtastic_StoreForwardPlusPlus_SFPP_message_type)(meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF+1)) + #define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET #define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX #define _meshtastic_MeshPacket_Priority_ARRAYSIZE ((meshtastic_MeshPacket_Priority)(meshtastic_MeshPacket_Priority_MAX+1)) @@ -1338,6 +1384,8 @@ extern "C" { #define meshtastic_Data_portnum_ENUMTYPE meshtastic_PortNum +#define meshtastic_StoreForwardPlusPlus_sfpp_message_type_ENUMTYPE meshtastic_StoreForwardPlusPlus_SFPP_message_type + #define meshtastic_MeshPacket_priority_ENUMTYPE meshtastic_MeshPacket_Priority @@ -1380,6 +1428,7 @@ extern "C" { #define meshtastic_Routing_init_default {0, {meshtastic_RouteDiscovery_init_default}} #define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}} +#define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} @@ -1411,6 +1460,7 @@ extern "C" { #define meshtastic_Routing_init_zero {0, {meshtastic_RouteDiscovery_init_zero}} #define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}} +#define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} @@ -1489,6 +1539,15 @@ extern "C" { #define meshtastic_KeyVerification_nonce_tag 1 #define meshtastic_KeyVerification_hash1_tag 2 #define meshtastic_KeyVerification_hash2_tag 3 +#define meshtastic_StoreForwardPlusPlus_sfpp_message_type_tag 1 +#define meshtastic_StoreForwardPlusPlus_message_hash_tag 2 +#define meshtastic_StoreForwardPlusPlus_commit_hash_tag 3 +#define meshtastic_StoreForwardPlusPlus_root_hash_tag 4 +#define meshtastic_StoreForwardPlusPlus_message_tag 5 +#define meshtastic_StoreForwardPlusPlus_encapsulated_id_tag 6 +#define meshtastic_StoreForwardPlusPlus_encapsulated_to_tag 7 +#define meshtastic_StoreForwardPlusPlus_encapsulated_from_tag 8 +#define meshtastic_StoreForwardPlusPlus_encapsulated_rxtime_tag 9 #define meshtastic_Waypoint_id_tag 1 #define meshtastic_Waypoint_latitude_i_tag 2 #define meshtastic_Waypoint_longitude_i_tag 3 @@ -1705,6 +1764,19 @@ X(a, STATIC, SINGULAR, BYTES, hash2, 3) #define meshtastic_KeyVerification_CALLBACK NULL #define meshtastic_KeyVerification_DEFAULT NULL +#define meshtastic_StoreForwardPlusPlus_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, sfpp_message_type, 1) \ +X(a, STATIC, SINGULAR, BYTES, message_hash, 2) \ +X(a, STATIC, SINGULAR, BYTES, commit_hash, 3) \ +X(a, STATIC, SINGULAR, BYTES, root_hash, 4) \ +X(a, STATIC, SINGULAR, BYTES, message, 5) \ +X(a, STATIC, SINGULAR, UINT32, encapsulated_id, 6) \ +X(a, STATIC, SINGULAR, UINT32, encapsulated_to, 7) \ +X(a, STATIC, SINGULAR, UINT32, encapsulated_from, 8) \ +X(a, STATIC, SINGULAR, UINT32, encapsulated_rxtime, 9) +#define meshtastic_StoreForwardPlusPlus_CALLBACK NULL +#define meshtastic_StoreForwardPlusPlus_DEFAULT NULL + #define meshtastic_Waypoint_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, id, 1) \ X(a, STATIC, OPTIONAL, SFIXED32, latitude_i, 2) \ @@ -1980,6 +2052,7 @@ extern const pb_msgdesc_t meshtastic_RouteDiscovery_msg; extern const pb_msgdesc_t meshtastic_Routing_msg; extern const pb_msgdesc_t meshtastic_Data_msg; extern const pb_msgdesc_t meshtastic_KeyVerification_msg; +extern const pb_msgdesc_t meshtastic_StoreForwardPlusPlus_msg; extern const pb_msgdesc_t meshtastic_Waypoint_msg; extern const pb_msgdesc_t meshtastic_MqttClientProxyMessage_msg; extern const pb_msgdesc_t meshtastic_MeshPacket_msg; @@ -2013,6 +2086,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_Routing_fields &meshtastic_Routing_msg #define meshtastic_Data_fields &meshtastic_Data_msg #define meshtastic_KeyVerification_fields &meshtastic_KeyVerification_msg +#define meshtastic_StoreForwardPlusPlus_fields &meshtastic_StoreForwardPlusPlus_msg #define meshtastic_Waypoint_fields &meshtastic_Waypoint_msg #define meshtastic_MqttClientProxyMessage_fields &meshtastic_MqttClientProxyMessage_msg #define meshtastic_MeshPacket_fields &meshtastic_MeshPacket_msg @@ -2069,6 +2143,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 256 #define meshtastic_Routing_size 259 +#define meshtastic_StoreForwardPlusPlus_size 371 #define meshtastic_ToRadio_size 504 #define meshtastic_User_size 115 #define meshtastic_Waypoint_size 165 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 67adc60cc55..6b89c6a37a6 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -86,6 +86,11 @@ typedef enum _meshtastic_PortNum { /* Paxcounter lib included in the firmware ENCODING: protobuf */ meshtastic_PortNum_PAXCOUNTER_APP = 34, + /* Store and Forward++ module included in the firmware + ENCODING: protobuf + This module is specifically for Native Linux nodes, and provides a Git-style + chain of messages. */ + meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP = 35, /* Provides a hardware serial interface to send and receive from the Meshtastic network. Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network. diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 6d17cdd1433..a539589f60f 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -234,19 +234,24 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta break; } case meshtastic_AdminMessage_reboot_ota_seconds_tag: { - // TODO: Update to new protobufs - int32_t s = r->reboot_ota_seconds; #if defined(ARCH_ESP32) + if (r->ota_hash.size != 32) { + LOG_INFO("OTA Failed: Invalid `ota_hash` provided"); + break; + } + + meshtastic_OTAMode mode = r->reboot_ota_mode; if (MeshtasticOTA::trySwitchToOTA()) { LOG_INFO("OTA Requested"); if (screen) screen->startFirmwareUpdateScreen(); - MeshtasticOTA::saveConfig(&config.network, (s % 2) & 0b1); + MeshtasticOTA::saveConfig(&config.network, mode, r->ota_hash.bytes); LOG_INFO("Rebooting to WiFi OTA"); } else { LOG_INFO("WIFI OTA Failed"); } #endif + int s = 1; // Reboot in 1 second, hard coded LOG_INFO("Reboot in %d seconds", s); rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000); break; diff --git a/src/platform/esp32/MeshtasticOTA.cpp b/src/platform/esp32/MeshtasticOTA.cpp index 62bd2e327b0..28776c066c7 100644 --- a/src/platform/esp32/MeshtasticOTA.cpp +++ b/src/platform/esp32/MeshtasticOTA.cpp @@ -43,13 +43,14 @@ void recoverConfig(meshtastic_Config_NetworkConfig *network) strncpy(network->wifi_psk, psk.c_str(), sizeof(network->wifi_psk)); } -void saveConfig(meshtastic_Config_NetworkConfig *network, bool method) +void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t* ota_hash) { LOG_INFO("Saving WiFi settings for upcoming OTA update"); Preferences prefs; prefs.begin(nvsNamespace); prefs.putUChar("method", method); + prefs.putBytes("ota_hash", ota_hash, 32); prefs.putString("ssid", network->wifi_ssid); prefs.putString("psk", network->wifi_psk); prefs.putBool("updated", false); diff --git a/src/platform/esp32/MeshtasticOTA.h b/src/platform/esp32/MeshtasticOTA.h index eac39fcb40e..69175877501 100644 --- a/src/platform/esp32/MeshtasticOTA.h +++ b/src/platform/esp32/MeshtasticOTA.h @@ -10,7 +10,7 @@ void initialize(); bool isUpdated(); void recoverConfig(meshtastic_Config_NetworkConfig *network); -void saveConfig(meshtastic_Config_NetworkConfig *network, bool method); +void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t* ota_hash); bool trySwitchToOTA(); const char *getVersion(); } // namespace MeshtasticOTA From 59ca02aca014e7e346518f067e1536c4d811c590 Mon Sep 17 00:00:00 2001 From: Jake-B Date: Sat, 27 Dec 2025 19:10:26 -0500 Subject: [PATCH 3/9] OTA protobuf changes --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.cpp | 3 ++ src/mesh/generated/meshtastic/admin.pb.h | 42 ++++++++++++++++------ src/modules/AdminModule.cpp | 8 ++--- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/protobufs b/protobufs index c39b564ca64..4b9f104a18e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit c39b564ca6498d23a3002b6eaade67a69194b459 +Subproject commit 4b9f104a18ea43b1b2091ee2b48899fe43ad8a0b diff --git a/src/mesh/generated/meshtastic/admin.pb.cpp b/src/mesh/generated/meshtastic/admin.pb.cpp index ccece50d374..e358bc96d35 100644 --- a/src/mesh/generated/meshtastic/admin.pb.cpp +++ b/src/mesh/generated/meshtastic/admin.pb.cpp @@ -12,6 +12,9 @@ PB_BIND(meshtastic_AdminMessage, meshtastic_AdminMessage, 2) PB_BIND(meshtastic_AdminMessage_InputEvent, meshtastic_AdminMessage_InputEvent, AUTO) +PB_BIND(meshtastic_AdminMessage_OTAEvent, meshtastic_AdminMessage_OTAEvent, AUTO) + + PB_BIND(meshtastic_HamParameters, meshtastic_HamParameters, AUTO) diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index b02c6e331fd..ec6e13e9e73 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -113,6 +113,17 @@ typedef struct _meshtastic_AdminMessage_InputEvent { uint16_t touch_y; } meshtastic_AdminMessage_InputEvent; +typedef PB_BYTES_ARRAY_T(32) meshtastic_AdminMessage_OTAEvent_ota_hash_t; +/* User is requesting an over the air update. + Node will reboot into the OTA loader */ +typedef struct _meshtastic_AdminMessage_OTAEvent { + /* Tell the node to reboot into OTA mode for firmware update via BLE or WiFi (ESP32 only for now) */ + meshtastic_OTAMode reboot_ota_mode; + /* A 32 byte hash of the OTA firmware. + Used to verify the integrity of the firmware before applying an update. */ + meshtastic_AdminMessage_OTAEvent_ota_hash_t ota_hash; +} meshtastic_AdminMessage_OTAEvent; + /* Parameters for setting up Meshtastic for ameteur radio usage */ typedef struct _meshtastic_HamParameters { /* Amateur radio call sign, eg. KD2ABC */ @@ -158,7 +169,6 @@ typedef struct _meshtastic_KeyVerificationAdmin { uint32_t security_number; } meshtastic_KeyVerificationAdmin; -typedef PB_BYTES_ARRAY_T(32) meshtastic_AdminMessage_ota_hash_t; typedef PB_BYTES_ARRAY_T(8) meshtastic_AdminMessage_session_passkey_t; /* This message is handled by the Admin module and is responsible for all settings/channel read/write operations. This message is used to do settings operations to both remote AND local nodes. @@ -269,8 +279,6 @@ typedef struct _meshtastic_AdminMessage { meshtastic_SharedContact add_contact; /* Initiate or respond to a key verification request */ meshtastic_KeyVerificationAdmin key_verification; - /* Tell the node to reboot into OTA mode for firmware update via BLE or WiFi (ESP32 only for now) */ - meshtastic_OTAMode reboot_ota_mode; /* Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. */ int32_t factory_reset_device; /* Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot) @@ -289,9 +297,8 @@ typedef struct _meshtastic_AdminMessage { /* Tell the node to reset the nodedb. When true, favorites are preserved through reset. */ bool nodedb_reset; - /* A 32 byte hash of the OTA firmware. - Used to verify the integrity of the firmware before applying an update. */ - meshtastic_AdminMessage_ota_hash_t ota_hash; + /* Tell the node to reset into the OTA Loader */ + meshtastic_AdminMessage_OTAEvent ota_request; }; /* The node generates this key and sends it with any get_x_response packets. The client MUST include the same key with any set_x commands. Key expires after 300 seconds. @@ -330,9 +337,10 @@ extern "C" { #define meshtastic_AdminMessage_payload_variant_backup_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation #define meshtastic_AdminMessage_payload_variant_restore_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation #define meshtastic_AdminMessage_payload_variant_remove_backup_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation -#define meshtastic_AdminMessage_payload_variant_reboot_ota_mode_ENUMTYPE meshtastic_OTAMode +#define meshtastic_AdminMessage_OTAEvent_reboot_ota_mode_ENUMTYPE meshtastic_OTAMode + @@ -342,12 +350,14 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0} +#define meshtastic_AdminMessage_OTAEvent_init_default {_meshtastic_OTAMode_MIN, {0, {0}}} #define meshtastic_HamParameters_init_default {"", 0, 0, ""} #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} #define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0} #define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} #define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0} +#define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}} #define meshtastic_HamParameters_init_zero {"", 0, 0, ""} #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} #define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0} @@ -358,6 +368,8 @@ extern "C" { #define meshtastic_AdminMessage_InputEvent_kb_char_tag 2 #define meshtastic_AdminMessage_InputEvent_touch_x_tag 3 #define meshtastic_AdminMessage_InputEvent_touch_y_tag 4 +#define meshtastic_AdminMessage_OTAEvent_reboot_ota_mode_tag 1 +#define meshtastic_AdminMessage_OTAEvent_ota_hash_tag 2 #define meshtastic_HamParameters_call_sign_tag 1 #define meshtastic_HamParameters_tx_power_tag 2 #define meshtastic_HamParameters_frequency_tag 3 @@ -418,7 +430,6 @@ extern "C" { #define meshtastic_AdminMessage_commit_edit_settings_tag 65 #define meshtastic_AdminMessage_add_contact_tag 66 #define meshtastic_AdminMessage_key_verification_tag 67 -#define meshtastic_AdminMessage_reboot_ota_mode_tag 68 #define meshtastic_AdminMessage_factory_reset_device_tag 94 #define meshtastic_AdminMessage_reboot_ota_seconds_tag 95 #define meshtastic_AdminMessage_exit_simulator_tag 96 @@ -426,7 +437,7 @@ extern "C" { #define meshtastic_AdminMessage_shutdown_seconds_tag 98 #define meshtastic_AdminMessage_factory_reset_config_tag 99 #define meshtastic_AdminMessage_nodedb_reset_tag 100 -#define meshtastic_AdminMessage_ota_hash_tag 102 +#define meshtastic_AdminMessage_ota_request_tag 102 #define meshtastic_AdminMessage_session_passkey_tag 101 /* Struct field encoding specification for nanopb */ @@ -478,7 +489,6 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_ed X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,add_contact,add_contact), 66) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,key_verification,key_verification), 67) \ -X(a, STATIC, ONEOF, UENUM, (payload_variant,reboot_ota_mode,reboot_ota_mode), 68) \ X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_device,factory_reset_device), 94) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulator), 96) \ @@ -487,7 +497,7 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_se X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \ X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) \ -X(a, STATIC, ONEOF, BYTES, (payload_variant,ota_hash,ota_hash), 102) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL #define meshtastic_AdminMessage_payload_variant_get_channel_response_MSGTYPE meshtastic_Channel @@ -508,6 +518,7 @@ X(a, STATIC, ONEOF, BYTES, (payload_variant,ota_hash,ota_hash), 102) #define meshtastic_AdminMessage_payload_variant_store_ui_config_MSGTYPE meshtastic_DeviceUIConfig #define meshtastic_AdminMessage_payload_variant_add_contact_MSGTYPE meshtastic_SharedContact #define meshtastic_AdminMessage_payload_variant_key_verification_MSGTYPE meshtastic_KeyVerificationAdmin +#define meshtastic_AdminMessage_payload_variant_ota_request_MSGTYPE meshtastic_AdminMessage_OTAEvent #define meshtastic_AdminMessage_InputEvent_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, event_code, 1) \ @@ -517,6 +528,12 @@ X(a, STATIC, SINGULAR, UINT32, touch_y, 4) #define meshtastic_AdminMessage_InputEvent_CALLBACK NULL #define meshtastic_AdminMessage_InputEvent_DEFAULT NULL +#define meshtastic_AdminMessage_OTAEvent_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, reboot_ota_mode, 1) \ +X(a, STATIC, SINGULAR, BYTES, ota_hash, 2) +#define meshtastic_AdminMessage_OTAEvent_CALLBACK NULL +#define meshtastic_AdminMessage_OTAEvent_DEFAULT NULL + #define meshtastic_HamParameters_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, call_sign, 1) \ X(a, STATIC, SINGULAR, INT32, tx_power, 2) \ @@ -550,6 +567,7 @@ X(a, STATIC, OPTIONAL, UINT32, security_number, 4) extern const pb_msgdesc_t meshtastic_AdminMessage_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg; +extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg; extern const pb_msgdesc_t meshtastic_HamParameters_msg; extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg; extern const pb_msgdesc_t meshtastic_SharedContact_msg; @@ -558,6 +576,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg #define meshtastic_AdminMessage_InputEvent_fields &meshtastic_AdminMessage_InputEvent_msg +#define meshtastic_AdminMessage_OTAEvent_fields &meshtastic_AdminMessage_OTAEvent_msg #define meshtastic_HamParameters_fields &meshtastic_HamParameters_msg #define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg #define meshtastic_SharedContact_fields &meshtastic_SharedContact_msg @@ -566,6 +585,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size #define meshtastic_AdminMessage_InputEvent_size 14 +#define meshtastic_AdminMessage_OTAEvent_size 36 #define meshtastic_AdminMessage_size 511 #define meshtastic_HamParameters_size 31 #define meshtastic_KeyVerificationAdmin_size 25 diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index a539589f60f..e1f85d31cd3 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -233,19 +233,19 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta reboot(r->reboot_seconds); break; } - case meshtastic_AdminMessage_reboot_ota_seconds_tag: { + case meshtastic_AdminMessage_ota_request_tag: { #if defined(ARCH_ESP32) - if (r->ota_hash.size != 32) { + if (r->ota_request.ota_hash.size != 32) { LOG_INFO("OTA Failed: Invalid `ota_hash` provided"); break; } - meshtastic_OTAMode mode = r->reboot_ota_mode; + meshtastic_OTAMode mode = r->ota_request.reboot_ota_mode; if (MeshtasticOTA::trySwitchToOTA()) { LOG_INFO("OTA Requested"); if (screen) screen->startFirmwareUpdateScreen(); - MeshtasticOTA::saveConfig(&config.network, mode, r->ota_hash.bytes); + MeshtasticOTA::saveConfig(&config.network, mode, r->ota_request.ota_hash.bytes); LOG_INFO("Rebooting to WiFi OTA"); } else { LOG_INFO("WIFI OTA Failed"); From 6574af05513906fea61fe58df5a32315488b52f3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 14 Jan 2026 16:16:38 -0600 Subject: [PATCH 4/9] Trunk fmt --- src/detect/reClockI2C.h | 5 ++- src/modules/Telemetry/AirQualityTelemetry.cpp | 45 +++++++++---------- src/modules/Telemetry/AirQualityTelemetry.h | 4 +- .../Telemetry/EnvironmentTelemetry.cpp | 2 +- .../Telemetry/Sensor/AddI2CSensorTemplate.h | 2 +- .../Telemetry/Sensor/PMSA003ISensor.cpp | 16 +++---- src/modules/Telemetry/Sensor/PMSA003ISensor.h | 8 ++-- .../Telemetry/Sensor/TelemetrySensor.h | 2 +- src/platform/esp32/MeshtasticOTA.cpp | 2 +- src/platform/esp32/MeshtasticOTA.h | 2 +- 10 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/detect/reClockI2C.h b/src/detect/reClockI2C.h index edcd0afb601..689e88d6f19 100644 --- a/src/detect/reClockI2C.h +++ b/src/detect/reClockI2C.h @@ -1,7 +1,8 @@ #ifdef CAN_RECLOCK_I2C #include "ScanI2CTwoWire.h" -uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus) { +uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus) +{ uint32_t currentClock; @@ -31,7 +32,7 @@ uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus) { return 0; #endif - if (currentClock != desiredClock){ + if (currentClock != desiredClock) { LOG_DEBUG("Changing I2C clock to %u", desiredClock); i2cBus->setClock(desiredClock); } diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index dff23abf1b3..01f5da2c6c8 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -3,26 +3,25 @@ #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "Default.h" #include "AirQualityTelemetry.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" #include "RTC.h" #include "Router.h" +#include "Sensor/AddI2CSensorTemplate.h" #include "UnitConversions.h" +#include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" #include "graphics/images.h" -#include "graphics/ScreenFonts.h" #include "main.h" #include "sleep.h" #include -#include "Sensor/AddI2CSensorTemplate.h" // Sensors #include "Sensor/PMSA003ISensor.h" - void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) { if (!moduleConfig.telemetry.air_quality_enabled && !AIR_QUALITY_TELEMETRY_MODULE_ENABLE) { @@ -57,7 +56,7 @@ int32_t AirQualityTelemetryModule::runOnce() uint32_t result = UINT32_MAX; - if (!(moduleConfig.telemetry.air_quality_enabled || moduleConfig.telemetry.air_quality_screen_enabled || + if (!(moduleConfig.telemetry.air_quality_enabled || moduleConfig.telemetry.air_quality_screen_enabled || AIR_QUALITY_TELEMETRY_MODULE_ENABLE)) { // If this module is not enabled, and the user doesn't want the display screen don't waste any OSThread time on it return disable(); @@ -74,7 +73,6 @@ int32_t AirQualityTelemetryModule::runOnce() if (!sensors.empty()) { result = DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } - } // it's possible to have this module enabled, only for displaying values on the screen. @@ -95,27 +93,26 @@ int32_t AirQualityTelemetryModule::runOnce() } if (((lastSentToMesh == 0) || - !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( - moduleConfig.telemetry.air_quality_interval, - default_telemetry_broadcast_interval_secs, numOnlineNodes))) && + !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( + moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = millis(); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && - (service->isToPhoneQueueEmpty())) { + (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) sendTelemetry(NODENUM_BROADCAST, true); lastSentToPhone = millis(); } - // Send to sleep sensors that consume power - LOG_INFO("Sending sensors to sleep"); - for (TelemetrySensor *sensor : sensors) { - sensor->sleep(); - } - + // Send to sleep sensors that consume power + LOG_INFO("Sending sensors to sleep"); + for (TelemetrySensor *sensor : sensors) { + sensor->sleep(); + } } return min(sendToPhoneIntervalMs, result); } @@ -161,8 +158,8 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta const auto &m = telemetry.variant.air_quality_metrics; // Check if any telemetry field has valid data - bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard || m.has_pm10_environmental || m.has_pm25_environmental || - m.has_pm100_environmental; + bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard || m.has_pm10_environmental || + m.has_pm25_environmental || m.has_pm100_environmental; if (!hasAny) { display->drawString(x, currentY, "No Telemetry"); @@ -296,10 +293,10 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.time = getTime(); if (getAirQualityTelemetry(&m)) { LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u, \ - pm10_environmental=%u, pm25_environmental=%u, pm100_environmental=%u", \ - m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard, \ - m.variant.air_quality_metrics.pm100_standard, m.variant.air_quality_metrics.pm10_environmental, \ - m.variant.air_quality_metrics.pm25_environmental, m.variant.air_quality_metrics.pm100_environmental); + pm10_environmental=%u, pm25_environmental=%u, pm100_environmental=%u", + m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard, + m.variant.air_quality_metrics.pm100_standard, m.variant.air_quality_metrics.pm10_environmental, + m.variant.air_quality_metrics.pm25_environmental, m.variant.air_quality_metrics.pm100_environmental); meshtastic_MeshPacket *p = allocDataProtobuf(m); p->to = dest; @@ -341,8 +338,8 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) } AdminMessageHandleResult AirQualityTelemetryModule::handleAdminMessageForModule(const meshtastic_MeshPacket &mp, - meshtastic_AdminMessage *request, - meshtastic_AdminMessage *response) + meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) { AdminMessageHandleResult result = AdminMessageHandleResult::NOT_HANDLED; diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index af9c4ebc000..2b88b74bacf 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -16,8 +16,8 @@ #include class AirQualityTelemetryModule : private concurrency::OSThread, - public ScanI2CConsumer, - public ProtobufModule + public ScanI2CConsumer, + public ProtobufModule { CallbackObserver nodeStatusObserver = CallbackObserver(this, diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 5d70ac308ff..ec6fe479962 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -141,9 +141,9 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true +#include "Sensor/AddI2CSensorTemplate.h" #include "graphics/ScreenFonts.h" #include -#include "Sensor/AddI2CSensorTemplate.h" void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) { diff --git a/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h b/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h index 01aacc6741b..37d909d71f1 100644 --- a/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h +++ b/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h @@ -1,10 +1,10 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR -#include #include "TelemetrySensor.h" #include "detect/ScanI2C.h" #include "detect/ScanI2CTwoWire.h" #include +#include static std::forward_list sensors; diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp index 467659efe6c..2225a4d8728 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp @@ -2,17 +2,14 @@ #if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR +#include "../detect/reClockI2C.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "PMSA003ISensor.h" #include "TelemetrySensor.h" -#include "../detect/reClockI2C.h" #include -PMSA003ISensor::PMSA003ISensor() - : TelemetrySensor(meshtastic_TelemetrySensorType_PMSA003I, "PMSA003I") -{ -} +PMSA003ISensor::PMSA003ISensor() : TelemetrySensor(meshtastic_TelemetrySensorType_PMSA003I, "PMSA003I") {} bool PMSA003ISensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { @@ -26,7 +23,7 @@ bool PMSA003ISensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) #if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) uint32_t currentClock = reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus); - if (!currentClock){ + if (!currentClock) { LOG_WARN("PMSA003I can't be used at this clock speed"); return false; } @@ -51,7 +48,7 @@ bool PMSA003ISensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) { - if(!isActive()){ + if (!isActive()) { LOG_WARN("PMSA003I is not active"); return false; } @@ -79,9 +76,7 @@ bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) return false; } - auto read16 = [](uint8_t *data, uint8_t idx) -> uint16_t { - return (data[idx] << 8) | data[idx + 1]; - }; + auto read16 = [](uint8_t *data, uint8_t idx) -> uint16_t { return (data[idx] << 8) | data[idx + 1]; }; computedChecksum = 0; @@ -141,7 +136,6 @@ bool PMSA003ISensor::isActive() return state == State::ACTIVE; } - void PMSA003ISensor::sleep() { #ifdef PMSA003I_ENABLE_PIN diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.h b/src/modules/Telemetry/Sensor/PMSA003ISensor.h index 47c8a05cc5d..09b43d620c0 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.h +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.h @@ -6,12 +6,12 @@ #include "TelemetrySensor.h" #define PMSA003I_I2C_CLOCK_SPEED 100000 -#define PMSA003I_FRAME_LENGTH 32 +#define PMSA003I_FRAME_LENGTH 32 #define PMSA003I_WARMUP_MS 30000 class PMSA003ISensor : public TelemetrySensor { -public: + public: PMSA003ISensor(); virtual bool getMetrics(meshtastic_Telemetry *measurement) override; virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; @@ -20,7 +20,7 @@ class PMSA003ISensor : public TelemetrySensor virtual void sleep() override; virtual uint32_t wakeUp() override; -private: + private: enum class State { IDLE, ACTIVE }; State state = State::ACTIVE; @@ -28,7 +28,7 @@ class PMSA003ISensor : public TelemetrySensor uint16_t receivedChecksum = 0; uint8_t buffer[PMSA003I_FRAME_LENGTH]{}; - TwoWire * _bus{}; + TwoWire *_bus{}; uint8_t _address{}; }; diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h index 4a325aeed79..af51ddfad5a 100644 --- a/src/modules/Telemetry/Sensor/TelemetrySensor.h +++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h @@ -59,7 +59,7 @@ class TelemetrySensor // TODO: delete after migration bool hasSensor() { return nodeTelemetrySensorsMap[sensorType].first > 0; } // Functions to sleep / wakeup sensors that support it - virtual void sleep() {}; + virtual void sleep(){}; virtual uint32_t wakeUp() { return 0; } // Return active by default, override per sensor virtual bool isActive() { return true; } diff --git a/src/platform/esp32/MeshtasticOTA.cpp b/src/platform/esp32/MeshtasticOTA.cpp index 28776c066c7..b8cb052ef8f 100644 --- a/src/platform/esp32/MeshtasticOTA.cpp +++ b/src/platform/esp32/MeshtasticOTA.cpp @@ -43,7 +43,7 @@ void recoverConfig(meshtastic_Config_NetworkConfig *network) strncpy(network->wifi_psk, psk.c_str(), sizeof(network->wifi_psk)); } -void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t* ota_hash) +void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash) { LOG_INFO("Saving WiFi settings for upcoming OTA update"); diff --git a/src/platform/esp32/MeshtasticOTA.h b/src/platform/esp32/MeshtasticOTA.h index 69175877501..001eba039bd 100644 --- a/src/platform/esp32/MeshtasticOTA.h +++ b/src/platform/esp32/MeshtasticOTA.h @@ -10,7 +10,7 @@ void initialize(); bool isUpdated(); void recoverConfig(meshtastic_Config_NetworkConfig *network); -void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t* ota_hash); +void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash); bool trySwitchToOTA(); const char *getVersion(); } // namespace MeshtasticOTA From f7a374600c7c17ff3af109064d7b46fd35317922 Mon Sep 17 00:00:00 2001 From: Jake-B Date: Thu, 15 Jan 2026 12:37:03 -0500 Subject: [PATCH 5/9] Partition header check for OTA type --- src/main.cpp | 35 ++++++++++++++- src/modules/AdminModule.cpp | 64 +++++++++++++++++++++++++--- src/modules/AdminModule.h | 4 +- src/platform/esp32/MeshtasticOTA.cpp | 41 ++++++++++++++---- src/platform/esp32/MeshtasticOTA.h | 8 +++- 5 files changed, 135 insertions(+), 17 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index cdaf1ce37a1..d69b4a62817 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -105,6 +105,39 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #include #endif +#include "esp_partition.h" + +void printPartitionTable() +{ + printf("\n--- Partition Table ---\n"); + // Print Column Headers + printf("| %-16s | %-4s | %-7s | %-10s | %-10s |\n", "Label", "Type", "Subtype", "Offset", "Size"); + printf("|------------------|------|---------|------------|------------|\n"); + + // Create an iterator to find ALL partitions (Type ANY, Subtype ANY) + esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL); + + // Loop through the iterator + if (it != NULL) { + do { + const esp_partition_t *part = esp_partition_get(it); + + // Print details: Label, Type (Hex), Subtype (Hex), Offset (Hex), Size (Hex) + printf("| %-16s | 0x%02x | 0x%02x | 0x%08x | 0x%08x |\n", part->label, part->type, part->subtype, part->address, + part->size); + + // Move to next partition + it = esp_partition_next(it); + } while (it != NULL); + + // Release the iterator memory + esp_partition_iterator_release(it); + } else { + printf("No partitions found.\n"); + } + printf("-----------------------\n"); +} + #if HAS_BUTTON || defined(ARCH_PORTDUINO) #include "input/ButtonThread.h" @@ -648,7 +681,7 @@ void setup() sensor_detected = true; #endif } - + printPartitionTable(); #ifdef ARCH_ESP32 // Don't init display if we don't have one or we are waking headless due to a timer event if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) { diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 990ca0f4609..1fda9bf1336 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -235,22 +235,46 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } case meshtastic_AdminMessage_ota_request_tag: { #if defined(ARCH_ESP32) + LOG_INFO("OTA Requested"); + if (r->ota_request.ota_hash.size != 32) { suppressRebootBanner = true; - LOG_INFO("OTA Failed: Invalid `ota_hash` provided"); + sendWarningAndLog("Cannot start OTA: Invalid `ota_hash` provided."); break; } meshtastic_OTAMode mode = r->ota_request.reboot_ota_mode; + const char *mode_name = (mode == METHOD_OTA_BLE ? "BLE" : "WiFi"); + + // Check that we have an OTA partition + const esp_partition_t *part = MeshtasticOTA::getAppPartition(); + if (part == NULL) { + suppressRebootBanner = true; + sendWarningAndLog("Cannot start OTA: Cannot find OTA Loader partition."); + break; + } + + static esp_app_desc_t app_desc; + if (!MeshtasticOTA::getAppDesc(part, &app_desc)) { + suppressRebootBanner = true; + sendWarningAndLog("Cannot start OTA: Device does have a valid OTA Loader."); + break; + } + + if (!MeshtasticOTA::checkOTACapability(&app_desc, mode)) { + suppressRebootBanner = true; + sendWarningAndLog("OTA Loader does not support %s", mode_name); + break; + } + if (MeshtasticOTA::trySwitchToOTA()) { - LOG_INFO("OTA Requested"); suppressRebootBanner = true; if (screen) screen->startFirmwareUpdateScreen(); MeshtasticOTA::saveConfig(&config.network, mode, r->ota_request.ota_hash.bytes); - LOG_INFO("Rebooting to WiFi OTA"); + sendWarningAndLog("Rebooting to %s OTA", mode_name); } else { - LOG_INFO("WIFI OTA Failed"); + sendWarningAndLog("Unable to switch to the OTA partition."); } #endif int s = 1; // Reboot in 1 second, hard coded @@ -1472,15 +1496,43 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent #endif } -void AdminModule::sendWarning(const char *message) +void AdminModule::sendWarning(const char *format, ...) { meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + if (!cn) + return; + cn->level = meshtastic_LogRecord_Level_WARNING; cn->time = getValidTime(RTCQualityFromNet); - strncpy(cn->message, message, sizeof(cn->message)); + + va_list args; + va_start(args, format); + // Format the arguments directly into the notification object + vsnprintf(cn->message, sizeof(cn->message), format, args); + va_end(args); + service->sendClientNotification(cn); } +void AdminModule::sendWarningAndLog(const char *format, ...) +{ + // We need a temporary buffer to hold the formatted text so we can log it + // Using 250 bytes as a safe upper limit for typical text notifications + char buf[250]; + + va_list args; + va_start(args, format); + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + LOG_WARN(buf); + // 2. Call sendWarning + // SECURITY NOTE: We pass "%s", buf instead of just 'buf'. + // If 'buf' contained a % symbol (e.g. "Battery 50%"), passing it directly + // would crash sendWarning. "%s" treats it purely as text. + sendWarning("%s", buf); +} + void disableBluetooth() { #if HAS_BLUETOOTH diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index 867751f499c..e13d9c274c3 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -1,3 +1,4 @@ +#include #include #pragma once @@ -71,7 +72,8 @@ class AdminModule : public ProtobufModule, public Obser bool messageIsResponse(const meshtastic_AdminMessage *r); bool messageIsRequest(const meshtastic_AdminMessage *r); - void sendWarning(const char *message); + void sendWarning(const char *format, ...) __attribute__((format(printf, 2, 3))); + void sendWarningAndLog(const char *format, ...) __attribute__((format(printf, 2, 3))); }; static constexpr const char *licensedModeMessage = diff --git a/src/platform/esp32/MeshtasticOTA.cpp b/src/platform/esp32/MeshtasticOTA.cpp index b8cb052ef8f..8846d36a8f5 100644 --- a/src/platform/esp32/MeshtasticOTA.cpp +++ b/src/platform/esp32/MeshtasticOTA.cpp @@ -7,7 +7,9 @@ namespace MeshtasticOTA { static const char *nvsNamespace = "MeshtasticOTA"; -static const char *appProjectName = "MeshtasticOTA"; +static const char *combinedAppProjectName = "MeshtasticOTA"; +static const char *bleOnlyAppProjectName = "MeshtasticOTA-BLE"; +static const char *wifiOnlyAppProjectName = "MeshtasticOTA-WiFi"; static bool updated = false; @@ -68,21 +70,44 @@ bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc) LOG_INFO("esp_ota_get_partition_description failed"); return false; } - if (strcmp(app_desc->project_name, appProjectName) != 0) { - LOG_INFO("app_desc->project_name == 0"); - return false; - } return true; } +bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method) +{ + // Combined loader supports all (both) transports, BLE and WiFi + if (strcmp(app_desc->project_name, combinedAppProjectName) == 0) { + LOG_INFO("OTA partition contains combined BLE/WiFi OTA Loader"); + return true; + } + if (method == METHOD_OTA_BLE && strcmp(app_desc->project_name, bleOnlyAppProjectName) == 0) { + LOG_INFO("OTA partition contains BLE-only OTA Loader"); + return true; + } + if (method == METHOD_OTA_WIFI && strcmp(app_desc->project_name, wifiOnlyAppProjectName) == 0) { + LOG_INFO("OTA partition contains WiFi-only OTA Loader"); + return true; + } + LOG_INFO("OTA partition does not contain a known OTA loader"); + return false; +} + bool trySwitchToOTA() { const esp_partition_t *part = getAppPartition(); - esp_app_desc_t app_desc; - if (!getAppDesc(part, &app_desc)) + + if (part == NULL) { + LOG_WARN("Unable to get app partition in preparation of OTA reboot"); return false; - if (esp_ota_set_boot_partition(part) != ESP_OK) + } + + uint8_t result = esp_ota_set_boot_partition(part); + // Partition and app checks should now be done in the AdminModule before this is called + if (result != ESP_OK) { + LOG_WARN("Unable to switch to OTA partiton. (Reason %d)", result); return false; + } + return true; } diff --git a/src/platform/esp32/MeshtasticOTA.h b/src/platform/esp32/MeshtasticOTA.h index 001eba039bd..84e67350acd 100644 --- a/src/platform/esp32/MeshtasticOTA.h +++ b/src/platform/esp32/MeshtasticOTA.h @@ -3,12 +3,18 @@ #include "mesh-pb-constants.h" #include +#include + +#define METHOD_OTA_BLE 1 +#define METHOD_OTA_WIFI 2 namespace MeshtasticOTA { void initialize(); bool isUpdated(); - +const esp_partition_t *getAppPartition(); +bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc); +bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method); void recoverConfig(meshtastic_Config_NetworkConfig *network); void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash); bool trySwitchToOTA(); From fb4486c74b5315b9ee3812391c73cb4a532372e1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 15 Jan 2026 12:05:36 -0600 Subject: [PATCH 6/9] Guards --- src/main.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 0977c8f0619..c1096a240ea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -105,6 +105,8 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #include #endif +#ifdef ARCH_ESP32 +#ifdef DEBUG_PARTITION_TABLE #include "esp_partition.h" void printPartitionTable() @@ -137,6 +139,8 @@ void printPartitionTable() } printf("-----------------------\n"); } +#endif // DEBUG_PARTITION_TABLE +#endif // ARCH_ESP32 #if HAS_BUTTON || defined(ARCH_PORTDUINO) #include "input/ButtonThread.h" @@ -681,7 +685,11 @@ void setup() sensor_detected = true; #endif } +#ifdef ARCH_ESP32 +#ifdef DEBUG_PARTITION_TABLE printPartitionTable(); +#endif +#endif // ARCH_ESP32 #ifdef ARCH_ESP32 // Don't init display if we don't have one or we are waking headless due to a timer event if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) { From 72acd8f58dc3b047440d912a50dece324adcc281 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 15 Jan 2026 13:04:12 -0600 Subject: [PATCH 7/9] Guards --- src/modules/AdminModule.h | 7 ++++--- src/platform/esp32/MeshtasticOTA.cpp | 2 ++ src/platform/esp32/MeshtasticOTA.h | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index e13d9c274c3..11554a1d1c5 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -1,8 +1,9 @@ -#include -#include - #pragma once +#ifdef ARCH_ESP32 +#include +#endif #include "ProtobufModule.h" +#include #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif diff --git a/src/platform/esp32/MeshtasticOTA.cpp b/src/platform/esp32/MeshtasticOTA.cpp index 8846d36a8f5..71645ec0f4b 100644 --- a/src/platform/esp32/MeshtasticOTA.cpp +++ b/src/platform/esp32/MeshtasticOTA.cpp @@ -1,7 +1,9 @@ #include "MeshtasticOTA.h" #include "configuration.h" +#ifdef ARCH_ESP32 #include #include +#endif namespace MeshtasticOTA { diff --git a/src/platform/esp32/MeshtasticOTA.h b/src/platform/esp32/MeshtasticOTA.h index 84e67350acd..df2477d5aab 100644 --- a/src/platform/esp32/MeshtasticOTA.h +++ b/src/platform/esp32/MeshtasticOTA.h @@ -3,7 +3,9 @@ #include "mesh-pb-constants.h" #include +#ifdef ARCH_ESP32 #include +#endif #define METHOD_OTA_BLE 1 #define METHOD_OTA_WIFI 2 From d07db5a850b528d0b6c75784411fbcdb207f9657 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 15 Jan 2026 13:31:27 -0600 Subject: [PATCH 8/9] Derp --- src/platform/esp32/MeshtasticOTA.cpp | 2 +- src/platform/esp32/MeshtasticOTA.h | 2 +- variants/esp32c6/m5stack_unitc6l/platformio.ini | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/esp32/MeshtasticOTA.cpp b/src/platform/esp32/MeshtasticOTA.cpp index 71645ec0f4b..4ca0747234f 100644 --- a/src/platform/esp32/MeshtasticOTA.cpp +++ b/src/platform/esp32/MeshtasticOTA.cpp @@ -1,6 +1,6 @@ #include "MeshtasticOTA.h" #include "configuration.h" -#ifdef ARCH_ESP32 +#ifdef ESP_PLATFORM #include #include #endif diff --git a/src/platform/esp32/MeshtasticOTA.h b/src/platform/esp32/MeshtasticOTA.h index df2477d5aab..7c158775f98 100644 --- a/src/platform/esp32/MeshtasticOTA.h +++ b/src/platform/esp32/MeshtasticOTA.h @@ -3,7 +3,7 @@ #include "mesh-pb-constants.h" #include -#ifdef ARCH_ESP32 +#ifdef ESP_PLATFORM #include #endif diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index 3054d342a7e..ed26598d2da 100644 --- a/variants/esp32c6/m5stack_unitc6l/platformio.ini +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -10,6 +10,8 @@ custom_meshtastic_tags = M5Stack extends = esp32c6_base board = esp32-c6-devkitc-1 +board_upload.flash_size = 16MB +board_build.partitions = default_16MB.csv ;OpenOCD flash method ;upload_protocol = esp-builtin ;Normal method From e2b274bba8a65be09bcddfae210599a7f57ca6f3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 15 Jan 2026 14:10:26 -0600 Subject: [PATCH 9/9] Missed one --- src/modules/AdminModule.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index 11554a1d1c5..c446887b3ea 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -1,5 +1,5 @@ #pragma once -#ifdef ARCH_ESP32 +#ifdef ESP_PLATFORM #include #endif #include "ProtobufModule.h"