Fix classic ESP32 full OTA builds

This commit is contained in:
mikecarper committed 2026-08-04 22:50:26 -07:00
1 parent eed6581299
commit 7d5c7d4065
4 files changed
+80 -24

No files matched your search

+25 -7
View File
@@ -63,6 +63,7 @@ SUPPORTED_PLATFORM_PATTERN='ESP32_PLATFORM|NRF52_PLATFORM|STM32_PLATFORM|RP2040_
OUTPUT_DIR="out"
ESP32_LORA_OTA_APP_LIMIT=$((0x150000 - 0x10000))
ESP32_FULL_MAX_NEIGHBOURS=254
ESP32_FULL_DRAM_LIMITED_MAX_NEIGHBOURS=50
FALLBACK_VERSION_PREFIX="dev"
FALLBACK_VERSION_DATE_FORMAT='+%Y-%m-%d-%H-%M'
@@ -82,8 +83,8 @@ Commands:
build-firmware <target>: Build the firmware for the given build target.
build-firmwares: Build all firmwares for all targets.
build-firmwares-logging-matrix: Build all firmwares in standard, logging, MQTT, FULL ESP32 MQTT, and FULL ESP32 logging (no MQTT) profiles, logging each target under out/build-logs/ and continuing after failures.
build-full-esp32-firmwares: Build only feature-complete ESP32 MQTT profiles with 254 neighbors, LoRa OTA, and expanded dual-OTA partitions.
build-full-esp32-logging-firmwares: Build only feature-complete ESP32 profiles with 254 neighbors, logging, MQTT disabled, LoRa OTA, and expanded dual-OTA partitions.
build-full-esp32-firmwares: Build only feature-complete ESP32 MQTT profiles with up to 254 neighbors, LoRa OTA, and expanded dual-OTA partitions.
build-full-esp32-logging-firmwares: Build only feature-complete ESP32 profiles with up to 254 neighbors, logging, MQTT disabled, LoRa OTA, and expanded dual-OTA partitions.
build-matching-firmwares <build-match-spec>: Build all firmwares for build targets containing the string given for <build-match-spec>.
build-companion-firmwares: Build all companion firmwares for all build targets.
build-repeater-firmwares: Build all repeater firmwares for all build targets.
@@ -1738,6 +1739,16 @@ requires_esp32_companion_full_ota_fallback() {
esac
}
requires_esp32_full_dram_limited_neighbors() {
# These classic ESP32 MQTT observers already declare a 50-entry table because
# their GPS, TLS, and OTA state leave little internal DRAM. FULL restores flash-
# limited features, but must not override that explicit RAM safety limit.
case "${1,,}" in
tbeam_sx1262_repeater_observer_mqtt|tbeam_sx1262_room_server_observer_mqtt|tbeam_sx1276_repeater_observer_mqtt|tbeam_sx1276_room_server_observer_mqtt) return 0 ;;
*) return 1 ;;
esac
}
is_lora_ota_build() {
local env_name=$1
local env_name_lc=${env_name,,}
@@ -1879,6 +1890,7 @@ apply_esp32_lora_ota_size_profile() {
apply_esp32_full_size_profile() {
local env_name=$1
local max_neighbours=$ESP32_FULL_MAX_NEIGHBOURS
if [ "$ESP32_FULL_BUILD" != "1" ] || ! supports_esp32_full_build "$env_name"; then
return 0
@@ -1890,9 +1902,15 @@ apply_esp32_full_size_profile() {
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UWEBCONFIG_DISABLED -DWIFI_OTA_SEEDER=1"
# Keep ordinary builds at their board-defined neighbor capacity. FULL builds
# use the largest table supported by the one-byte neighbor discovery indexes.
append_platformio_build_unflags "-DMAX_NEIGHBOURS=50 -DMAX_NEIGHBOURS=8"
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DMAX_NEIGHBOURS=${ESP32_FULL_MAX_NEIGHBOURS}"
# normally use the largest table supported by the one-byte neighbor discovery
# indexes. Explicitly DRAM-limited classic ESP32 observers retain 50 entries.
if requires_esp32_full_dram_limited_neighbors "$env_name"; then
max_neighbours=$ESP32_FULL_DRAM_LIMITED_MAX_NEIGHBOURS
append_platformio_build_unflags "-DMAX_NEIGHBOURS=8"
else
append_platformio_build_unflags "-DMAX_NEIGHBOURS=50 -DMAX_NEIGHBOURS=8"
fi
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DMAX_NEIGHBOURS=${max_neighbours}"
# Restore the full ElegantOTA implementation only when the target already
# declares its dependency. Some ESP32-C6 targets intentionally have no
@@ -2847,14 +2865,14 @@ run_full_esp32_profile() {
fi
if [ "$logging_mode" = "on" ]; then
echo "${profile_label}: building ${#full_targets[@]} feature-complete ESP32 target(s) with ${ESP32_FULL_MAX_NEIGHBOURS} neighbors, logging on, MQTT off, and expanded dual-OTA partitions."
echo "${profile_label}: building ${#full_targets[@]} feature-complete ESP32 target(s) with up to ${ESP32_FULL_MAX_NEIGHBOURS} neighbors (target DRAM limits apply), logging on, MQTT off, and expanded dual-OTA partitions."
echo "FULL logging artifacts exclude MQTT, include LoRa OTA, and use filename form: name-full-logging-ota-version."
MESHDEBUG_OVERRIDE="on"
PACKET_LOGGING_OVERRIDE="on"
MQTT_BRIDGE_OVERRIDE="off"
FIRMWARE_FILENAME_INFIX="full-logging"
else
echo "${profile_label}: building ${#full_targets[@]} feature-complete ESP32 MQTT target(s) with ${ESP32_FULL_MAX_NEIGHBOURS} neighbors, logging off, and expanded dual-OTA partitions."
echo "${profile_label}: building ${#full_targets[@]} feature-complete ESP32 MQTT target(s) with up to ${ESP32_FULL_MAX_NEIGHBOURS} neighbors (target DRAM limits apply), logging off, and expanded dual-OTA partitions."
echo "FULL artifacts include MQTT and LoRa OTA and use filename form: name-full-ota-version."
MESHDEBUG_OVERRIDE="off"
PACKET_LOGGING_OVERRIDE="off"
+5 -4
View File
@@ -335,15 +335,16 @@ role.
| Standard | Uses the selected target's role. Ordinary portable ESP32 repeater/room-server artifacts omit WebConfig to fit the legacy app slot. Explicit MQTT and WiFi-companion targets still use WiFi. |
| Logging | Enables USB/debug packet logging and disables the MQTT bridge. Logging output is not an MQTT uplink. |
| MQTT | Builds explicit MQTT observer or WiFi-companion-MQTT targets with USB packet logging off. |
| FULL ESP32 | Uses the board's MQTT target with logging off, expanded dual-OTA partitions, 254 neighbors, LoRa OTA, and full-size ESP32 features such as WebConfig where supported. |
| FULL ESP32 logging | Uses the board's non-MQTT target with debug and packet logging enabled, expanded dual-OTA partitions, 254 neighbors, and LoRa OTA. |
| FULL ESP32 | Uses the board's MQTT target with logging off, expanded dual-OTA partitions, up to 254 neighbors, LoRa OTA, and full-size ESP32 features such as WebConfig where supported. Classic T-Beam MQTT observers retain their 50-entry table because they have no PSRAM. |
| FULL ESP32 logging | Uses the board's non-MQTT target with debug and packet logging enabled, expanded dual-OTA partitions, up to 254 neighbors, and LoRa OTA. |
| LoRa-OTA no-external-sensors | A lean repeater image with no MQTT; ESP32 builds retain the compact on-demand browser WiFi uploader and 254 neighbors. |
The interactive Option 1 **FULL everything** choice selects the FULL logging
profile: logging is enabled and MQTT is disabled. The standalone FULL ESP32
profile and Profile 4 of the five-profile matrix use the matching MQTT target
instead. Both FULL profiles include LoRa OTA, WebConfig where supported, 254
neighbors, and expanded dual-OTA partitions.
instead. Both FULL profiles include LoRa OTA, WebConfig where supported, up to
254 neighbors, and expanded dual-OTA partitions. Target-specific internal-DRAM
limits still apply.
FULL images change the ESP32 partition layout. Flash the matching
`*-merged.bin` once when installing that layout. A partition-layout change can
+27 -11
View File
@@ -10,6 +10,19 @@
namespace mesh {
namespace ota {
OtaManager::~OtaManager() {
free(_catalog_heap);
}
bool OtaManager::expandCatalog() {
if (_catalog_heap || OTA_INLINE_CATALOG >= OTA_MAX_CATALOG) return false;
CatRow* expanded = static_cast<CatRow*>(malloc(sizeof(CatRow) * OTA_MAX_CATALOG));
if (!expanded) return false;
memcpy(expanded, _catalog_inline, sizeof(CatRow) * _n_cat);
_catalog_heap = expanded;
return true;
}
uint8_t* OtaManager::ensureSourceLeaves() {
#if defined(ESP32_PLATFORM)
if (!_src_leaves) {
@@ -539,25 +552,27 @@ void OtaManager::handleHave(const uint8_t* m, uint16_t n) {
uint32_t target = rd_u32le(row + 4), fwver = rd_u32le(row + 8);
uint8_t codec = row[12], flags = row[13];
uint32_t have_count = rd_u16le(row + 14); // this source's progress
CatRow* catalog = catalogData();
int slot = -1, lru = 0; // upsert into the catalog (dedup by mid)
for (int i = 0; i < _n_cat; i++) {
if (memcmp(_catalog[i].mid, mid, 4) == 0) { slot = i; break; }
if (_catalog[i].last_ms < _catalog[lru].last_ms) lru = i;
if (memcmp(catalog[i].mid, mid, 4) == 0) { slot = i; break; }
if (catalog[i].last_ms < catalog[lru].last_ms) lru = i;
}
if (slot < 0) {
slot = (_n_cat < OTA_MAX_CATALOG) ? _n_cat++ : lru;
_catalog[slot] = CatRow{};
memcpy(_catalog[slot].mid, mid, 4);
memcpy(_catalog[slot].seeders[0], hv.seeder_id, 4);
_catalog[slot].n_seeders = 1;
if (_n_cat >= catalogCapacity() && expandCatalog()) catalog = catalogData();
slot = (_n_cat < catalogCapacity()) ? _n_cat++ : lru;
catalog[slot] = CatRow{};
memcpy(catalog[slot].mid, mid, 4);
memcpy(catalog[slot].seeders[0], hv.seeder_id, 4);
catalog[slot].n_seeders = 1;
} else {
CatRow& cc = _catalog[slot]; // count DISTINCT sources (no double-count)
CatRow& cc = catalog[slot]; // count DISTINCT sources (no double-count)
bool known = false;
for (uint8_t k = 0; k < cc.n_seeders; k++)
if (memcmp(cc.seeders[k], hv.seeder_id, 4) == 0) { known = true; break; }
if (!known && cc.n_seeders < OTA_CAT_SEEDERS) memcpy(cc.seeders[cc.n_seeders++], hv.seeder_id, 4);
}
CatRow& c = _catalog[slot];
CatRow& c = catalog[slot];
c.target_id = target; c.fw_version = fwver; c.codec = codec; c.flags = flags; c.last_ms = _now_ms;
if (have_count > c.have_max) c.have_max = have_count; // best-known progress among sources
if (wantRow(mid, target, codec, flags)) startFetch(mid, target);
@@ -566,9 +581,10 @@ void OtaManager::handleHave(const uint8_t* m, uint16_t n) {
void OtaManager::deferCatalog(const uint8_t mid[4], uint32_t until_ms) {
if (!mid) return;
CatRow* catalog = catalogData();
for (uint8_t i = 0; i < _n_cat; i++) {
if (memcmp(_catalog[i].mid, mid, 4) == 0) {
_catalog[i].retry_after_ms = until_ms;
if (memcmp(catalog[i].mid, mid, 4) == 0) {
catalog[i].retry_after_ms = until_ms;
return;
}
}
+23 -2
View File
@@ -93,6 +93,16 @@ typedef bool (*ServeReadFn)(void* ctx, uint32_t off, uint8_t* buf, uint32_t len)
#ifndef OTA_MAX_CATALOG
#define OTA_MAX_CATALOG 255 // protocol maximum; large SD seeders must remain browsable remotely
#endif
#ifndef OTA_INLINE_CATALOG
#define OTA_INLINE_CATALOG 12 // keep the common case static; expand to OTA_MAX_CATALOG only on demand
#endif
#if OTA_INLINE_CATALOG < 1
#error "OTA_INLINE_CATALOG must be at least 1"
#endif
#if OTA_INLINE_CATALOG > OTA_MAX_CATALOG
#undef OTA_INLINE_CATALOG
#define OTA_INLINE_CATALOG OTA_MAX_CATALOG
#endif
#ifndef OTA_QUERY_MIN_MS
#define OTA_QUERY_MIN_MS 300 // min delay before sending a catalog query (overhear-suppression window)
#endif
@@ -128,6 +138,11 @@ typedef bool (*ServeReadFn)(void* ctx, uint32_t off, uint8_t* buf, uint32_t len)
class OtaManager {
public:
OtaManager() = default;
~OtaManager();
OtaManager(const OtaManager&) = delete;
OtaManager& operator=(const OtaManager&) = delete;
// PAUSED: a folder-destination write failed mid-transfer (the seeder link dropped). Progress is held on
// the host; the manager stops requesting and does NOT fall back to RAM/flash. resumeStaged() (called on
// reconnect) re-STATs the host file, recomputes which blocks are missing, and resumes.
@@ -319,7 +334,7 @@ public:
uint32_t retry_after_ms; // per-image archive retry deadline (0 = eligible)
};
uint8_t catalogCount() const { return _n_cat; }
const CatRow* catalogRow(uint8_t i) const { return i < _n_cat ? &_catalog[i] : nullptr; }
const CatRow* catalogRow(uint8_t i) const { return i < _n_cat ? &catalogData()[i] : nullptr; }
uint8_t sourceCount() const { return _n_src; } // distinct OTA sources (beacon senders) heard
void deferCatalog(const uint8_t mid[4], uint32_t until_ms);
bool catalogReady(const CatRow* row, uint32_t now_ms) const {
@@ -327,6 +342,11 @@ public:
}
private:
CatRow* catalogData() { return _catalog_heap ? _catalog_heap : _catalog_inline; }
const CatRow* catalogData() const { return _catalog_heap ? _catalog_heap : _catalog_inline; }
uint16_t catalogCapacity() const { return _catalog_heap ? OTA_MAX_CATALOG : OTA_INLINE_CATALOG; }
bool expandCatalog();
void emit(const uint8_t* b, uint16_t n, bool flood) { if (_send && n) _send(_ctx, b, n, flood); }
void handleAdv(const uint8_t* m, uint16_t n); // beacon -> sources table (+ query if interested)
void handleQuery(const uint8_t* m, uint16_t n); // serve: reply OTA_HAVE catalog
@@ -464,7 +484,8 @@ private:
};
Source _sources[OTA_MAX_SOURCES];
uint8_t _n_src = 0;
CatRow _catalog[OTA_MAX_CATALOG];
CatRow _catalog_inline[OTA_INLINE_CATALOG];
CatRow* _catalog_heap = nullptr;
uint8_t _n_cat = 0;
uint32_t _now_ms = 0; // coarse clock (fed by set_clock; for ages/LRU/jitter)
};