#!/usr/bin/env bash require_uv() { if ! command -v uv >/dev/null 2>&1; then echo "uv is required. Install it from https://docs.astral.sh/uv/" >&2 exit 1 fi } sync_python_tooling() { uv sync --locked --no-group docs >/dev/null uv venv --allow-existing --seed >/dev/null } run_python() { uv run --no-sync python "$@" } run_pio() { uv run --no-sync pio "$@" } global_usage() { cat - < [target] Commands: help|usage|-h|--help: Shows this message. list|-l: List firmwares available to build. list-mqtt: List only the PosadMesh MQTT build targets. build-firmware : Build the firmware for the given build target. build-firmwares: Build all PosadMesh MQTT firmwares. build-mqtt-firmwares: Build all MQTT firmwares (uplink, ESP-NOW bridge and MQTT bridge). build-matching-firmwares : Build all firmwares for build targets containing the string given for . build-repeater-mqtt-firmwares: Build the MQTT uplink firmwares. build-repeater-mqtt-espnow-firmwares: Build the MQTT uplink firmwares that also run a local ESP-NOW bridge. build-repeater-mqtt-bridge-firmwares: Build the MQTT bridge firmwares. build-repeater-firmwares: Build all upstream repeater firmwares. build-repeater-bridge-espnow-firmwares: Build all repeater ESP-NOW bridge firmwares. build-room-server-firmwares: Build all chat room server firmwares. Examples: Build firmware for the "heltec_v4_repeater_mqtt" device target $ sh posadmesh-build.sh build-firmware heltec_v4_repeater_mqtt Build all MQTT firmwares $ sh posadmesh-build.sh build-mqtt-firmwares Build the MQTT uplink firmwares $ sh posadmesh-build.sh build-repeater-mqtt-firmwares Build the MQTT uplink firmwares with the local ESP-NOW bridge $ sh posadmesh-build.sh build-repeater-mqtt-espnow-firmwares Build the MQTT bridge firmwares $ sh posadmesh-build.sh build-repeater-mqtt-bridge-firmwares Build all firmwares for device targets containing the string "Xiao" $ sh posadmesh-build.sh build-matching-firmwares Xiao 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 posadmesh-build.sh build-firmware RAK_4631_repeater Build with debug logging (default, uses flags from variant files): $ export FIRMWARE_VERSION=v1.0.0 $ sh posadmesh-build.sh build-firmware RAK_4631_repeater EOF } # get a list of pio env names that start with "env:" get_pio_envs() { run_pio project config --json-output | run_python -c " import json import sys for section, _options in json.load(sys.stdin): if section.startswith('env:'): print(section[4:]) " | tr -d '\r' } # Catch cries for help before doing anything else. case $1 in help|usage|-h|--help) global_usage exit 1 ;; list|-l) require_uv sync_python_tooling get_pio_envs exit 0 ;; list-mqtt) require_uv sync_python_tooling get_pio_envs | grep -E '_(repeater_mqtt|repeater_mqtt_espnow|repeater_mqtt_bridge)$' | sort exit 0 ;; esac # cache project config json for use in get_platform_for_env() require_uv sync_python_tooling PIO_CONFIG_JSON=$(run_pio project config --json-output) # $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 echo "$PIO_CONFIG_JSON" | run_python -c " import sys, json, re data = json.load(sys.stdin) 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)', flag) if match: print(match.group(1)) sys.exit(0) " | tr -d '\r' } # 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 } get_firmware_build_date() { local env_name="$1" local header="examples/simple_repeater/MyMesh.h" if [[ "$env_name" == *room_server* ]]; then header="examples/simple_room_server/MyMesh.h" elif [[ "$env_name" == *sensor* ]]; then header="examples/simple_sensor/SensorMesh.h" fi sed -n 's/^[[:space:]]*#define[[:space:]][[:space:]]*FIRMWARE_BUILD_DATE[[:space:]][[:space:]]*"\([^"]*\)".*/\1/p' "$header" | head -n 1 | tr -d '\r' } # 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) # use the same fallback build date as direct PlatformIO builds FIRMWARE_BUILD_DATE=$(get_firmware_build_date "$1") if [ -z "$FIRMWARE_BUILD_DATE" ]; then echo "FIRMWARE_BUILD_DATE could not be read for $1" exit 1 fi # 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 artifact version string # e.g: v1.2.3-posadmesh-v1.0.1-abcdef FIRMWARE_VERSION_BASE="${FIRMWARE_VERSION}" if [ -n "$POSADMESH_VERSION" ]; then FIRMWARE_VERSION_BASE="${FIRMWARE_VERSION_BASE}-posadmesh-${POSADMESH_VERSION}" fi FIRMWARE_VERSION_STRING="${FIRMWARE_VERSION_BASE}-${COMMIT_HASH}" CLIENT_VERSION_STRING="PosadMesh/${COMMIT_HASH}" if [ -n "$POSADMESH_VERSION" ]; then CLIENT_VERSION_STRING="PosadMesh/${POSADMESH_VERSION}-${COMMIT_HASH}" fi # craft filename # e.g: RAK_4631_Repeater-v1.0.0-SHA FIRMWARE_FILENAME="$1-${FIRMWARE_VERSION_STRING}" # add build metadata to end of existing platformio build flags in environment vars export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION}\"' -DCLIENT_VERSION='\"${CLIENT_VERSION_STRING}\"' -DCLIENT_ENV='\"${1}\"'" # disable debug flags if requested disable_debug_flags # build firmware target run_pio run -e $1 # build merge-bin for esp32 fresh install, copy .bins to out folder (e.g: Heltec_v3_room_server-v1.0.0-SHA.bin) if [ "$ENV_PLATFORM" == "ESP32_PLATFORM" ]; then run_pio run -t mergebin -e $1 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 fi # build .uf2 for nrf52 boards, copy .uf2 and .zip to out folder (e.g: RAK_4631_Repeater-v1.0.0-SHA.uf2) if [ "$ENV_PLATFORM" == "NRF52_PLATFORM" ]; then run_python bin/uf2conv/uf2conv.py .pio/build/$1/firmware.hex -c -o .pio/build/$1/firmware.uf2 -f 0xADA52840 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 fi # for stm32, copy .bin and .hex to out folder if [ "$ENV_PLATFORM" == "STM32_PLATFORM" ]; then cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true cp .pio/build/$1/firmware.hex out/${FIRMWARE_FILENAME}.hex 2>/dev/null || true fi # for rp2040, copy .bin and .uf2 to out folder if [ "$ENV_PLATFORM" == "RP2040_PLATFORM" ]; then cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 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_repeater_bridge_espnow_firmwares() { build_all_firmwares_by_suffix "_repeater_bridge_espnow" } build_repeater_mqtt_firmwares() { build_all_firmwares_by_suffix "_repeater_mqtt" } build_repeater_mqtt_espnow_firmwares() { build_all_firmwares_by_suffix "_repeater_mqtt_espnow" } build_repeater_mqtt_bridge_firmwares() { build_all_firmwares_by_suffix "_repeater_mqtt_bridge" } # every MQTT firmware track: uplink, uplink with ESP-NOW bridge, and MQTT bridge build_mqtt_firmwares() { build_repeater_mqtt_firmwares build_repeater_mqtt_espnow_firmwares build_repeater_mqtt_bridge_firmwares } 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_mqtt_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 " exit 1 fi elif [[ $1 == "build-matching-firmwares" ]]; then if [ "$2" ]; then build_all_firmwares_matching $2 else echo "usage: $0 build-matching-firmwares " exit 1 fi elif [[ $1 == "build-firmwares" ]]; then build_firmwares elif [[ $1 == "build-mqtt-firmwares" ]]; then build_mqtt_firmwares elif [[ $1 == "build-repeater-firmwares" ]]; then build_repeater_firmwares elif [[ $1 == "build-repeater-bridge-espnow-firmwares" ]]; then build_repeater_bridge_espnow_firmwares elif [[ $1 == "build-repeater-mqtt-firmwares" ]]; then build_repeater_mqtt_firmwares elif [[ $1 == "build-repeater-mqtt-espnow-firmwares" ]]; then build_repeater_mqtt_espnow_firmwares elif [[ $1 == "build-repeater-mqtt-bridge-firmwares" ]]; then build_repeater_mqtt_bridge_firmwares elif [[ $1 == "build-room-server-firmwares" ]]; then build_room_server_firmwares fi