diff --git a/platformio.ini b/platformio.ini index 4fe2deea..db28b16b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -72,6 +72,7 @@ monitor_filters = esp32_exception_decoder ; it needs a custom bootloader; RP2040/STM32 have no A/B path yet.) extra_scripts = pre:scripts/generate_webconfig_html.py + pre:scripts/meshcore_image_identity.py pre:scripts/portable_esp32_link.py merge-bin.py post:tools/mota/pio_endf.py @@ -79,6 +80,9 @@ build_flags = ${arduino_base.build_flags} -D ESP32_PLATFORM -D ENABLE_OTA=1 -D OTA_FLASH_STORE=1 + ; Keep the fixed-offset MeshCore image identity even though application code + ; does not reference it directly. The flasher reads it before boot. + -Wl,-u,meshcore_image_identity -D OTA_FOLDER_SERIAL ; `ota folder on` relays a host folder of .mota over the USB console (no extra HW) ; Route esp_transport_ws_init through src/helpers/ESP32WsTransportFix.cpp to ; fix a heap-overflow in the precompiled IDF 4.4 WS transport (see that file). diff --git a/scripts/meshcore_image_identity.py b/scripts/meshcore_image_identity.py new file mode 100644 index 00000000..25ebd0ea --- /dev/null +++ b/scripts/meshcore_image_identity.py @@ -0,0 +1,39 @@ +import glob +import os +import re + +Import("env") + + +def source_filter_text(): + value = env.GetProjectOption("build_src_filter", "") + return " ".join(map(str, value)) if isinstance(value, (list, tuple)) else str(value) + + +def default_firmware_version(): + pattern = re.compile(r'#\s*define\s+FIRMWARE_VERSION\s+"([^"]+)"') + example_dirs = set(re.findall(r"examples[/\\]([A-Za-z0-9_]+)", source_filter_text())) + versions = set() + for example_dir in example_dirs: + header_pattern = os.path.join( + env["PROJECT_DIR"], "examples", example_dir, "*.h" + ) + for path in glob.glob(header_pattern): + with open(path, encoding="utf-8", errors="ignore") as header: + match = pattern.search(header.read()) + if match: + versions.add(match.group(1)) + return next(iter(versions)) if len(versions) == 1 else "unknown" + +# build.sh supplies OTA_VARIANT for release overlays whose logical target can +# differ from PIOENV. Direct `pio run -e ...` builds still need a useful board +# identity, so expose the concrete PlatformIO environment as the fallback. +env.AppendUnique( + CPPDEFINES=[ + ("MESHCORE_BUILD_ENV", env.StringifyMacro(env["PIOENV"])), + ( + "MESHCORE_DEFAULT_FIRMWARE_VERSION", + env.StringifyMacro(default_firmware_version()), + ), + ] +) diff --git a/src/MeshCoreImageIdentity.cpp b/src/MeshCoreImageIdentity.cpp new file mode 100644 index 00000000..690a1d65 --- /dev/null +++ b/src/MeshCoreImageIdentity.cpp @@ -0,0 +1,49 @@ +#if defined(ESP32_PLATFORM) + +#include + +#ifndef MESHCORE_BUILD_ENV +#define MESHCORE_BUILD_ENV "unknown" +#endif + +#ifndef OTA_VARIANT +#define OTA_VARIANT MESHCORE_BUILD_ENV +#endif + +#ifndef FIRMWARE_VERSION +#define FIRMWARE_VERSION MESHCORE_DEFAULT_FIRMWARE_VERSION +#endif + +namespace { + +constexpr uint16_t MESHCORE_IMAGE_IDENTITY_SCHEMA = 1; +constexpr uint32_t MESHCORE_IMAGE_IDENTITY_END_MAGIC = 0x3144494dUL; // "MID1" + +struct MeshCoreImageIdentity { + char magic[8]; + uint16_t schema; + uint16_t size; + char environment[96]; + char firmware_version[96]; + uint32_t end_magic; +}; + +static_assert(sizeof(MeshCoreImageIdentity) == 208, + "MeshCore image identity layout changed"); + +} // namespace + +// ESP-IDF places .rodata_custom_desc immediately after esp_app_desc_t. The +// record therefore starts at byte 0x120 relative to every ESP32 app image, +// independent of the app partition's absolute flash address. +extern "C" __attribute__((section(".rodata_custom_desc"), used)) +const MeshCoreImageIdentity meshcore_image_identity = { + {'M', 'C', 'F', 'W', 'I', 'D', '0', '1'}, + MESHCORE_IMAGE_IDENTITY_SCHEMA, + sizeof(MeshCoreImageIdentity), + OTA_VARIANT, + FIRMWARE_VERSION, + MESHCORE_IMAGE_IDENTITY_END_MAGIC, +}; + +#endif