From 0319a81d6958a46dd8b4d2da3c4431df8ffafbc8 Mon Sep 17 00:00:00 2001 From: Valentin Kivachuk Burda Date: Fri, 3 Jul 2026 08:02:59 +0200 Subject: [PATCH] ota: stamp the real FIRMWARE_VERSION into the EndF trailer (was v0.0.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EndF post-build stamper read the version via _cppdef('FIRMWARE_VERSION'), which only sees -D build flags โ€” but MeshCore authors FIRMWARE_VERSION as a header #define in each example, so the stamper found nothing and defaulted fw_version to 0. Every .mota / OTA advert then reported v0.0.0 while `ver` (which reads the header at runtime) showed the real version. Read the header value as a fallback WITHOUT moving where MeshCore authors it: honor a -D override first (the header's #ifndef guard invites it, for release builds), else read the #define from the example this env builds (via build_src_filter), falling back to the repo-wide value when unambiguous. Purely additive to our own EndF tooling โ€” no MeshCore source changed. HW-verified: RAK4631 now reports `fw v1.17.0` in ota stats/ls (was v0.0.0), matching `ver`. --- tools/mota/pio_endf.py | 49 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/tools/mota/pio_endf.py b/tools/mota/pio_endf.py index 61ad0e425..1ff64d3ea 100644 --- a/tools/mota/pio_endf.py +++ b/tools/mota/pio_endf.py @@ -52,14 +52,61 @@ def _cppdef(name): # value of a -D= return None +def _version_from_headers(): + """FIRMWARE_VERSION is a header ``#define`` in the example (upstream MeshCore convention), not a -D, so + _cppdef() can't see it and the EndF version would otherwise default to 0. Read it from the source WITHOUT + changing where MeshCore authors it: prefer the example this env actually builds (from build_src_filter), + else fall back to the repo-wide value when it's unambiguous. Purely additive โ€” no MeshCore file changes, + and a -D override (checked first in _firmware_ident) still wins for release builds.""" + import re, glob + proj = env["PROJECT_DIR"] # noqa: F821 + pat = re.compile(r'#\s*define\s+FIRMWARE_VERSION\s+"([^"]+)"') + + def scan(paths): # {version_string: first_path_seen} + vals = {} + for p in paths: + try: + with open(p, encoding="utf-8", errors="ignore") as f: + for line in f: + mm = pat.search(line) + if mm: + vals.setdefault(mm.group(1), p) + except OSError: + pass + return vals + + # 1) restrict to the example(s) this env compiles (build_src_filter -> examples/) + srcf = "" + for getter in (lambda: env.GetProjectOption("build_src_filter", ""), # noqa: F821 + lambda: env.GetProjectOption("src_filter", ""), # noqa: F821 + lambda: env.get("SRC_FILTER", "")): # noqa: F821 + try: + v = getter() + srcf += " " + (" ".join(map(str, v)) if isinstance(v, (list, tuple)) else str(v)) + except Exception: + pass + ex_dirs = set(re.findall(r"examples[/\\]([A-Za-z0-9_]+)", srcf)) + hdrs = [] + for d in ex_dirs: + hdrs += glob.glob(os.path.join(proj, "examples", d, "*.h")) + vals = scan(hdrs) + if len(vals) == 1: + return next(iter(vals)) + # 2) fall back to the repo-wide value; use it only if all examples agree (they do today: v1.17.0) + vals = scan(glob.glob(os.path.join(proj, "examples", "*", "*.h"))) + return next(iter(vals)) if len(vals) == 1 else "" + + def _firmware_ident(): """Self-describing identity to embed in EndF (docs/ota_protocol.md ยง2): target_id is computed from the PlatformIO env name (so it's correct even without build.sh's -D MOTA_TARGET_ID), hw_id from MOTA_HW_ID, - fw_version parsed from FIRMWARE_VERSION.""" + fw_version from FIRMWARE_VERSION (a -D if set, else the example's header #define).""" import re target_id = ml.target_id_for_env(env["PIOENV"]) # noqa: F821 hw_id = (_cppdef("MOTA_HW_ID") or "").replace("\\", "").strip().strip('"').strip("'") ver_s = (_cppdef("FIRMWARE_VERSION") or "").replace("\\", "").strip().strip('"').strip("'") + if not ver_s: # not a -D -> read the header MeshCore ships + ver_s = _version_from_headers() m = re.search(r"(\d+)\.(\d+)(?:\.(\d+))?", ver_s) fw_version = ml.pack_version(f"{m.group(1)}.{m.group(2)}.{m.group(3) or 0}") if m else 0 return ml.FwIdent(fw_version=fw_version, target_id=target_id, hw_id=hw_id)