mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-17 01:44:25 +00:00
Publishes a parallel observer firmware channel that cannot cross-contaminate production. Manual dispatch only, so the branch is chosen in the Actions UI rather than hardcoded here. Channel separation (each of these is load-bearing, not cosmetic): - OTA_MANIFEST_BASE_URL -> beta nodes only ever read beta manifests. This is the one that actually keeps devices on-channel. - Separate RELEASE_TAG: the publish step prunes all but KEEP_BUILDS hashes WITHIN its tag, so a shared tag would make each channel delete the other's assets. - Separate build counter: shared counters would interleave and make OTA's "N behind" comparison meaningless. - Separate staticPath via a derived config-beta.json. FIRMWARE_VERSION deliberately matches production: the OTA logic treats a different base version as "always an update", so channels must be separated by manifest URL, not base version. OTA_CHANNEL_TAG marks the embedded version instead (v1.16.0.N-observer-beta-<hash>) so `ver` identifies the channel. config-beta.json is derived per build rather than committed - a checked-in copy would be a 56-entry duplicate of config.json that goes stale as devices are added. Deriving keeps the beta device list identical by construction. Two verify steps fail the build rather than publish firmware that would OTA itself onto production: one checks the beta URL is baked into a binary (and the production URL is not), one checks the generated manifests use the beta host. Production's changelog and docs sync steps are omitted - those rewrite site-wide content the production channel owns. The flasher commit is scoped to the beta manifest dir and counter for the same reason. Also adds OTA_CHANNEL_TAG support to build.sh. Safe for OTA version parsing: ota_parseVersion() reads to the first '-' and ota_extractHash() takes the token after the last, so an extra tag between them changes neither. Verified on a real build: v1.16.0.7-observer-beta-36831271.
324 lines
11 KiB
Bash
Executable File
324 lines
11 KiB
Bash
Executable File
# cache project config json for use in get_platform_for_env()
|
|
PIO_CONFIG_JSON=$(pio project config --json-output)
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
global_usage() {
|
|
cat - <<EOF
|
|
Usage:
|
|
sh build.sh <command> [target]
|
|
|
|
Commands:
|
|
help|usage|-h|--help: Shows this message.
|
|
list|-l: List firmwares available to build.
|
|
build-firmware <target>: Build the firmware for the given build target.
|
|
build-firmwares: Build all firmwares for all targets.
|
|
build-matching-firmwares <build-match-spec>: Build all firmwares for build targets containing the string given for <build-match-spec>.
|
|
build-companion-firmwares: Build all companion firmwares for all build targets.
|
|
build-repeater-firmwares: Build all repeater firmwares for all build targets.
|
|
build-room-server-firmwares: Build all chat room server firmwares for all build targets.
|
|
|
|
Examples:
|
|
Build firmware for the "RAK_4631_repeater" device target
|
|
$ sh build.sh build-firmware RAK_4631_repeater
|
|
|
|
Build all firmwares for device targets containing the string "RAK_4631"
|
|
$ sh build.sh build-matching-firmwares <build-match-spec>
|
|
|
|
Build all companion firmwares
|
|
$ sh build.sh build-companion-firmwares
|
|
|
|
Build all repeater firmwares
|
|
$ sh build.sh build-repeater-firmwares
|
|
|
|
Build all chat room server firmwares
|
|
$ sh build.sh build-room-server-firmwares
|
|
|
|
Environment Variables:
|
|
DISABLE_DEBUG=1: Disables all debug logging flags (MESH_DEBUG, MESH_PACKET_LOGGING, etc.)
|
|
If not set, debug flags from variant platformio.ini files are used.
|
|
|
|
Examples:
|
|
Build without debug logging:
|
|
$ export FIRMWARE_VERSION=v1.0.0
|
|
$ export DISABLE_DEBUG=1
|
|
$ sh build.sh build-firmware RAK_4631_repeater
|
|
|
|
Build with debug logging (default, uses flags from variant files):
|
|
$ export FIRMWARE_VERSION=v1.0.0
|
|
$ sh build.sh build-firmware RAK_4631_repeater
|
|
EOF
|
|
}
|
|
|
|
# get a list of pio env names that start with "env:"
|
|
get_pio_envs() {
|
|
pio project config | grep 'env:' | sed 's/env://'
|
|
}
|
|
|
|
# Catch cries for help before doing anything else.
|
|
case $1 in
|
|
help|usage|-h|--help)
|
|
global_usage
|
|
exit 1
|
|
;;
|
|
list|-l)
|
|
get_pio_envs
|
|
exit 0
|
|
;;
|
|
esac
|
|
|
|
# $1 should be the string to find (case insensitive)
|
|
get_pio_envs_containing_string() {
|
|
shopt -s nocasematch
|
|
envs=($(get_pio_envs))
|
|
for env in "${envs[@]}"; do
|
|
if [[ "$env" == *${1}* ]]; then
|
|
echo $env
|
|
fi
|
|
done
|
|
}
|
|
|
|
# $1 should be the string to find (case insensitive)
|
|
get_pio_envs_ending_with_string() {
|
|
shopt -s nocasematch
|
|
envs=($(get_pio_envs))
|
|
for env in "${envs[@]}"; do
|
|
if [[ "$env" == *${1} ]]; then
|
|
echo $env
|
|
fi
|
|
done
|
|
}
|
|
|
|
# get platform flag for a given environment
|
|
# $1 should be the environment name
|
|
get_platform_for_env() {
|
|
local env_name=$1
|
|
printf '%s' "$PIO_CONFIG_JSON" | python3 -c "
|
|
import sys, json, re
|
|
raw = sys.stdin.read()
|
|
data = json.loads(raw, strict=False)
|
|
for section, options in data:
|
|
if section == 'env:$env_name':
|
|
for key, value in options:
|
|
if key == 'build_flags':
|
|
for flag in value:
|
|
match = re.search(r'(ESP32_PLATFORM|NRF52_PLATFORM|STM32_PLATFORM|RP2040_PLATFORM)', str(flag))
|
|
if match:
|
|
print(match.group(1))
|
|
sys.exit(0)
|
|
" 2>/dev/null || true
|
|
}
|
|
|
|
# disable all debug logging flags if DISABLE_DEBUG=1 is set
|
|
disable_debug_flags() {
|
|
if [ "$DISABLE_DEBUG" == "1" ]; then
|
|
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UMESH_DEBUG -UBLE_DEBUG_LOGGING -UWIFI_DEBUG_LOGGING -UBRIDGE_DEBUG -UGPS_NMEA_DEBUG -UCORE_DEBUG_LEVEL -UESPNOW_DEBUG_LOGGING -UDEBUG_RP2040_WIRE -UDEBUG_RP2040_SPI -UDEBUG_RP2040_CORE -UDEBUG_RP2040_PORT -URADIOLIB_DEBUG_SPI -UCFG_DEBUG -URADIOLIB_DEBUG_BASIC -URADIOLIB_DEBUG_PROTOCOL"
|
|
fi
|
|
}
|
|
|
|
# build firmware for the provided pio env in $1
|
|
build_firmware() {
|
|
# get env platform for post build actions
|
|
ENV_PLATFORM=($(get_platform_for_env $1))
|
|
|
|
# get git commit sha
|
|
COMMIT_HASH=$(git rev-parse --short HEAD)
|
|
|
|
# set firmware build date (e.g. "6 Jun 2026"; %-d drops the leading zero on GNU date / the Linux CI runner)
|
|
FIRMWARE_BUILD_DATE=$(date '+%-d %b %Y')
|
|
|
|
# get FIRMWARE_VERSION, which should be provided by the environment
|
|
if [ -z "$FIRMWARE_VERSION" ]; then
|
|
echo "FIRMWARE_VERSION must be set in environment"
|
|
exit 1
|
|
fi
|
|
|
|
# set firmware version string (used for the output filename)
|
|
# e.g: v1.0.0-abcdef
|
|
FIRMWARE_VERSION_STRING="${FIRMWARE_VERSION}-${COMMIT_HASH}"
|
|
|
|
# craft filename
|
|
# e.g: RAK_4631_Repeater-v1.0.0-SHA
|
|
FIRMWARE_FILENAME="$1-${FIRMWARE_VERSION_STRING}"
|
|
|
|
# Tag the *embedded* version for observer builds, e.g. v1.0.0-observer-abcdef,
|
|
# so `ver`, the MQTT firmware_version/client_version, and SNMP all identify the
|
|
# fork. The filename above is intentionally left untagged: the env name already
|
|
# contains "observer", and the web flasher keys off that existing pattern.
|
|
VARIANT_TAG=""
|
|
case "$1" in
|
|
*observer*) VARIANT_TAG="-observer" ;;
|
|
esac
|
|
|
|
# Optional release-channel marker (e.g. OTA_CHANNEL_TAG=beta -> "-observer-beta"),
|
|
# so `ver` / MQTT firmware_version / SNMP identify which channel a node runs
|
|
# without having to infer it from log behavior. Safe for the OTA version logic:
|
|
# ota_parseVersion() reads only up to the first '-' and ota_extractHash() takes
|
|
# the token after the LAST '-', so extra tags in between change neither.
|
|
if [ -n "$OTA_CHANNEL_TAG" ]; then
|
|
VARIANT_TAG="${VARIANT_TAG}-${OTA_CHANNEL_TAG}"
|
|
fi
|
|
|
|
# Observer build number: when CI provides FIRMWARE_BUILD_NUMBER (the per-base
|
|
# published-build counter), append it as a 4th version component so the node
|
|
# reports e.g. v1.16.0.5-observer-abcdef and `ota check` can show how many
|
|
# builds behind it is. Local dev builds leave it unset → no 4th component.
|
|
# The *filename* (FIRMWARE_VERSION_STRING above) is deliberately left without
|
|
# the build number so assets stay <env>-v<base>-<hash>.bin.
|
|
BUILD_NUMBER_SUFFIX=""
|
|
if [ -n "$FIRMWARE_BUILD_NUMBER" ]; then
|
|
BUILD_NUMBER_SUFFIX=".${FIRMWARE_BUILD_NUMBER}"
|
|
fi
|
|
EMBEDDED_VERSION_STRING="${FIRMWARE_VERSION}${BUILD_NUMBER_SUFFIX}${VARIANT_TAG}-${COMMIT_HASH}"
|
|
|
|
# Release channel. The observer pull-OTA fetches its slim per-variant manifest
|
|
# from <OTA_MANIFEST_BASE>/<OTA_VARIANT>.json, so this URL IS the channel: a
|
|
# device only ever sees updates published under the base it was built with.
|
|
# Override OTA_MANIFEST_BASE_URL to publish a parallel channel (e.g. beta);
|
|
# unset gives the production channel.
|
|
#
|
|
# Deliberately injected here rather than declared in variants/*/platformio.ini
|
|
# (where it used to be duplicated 28 times), for symmetry with OTA_VARIANT and
|
|
# so a plain `pio run` leaves BOTH macros undefined — which is what makes
|
|
# ESP32Board.cpp's "ERR: OTA not configured (build via build.sh)" guard fire on
|
|
# dev builds. Do not add a default in a header: that would silently arm OTA on
|
|
# locally built firmware. Note that PLATFORMIO_BUILD_FLAGS cannot reliably
|
|
# override a -D coming from build_flags (SCons reorders -U/-D), which is why
|
|
# the .ini declarations were removed rather than overridden.
|
|
OTA_MANIFEST_BASE_URL="${OTA_MANIFEST_BASE_URL:-https://observer.gessaman.com/v}"
|
|
|
|
# add firmware version info to end of existing platformio build flags in environment vars.
|
|
# OTA_VARIANT is the env name ($1) — it selects this build's slim per-variant manifest
|
|
# (<OTA_MANIFEST_BASE>/<OTA_VARIANT>.json) that the observer pull-OTA fetches.
|
|
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${EMBEDDED_VERSION_STRING}\"' -DOTA_VARIANT='\"$1\"' -DOTA_MANIFEST_BASE='\"${OTA_MANIFEST_BASE_URL}\"'"
|
|
|
|
# disable debug flags if requested
|
|
disable_debug_flags
|
|
|
|
# build firmware target
|
|
pio run -e $1
|
|
|
|
# Build merged binaries where supported (ESP32 targets).
|
|
pio run -t mergebin -e $1 >/dev/null 2>&1 || true
|
|
|
|
# Generate UF2 from HEX when useful and UF2 is not already present.
|
|
if [ -f ".pio/build/$1/firmware.hex" ] && [ ! -f ".pio/build/$1/firmware.uf2" ]; then
|
|
python3 bin/uf2conv/uf2conv.py .pio/build/$1/firmware.hex -c -o .pio/build/$1/firmware.uf2 -f 0xADA52840 >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
# Copy any produced artifacts to out folder.
|
|
cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true
|
|
cp .pio/build/$1/firmware-merged.bin out/${FIRMWARE_FILENAME}-merged.bin 2>/dev/null || true
|
|
cp .pio/build/$1/firmware.hex out/${FIRMWARE_FILENAME}.hex 2>/dev/null || true
|
|
cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true
|
|
cp .pio/build/$1/firmware.zip out/${FIRMWARE_FILENAME}.zip 2>/dev/null || true
|
|
|
|
# Emit the partition-table signature (ESP32) for OTA partition-compatibility
|
|
# checks. Keyed by env name so the slim-manifest generator can find it; the
|
|
# firmware computes the same signature at runtime from its flashed table.
|
|
if [ -f ".pio/build/$1/partitions.bin" ]; then
|
|
python3 scripts/partition_signature.py ".pio/build/$1/partitions.bin" > "out/$1.partsig" 2>/dev/null || true
|
|
fi
|
|
|
|
}
|
|
|
|
# firmwares containing $1 will be built
|
|
build_all_firmwares_matching() {
|
|
envs=($(get_pio_envs_containing_string "$1"))
|
|
for env in "${envs[@]}"; do
|
|
build_firmware $env
|
|
done
|
|
}
|
|
|
|
# firmwares ending with $1 will be built
|
|
build_all_firmwares_by_suffix() {
|
|
envs=($(get_pio_envs_ending_with_string "$1"))
|
|
for env in "${envs[@]}"; do
|
|
build_firmware $env
|
|
done
|
|
}
|
|
|
|
build_repeater_firmwares() {
|
|
|
|
# # build specific repeater firmwares
|
|
# build_firmware "Heltec_v2_repeater"
|
|
# build_firmware "Heltec_v3_repeater"
|
|
# build_firmware "Xiao_C3_Repeater_sx1262"
|
|
# build_firmware "Xiao_S3_WIO_Repeater"
|
|
# build_firmware "LilyGo_T3S3_sx1262_Repeater"
|
|
# build_firmware "RAK_4631_Repeater"
|
|
|
|
# build all repeater firmwares
|
|
build_all_firmwares_by_suffix "_repeater"
|
|
|
|
}
|
|
|
|
build_companion_firmwares() {
|
|
|
|
# # build specific companion firmwares
|
|
# build_firmware "Heltec_v2_companion_radio_usb"
|
|
# build_firmware "Heltec_v2_companion_radio_ble"
|
|
# build_firmware "Heltec_v3_companion_radio_usb"
|
|
# build_firmware "Heltec_v3_companion_radio_ble"
|
|
# build_firmware "Xiao_S3_WIO_companion_radio_ble"
|
|
# build_firmware "LilyGo_T3S3_sx1262_companion_radio_usb"
|
|
# build_firmware "LilyGo_T3S3_sx1262_companion_radio_ble"
|
|
# build_firmware "RAK_4631_companion_radio_usb"
|
|
# build_firmware "RAK_4631_companion_radio_ble"
|
|
# build_firmware "t1000e_companion_radio_ble"
|
|
|
|
# build all companion firmwares
|
|
build_all_firmwares_by_suffix "_companion_radio_usb"
|
|
build_all_firmwares_by_suffix "_companion_radio_ble"
|
|
|
|
}
|
|
|
|
build_room_server_firmwares() {
|
|
|
|
# # build specific room server firmwares
|
|
# build_firmware "Heltec_v3_room_server"
|
|
# build_firmware "RAK_4631_room_server"
|
|
|
|
# build all room server firmwares
|
|
build_all_firmwares_by_suffix "_room_server"
|
|
|
|
}
|
|
|
|
build_firmwares() {
|
|
build_companion_firmwares
|
|
build_repeater_firmwares
|
|
build_room_server_firmwares
|
|
}
|
|
|
|
# clean build dir
|
|
rm -rf out
|
|
mkdir -p out
|
|
|
|
# handle script args
|
|
if [[ $1 == "build-firmware" ]]; then
|
|
TARGETS=${@:2}
|
|
if [ "$TARGETS" ]; then
|
|
for env in $TARGETS; do
|
|
build_firmware $env
|
|
done
|
|
else
|
|
echo "usage: $0 build-firmware <target>"
|
|
exit 1
|
|
fi
|
|
elif [[ $1 == "build-matching-firmwares" ]]; then
|
|
if [ "$2" ]; then
|
|
build_all_firmwares_matching $2
|
|
else
|
|
echo "usage: $0 build-matching-firmwares <build-match-spec>"
|
|
exit 1
|
|
fi
|
|
elif [[ $1 == "build-firmwares" ]]; then
|
|
build_firmwares
|
|
elif [[ $1 == "build-companion-firmwares" ]]; then
|
|
build_companion_firmwares
|
|
elif [[ $1 == "build-repeater-firmwares" ]]; then
|
|
build_repeater_firmwares
|
|
elif [[ $1 == "build-room-server-firmwares" ]]; then
|
|
build_room_server_firmwares
|
|
fi
|