Skip to content

Commit 64116cd

Browse files
thebenternjake-b
andauthored
Meshtastic OTA (moar) (#9327)
* Initial commit of combined BLE and WiFi OTA * Incorporate ota_hash in AdminMessage protobuf * OTA protobuf changes * Trunk fmt * Partition header check for OTA type * Guards * Guards * Derp * Missed one --------- Co-authored-by: Jake-B <jake-b@users.noreply.github.com>
1 parent c8f0295 commit 64116cd

6 files changed

Lines changed: 152 additions & 19 deletions

File tree

src/main.cpp

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,43 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
105105
#include <string>
106106
#endif
107107

108+
#ifdef ARCH_ESP32
109+
#ifdef DEBUG_PARTITION_TABLE
110+
#include "esp_partition.h"
111+
112+
void printPartitionTable()
113+
{
114+
printf("\n--- Partition Table ---\n");
115+
// Print Column Headers
116+
printf("| %-16s | %-4s | %-7s | %-10s | %-10s |\n", "Label", "Type", "Subtype", "Offset", "Size");
117+
printf("|------------------|------|---------|------------|------------|\n");
118+
119+
// Create an iterator to find ALL partitions (Type ANY, Subtype ANY)
120+
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
121+
122+
// Loop through the iterator
123+
if (it != NULL) {
124+
do {
125+
const esp_partition_t *part = esp_partition_get(it);
126+
127+
// Print details: Label, Type (Hex), Subtype (Hex), Offset (Hex), Size (Hex)
128+
printf("| %-16s | 0x%02x | 0x%02x | 0x%08x | 0x%08x |\n", part->label, part->type, part->subtype, part->address,
129+
part->size);
130+
131+
// Move to next partition
132+
it = esp_partition_next(it);
133+
} while (it != NULL);
134+
135+
// Release the iterator memory
136+
esp_partition_iterator_release(it);
137+
} else {
138+
printf("No partitions found.\n");
139+
}
140+
printf("-----------------------\n");
141+
}
142+
#endif // DEBUG_PARTITION_TABLE
143+
#endif // ARCH_ESP32
144+
108145
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
109146
#include "input/ButtonThread.h"
110147

@@ -648,7 +685,11 @@ void setup()
648685
sensor_detected = true;
649686
#endif
650687
}
651-
688+
#ifdef ARCH_ESP32
689+
#ifdef DEBUG_PARTITION_TABLE
690+
printPartitionTable();
691+
#endif
692+
#endif // ARCH_ESP32
652693
#ifdef ARCH_ESP32
653694
// Don't init display if we don't have one or we are waking headless due to a timer event
654695
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) {

src/modules/AdminModule.cpp

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,22 +235,46 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
235235
}
236236
case meshtastic_AdminMessage_ota_request_tag: {
237237
#if defined(ARCH_ESP32)
238+
LOG_INFO("OTA Requested");
239+
238240
if (r->ota_request.ota_hash.size != 32) {
239241
suppressRebootBanner = true;
240-
LOG_INFO("OTA Failed: Invalid `ota_hash` provided");
242+
sendWarningAndLog("Cannot start OTA: Invalid `ota_hash` provided.");
241243
break;
242244
}
243245

244246
meshtastic_OTAMode mode = r->ota_request.reboot_ota_mode;
247+
const char *mode_name = (mode == METHOD_OTA_BLE ? "BLE" : "WiFi");
248+
249+
// Check that we have an OTA partition
250+
const esp_partition_t *part = MeshtasticOTA::getAppPartition();
251+
if (part == NULL) {
252+
suppressRebootBanner = true;
253+
sendWarningAndLog("Cannot start OTA: Cannot find OTA Loader partition.");
254+
break;
255+
}
256+
257+
static esp_app_desc_t app_desc;
258+
if (!MeshtasticOTA::getAppDesc(part, &app_desc)) {
259+
suppressRebootBanner = true;
260+
sendWarningAndLog("Cannot start OTA: Device does have a valid OTA Loader.");
261+
break;
262+
}
263+
264+
if (!MeshtasticOTA::checkOTACapability(&app_desc, mode)) {
265+
suppressRebootBanner = true;
266+
sendWarningAndLog("OTA Loader does not support %s", mode_name);
267+
break;
268+
}
269+
245270
if (MeshtasticOTA::trySwitchToOTA()) {
246-
LOG_INFO("OTA Requested");
247271
suppressRebootBanner = true;
248272
if (screen)
249273
screen->startFirmwareUpdateScreen();
250274
MeshtasticOTA::saveConfig(&config.network, mode, r->ota_request.ota_hash.bytes);
251-
LOG_INFO("Rebooting to WiFi OTA");
275+
sendWarningAndLog("Rebooting to %s OTA", mode_name);
252276
} else {
253-
LOG_INFO("WIFI OTA Failed");
277+
sendWarningAndLog("Unable to switch to the OTA partition.");
254278
}
255279
#endif
256280
int s = 1; // Reboot in 1 second, hard coded
@@ -1472,15 +1496,43 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent
14721496
#endif
14731497
}
14741498

