diff --git a/local_build.sh b/local_build.sh new file mode 100755 index 00000000..c37cf319 --- /dev/null +++ b/local_build.sh @@ -0,0 +1,90 @@ +#!/bin/sh +# Local firmware build wrapper: bakes this checkout's preferred defaults into +# every build, then hands off to build.sh with the same arguments. +# +# Usage: +# ./local_build.sh build-firmware [build.sh options] +# +# Defaults baked in (edit the list below to change them): +# Radio 919.5 MHz / BW500 / SF10 / CR5 (matches the local mesh; +# Hazelwood KM-Test repeater) +# BLE PIN code macro 123456 (on display boards such as Heltec V3 the +# firmware treats 123456 as "show a random PIN on screen each +# session"; set a fixed PIN at runtime from the app/CLI instead) +# Settings profile: target defaults (--profile default), not Cascade +# Version label: see local_version.py +# +# How it works: build.sh normally injects the USA Cascadia radio preset after +# any caller flags, which would win. `--radio-preset target` disables that +# injection, so the LORA_* flags exported here take effect. build.sh keeps an +# existing PLATFORMIO_BUILD_FLAGS and appends its own recipe flags after it. +# +# Firmware defaults only apply to a node without saved preferences (fresh +# flash or erase). A node that already has saved settings keeps them. +# +# macOS: build.sh needs bash >= 4, flock and GNU date/sed: +# brew install bash flock coreutils gnu-sed +# +# Lives on the personal Gitea branch, not in the upstream fork. + +set -e + +LOCAL_BUILD_FLAGS="-DLORA_FREQ=919.5 -DLORA_BW=500 -DLORA_SF=10 -DLORA_CR=5 -DBLE_PIN_CODE=123456" + +export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS:+${PLATFORMIO_BUILD_FLAGS} }${LOCAL_BUILD_FLAGS}" + +if [ -d /opt/homebrew/bin ]; then + PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:/opt/homebrew/opt/gnu-sed/libexec/gnubin:/opt/homebrew/bin:${PATH}" + export PATH +fi + +cd "$(dirname "$0")" + +# Firmware version label (applied by local_version.py, see that file): +# hazelwood_repeater v (core v) / companion: v hazelwood (core v) +# VERSION follows the fork's own numbering: the highest vX.Y.Z.W tag reachable +# from HEAD (e.g. v1.17.1.10 from rak4631-full-companion-v1.17.1.10-preview3). +# Local code changes add an identifier; build settings (untracked files) don't: +# -hz.N N local commits not in the maintainer's fork (upstream/keymindCascade; +# origin is the personal Gitea copy, so @{upstream} would hide them) +# .dirty uncommitted edits to tracked files +# CORE is the upstream MeshCore version from examples/simple_repeater/MyMesh.h. +LOCAL_FW_NAME=hazelwood + +fork_version=$(git tag --merged HEAD \ + | sed -nE 's/.*v([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/p' \ + | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | tail -n 1) +if [ -z "$fork_version" ]; then + echo "local_build.sh: no vX.Y.Z.W tag reachable from HEAD; run git fetch --tags" >&2 + exit 1 +fi +local_id="" +LOCAL_FW_UPSTREAM_REF=${LOCAL_FW_UPSTREAM_REF:-upstream/keymindCascade} +local_commits=$(git rev-list --count "${LOCAL_FW_UPSTREAM_REF}..HEAD" 2>/dev/null || echo 0) +if [ "$local_commits" -gt 0 ]; then + local_id="-hz.${local_commits}" +fi +if [ -n "$(git status --porcelain --untracked-files=no)" ]; then + local_id="${local_id:--hz}.dirty" +fi +LOCAL_FW_VERSION="v${fork_version}${local_id}" +LOCAL_FW_CORE_VERSION=$(sed -n 's/^ *#define FIRMWARE_VERSION *"v\([0-9.]*\)".*/\1/p' examples/simple_repeater/MyMesh.h | head -n 1) +export LOCAL_FW_NAME LOCAL_FW_VERSION LOCAL_FW_CORE_VERSION + +# build.sh deletes and recreates its output directory on every run (unless +# RESUME_BUILD_OUTPUT=1), so give each run its own folder under out/ instead of +# letting it wipe out/ itself. Never keep anything else in an output folder. +if [ -z "${OUTPUT_DIR:-}" ]; then + OUTPUT_DIR="out/$(date +%Y%m%d-%H%M%S)" + export OUTPUT_DIR +fi +echo "Build output: ${OUTPUT_DIR}" + +# FIRMWARE_VERSION also names the output files (build.sh appends -). +export FIRMWARE_VERSION="${LOCAL_FW_VERSION}" +export PLATFORMIO_EXTRA_SCRIPTS="${PLATFORMIO_EXTRA_SCRIPTS:+${PLATFORMIO_EXTRA_SCRIPTS} +}pre:local_version.py" + +echo "Local build defaults: ${LOCAL_BUILD_FLAGS} (--radio-preset target --profile default)" +echo "Local firmware version: ${FIRMWARE_VERSION} (core v${LOCAL_FW_CORE_VERSION})" +exec bash build.sh "$@" --radio-preset target --profile default diff --git a/local_version.py b/local_version.py new file mode 100644 index 00000000..fbd6c069 --- /dev/null +++ b/local_version.py @@ -0,0 +1,71 @@ +# Hazelwood PlatformIO pre-script (personal branch, not in the upstream fork). Injected by +# ./local_build.sh through PLATFORMIO_EXTRA_SCRIPTS; rewrites FIRMWARE_VERSION +# to the Hazelwood display label before PlatformIO parses the build flags. +# +# repeater/room/sensor: hazelwood_repeater v1.17.1.10 (core v1.17.1) +# companion: v1.17.1.10 hazelwood (core v1.17.1) +# with local code changes the version gains -hz.N and/or .dirty (local_build.sh) +# +# Companions keep the version first because the app's device-info frame +# carries only the first 19 characters (companion MyMesh.cpp strzcpy(..., 20)). +# LoRa OTA (EndF trailer, ota self, lora_ota.py) packs the first X.Y.Z[.W] in +# the string, so it sees v1.17.1.10; the local identifier is display-only. +# +# Observer targets are left untouched: their HTTP pull-OTA parses the commit +# hash after the last '-' of FIRMWARE_VERSION. + +import os +import re + +Import("env") # noqa: F821 + +version = os.environ.get("LOCAL_FW_VERSION", "").strip() +core = os.environ.get("LOCAL_FW_CORE_VERSION", "").strip() +name = os.environ.get("LOCAL_FW_NAME", "hazelwood").strip() +pioenv = env["PIOENV"].lower() # noqa: F821 + +ROLES = ( + ("companion_radio", "companion"), + ("observer", None), + ("room_server", "room_server"), + ("repeater", "repeater"), + ("sensor", "sensor"), + ("kiss", "kiss_modem"), +) + + +def role_for(env_name): + for token, role in ROLES: + if token in env_name: + return role + return "firmware" + + +role = role_for(pioenv) +if version and core and role is not None: + if role == "companion": + label = f"{version} {name} (core v{core})" + else: + label = f"{name}_{role} {version} (core v{core})" + + # Plain quotes: PlatformIO escapes them and SCons wraps the spaced value. + define = "-DFIRMWARE_VERSION='\"" + label + "\"'" + pattern = re.compile(r"""-D\s?FIRMWARE_VERSION=(?:'[^']*'|"[^"]*"|\S+)""") + flags = env.get("BUILD_FLAGS", []) # noqa: F821 + if isinstance(flags, str): + flags = [flags] + replaced = False + new_flags = [] + for flag in flags: + updated, count = pattern.subn(lambda _m: define, str(flag)) + replaced = replaced or count > 0 + new_flags.append(updated) + if not replaced: + new_flags.append(define) + env.Replace(BUILD_FLAGS=new_flags) # noqa: F821 + # deduplicate_full_build_flags.py (ESP32 FULL) re-reads this overlay; keep + # it identical so the label is not treated as a foreign flag. + overlay = os.environ.get("PLATFORMIO_BUILD_FLAGS", "") + if overlay: + os.environ["PLATFORMIO_BUILD_FLAGS"] = pattern.sub(lambda _m: define, overlay) + print(f"Local firmware label: {label}") diff --git a/platformio.local.ini b/platformio.local.ini new file mode 100644 index 00000000..321e9d03 --- /dev/null +++ b/platformio.local.ini @@ -0,0 +1,26 @@ +; Hazelwood PlatformIO environments (force-tracked on the personal branch despite +; .gitignore; loaded via platformio.ini +; extra_configs). Build with: ./local_build.sh build-firmware + +; Heltec V3 Full Companion without the MQTT bridge. The stock Full recipe needs +; ~198 KB of runtime heap against ~181 KB available on the V3; dropping MQTT +; removes its 24 KB startup allowance plus its code/static RAM. USB, BLE, WiFi, +; WebConfig and the LoRa OTA source (USB + WiFi :5001) are unchanged. +[env:Heltec_v3_companion_radio_full_nomqtt] +extends = env:Heltec_v3_companion_radio_full +build_unflags = + ${env:Heltec_v3_companion_radio_full.build_unflags} + -D WITH_MQTT_BRIDGE=1 + -D MQTT_RUNTIME_SLOT_COUNT=1 + -D MQTT_WIFI_TX_POWER=WIFI_POWER_11dBm + -D MAX_CONTACTS=350 +; build_legacy.sh applies this 100-contact limit only to the exact +; heltec_v3_companion_radio_full name, so repeat it here. +build_flags = + ${env:Heltec_v3_companion_radio_full.build_flags} + -D MAX_CONTACTS=100 +build_src_filter = + ${env:Heltec_v3_companion_radio_full.build_src_filter} + - + - + -