mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-12 19:35:32 +00:00
Merge PR #7 with reviewed ExpressLRS power and memory fixes
Integrate ExpressLRS TX module support and its stacked ESP32 heap changes. Use the approved Linkflow calibration (17-30 dBm), preserve the PA drive and output path after radio recovery, and keep LoRa OTA enabled. Preserve stored ACLs and filters on allocation failure, release owned client/filter buffers, service heap OTA contexts on Companions, release self-serving workspaces after TempRadio, and reset staged-resume state when a context is released. Keep manual staging and active operations alive. Account for all moved allocations in the runtime RAM gate. Validation: 1,393 native cases; radio-power, heap-context, ACL persistence, shared-queue transfer, display/inbox, radio receive, and memory regressions. Firmware builds passed for Linkflow, Heltec V2 Companion, T-Beam MQTT repeater, Heltec V4 R8 MQTT repeater, RAK4631 repeater, and Indicator Full. Physical verification awaits access to the currently offline lab Pi.
This commit is contained in:
@@ -89,6 +89,17 @@ def requirements(platform, defines, target):
|
||||
parts["core_filesystems_sensors"] = 8192
|
||||
# Packet bytes plus all three queue tables and allocation overhead.
|
||||
parts["radio_packet_pool"] = 5120 if companion else 10240
|
||||
# These tables moved out of .bss, so linker heap bounds now include their
|
||||
# space. Count it as startup allocation instead; C++ assertions bind the
|
||||
# per-entry bounds to the production structures.
|
||||
if re.search(r"repeater|room_server|sensor", target, re.I) or "COMPANION_MESH_CLOCK_SYNC" in defines:
|
||||
parts["client_table"] = integer(defines, "MAX_CLIENTS", 32) * 320 + 16
|
||||
if "repeater" in target.lower():
|
||||
engine = integer(defines, "MESH_ENABLE_FLOOD_RULE_ENGINE", int(platform != "STM32_PLATFORM"))
|
||||
slots = integer(defines, "FLOOD_PACKET_FILTER_SLOTS", 63 if engine else 16)
|
||||
parts["flood_filter_table"] = slots * (200 if engine else 40) + 16
|
||||
if "ENABLE_OTA" in defines and "OTA_HEAP_CONTEXT" in defines:
|
||||
parts["ota_context"] = 16384 + 16
|
||||
if display and display != "NullDisplayDriver":
|
||||
parts["display_pixels_and_driver"] = display_heap
|
||||
parts["screen_objects_and_history"] = 8192 if companion else 2048
|
||||
@@ -113,6 +124,8 @@ def requirements(platform, defines, target):
|
||||
largest = max(display_heap, parts["radio_packet_pool"], 8192 if platform == "ESP32_PLATFORM" else 0)
|
||||
if "expanded_message_previews" in parts:
|
||||
largest = max(largest, 8192 + parts["expanded_message_previews"])
|
||||
for name in ("client_table", "flood_filter_table", "ota_context"):
|
||||
largest = max(largest, parts.get(name, 0))
|
||||
return {"required_heap_bytes": required, "required_contiguous_bytes": largest,
|
||||
"components": parts, "display": display, "full_companion": full}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/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 re
|
||||
|
||||
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.
|
||||
flags = env.get("BUILD_FLAGS", [])
|
||||
text = flags if isinstance(flags, str) else " ".join(map(str, flags))
|
||||
return bool(re.search(r"(?:^|\s)-D\s*(?:" + "|".join(CONFLICTING)
|
||||
+ r")(?=[=\s]|$)", text))
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user