Enable the on-demand mOTA context for every classic ESP32 build
Move OTA_HEAP_CONTEXT from two hand-edited envs to a pre-script gated on build.mcu == "esp32", so every classic ESP32 image gets it and none can drift. S2/S3/C-series and nRF52 keep the .bss singleton: they have no equivalent static-DRAM ceiling, so there a guaranteed-present workspace is the better trade for what is ultimately a recovery path. It has to be a build flag rather than a header default, because OTA_HEAP_CONTEXT must hold the same value in every translation unit that sees OtaContext.h - OtaContext.cpp included - and a header test on CONFIG_IDF_TARGET_ESP32 would depend on include order relative to sdkconfig.h. The script defers to OTA_SHARED_COMPANION_QUEUE where a Full Companion recipe already owns the storage. Fix a gap this exposes in the roles that now use it. Only CLI entry points acquire the context, so once it was released, a repeater with the temporary radio profile up would no longer serve or announce its own firmware - LoRa OTA would look enabled and silently do nothing. ota_service_temp_radio_context() holds the workspace for exactly the temp-radio window and hands it back outside it, which is where the saving was coming from anyway. Wired into the repeater, room server and sensor loops; the Companion keeps its own acquire-on-host-demand policy, since its context is borrowed from the offline message queue and must not be taken speculatively. Static DRAM, classic ESP32 (bytes occupied of a 124,580 region): Heltec_v2_repeater 108,332 -> 70,732 Heltec_v2_room_server 83,716 -> 68,300 Heltec_v2_companion_radio_ble 123,236 -> 107,772 (was failing on main) Tbeam_SX1262_repeater 86,932 -> 71,468 Tbeam_SX1262_repeater_observer_mqtt 121,132 -> 83,532 (was failing)
This commit is contained in:
1 parent
f6bc7a194a
commit
f0c4c6af00
8 files changed
+86
-6
No files matched your search
@@ -12036,10 +12036,7 @@ void MyMesh::loop() {
|
||||
processDeferredCliCommand();
|
||||
servicePostMeshLoop();
|
||||
#if defined(ENABLE_OTA) && OTA_DYNAMIC_CONTEXT
|
||||
// Hand the OTA workspace back once no transfer, apply or folder link needs
|
||||
// it. A repeater is idle almost all of its life, so this is where the bulk
|
||||
// of the saving actually lands.
|
||||
mesh::ota::ota_release_context_if_idle(isTempRadioActive());
|
||||
mesh::ota::ota_service_temp_radio_context(isTempRadioActive());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "MyMesh.h"
|
||||
#include <helpers/UsbLogging.h>
|
||||
#if defined(ENABLE_OTA)
|
||||
#include <helpers/ota/OtaContext.h>
|
||||
#endif
|
||||
#include <helpers/FileRead.h>
|
||||
#include <helpers/radiolib/RxBoostedGainDefaults.h>
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
@@ -2774,6 +2777,9 @@ void MyMesh::loop() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(ENABLE_OTA) && OTA_DYNAMIC_CONTEXT
|
||||
mesh::ota::ota_service_temp_radio_context(isTempRadioActive());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MyMesh::isMillisTimerDue(unsigned long timestamp) const {
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#include <helpers/LazyPersistence.h>
|
||||
#include <helpers/radiolib/RXPowerSaving.h>
|
||||
#include <helpers/UsbLogging.h>
|
||||
#if defined(ENABLE_OTA)
|
||||
#include <helpers/ota/OtaContext.h>
|
||||
#endif
|
||||
|
||||
static uint32_t nextRadioApplyRetryDelay(uint8_t& failure_count) {
|
||||
uint8_t shift = failure_count < 5 ? failure_count : 5;
|
||||
@@ -1343,6 +1346,9 @@ void SensorMesh::loop() {
|
||||
(unsigned long)retry_delay);
|
||||
}
|
||||
}
|
||||
#if defined(ENABLE_OTA) && OTA_DYNAMIC_CONTEXT
|
||||
mesh::ota::ota_service_temp_radio_context(isTempRadioActive());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SensorMesh::isMillisTimerDue(unsigned long timestamp) const {
|
||||
|
||||
@@ -74,6 +74,7 @@ extra_scripts =
|
||||
pre:scripts/generate_webconfig_html.py
|
||||
pre:scripts/meshcore_image_identity.py
|
||||
pre:scripts/portable_esp32_link.py
|
||||
pre:scripts/esp32_ota_heap_context.py
|
||||
merge-bin.py
|
||||
post:tools/mota/pio_endf.py
|
||||
post:scripts/check_esp32_dram.py
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep the idle mOTA workspace off classic ESP32's static DRAM.
|
||||
|
||||
Classic ESP32 links into a dram0_0_seg of ~124 KiB: memory.ld reserves the BT
|
||||
controller's 0xdb5c bytes off the 0x2c200 window before the application gets
|
||||
any, and no runtime call gives those static bytes back. OtaContext is several
|
||||
kilobytes of that budget, held for the life of the device even though a
|
||||
repeater or room server is outside an OTA window essentially always.
|
||||
|
||||
OTA_HEAP_CONTEXT switches OtaContext to the on-demand storage path, so it is
|
||||
allocated when an OTA operation needs it and freed once idle. Applied here
|
||||
rather than per-env so every classic ESP32 build gets it and none can drift.
|
||||
|
||||
Scoped to build.mcu == "esp32" deliberately. S2/S3/C-series and nRF52 have no
|
||||
equivalent static-DRAM ceiling, so there the .bss singleton is the better
|
||||
trade: it guarantees the workspace is present, and OTA is a recovery path.
|
||||
|
||||
This must be a build flag, not a header default. OTA_HEAP_CONTEXT has to hold
|
||||
the same value in every translation unit that sees OtaContext.h - including
|
||||
OtaContext.cpp, which defines the storage - and a header test on
|
||||
CONFIG_IDF_TARGET_ESP32 would depend on whether that unit had already reached
|
||||
sdkconfig.h.
|
||||
"""
|
||||
|
||||
Import("env") # noqa: F821 -- PlatformIO/SCons supplies Import
|
||||
|
||||
# Both name the owner of the context storage, and OtaContext.h rejects the
|
||||
# pair. OTA_SHARED_COMPANION_QUEUE arrives via PLATFORMIO_BUILD_FLAGS from
|
||||
# build.sh's Full Companion recipes, which are nRF52/S3 today - but a future
|
||||
# classic ESP32 Full Companion must lose this default, not fail to compile.
|
||||
CONFLICTING = ("OTA_HEAP_CONTEXT", "OTA_SHARED_COMPANION_QUEUE")
|
||||
|
||||
|
||||
def _already_defined(env):
|
||||
for define in env.get("CPPDEFINES", []):
|
||||
name = define[0] if isinstance(define, (list, tuple)) else define
|
||||
if str(name) in CONFLICTING:
|
||||
return True
|
||||
# PLATFORMIO_BUILD_FLAGS reaches BUILD_FLAGS as raw text, which may not be
|
||||
# parsed into CPPDEFINES yet when this pre-script runs.
|
||||
for flag in env.get("BUILD_FLAGS", []):
|
||||
text = str(flag)
|
||||
if any(("-D" + name) in text for name in CONFLICTING):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _apply(env):
|
||||
if str(env.BoardConfig().get("build.mcu", "")).lower() != "esp32":
|
||||
return
|
||||
if _already_defined(env):
|
||||
return
|
||||
env.Append(CPPDEFINES=[("OTA_HEAP_CONTEXT", 1)])
|
||||
|
||||
|
||||
_apply(env) # noqa: F821
|
||||
@@ -681,6 +681,22 @@ uint8_t ota_hop_limit();
|
||||
void ota_set_context_storage(void* owner, OtaContext* (*acquire)(void*),
|
||||
void (*release)(void*));
|
||||
void ota_release_context_if_idle(bool temporary_radio_active);
|
||||
|
||||
// Loop helper for roles whose LoRa OTA only runs under the temporary radio
|
||||
// profile (repeater, room server, sensor). Nothing else acquires the context
|
||||
// on their behalf: without this, serving and announcing would stop the moment
|
||||
// the context was released, because only CLI entry points acquire. Holding it
|
||||
// for the temp-radio window keeps behaviour identical to a permanent context,
|
||||
// and the node is outside that window nearly all the time, which is where the
|
||||
// saving comes from. Not for the Companion, whose context is borrowed from the
|
||||
// offline message queue and must only be taken on explicit host demand.
|
||||
inline void ota_service_temp_radio_context(bool temporary_radio_active) {
|
||||
if (temporary_radio_active) {
|
||||
ota_acquire_context(nullptr, 0); // failure is reported at the CLI entry points
|
||||
} else {
|
||||
ota_release_context_if_idle(false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace ota
|
||||
|
||||
@@ -38,7 +38,6 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D FLOOD_CHANNEL_SCOPE_SLOTS=31 ; classic ESP32 DRAM cannot fit the default 255 with LoRa OTA
|
||||
-D OTA_HEAP_CONTEXT=1 ; keep the idle mOTA workspace out of static DRAM
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
|
||||
@@ -171,7 +171,6 @@ build_flags =
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_MQTT_BRIDGE=1
|
||||
-D FLOOD_CHANNEL_SCOPE_SLOTS=31 ; retain scope support within this observer's tight internal DRAM
|
||||
-D OTA_HEAP_CONTEXT=1 ; keep the idle mOTA workspace out of static DRAM
|
||||
-D OTA_MANIFEST_BASE='"https://observer.gessaman.com/v"'
|
||||
-D MQTT_MAX_PACKET_SIZE=1024
|
||||
; -D MQTT_DEBUG=1
|
||||
|
||||
Reference in new issue
Block a user