diff --git a/src/main.cpp b/src/main.cpp index 88282c837..c1096a240 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -105,6 +105,43 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #include #endif +#ifdef ARCH_ESP32 +#ifdef DEBUG_PARTITION_TABLE +#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"); +} +#endif // DEBUG_PARTITION_TABLE +#endif // ARCH_ESP32 + #if HAS_BUTTON || defined(ARCH_PORTDUINO) #include "input/ButtonThread.h" @@ -648,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) { diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 990ca0f46..1fda9bf13 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 867751f49..c446887b3 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -1,7 +1,9 @@ -#include - #pragma once +#ifdef ESP_PLATFORM +#include +#endif #include "ProtobufModule.h" +#include #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif @@ -71,7 +73,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 b8cb052ef..4ca074723 100644 --- a/src/platform/esp32/MeshtasticOTA.cpp +++ b/src/platform/esp32/MeshtasticOTA.cpp @@ -1,13 +1,17 @@ #include "MeshtasticOTA.h" #include "configuration.h" +#ifdef ESP_PLATFORM #include #include +#endif 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 +72,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 001eba039..7c158775f 100644 --- a/src/platform/esp32/MeshtasticOTA.h +++ b/src/platform/esp32/MeshtasticOTA.h @@ -3,12 +3,20 @@ #include "mesh-pb-constants.h" #include +#ifdef ESP_PLATFORM +#include +#endif + +#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(); diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index 3054d342a..ed26598d2 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