1475-
void AdminModule::sendWarning(const char *message)
1499+
void AdminModule::sendWarning(const char *format, ...)
14761500
{
14771501
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
1502+
if (!cn)
1503+
return;
1504+
14781505
cn->level = meshtastic_LogRecord_Level_WARNING;
14791506
cn->time = getValidTime(RTCQualityFromNet);
1480-
strncpy(cn->message, message, sizeof(cn->message));
1507+
1508+
va_list args;
1509+
va_start(args, format);
1510+
// Format the arguments directly into the notification object
1511+
vsnprintf(cn->message, sizeof(cn->message), format, args);
1512+
va_end(args);
1513+
14811514
service->sendClientNotification(cn);
14821515
}
14831516

1517+
void AdminModule::sendWarningAndLog(const char *format, ...)
1518+
{
1519+
// We need a temporary buffer to hold the formatted text so we can log it
1520+
// Using 250 bytes as a safe upper limit for typical text notifications
1521+
char buf[250];
1522+
1523+
va_list args;
1524+
va_start(args, format);
1525+
vsnprintf(buf, sizeof(buf), format, args);
1526+
va_end(args);
1527+
1528+
LOG_WARN(buf);
1529+
// 2. Call sendWarning
1530+
// SECURITY NOTE: We pass "%s", buf instead of just 'buf'.
1531+
// If 'buf' contained a % symbol (e.g. "Battery 50%"), passing it directly
1532+
// would crash sendWarning. "%s" treats it purely as text.
1533+
sendWarning("%s", buf);
1534+
}
1535+
14841536
void disableBluetooth()
14851537
{
14861538
#if HAS_BLUETOOTH

src/modules/AdminModule.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
#include <sys/types.h>
2-
31
#pragma once
2+
#ifdef ESP_PLATFORM
3+
#include <esp_ota_ops.h>
4+
#endif
45
#include "ProtobufModule.h"
6+
#include <sys/types.h>
57
#if HAS_WIFI
68
#include "mesh/wifi/WiFiAPClient.h"
79
#endif
@@ -71,7 +73,8 @@ class AdminModule : public ProtobufModule<meshtastic_AdminMessage>, public Obser
7173

7274
bool messageIsResponse(const meshtastic_AdminMessage *r);
7375
bool messageIsRequest(const meshtastic_AdminMessage *r);
74-
void sendWarning(const char *message);
76+
void sendWarning(const char *format, ...) __attribute__((format(printf, 2, 3)));
77+
void sendWarningAndLog(const char *format, ...) __attribute__((format(printf, 2, 3)));
7578
};
7679

7780
static constexpr const char *licensedModeMessage =

src/platform/esp32/MeshtasticOTA.cpp

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
#include "MeshtasticOTA.h"
22
#include "configuration.h"
3+
#ifdef ESP_PLATFORM
34
#include <Preferences.h>
45
#include <esp_ota_ops.h>
6+
#endif
57

68
namespace MeshtasticOTA
79
{
810

911
static const char *nvsNamespace = "MeshtasticOTA";
10-
static const char *appProjectName = "MeshtasticOTA";
12+
static const char *combinedAppProjectName = "MeshtasticOTA";
13+
static const char *bleOnlyAppProjectName = "MeshtasticOTA-BLE";
14+
static const char *wifiOnlyAppProjectName = "MeshtasticOTA-WiFi";
1115

1216
static bool updated = false;
1317

@@ -68,21 +72,44 @@ bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc)
6872
LOG_INFO("esp_ota_get_partition_description failed");
6973
return false;
7074
}
71-
if (strcmp(app_desc->project_name, appProjectName) != 0) {
72-
LOG_INFO("app_desc->project_name == 0");
73-
return false;
74-
}
7575
return true;
7676
}
7777

78+
bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method)
79+
{
80+
// Combined loader supports all (both) transports, BLE and WiFi
81+
if (strcmp(app_desc->project_name, combinedAppProjectName) == 0) {
82+
LOG_INFO("OTA partition contains combined BLE/WiFi OTA Loader");
83+
return true;
84+
}
85+
if (method == METHOD_OTA_BLE && strcmp(app_desc->project_name, bleOnlyAppProjectName) == 0) {
86+
LOG_INFO("OTA partition contains BLE-only OTA Loader");
87+
return true;
88+
}
89+
if (method == METHOD_OTA_WIFI && strcmp(app_desc->project_name, wifiOnlyAppProjectName) == 0) {
90+
LOG_INFO("OTA partition contains WiFi-only OTA Loader");
91+
return true;
92+
}
93+
LOG_INFO("OTA partition does not contain a known OTA loader");
94+
return false;
95+
}
96+
7897
bool trySwitchToOTA()
7998
{
8099
const esp_partition_t *part = getAppPartition();
81-
esp_app_desc_t app_desc;
82-
if (!getAppDesc(part, &app_desc))
100+
101+
if (part == NULL) {
102+
LOG_WARN("Unable to get app partition in preparation of OTA reboot");
83103
return false;
84-
if (esp_ota_set_boot_partition(part) != ESP_OK)
104+
}
105+
106+
uint8_t result = esp_ota_set_boot_partition(part);
107+
// Partition and app checks should now be done in the AdminModule before this is called
108+
if (result != ESP_OK) {
109+
LOG_WARN("Unable to switch to OTA partiton. (Reason %d)", result);
85110
return false;
111+
}
112+
86113
return true;
87114
}
88115

src/platform/esp32/MeshtasticOTA.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,20 @@
33

44
#include "mesh-pb-constants.h"
55
#include <Arduino.h>
6+
#ifdef ESP_PLATFORM
7+
#include <esp_ota_ops.h>
8+
#endif
9+
10+
#define METHOD_OTA_BLE 1
11+
#define METHOD_OTA_WIFI 2
612

713
namespace MeshtasticOTA
814
{
915
void initialize();
1016
bool isUpdated();
11-
17+
const esp_partition_t *getAppPartition();
18+
bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc);
19+
bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method);
1220
void recoverConfig(meshtastic_Config_NetworkConfig *network);
1321
void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash);
1422
bool trySwitchToOTA();

variants/esp32c6/m5stack_unitc6l/platformio.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ custom_meshtastic_tags = M5Stack
1010

1111
extends = esp32c6_base
1212
board = esp32-c6-devkitc-1
13+
board_upload.flash_size = 16MB
14+
board_build.partitions = default_16MB.csv
1315
;OpenOCD flash method
1416
;upload_protocol = esp-builtin
1517
;Normal method

0 commit comments

Comments
 (0)