feat: add capability-aware firmware build profiles
This commit is contained in:
1 parent
5938cbe614
commit
ba2170d21b
21 files changed
+1084
-101
No files matched your search
@@ -9,6 +9,7 @@ declare -A PIO_ENV_OTA_BY_NAME=()
|
||||
declare -A PIO_ENV_SD_OTA_BY_NAME=()
|
||||
declare -A PIO_ENV_QSPI_OTA_BY_NAME=()
|
||||
declare -A PIO_ENV_BUILD_BASE_BY_NAME=()
|
||||
declare -A PIO_ENV_COMPLETE_OTA_BASE_BY_NAME=()
|
||||
declare -A PIO_ENV_FULL_BUILD_BY_NAME=()
|
||||
declare -A PIO_ENV_FULL_WIFI_OTA_BY_NAME=()
|
||||
PIO_CONFIG_JSON=""
|
||||
@@ -22,6 +23,16 @@ MQTT_DEBUG_OVERRIDE=""
|
||||
FIRMWARE_FILENAME_INFIX=""
|
||||
ESP32_FULL_BUILD=0
|
||||
SINGLE_TARGET_FULL_BUILD=0
|
||||
AUTO_PREFER_FULL_BUILD=0
|
||||
AUTO_COMPLETE_FIRST_PASS=0
|
||||
AUTO_REDUCED_FALLBACK_TARGET=""
|
||||
AUTO_PUBLISH_REDUCED_SECOND_PASS=0
|
||||
SKIP_DECLARED_REDUCTIONS=0
|
||||
COMPLETE_OTA_FIRST_PASS=0
|
||||
FIRMWARE_OUTPUT_ENV_NAME=""
|
||||
BUILD_PROFILE_OVERRIDE="${BUILD_PROFILE_OVERRIDE:-auto}"
|
||||
BUILD_PROFILE_EXPLICIT=0
|
||||
BUILD_PROFILE_EFFECTIVE="auto"
|
||||
RADIO_SETTINGS_API_URL="https://api.meshcore.nz/api/v1/config"
|
||||
USA_CASCADIA_RADIO_TITLE="USA Cascadia"
|
||||
USA_CASCADIA_FALLBACK_FREQ="910.525"
|
||||
@@ -76,7 +87,7 @@ FALLBACK_VERSION_DATE_FORMAT='+%Y-%m-%d-%H-%M'
|
||||
|
||||
# External programs invoked by this script:
|
||||
# bash, cat, cp, date, find, flock, git, grep, head, mkdir, mv, pgrep, pio,
|
||||
# python3, rm, sed, sleep, sort, wc
|
||||
# python3, rm, sed, sleep, sort, tee, wc
|
||||
# Keep this list in sync when adding or removing non-builtin command usage.
|
||||
|
||||
global_usage() {
|
||||
@@ -107,13 +118,18 @@ Commands:
|
||||
Options:
|
||||
--firmware-version <version>: Firmware version to embed.
|
||||
--radio-preset <name|number>: Override the USA Cascadia radio default. Stable names are usa-cascadia and target; legacy menu numbers remain accepted.
|
||||
--profile <default|cascade>: Override the default Cascade firmware settings profile.
|
||||
--profile <default|cascade>: Override runtime settings embedded in the firmware (not its feature set).
|
||||
--build-profile <auto|standard|full>: Select feature/partition policy. Auto first builds complete LoRa-OTA-capable firmware. A measured size failure triggers the reduced recipe; internal-flash nRF52 repeaters also publish the reduced image for extra delta-staging headroom. Standard applies declared portable-image reductions immediately; full requires the expanded ESP32 recipe.
|
||||
--auto|--standard|--full: Short forms of --build-profile.
|
||||
--skip-kiss|--include-kiss: Exclude (default) or include KISS modem targets in bulk builds.
|
||||
--clean|--resume: Clean output or resume existing Option 3/FULL-only artifacts.
|
||||
|
||||
Examples:
|
||||
Build firmware for the "RAK_4631_repeater" device target
|
||||
$ bash build.sh build-firmware RAK_4631_repeater
|
||||
Build firmware for the "RAK_4631_repeater" device target. Auto first builds
|
||||
the complete LoRa OTA recipe, then also publishes the reduced
|
||||
no-external-sensors receiver because this nRF52 repeater stages deltas in
|
||||
internal flash.
|
||||
$ bash build.sh build-firmware RAK_4631_repeater --build-profile auto
|
||||
|
||||
Run without arguments to choose an interactive build action/target, an optional
|
||||
FULL-everything profile for supported ESP32 Option 1 targets, debug options,
|
||||
@@ -304,6 +320,7 @@ for section, options in data:
|
||||
solarxiao_30s_repeater|solarxiao_33s_repeater) continue ;;
|
||||
esac
|
||||
ota_env="${env_name%_}_lora_ota_no_external_sensors"
|
||||
PIO_ENV_COMPLETE_OTA_BASE_BY_NAME["$ota_env"]="$env_name"
|
||||
if [ -n "${PIO_ENV_PLATFORM_BY_NAME[$ota_env]+x}" ]; then
|
||||
continue
|
||||
fi
|
||||
@@ -581,13 +598,16 @@ prompt_for_build_mode() {
|
||||
|
||||
prompt_for_single_target_build_profile() {
|
||||
SINGLE_TARGET_FULL_BUILD=0
|
||||
BUILD_PROFILE_OVERRIDE="auto"
|
||||
BUILD_PROFILE_EXPLICIT=1
|
||||
if ! supports_esp32_full_build "$SELECTED_TARGET"; then
|
||||
echo "Selected target uses its standard build profile; FULL everything is available only for supported ESP32 targets."
|
||||
echo "Using the smart auto profile in the target's current partition; expanded FULL is unavailable for this target."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local options=(
|
||||
"Standard/custom build"
|
||||
"Auto (keep target capabilities and enforce the current partition)"
|
||||
"Standard portable image (allow documented legacy-slot reductions)"
|
||||
"FULL everything (all features, 254 neighbors, USB logging, WiFi MQTT where available, LoRa OTA, expanded dual-OTA partitions)"
|
||||
)
|
||||
|
||||
@@ -602,10 +622,17 @@ prompt_for_single_target_build_profile() {
|
||||
|
||||
case "$MENU_CHOICE" in
|
||||
1)
|
||||
echo "Using the standard/custom single-target build."
|
||||
BUILD_PROFILE_OVERRIDE="auto"
|
||||
echo "Using auto: target capabilities are kept and checked against the current partition."
|
||||
return 0
|
||||
;;
|
||||
2)
|
||||
BUILD_PROFILE_OVERRIDE="standard"
|
||||
echo "Using standard: documented portable-image reductions may be applied."
|
||||
return 0
|
||||
;;
|
||||
3)
|
||||
BUILD_PROFILE_OVERRIDE="full"
|
||||
SINGLE_TARGET_FULL_BUILD=1
|
||||
echo "Using FULL everything: all features, 254 neighbors, USB logging, WiFi MQTT where available, LoRa OTA, and expanded dual-OTA partitions."
|
||||
return 0
|
||||
@@ -740,7 +767,7 @@ parse_cli_options() {
|
||||
RADIO_PRESET_SELECTION=$2
|
||||
shift 2
|
||||
;;
|
||||
--profile)
|
||||
--profile|--settings-profile)
|
||||
if [ $# -lt 2 ]; then echo "$1 requires a value"; return 1; fi
|
||||
case "${2,,}" in
|
||||
default) FIRMWARE_PROFILE_OVERRIDE="" ;;
|
||||
@@ -749,6 +776,30 @@ parse_cli_options() {
|
||||
esac
|
||||
shift 2
|
||||
;;
|
||||
--build-profile)
|
||||
if [ $# -lt 2 ] || [ -z "$2" ]; then echo "$1 requires a value"; return 1; fi
|
||||
case "${2,,}" in
|
||||
auto|standard|full) BUILD_PROFILE_OVERRIDE="${2,,}" ;;
|
||||
*) echo "Invalid build profile: $2 (use auto, standard, or full)"; return 1 ;;
|
||||
esac
|
||||
BUILD_PROFILE_EXPLICIT=1
|
||||
shift 2
|
||||
;;
|
||||
--auto)
|
||||
BUILD_PROFILE_OVERRIDE="auto"
|
||||
BUILD_PROFILE_EXPLICIT=1
|
||||
shift
|
||||
;;
|
||||
--standard)
|
||||
BUILD_PROFILE_OVERRIDE="standard"
|
||||
BUILD_PROFILE_EXPLICIT=1
|
||||
shift
|
||||
;;
|
||||
--full)
|
||||
BUILD_PROFILE_OVERRIDE="full"
|
||||
BUILD_PROFILE_EXPLICIT=1
|
||||
shift
|
||||
;;
|
||||
--skip-kiss)
|
||||
KISS_MODE_OVERRIDE="skip"
|
||||
shift
|
||||
@@ -2196,6 +2247,33 @@ is_repeater_role_target() {
|
||||
esac
|
||||
}
|
||||
|
||||
is_room_server_role_target() {
|
||||
case "${1,,}" in
|
||||
*room_server*|*room_svr*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
get_reduced_lora_ota_target() {
|
||||
local target=$1
|
||||
local candidate
|
||||
|
||||
if is_lora_ota_only_target "$target"; then
|
||||
echo "$target"
|
||||
return 0
|
||||
fi
|
||||
if ! is_repeater_role_target "$target"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
candidate="${target%_}_lora_ota_no_external_sensors"
|
||||
if is_supported_build_env "$candidate"; then
|
||||
echo "$candidate"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
is_lora_ota_build() {
|
||||
local env_name=$1
|
||||
local env_name_lc=${env_name,,}
|
||||
@@ -2301,10 +2379,78 @@ requires_esp32_full_cli_profile() {
|
||||
|| [[ "${env_name,,}" == *bridge_espnow* ]]; }
|
||||
}
|
||||
|
||||
record_build_capability() {
|
||||
BUILD_CAPABILITIES+=("$1")
|
||||
}
|
||||
|
||||
record_build_reduction() {
|
||||
BUILD_REDUCTIONS+=("$1")
|
||||
}
|
||||
|
||||
record_build_expectation() {
|
||||
BUILD_EXPECTATIONS+=("$1=$2")
|
||||
}
|
||||
|
||||
declare_build_capability_contract() {
|
||||
local env_name=$1
|
||||
local env_platform=$2
|
||||
local env_name_lc=${env_name,,}
|
||||
local pio_env_name
|
||||
|
||||
record_build_capability "profile.${BUILD_PROFILE_FOR_TARGET}"
|
||||
|
||||
if is_repeater_role_target "$env_name" \
|
||||
|| is_room_server_role_target "$env_name"; then
|
||||
record_build_expectation "cli.retry_preset" "retry.preset"
|
||||
fi
|
||||
|
||||
if is_lora_ota_build "$env_name"; then
|
||||
record_build_expectation "ota.cli" "OTA: status"
|
||||
fi
|
||||
|
||||
if [ "$env_platform" = "ESP32_PLATFORM" ] \
|
||||
&& [ "$BUILD_PROFILE_FOR_TARGET" = "full" ] \
|
||||
&& ! is_esp32_companion_build "$env_name"; then
|
||||
pio_env_name=$(get_pio_build_env "$env_name")
|
||||
if pio_env_option_contains "$pio_env_name" build_flags "ADMIN_PASSWORD"; then
|
||||
record_build_expectation "web.webconfig" "start webconfig"
|
||||
fi
|
||||
fi
|
||||
|
||||
if is_room_server_role_target "$env_name" \
|
||||
&& [ "$BUILD_PROFILE_FOR_TARGET" = "full" ]; then
|
||||
record_build_expectation "room.flood_rule_engine" "flood.rule"
|
||||
fi
|
||||
|
||||
if is_companion_radio_full_target "$env_name"; then
|
||||
record_build_expectation "companion.temp_radio" "tempradio"
|
||||
record_build_expectation "companion.ota_cli" "ota folder"
|
||||
if [ "$env_platform" = "ESP32_PLATFORM" ]; then
|
||||
record_build_expectation "companion.network_terminal" \
|
||||
"Full Companion terminal listening"
|
||||
record_build_expectation "web.webconfig" "start webconfig"
|
||||
else
|
||||
record_build_expectation "companion.usb_mota_source" "ota folder on"
|
||||
fi
|
||||
elif [ "$env_platform" = "ESP32_PLATFORM" ] \
|
||||
&& is_esp32_companion_build "$env_name" \
|
||||
&& is_lora_ota_build "$env_name"; then
|
||||
record_build_expectation "companion.temp_radio" "ERR usage: tempradio"
|
||||
record_build_expectation "companion.ota_cli" "OTA: status"
|
||||
fi
|
||||
|
||||
case "$env_name_lc" in
|
||||
*sensecapindicator*lora*comp*wifi*)
|
||||
record_build_expectation "web.webconfig" "start webconfig"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
apply_esp32_lora_ota_size_profile() {
|
||||
local env_name=$1
|
||||
|
||||
if [ "$ESP32_FULL_BUILD" = "1" ] || ! requires_esp32_portable_app_slot "$env_name"; then
|
||||
if [ "$BUILD_PROFILE_FOR_TARGET" != "standard" ] \
|
||||
|| ! requires_esp32_portable_app_slot "$env_name"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -2318,6 +2464,8 @@ apply_esp32_lora_ota_size_profile() {
|
||||
# Keep ENV_INCLUDE_GPS for boards with onboard GPS; their target sensor
|
||||
# managers require that support.
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DWEBCONFIG_DISABLED=1"
|
||||
record_build_reduction \
|
||||
"web.webconfig omitted to preserve the legacy portable ESP32 app slot"
|
||||
if is_lora_ota_only_target "$env_name"; then
|
||||
# The no-external-sensors image is also the self-updatable field image. Keep
|
||||
# manual browser OTA available on every ESP32 family through the compact
|
||||
@@ -2327,9 +2475,12 @@ apply_esp32_lora_ota_size_profile() {
|
||||
# dropping WiFi OTA, LoRa OTA, USB seeding, or CLI commands.
|
||||
append_platformio_build_unflags "-DDISABLE_WIFI_OTA=1 -DMAX_NEIGHBOURS=50 -DMAX_NEIGHBOURS=8 -fexceptions"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UDISABLE_WIFI_OTA -DLIGHTWEIGHT_WIFI_OTA=1 -DMAX_NEIGHBOURS=${ESP32_FULL_MAX_NEIGHBOURS} -fno-exceptions"
|
||||
record_build_capability "web.lightweight_browser_ota"
|
||||
else
|
||||
append_platformio_build_unflags "-DLIGHTWEIGHT_WIFI_OTA=1"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -ULIGHTWEIGHT_WIFI_OTA -DDISABLE_WIFI_OTA=1"
|
||||
record_build_reduction \
|
||||
"web.browser_ota omitted because this portable radio role does not otherwise require WiFi"
|
||||
fi
|
||||
|
||||
}
|
||||
@@ -2374,7 +2525,10 @@ apply_esp32_full_size_profile() {
|
||||
# The FULL artifact uses expanded dual-OTA slots, so restore features that
|
||||
# target or legacy-slot profiles disabled only to save application space.
|
||||
append_platformio_build_unflags "-DWEBCONFIG_DISABLED=1"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UWEBCONFIG_DISABLED -DWIFI_OTA_SEEDER=1 -DMESHCORE_ESP32_FULL_PROFILE=1"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UWEBCONFIG_DISABLED -DWIFI_OTA_SEEDER=1 -DMESHCORE_EXPANDED_PARTITION_PROFILE=1"
|
||||
if is_room_server_role_target "$env_name"; then
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DMESH_ENABLE_ROOM_FLOOD_RULE_ENGINE=1"
|
||||
fi
|
||||
|
||||
# Keep ordinary builds at their board-defined neighbor capacity. FULL builds
|
||||
# normally use the largest table supported by the one-byte neighbor discovery
|
||||
@@ -2414,6 +2568,8 @@ apply_repeater_neighbor_capacity() {
|
||||
if requires_dram_limited_neighbors "$env_name"; then
|
||||
max_neighbours=$DRAM_LIMITED_MAX_NEIGHBOURS
|
||||
append_platformio_build_unflags "-DMAX_NEIGHBOURS=8 -DMAX_NEIGHBOURS=254"
|
||||
record_build_reduction \
|
||||
"mesh.neighbors limited to ${DRAM_LIMITED_MAX_NEIGHBOURS} by measured RAM/flash capacity"
|
||||
else
|
||||
append_platformio_build_unflags "-DMAX_NEIGHBOURS=8 -DMAX_NEIGHBOURS=50"
|
||||
fi
|
||||
@@ -2445,6 +2601,9 @@ apply_nrf52_size_profile() {
|
||||
apply_lora_ota_no_external_sensors_profile() {
|
||||
local env_name=$1
|
||||
|
||||
if [ "$SKIP_DECLARED_REDUCTIONS" = "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
if ! is_lora_ota_build "$env_name" \
|
||||
|| ! is_lora_ota_no_external_sensors_target "$env_name"; then
|
||||
return 0
|
||||
@@ -2456,6 +2615,8 @@ apply_lora_ota_no_external_sensors_profile() {
|
||||
# their location provider even when optional external I2C sensors are absent.
|
||||
append_platformio_build_unflags "-DENV_INCLUDE_AHTX0=1 -DENV_INCLUDE_BME280=1 -DENV_INCLUDE_BMP280=1 -DENV_INCLUDE_SHTC3=1 -DENV_INCLUDE_SHT4X=1 -DENV_INCLUDE_LPS22HB=1 -DENV_INCLUDE_INA3221=1 -DENV_INCLUDE_INA219=1 -DENV_INCLUDE_INA226=1 -DENV_INCLUDE_INA260=1 -DENV_INCLUDE_MLX90614=1 -DENV_INCLUDE_VL53L0X=1 -DENV_INCLUDE_BME680=1 -DENV_INCLUDE_BMP085=1 -DENV_INCLUDE_RAK12035=1 -DENV_INCLUDE_BME680_BSEC=1"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UENV_INCLUDE_AHTX0 -UENV_INCLUDE_BME280 -UENV_INCLUDE_BMP280 -UENV_INCLUDE_SHTC3 -UENV_INCLUDE_SHT4X -UENV_INCLUDE_LPS22HB -UENV_INCLUDE_INA3221 -UENV_INCLUDE_INA219 -UENV_INCLUDE_INA226 -UENV_INCLUDE_INA260 -UENV_INCLUDE_MLX90614 -UENV_INCLUDE_VL53L0X -UENV_INCLUDE_BME680 -UENV_INCLUDE_BMP085 -UENV_INCLUDE_RAK12035 -UENV_INCLUDE_BME680_BSEC"
|
||||
record_build_reduction \
|
||||
"sensors.external omitted by the explicitly named no_external_sensors target"
|
||||
}
|
||||
|
||||
append_platformio_build_unflags() {
|
||||
@@ -2544,6 +2705,17 @@ apply_nrf52_lora_ota_build_recipe() {
|
||||
append_platformio_extra_script "post:tools/mota/pio_endf.py"
|
||||
fi
|
||||
|
||||
# A few feature-rich legacy environments deliberately end their own flags
|
||||
# with -UENABLE_OTA. PlatformIO build_unflags cannot remove a trailing -U,
|
||||
# so a pre-script must make the intentional OTA overlay authoritative for
|
||||
# both compilation and the EndF geometry hook.
|
||||
if pio_env_option_contains "$pio_env_name" build_flags "-UENABLE_OTA"; then
|
||||
export MESHCORE_FORCE_LORA_OTA=1
|
||||
append_platformio_extra_script "pre:scripts/force_lora_ota.py"
|
||||
else
|
||||
unset MESHCORE_FORCE_LORA_OTA
|
||||
fi
|
||||
|
||||
if supports_nrf52_internal_bootloader_update "$env_name"; then
|
||||
export MESHCORE_NRF52_INTERNAL_BOOTLOADER_UPDATE=1
|
||||
else
|
||||
@@ -2586,12 +2758,52 @@ apply_lora_ota_override() {
|
||||
append_platformio_build_unflags "-UENABLE_OTA -DDISABLE_LORA_OTA=1"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UDISABLE_LORA_OTA -DENABLE_OTA=1 -DOTA_FLASH_STORE=1 -DOTA_FOLDER_SERIAL"
|
||||
fi
|
||||
if [ "${PIO_ENV_PLATFORM_BY_NAME[$env_name]:-}" = "ESP32_PLATFORM" ] \
|
||||
&& is_esp32_companion_build "$env_name"; then
|
||||
# Do not link an OTA manager into a Companion while compiling out the
|
||||
# terminal controls needed to operate it over LoRa.
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DCOMPANION_FEATURE_TEMP_RADIO=1 -DCOMPANION_FEATURE_OTA_CLI=1"
|
||||
fi
|
||||
else
|
||||
append_platformio_build_unflags "-DENABLE_OTA=1"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UENABLE_OTA"
|
||||
fi
|
||||
}
|
||||
|
||||
apply_logical_ota_tuning_flags() {
|
||||
local logical_env_name=$1
|
||||
local pio_env_name=$2
|
||||
local ota_flags
|
||||
|
||||
if [ "$logical_env_name" = "$pio_env_name" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
ota_flags=$(python3 -c '
|
||||
import json
|
||||
import sys
|
||||
|
||||
logical_env = sys.argv[1]
|
||||
data = json.load(sys.stdin)
|
||||
for section, options in data:
|
||||
if section != f"env:{logical_env}":
|
||||
continue
|
||||
for key, value in options:
|
||||
if key != "build_flags":
|
||||
continue
|
||||
values = value if isinstance(value, list) else [str(value)]
|
||||
for item in values:
|
||||
flag = str(item).strip()
|
||||
if flag.startswith("-D OTA_") or flag.startswith("-DOTA_"):
|
||||
print(flag)
|
||||
break
|
||||
' "$logical_env_name" <<<"$PIO_CONFIG_JSON")
|
||||
|
||||
if [ -n "$ota_flags" ]; then
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} ${ota_flags}"
|
||||
fi
|
||||
}
|
||||
|
||||
apply_companion_radio_full_profile() {
|
||||
local env_name=$1
|
||||
local pio_env_name=$2
|
||||
@@ -2603,7 +2815,7 @@ apply_companion_radio_full_profile() {
|
||||
# folder transport. Full also restores WebConfig when a constrained legacy
|
||||
# WiFi sibling disabled it only to fit its smaller application partition.
|
||||
append_platformio_build_unflags "-UENABLE_OTA -DOTA_FLASH_STORE=1 -DOTA_SD_STORE=1 -DDISABLE_LORA_OTA=1 -DWEBCONFIG_DISABLED=1"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UDISABLE_LORA_OTA -DENABLE_OTA=1 -UOTA_FLASH_STORE -UOTA_SD_STORE -UWEBCONFIG_DISABLED -DOTA_SEEDER_ONLY=1 -DMOTA_TARGET_ID=0 -DCOMPANION_RADIO_FULL=1 -DENABLE_USB_INTERFACE=1 -DBLE_PIN_CODE=123456"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UDISABLE_LORA_OTA -DENABLE_OTA=1 -UOTA_FLASH_STORE -UOTA_SD_STORE -UWEBCONFIG_DISABLED -DOTA_SEEDER_ONLY=1 -DMOTA_TARGET_ID=0 -DCOMPANION_RADIO_FULL=1 -DCOMPANION_FEATURE_TEMP_RADIO=1 -DCOMPANION_FEATURE_OTA_CLI=1 -DENABLE_USB_INTERFACE=1 -DBLE_PIN_CODE=123456"
|
||||
|
||||
if is_nrf52_companion_radio_full_target "$env_name"; then
|
||||
# CDC 0 starts as Binary Companion. `motatool serve --serial` switches it
|
||||
@@ -2611,7 +2823,7 @@ apply_companion_radio_full_profile() {
|
||||
# preamble. CDC 1 is a write-only plaintext packet/debug logging stream;
|
||||
# BLE remains an independent Companion link.
|
||||
append_platformio_build_unflags "-UOTA_FOLDER_SERIAL"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DOTA_FOLDER_SERIAL=1 -DCFG_TUD_CDC=2 -DMESH_DUAL_CDC_LOGGING=1 -DMESH_DEBUG=1 -DMESH_PACKET_LOGGING=1"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DOTA_FOLDER_SERIAL=1 -DCOMPANION_FEATURE_USB_MOTA_SOURCE=1 -DCOMPANION_FEATURE_DEDICATED_USB_LOGGING=1 -DCFG_TUD_CDC=2 -DMESH_DUAL_CDC_LOGGING=1 -DMESH_DEBUG=1 -DMESH_PACKET_LOGGING=1"
|
||||
|
||||
if ! pio_env_option_contains "$pio_env_name" build_src_filter "helpers/ota/"; then
|
||||
append_platformio_build_src_filter "+<helpers/ota/*.cpp>"
|
||||
@@ -2630,13 +2842,13 @@ apply_companion_radio_full_profile() {
|
||||
# flashing reboot gesture; CDC 1 is a write-only plaintext diagnostics
|
||||
# stream and cannot reboot the board.
|
||||
append_platformio_build_unflags "-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DARDUINO_USB_MODE=0 -DARDUINO_USB_CDC_ON_BOOT=1 -DMESH_DUAL_CDC_LOGGING=1 -DMESH_DEBUG=1 -DMESH_PACKET_LOGGING=1"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DARDUINO_USB_MODE=0 -DARDUINO_USB_CDC_ON_BOOT=1 -DCOMPANION_FEATURE_DEDICATED_USB_LOGGING=1 -DMESH_DUAL_CDC_LOGGING=1 -DMESH_DEBUG=1 -DMESH_PACKET_LOGGING=1"
|
||||
fi
|
||||
|
||||
# ESP32 keeps both source transports. TCP remains the normal unattended
|
||||
# path; a host may explicitly hold the native USB port in Companion terminal
|
||||
# mode when WiFi is unavailable and serve the same folder protocol there.
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DWIFI_OTA_SEEDER=1"
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DWIFI_OTA_SEEDER=1 -DCOMPANION_FEATURE_NETWORK_TERMINAL=1 -DCOMPANION_FEATURE_MEMORY_DIAGNOSTICS=1"
|
||||
|
||||
# BLE + WiFi exhaust internal DRAM on these high-capacity ESP32 recipes. Use
|
||||
# measured-safe tables for FULL OTA without changing ordinary USB/BLE/WiFi
|
||||
@@ -2670,9 +2882,14 @@ apply_firmware_profile_overrides() {
|
||||
}
|
||||
|
||||
print_build_flags() {
|
||||
local env_name=$1
|
||||
local pio_env_name=$1
|
||||
local logical_env_name=${2:-$pio_env_name}
|
||||
|
||||
echo "Build flags for ${env_name}:"
|
||||
if [ "$logical_env_name" = "$pio_env_name" ]; then
|
||||
echo "Build flags for ${logical_env_name}:"
|
||||
else
|
||||
echo "Build flags for ${logical_env_name} (PlatformIO base ${pio_env_name}):"
|
||||
fi
|
||||
python3 -c '
|
||||
import json
|
||||
import os
|
||||
@@ -2709,7 +2926,7 @@ def print_flags(title, flags):
|
||||
print_flags("platformio.ini build_flags", config_flags)
|
||||
print_flags("PLATFORMIO_BUILD_FLAGS", env_flags)
|
||||
print_flags("PLATFORMIO_BUILD_UNFLAGS", env_unflags)
|
||||
' "$env_name" <<<"$PIO_CONFIG_JSON"
|
||||
' "$pio_env_name" <<<"$PIO_CONFIG_JSON"
|
||||
}
|
||||
|
||||
copy_build_output() {
|
||||
@@ -2724,6 +2941,36 @@ copy_build_output() {
|
||||
cp -- "$source_path" "$output_path"
|
||||
}
|
||||
|
||||
write_build_capability_manifest() {
|
||||
local env_name=$1
|
||||
local env_platform=$2
|
||||
local pio_env_name=$3
|
||||
local firmware_filename=$4
|
||||
local build_output_dir="${PIO_BUILD_DIR_OVERRIDE:-${PLATFORMIO_BUILD_DIR:-.pio/build}}/${pio_env_name}"
|
||||
local -a checker_args=(
|
||||
--image "${build_output_dir}/firmware.elf"
|
||||
--output "${OUTPUT_DIR}/${firmware_filename}.capabilities.json"
|
||||
--target "$env_name"
|
||||
--artifact-target "${FIRMWARE_OUTPUT_ENV_NAME:-$env_name}"
|
||||
--platformio-env "$pio_env_name"
|
||||
--platform "$env_platform"
|
||||
--build-profile "$BUILD_PROFILE_FOR_TARGET"
|
||||
)
|
||||
local item
|
||||
|
||||
for item in "${BUILD_CAPABILITIES[@]}"; do
|
||||
checker_args+=(--capability "$item")
|
||||
done
|
||||
for item in "${BUILD_REDUCTIONS[@]}"; do
|
||||
checker_args+=(--reduction "$item")
|
||||
done
|
||||
for item in "${BUILD_EXPECTATIONS[@]}"; do
|
||||
checker_args+=(--expect "$item")
|
||||
done
|
||||
|
||||
python3 scripts/check_firmware_capabilities.py "${checker_args[@]}"
|
||||
}
|
||||
|
||||
collect_esp32_artifacts() {
|
||||
local env_name=$1
|
||||
local pio_env_name=$2
|
||||
@@ -2735,11 +2982,18 @@ collect_esp32_artifacts() {
|
||||
)
|
||||
local partsig_name=$env_name
|
||||
|
||||
if [ "$ESP32_FULL_BUILD" != "1" ] && requires_esp32_portable_size_ceiling "$env_name"; then
|
||||
if [ "$BUILD_PROFILE_FOR_TARGET" = "standard" ] \
|
||||
&& requires_esp32_portable_size_ceiling "$env_name"; then
|
||||
size_check_args+=("$ESP32_LORA_OTA_APP_LIMIT")
|
||||
fi
|
||||
|
||||
python3 scripts/check_esp32_app_size.py "${size_check_args[@]}" || return $?
|
||||
python3 scripts/check_esp32_app_size.py "${size_check_args[@]}"
|
||||
local size_status=$?
|
||||
if [ "$size_status" -eq 1 ]; then
|
||||
return 42
|
||||
elif [ "$size_status" -ne 0 ]; then
|
||||
return "$size_status"
|
||||
fi
|
||||
copy_build_output "${build_output_dir}/firmware.bin" "${OUTPUT_DIR}/${firmware_filename}.bin" || return $?
|
||||
copy_build_output "${build_output_dir}/firmware-merged.bin" "${OUTPUT_DIR}/${firmware_filename}-merged.bin" || return $?
|
||||
|
||||
@@ -2797,6 +3051,10 @@ build_artifacts_exist() {
|
||||
local env_platform=$1
|
||||
local firmware_filename=$2
|
||||
|
||||
output_artifact_exists "${firmware_filename}.capabilities.json" || return 1
|
||||
grep -q '"verified": true' \
|
||||
"${OUTPUT_DIR}/${firmware_filename}.capabilities.json" || return 1
|
||||
|
||||
case "$env_platform" in
|
||||
ESP32_PLATFORM)
|
||||
output_artifact_exists "${firmware_filename}.bin" \
|
||||
@@ -2829,22 +3087,25 @@ collect_build_artifacts() {
|
||||
# collector after the main firmware build succeeds.
|
||||
case "$env_platform" in
|
||||
ESP32_PLATFORM)
|
||||
collect_esp32_artifacts "$env_name" "$pio_env_name" "$firmware_filename"
|
||||
collect_esp32_artifacts "$env_name" "$pio_env_name" "$firmware_filename" || return $?
|
||||
;;
|
||||
NRF52_PLATFORM)
|
||||
collect_nrf52_artifacts "$env_name" "$pio_env_name" "$firmware_filename"
|
||||
collect_nrf52_artifacts "$env_name" "$pio_env_name" "$firmware_filename" || return $?
|
||||
;;
|
||||
STM32_PLATFORM)
|
||||
collect_stm32_artifacts "$env_name" "$pio_env_name" "$firmware_filename"
|
||||
collect_stm32_artifacts "$env_name" "$pio_env_name" "$firmware_filename" || return $?
|
||||
;;
|
||||
RP2040_PLATFORM)
|
||||
collect_rp2040_artifacts "$env_name" "$pio_env_name" "$firmware_filename"
|
||||
collect_rp2040_artifacts "$env_name" "$pio_env_name" "$firmware_filename" || return $?
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported or unknown platform for env: $env_name"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
write_build_capability_manifest \
|
||||
"$env_name" "$env_platform" "$pio_env_name" "$firmware_filename"
|
||||
}
|
||||
|
||||
get_firmware_filename() {
|
||||
@@ -2908,7 +3169,6 @@ build_firmware() {
|
||||
local original_platformio_build_unflags
|
||||
local original_platformio_build_src_filter
|
||||
local original_platformio_extra_scripts
|
||||
local target_extra_scripts
|
||||
local had_platformio_build_flags=0
|
||||
local had_platformio_build_unflags=0
|
||||
local had_platformio_build_src_filter=0
|
||||
@@ -2916,6 +3176,10 @@ build_firmware() {
|
||||
local build_status
|
||||
local platformio_package_lock_fd=""
|
||||
local -a pio_run_args=()
|
||||
local -a BUILD_CAPABILITIES=()
|
||||
local -a BUILD_REDUCTIONS=()
|
||||
local -a BUILD_EXPECTATIONS=()
|
||||
local BUILD_PROFILE_FOR_TARGET=$BUILD_PROFILE_EFFECTIVE
|
||||
|
||||
# Bash functions use dynamic scoping. These locals let one target be promoted
|
||||
# to FULL without changing the profile selected for later targets in the same
|
||||
@@ -2931,9 +3195,19 @@ build_firmware() {
|
||||
return 1
|
||||
fi
|
||||
pio_env_name=$(get_pio_build_env "$env_name")
|
||||
if [ "$COMPLETE_OTA_FIRST_PASS" = "1" ] \
|
||||
&& [ -n "${PIO_ENV_COMPLETE_OTA_BASE_BY_NAME[$env_name]+x}" ]; then
|
||||
pio_env_name=${PIO_ENV_COMPLETE_OTA_BASE_BY_NAME[$env_name]}
|
||||
echo "Complete OTA pass uses feature-rich base environment ${pio_env_name} with stable target identity ${env_name}."
|
||||
fi
|
||||
|
||||
if [ "$ESP32_FULL_BUILD" != "1" ] \
|
||||
&& requires_esp32_full_cli_profile "$env_name"; then
|
||||
if [ "$BUILD_PROFILE_EXPLICIT" = "1" ] \
|
||||
&& [ "${BUILD_PROFILE_OVERRIDE,,}" = "standard" ]; then
|
||||
echo "Target ${env_name} requires the expanded FULL profile to retain its complete CLI; refusing an explicit standard build."
|
||||
return 1
|
||||
fi
|
||||
ESP32_FULL_BUILD=1
|
||||
if [ "$FIRMWARE_FILENAME_INFIX" = "logging" ]; then
|
||||
FIRMWARE_FILENAME_INFIX="full-logging"
|
||||
@@ -2943,6 +3217,12 @@ build_firmware() {
|
||||
echo "Promoting ${env_name} to FULL so the complete CLI is retained."
|
||||
fi
|
||||
|
||||
if [ "$ESP32_FULL_BUILD" = "1" ] \
|
||||
|| is_companion_radio_full_target "$env_name"; then
|
||||
BUILD_PROFILE_FOR_TARGET="full"
|
||||
fi
|
||||
echo "Effective feature profile for ${env_name}: ${BUILD_PROFILE_FOR_TARGET}"
|
||||
|
||||
commit_hash=$(git rev-parse --short HEAD)
|
||||
firmware_build_date=$(date -u '+%d-%b-%Y')
|
||||
firmware_build_epoch=$(date -u '+%s')
|
||||
@@ -2958,7 +3238,8 @@ build_firmware() {
|
||||
fi
|
||||
|
||||
firmware_version_string="${firmware_version}-${commit_hash}"
|
||||
firmware_filename=$(get_firmware_filename "$env_name" "$firmware_version_string")
|
||||
firmware_filename=$(get_firmware_filename \
|
||||
"${FIRMWARE_OUTPUT_ENV_NAME:-$env_name}" "$firmware_version_string")
|
||||
|
||||
# OTA target id = sha2-256:4(env_name) as a little-endian uint32 (matches tools/mota target_id_for_env
|
||||
# and the device's MainBoard::getOtaTargetId()). Only OTA-enabled profiles receive this identifier.
|
||||
@@ -3023,6 +3304,7 @@ build_firmware() {
|
||||
apply_mqtt_bridge_override
|
||||
disable_usb_logging_for_mqtt "$env_name"
|
||||
apply_lora_ota_override "$env_name"
|
||||
apply_logical_ota_tuning_flags "$env_name" "$pio_env_name"
|
||||
apply_companion_radio_full_profile "$env_name" "$pio_env_name"
|
||||
apply_nrf52_lora_ota_build_recipe "$env_name" "$pio_env_name"
|
||||
apply_esp32_lora_ota_size_profile "$env_name"
|
||||
@@ -3033,6 +3315,7 @@ build_firmware() {
|
||||
apply_lora_ota_no_external_sensors_profile "$env_name"
|
||||
apply_radio_overrides
|
||||
apply_firmware_profile_overrides
|
||||
declare_build_capability_contract "$env_name" "$env_platform"
|
||||
|
||||
if [ "$ESP32_FULL_BUILD" = "1" ] || is_esp32_companion_radio_full_target "$env_name"; then
|
||||
export MESHCORE_ESP32_FULL_BUILD=1
|
||||
@@ -3041,20 +3324,16 @@ build_firmware() {
|
||||
else
|
||||
unset MESHCORE_COMPANION_RADIO_FULL
|
||||
fi
|
||||
target_extra_scripts=$original_platformio_extra_scripts
|
||||
if [[ "$target_extra_scripts" != *"scripts/esp32_full_partition.py"* ]]; then
|
||||
if [ -n "$target_extra_scripts" ]; then
|
||||
target_extra_scripts+=$'\n'
|
||||
fi
|
||||
target_extra_scripts+="pre:scripts/esp32_full_partition.py"
|
||||
if ! pio_env_option_contains "$pio_env_name" extra_scripts \
|
||||
"scripts/esp32_full_partition.py"; then
|
||||
append_platformio_extra_script "pre:scripts/esp32_full_partition.py"
|
||||
fi
|
||||
export PLATFORMIO_EXTRA_SCRIPTS="$target_extra_scripts"
|
||||
else
|
||||
unset MESHCORE_ESP32_FULL_BUILD
|
||||
unset MESHCORE_COMPANION_RADIO_FULL
|
||||
fi
|
||||
|
||||
print_build_flags "$env_name"
|
||||
print_build_flags "$pio_env_name" "$env_name"
|
||||
build_status=0
|
||||
if [ "$env_platform" = "ESP32_PLATFORM" ]; then
|
||||
# Official ESP32 and pioarduino builds share global package names even
|
||||
@@ -3086,8 +3365,17 @@ build_firmware() {
|
||||
pio_run_args+=(-t mergebin)
|
||||
fi
|
||||
if [ "$build_status" -eq 0 ]; then
|
||||
pio "${pio_run_args[@]}"
|
||||
build_status=$?
|
||||
local build_output_dir="${PIO_BUILD_DIR_OVERRIDE:-${PLATFORMIO_BUILD_DIR:-.pio/build}}/${pio_env_name}"
|
||||
local build_output_log="${build_output_dir}/.meshcore-build-output.log"
|
||||
mkdir -p -- "$build_output_dir"
|
||||
pio "${pio_run_args[@]}" 2>&1 | tee "$build_output_log"
|
||||
build_status=${PIPESTATUS[0]}
|
||||
if [ "$build_status" -ne 0 ] \
|
||||
&& grep -Eiq \
|
||||
'will not fit in region|region .+ overflowed by|section .+ will not fit|sketch too big|program size is greater than maximum|exceed(s|ing).*(flash|partition|app)' \
|
||||
"$build_output_log"; then
|
||||
build_status=42
|
||||
fi
|
||||
fi
|
||||
if [ "$build_status" -eq 0 ]; then
|
||||
collect_build_artifacts "$env_name" "$env_platform" "$pio_env_name" "$firmware_filename"
|
||||
@@ -3102,6 +3390,7 @@ build_firmware() {
|
||||
unset MESHCORE_ESP32_FULL_BUILD
|
||||
unset MESHCORE_COMPANION_RADIO_FULL
|
||||
unset MESHCORE_NRF52_INTERNAL_BOOTLOADER_UPDATE
|
||||
unset MESHCORE_FORCE_LORA_OTA
|
||||
if [ "$had_platformio_build_unflags" -eq 1 ]; then
|
||||
export PLATFORMIO_BUILD_UNFLAGS="$original_platformio_build_unflags"
|
||||
else
|
||||
@@ -3511,6 +3800,114 @@ resolve_command_targets() {
|
||||
fi
|
||||
}
|
||||
|
||||
configure_effective_build_profile() {
|
||||
local command_name=$1
|
||||
local target=""
|
||||
local reduced_target=""
|
||||
|
||||
if [ "${#RESOLVED_BUILD_TARGETS[@]}" -eq 1 ]; then
|
||||
target=${RESOLVED_BUILD_TARGETS[0]}
|
||||
fi
|
||||
|
||||
AUTO_PREFER_FULL_BUILD=0
|
||||
AUTO_COMPLETE_FIRST_PASS=0
|
||||
AUTO_REDUCED_FALLBACK_TARGET=""
|
||||
AUTO_PUBLISH_REDUCED_SECOND_PASS=0
|
||||
|
||||
case "${BUILD_PROFILE_OVERRIDE,,}" in
|
||||
auto|standard|full) ;;
|
||||
*)
|
||||
echo "Invalid build profile: ${BUILD_PROFILE_OVERRIDE} (use auto, standard, or full)"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$SINGLE_TARGET_FULL_BUILD" = "1" ]; then
|
||||
BUILD_PROFILE_EFFECTIVE="full"
|
||||
elif is_automatic_profile_command "$command_name"; then
|
||||
if [ "$BUILD_PROFILE_EXPLICIT" = "1" ] \
|
||||
&& [ "${BUILD_PROFILE_OVERRIDE,,}" != "auto" ]; then
|
||||
echo "${command_name} manages standard and FULL profiles itself; do not combine it with --build-profile ${BUILD_PROFILE_OVERRIDE}."
|
||||
return 1
|
||||
fi
|
||||
BUILD_PROFILE_EFFECTIVE="standard"
|
||||
else
|
||||
case "${BUILD_PROFILE_OVERRIDE,,}" in
|
||||
auto)
|
||||
if [ "$command_name" = "build-firmware" ] \
|
||||
&& [ "${#RESOLVED_BUILD_TARGETS[@]}" -eq 1 ]; then
|
||||
if is_companion_radio_full_target "$target"; then
|
||||
BUILD_PROFILE_EFFECTIVE="full"
|
||||
elif supports_esp32_full_build "$target"; then
|
||||
BUILD_PROFILE_EFFECTIVE="full"
|
||||
AUTO_PREFER_FULL_BUILD=1
|
||||
AUTO_REDUCED_FALLBACK_TARGET=$(get_reduced_lora_ota_target "$target" || true)
|
||||
elif is_lora_ota_no_external_sensors_target "$target"; then
|
||||
# An explicitly selected reduced target means exactly what its
|
||||
# name says; do not silently put omitted sensors back into it.
|
||||
BUILD_PROFILE_EFFECTIVE="standard"
|
||||
elif reduced_target=$(get_reduced_lora_ota_target "$target"); then
|
||||
RESOLVED_BUILD_TARGETS[0]=$reduced_target
|
||||
BUILD_PROFILE_EFFECTIVE="auto"
|
||||
AUTO_COMPLETE_FIRST_PASS=1
|
||||
AUTO_REDUCED_FALLBACK_TARGET=$reduced_target
|
||||
if [ "${PIO_ENV_PLATFORM_BY_NAME[$target]:-}" = "NRF52_PLATFORM" ] \
|
||||
&& [ "${PIO_ENV_SD_OTA_BY_NAME[$reduced_target]:-0}" = "0" ] \
|
||||
&& [ "${PIO_ENV_QSPI_OTA_BY_NAME[$reduced_target]:-0}" = "0" ]; then
|
||||
AUTO_PUBLISH_REDUCED_SECOND_PASS=1
|
||||
fi
|
||||
echo "Auto pass 1 selected: complete LoRa OTA for ${reduced_target}; reductions are deferred unless it is too large."
|
||||
else
|
||||
# Companion and non-repeater targets without an expanded recipe
|
||||
# keep every capability declared by their target and are checked
|
||||
# against the current partition.
|
||||
BUILD_PROFILE_EFFECTIVE="auto"
|
||||
fi
|
||||
else
|
||||
# Canonical and matching builds are release-oriented. Preserve their
|
||||
# deployed partition contract unless FULL was explicitly requested.
|
||||
BUILD_PROFILE_EFFECTIVE="standard"
|
||||
fi
|
||||
;;
|
||||
standard)
|
||||
BUILD_PROFILE_EFFECTIVE="standard"
|
||||
;;
|
||||
full)
|
||||
if [ "$command_name" != "build-firmware" ] \
|
||||
|| [ "${#RESOLVED_BUILD_TARGETS[@]}" -ne 1 ]; then
|
||||
echo "--build-profile full requires exactly one build-firmware target; use a build-full-* command for a matrix."
|
||||
return 1
|
||||
fi
|
||||
if is_companion_radio_full_target "$target"; then
|
||||
BUILD_PROFILE_EFFECTIVE="full"
|
||||
elif supports_esp32_full_build "$target"; then
|
||||
BUILD_PROFILE_EFFECTIVE="full"
|
||||
SINGLE_TARGET_FULL_BUILD=1
|
||||
else
|
||||
echo "Target ${target} has no expanded FULL partition profile. Use --build-profile auto or standard."
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
case "$BUILD_PROFILE_EFFECTIVE" in
|
||||
auto)
|
||||
echo "Build profile: auto (keep target capabilities; enforce its current partition)."
|
||||
;;
|
||||
standard)
|
||||
echo "Build profile: standard (preserve the portable/deployed partition contract; documented reductions may apply)."
|
||||
;;
|
||||
full)
|
||||
if [ "${PIO_ENV_PLATFORM_BY_NAME[$target]:-}" = "ESP32_PLATFORM" ]; then
|
||||
echo "Build profile: full (expanded partition; a matching merged image is required when the partition table changes)."
|
||||
else
|
||||
echo "Build profile: full (feature-complete named target in its existing application layout)."
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
prepare_output_dir() {
|
||||
local output_dir="$OUTPUT_DIR"
|
||||
|
||||
@@ -4103,8 +4500,104 @@ validate_command() {
|
||||
esac
|
||||
}
|
||||
|
||||
run_auto_two_pass_build() {
|
||||
local target=$1
|
||||
local fallback_target=$AUTO_REDUCED_FALLBACK_TARGET
|
||||
local original_esp32_full_build=$ESP32_FULL_BUILD
|
||||
local original_build_profile_effective=$BUILD_PROFILE_EFFECTIVE
|
||||
local original_firmware_filename_infix=$FIRMWARE_FILENAME_INFIX
|
||||
local original_skip_declared_reductions=$SKIP_DECLARED_REDUCTIONS
|
||||
local original_complete_ota_first_pass=$COMPLETE_OTA_FIRST_PASS
|
||||
local original_firmware_output_env_name=$FIRMWARE_OUTPUT_ENV_NAME
|
||||
local build_status=0
|
||||
|
||||
if [ "$AUTO_PREFER_FULL_BUILD" = "1" ]; then
|
||||
ESP32_FULL_BUILD=1
|
||||
BUILD_PROFILE_EFFECTIVE="full"
|
||||
FIRMWARE_FILENAME_INFIX="full"
|
||||
SKIP_DECLARED_REDUCTIONS=0
|
||||
COMPLETE_OTA_FIRST_PASS=0
|
||||
FIRMWARE_OUTPUT_ENV_NAME=""
|
||||
echo "Auto pass 1/2: building the complete expanded-partition profile for ${target}."
|
||||
else
|
||||
ESP32_FULL_BUILD=0
|
||||
BUILD_PROFILE_EFFECTIVE="auto"
|
||||
FIRMWARE_FILENAME_INFIX="full-ota"
|
||||
SKIP_DECLARED_REDUCTIONS=1
|
||||
COMPLETE_OTA_FIRST_PASS=1
|
||||
if [ "$AUTO_PUBLISH_REDUCED_SECOND_PASS" = "1" ]; then
|
||||
FIRMWARE_OUTPUT_ENV_NAME=${PIO_ENV_COMPLETE_OTA_BASE_BY_NAME[$target]:-${target%_lora_ota_no_external_sensors}}
|
||||
else
|
||||
FIRMWARE_OUTPUT_ENV_NAME=""
|
||||
fi
|
||||
echo "Auto pass 1/2: building complete LoRa OTA for ${target} without declared feature reductions."
|
||||
fi
|
||||
|
||||
if build_firmware "$target"; then
|
||||
build_status=0
|
||||
else
|
||||
build_status=$?
|
||||
fi
|
||||
|
||||
if [ "$build_status" -eq 42 ]; then
|
||||
if [ -z "$fallback_target" ]; then
|
||||
echo "The complete image is too large and ${target} has no supported reduced LoRa OTA recipe."
|
||||
build_status=1
|
||||
else
|
||||
echo "Auto pass 1 exceeded measured flash/partition capacity; pass 2 is rebuilding ${fallback_target} with the standard reduced LoRa OTA recipe."
|
||||
ESP32_FULL_BUILD=0
|
||||
BUILD_PROFILE_EFFECTIVE="standard"
|
||||
FIRMWARE_FILENAME_INFIX="reduced-ota"
|
||||
SKIP_DECLARED_REDUCTIONS=0
|
||||
COMPLETE_OTA_FIRST_PASS=0
|
||||
FIRMWARE_OUTPUT_ENV_NAME=""
|
||||
if build_firmware "$fallback_target"; then
|
||||
build_status=0
|
||||
else
|
||||
build_status=$?
|
||||
fi
|
||||
if [ "$build_status" -eq 0 ]; then
|
||||
echo "Auto pass 2 succeeded: ${fallback_target} (reduced LoRa OTA)."
|
||||
fi
|
||||
fi
|
||||
elif [ "$build_status" -ne 0 ]; then
|
||||
echo "Auto pass 1 failed for a non-size reason; refusing to hide the compiler or capability failure with a reduced build."
|
||||
elif [ "$AUTO_PUBLISH_REDUCED_SECOND_PASS" = "1" ] \
|
||||
&& [ -n "$fallback_target" ]; then
|
||||
echo "Auto pass 1 succeeded. Internal-flash nRF52 repeater policy also publishes a reduced LoRa OTA image with more delta-staging headroom."
|
||||
ESP32_FULL_BUILD=0
|
||||
BUILD_PROFILE_EFFECTIVE="standard"
|
||||
FIRMWARE_FILENAME_INFIX="reduced-ota"
|
||||
SKIP_DECLARED_REDUCTIONS=0
|
||||
COMPLETE_OTA_FIRST_PASS=0
|
||||
FIRMWARE_OUTPUT_ENV_NAME=""
|
||||
if build_firmware "$fallback_target"; then
|
||||
build_status=0
|
||||
echo "Auto pass 2 succeeded: ${fallback_target} (reduced LoRa OTA)."
|
||||
else
|
||||
build_status=$?
|
||||
fi
|
||||
else
|
||||
echo "Auto pass 1 succeeded; no reductions were needed."
|
||||
fi
|
||||
|
||||
ESP32_FULL_BUILD=$original_esp32_full_build
|
||||
BUILD_PROFILE_EFFECTIVE=$original_build_profile_effective
|
||||
FIRMWARE_FILENAME_INFIX=$original_firmware_filename_infix
|
||||
SKIP_DECLARED_REDUCTIONS=$original_skip_declared_reductions
|
||||
COMPLETE_OTA_FIRST_PASS=$original_complete_ota_first_pass
|
||||
FIRMWARE_OUTPUT_ENV_NAME=$original_firmware_output_env_name
|
||||
return "$build_status"
|
||||
}
|
||||
|
||||
run_command() {
|
||||
# All build commands share execution after validation resolves their target list.
|
||||
if [ "$AUTO_PREFER_FULL_BUILD" = "1" ] \
|
||||
|| [ "$AUTO_COMPLETE_FIRST_PASS" = "1" ]; then
|
||||
run_auto_two_pass_build "${RESOLVED_BUILD_TARGETS[0]}"
|
||||
return $?
|
||||
fi
|
||||
|
||||
if [ "$SINGLE_TARGET_FULL_BUILD" = "1" ]; then
|
||||
run_full_esp32_build_targets "all" "${RESOLVED_BUILD_TARGETS[@]}"
|
||||
return $?
|
||||
@@ -4214,6 +4707,9 @@ main() {
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if ! configure_effective_build_profile "$1"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
refresh_firmware_version_tags
|
||||
prompt_for_resolved_firmware_version
|
||||
|
||||
@@ -12,6 +12,36 @@ page describes the commands intentionally omitted or limited by build profile.
|
||||
The [CLI Command Availability Matrix](cli_command_availability.md) expands this
|
||||
summary into separate nRF52 and ESP32 command tables.
|
||||
|
||||
## Selecting a build profile
|
||||
|
||||
The runtime settings profile and the feature profile are separate switches:
|
||||
|
||||
```bash
|
||||
bash build.sh build-firmware RAK_3401_repeater \
|
||||
--profile cascade \
|
||||
--build-profile auto
|
||||
```
|
||||
|
||||
`--profile default|cascade` selects saved-setting defaults. It does not select
|
||||
which code is linked. `--build-profile auto|standard|full` controls features
|
||||
and partition policy:
|
||||
|
||||
| Selection | Behavior |
|
||||
| --- | --- |
|
||||
| `auto` | For one explicit target, pass 1 builds the complete supported LoRa-OTA-capable recipe. ESP32 boards with a qualified expanded profile use it; other repeaters attempt the complete recipe in their current application region. A measured flash/partition overflow starts the standard `no_external_sensors` LoRa OTA pass. Internal-flash nRF52 repeaters publish that reduced pass even when the complete image fits, because the smaller running image leaves more room to stage a delta; matched QSPI/SD repeaters do not need the redundant artifact. Compiler errors and missing-capability checks never trigger or conceal a reduced build. Canonical bulk commands keep their established standard partition contract. |
|
||||
| `standard` | Immediately uses the deployed/portable partition contract and its documented reductions. This is useful when the operator already knows the expanded or complete image is unsuitable. |
|
||||
| `full` | Requires a qualified ESP32 expanded-partition target (or an explicitly named Full Companion). Install a matching merged image when this changes the partition table. |
|
||||
|
||||
Successful builds also emit
|
||||
`<firmware>.capabilities.json`. The sidecar records the effective profile,
|
||||
logical OTA target, actual PlatformIO base, artifact name target, promised
|
||||
capabilities, and every reduction selected by the script. The build
|
||||
fails if a promised linked marker is absent. Current invariants include
|
||||
`retry.preset` for repeater/room-server roles, WebConfig for the Indicator and
|
||||
ESP32 Full Companion, TempRadio/mOTA controls for Full Companion, and the
|
||||
advanced flood-rule engine for Full room servers. Resume mode will not accept
|
||||
an old or failed artifact without a verified sidecar.
|
||||
|
||||
## Role comes first
|
||||
|
||||
| Role | Text administration CLI |
|
||||
@@ -35,9 +65,10 @@ retain 50 because their MQTT discovery tables are constrained by internal DRAM.
|
||||
|
||||
| Build/profile | Command availability |
|
||||
|---|---|
|
||||
| Standard non-MQTT repeater or room server | Keeps the normal role CLI. Size-constrained ESP32 artifacts can omit WebConfig and browser WiFi OTA, so their WebConfig/WiFi commands are unavailable. |
|
||||
| Standard non-MQTT repeater or room server | Keeps the normal role CLI. The explicitly selected portable policy can omit WebConfig and browser WiFi OTA, so those commands are unavailable and the omission is recorded in the capability manifest. |
|
||||
| Standard logging | Logging does not remove commands by itself. It has the same CLI as the selected role/profile and adds compiled logging behavior. CommonCLI roles persist `get/set usb.logging`. ESP32 roles covered by unified FULL and nRF52 Companions covered by dual-CDC Full Companion are not duplicated here. |
|
||||
| LoRa-OTA (`-ota-`) | LoRa OTA adds the `ota ...` commands; it does not otherwise reduce the role CLI. ESP32 `no_external_sensors` artifacts retain the compact browser WiFi uploader, the complete CLI, and a 254-entry neighbor table. |
|
||||
| Internal-flash nRF52 repeater auto pair | `full-ota` retains the board's external-sensor drivers; `reduced-ota` omits only the declared external sensors to leave additional internal-flash staging room. Both carry the same stable OTA target identity and both are checked for `ota ...` and `retry.preset`. |
|
||||
| ESP32 MQTT observer or ESP-NOW bridge | Always uses the expanded FULL partition profile. The build never substitutes a reduced CLI to fit the legacy application slot. |
|
||||
| FULL ESP32 USB + WiFi | Uses the matching MQTT target with packet logging on, verbose debug off, and the complete command surface supported by that role and hardware. `get/set logging.output off\|usb\|wifi\|both` selects and persists the active output paths. |
|
||||
| FULL ESP32 logging fallback | Uses the matching non-MQTT target only when no WiFi MQTT sibling exists, with debug and packet logging enabled and the complete command surface supported by that role and hardware. Its persistent USB gate also covers output-off operation, avoiding a second FULL ESP-NOW image. |
|
||||
@@ -83,7 +114,9 @@ partitions, RAM use, active transports, and power behavior.
|
||||
|
||||
## Complete CLI policy
|
||||
|
||||
The compact ESP32 CLI has been removed. MQTT observers and ESP-NOW bridges are
|
||||
The compact ESP32 CLI has been removed. `retry.preset` is a checked invariant
|
||||
for every repeater and room-server artifact, not a FULL-only command. MQTT
|
||||
observers and ESP-NOW bridges are
|
||||
automatically promoted to FULL builds with expanded partitions rather than
|
||||
dropping administration commands. This keeps `tempradio`, LoRa OTA, power
|
||||
saving, RXPS, logging, statistics, sensor, ACL, routing, and advanced radio
|
||||
@@ -140,4 +173,6 @@ does not exist on that target:
|
||||
- `uf2reset` applies only to nRF52.
|
||||
|
||||
When diagnosing an unavailable command, check the role first, then the filename
|
||||
profile, and finally the target's compiled hardware features.
|
||||
profile, then the target's compiled hardware features and its adjacent
|
||||
`.capabilities.json` file. A current artifact that promises the command but
|
||||
does not contain its linked marker is rejected during the build.
|
||||
@@ -156,6 +156,16 @@ after WiFi/WebConfig so their peak startup allocations do not overlap.
|
||||
|
||||
Artifacts are written to `out/` by default.
|
||||
|
||||
Full Companion behavior is selected with independent capability macros for
|
||||
TempRadio, the OTA CLI, the TCP terminal, USB folder seeding, and memory
|
||||
diagnostics. The legacy `COMPANION_RADIO_FULL` flag remains an input for older
|
||||
target recipes, but application behavior no longer uses that umbrella as an
|
||||
unrelated compile guard. In particular, ESP32 WiFi/WebConfig terminal controls
|
||||
are compiled from their actual WiFi/WebConfig capability, and BLE is always
|
||||
started before WiFi on any combined ESP32 WiFi+BLE Companion to avoid heap
|
||||
fragmentation. The build's capability sidecar verifies the Full controls after
|
||||
linking.
|
||||
|
||||
On 4 MB ESP32 boards, the full target uses a single 3 MB application partition
|
||||
so WiFi, BLE, WebConfig, and source-only mOTA fit together. The T-Beam 1W Full
|
||||
Companion uses that same LilyGo factory-compatible boot layout on its 16 MB
|
||||
|
||||
@@ -102,6 +102,15 @@ ota`), the complete CLI, and a 254-entry neighbor table. RP2040 and STM32
|
||||
repeaters do not currently have a safe self-apply path, but current repeater
|
||||
firmware can still relay OTA packets opaquely during TempRadio.
|
||||
|
||||
For one explicitly selected internal-flash nRF52 repeater, the default
|
||||
`--build-profile auto` run emits two install-capable choices. The
|
||||
`*-full-ota-*` artifact retains external sensors, while the
|
||||
`*_lora_ota_no_external_sensors-reduced-ota-*` artifact trades only those
|
||||
optional drivers for more delta-staging space. They retain the same OTA target
|
||||
identity so update tooling does not confuse the optimization choice with a
|
||||
different board. QSPI- and SD-backed repeaters emit only the complete choice
|
||||
unless a measured size failure requires a fallback.
|
||||
|
||||
nRF52 `-ota-` siblings are compiled with size optimization instead of the Adafruit platform's default
|
||||
speed optimization. This prevents the retained software Ed25519 fallback from expanding beyond the fixed
|
||||
in-place workspace; CC310 hardware crypto, hardware RNG mixing, telemetry history, and board-native features
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
// Features are selected independently so adding a transport or changing an
|
||||
// image-size profile cannot accidentally remove unrelated Companion behavior.
|
||||
// Keep the legacy umbrella as a compatibility input for existing target files;
|
||||
// application code must use the capability macros below.
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#ifndef COMPANION_FEATURE_TEMP_RADIO
|
||||
#define COMPANION_FEATURE_TEMP_RADIO 1
|
||||
#endif
|
||||
#ifndef COMPANION_FEATURE_OTA_CLI
|
||||
#define COMPANION_FEATURE_OTA_CLI 1
|
||||
#endif
|
||||
#ifndef COMPANION_FEATURE_NETWORK_TERMINAL
|
||||
#define COMPANION_FEATURE_NETWORK_TERMINAL 1
|
||||
#endif
|
||||
#ifndef COMPANION_FEATURE_MEMORY_DIAGNOSTICS
|
||||
#define COMPANION_FEATURE_MEMORY_DIAGNOSTICS 1
|
||||
#endif
|
||||
#if defined(MESH_DUAL_CDC_LOGGING)
|
||||
#ifndef COMPANION_FEATURE_DEDICATED_USB_LOGGING
|
||||
#define COMPANION_FEATURE_DEDICATED_USB_LOGGING 1
|
||||
#endif
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM) && defined(OTA_FOLDER_SERIAL)
|
||||
#ifndef COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
#define COMPANION_FEATURE_USB_MOTA_SOURCE 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef COMPANION_FEATURE_TEMP_RADIO
|
||||
#define COMPANION_FEATURE_TEMP_RADIO 0
|
||||
#endif
|
||||
#ifndef COMPANION_FEATURE_OTA_CLI
|
||||
#define COMPANION_FEATURE_OTA_CLI 0
|
||||
#endif
|
||||
#ifndef COMPANION_FEATURE_NETWORK_TERMINAL
|
||||
#define COMPANION_FEATURE_NETWORK_TERMINAL 0
|
||||
#endif
|
||||
#ifndef COMPANION_FEATURE_MEMORY_DIAGNOSTICS
|
||||
#define COMPANION_FEATURE_MEMORY_DIAGNOSTICS 0
|
||||
#endif
|
||||
#ifndef COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
#define COMPANION_FEATURE_USB_MOTA_SOURCE 0
|
||||
#endif
|
||||
#ifndef COMPANION_FEATURE_DEDICATED_USB_LOGGING
|
||||
#define COMPANION_FEATURE_DEDICATED_USB_LOGGING 0
|
||||
#endif
|
||||
|
||||
#if COMPANION_FEATURE_NETWORK_TERMINAL && !defined(ENABLE_USB_INTERFACE)
|
||||
#error "COMPANION_FEATURE_NETWORK_TERMINAL requires ENABLE_USB_INTERFACE"
|
||||
#endif
|
||||
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE \
|
||||
&& !(defined(NRF52_PLATFORM) && defined(OTA_FOLDER_SERIAL) \
|
||||
&& defined(ENABLE_USB_INTERFACE))
|
||||
#error "COMPANION_FEATURE_USB_MOTA_SOURCE requires nRF52 USB folder seeding"
|
||||
#endif
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <helpers/TracePathHelpers.h>
|
||||
#endif
|
||||
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_OTA_CLI
|
||||
#include <helpers/ota/OtaCli.h>
|
||||
#endif
|
||||
|
||||
@@ -430,7 +430,7 @@ float MyMesh::getAirtimeBudgetFactor() const {
|
||||
// TempRadio is a short-lived, explicitly coordinated OTA channel. Let its
|
||||
// primary transfer use the full dispatcher budget; the persisted public
|
||||
// channel duty factor becomes authoritative again as soon as TempRadio ends.
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
if (isTempRadioActive()) return 0.0f;
|
||||
#endif
|
||||
return _prefs.airtime_factor;
|
||||
@@ -1413,7 +1413,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
_usb_logging_reboot_at = 0;
|
||||
#endif
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
_temp_radio_set_at = 0;
|
||||
_temp_radio_revert_at = 0;
|
||||
_temp_radio_retry_at = 0;
|
||||
@@ -1474,7 +1474,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_prefs.powersaving_enabled = 1;
|
||||
_prefs.powersaving_policy_version = 0;
|
||||
_prefs.wifi_enabled = 1;
|
||||
#if defined(COMPANION_RADIO_FULL) && defined(MESH_DUAL_CDC_LOGGING)
|
||||
#if defined(MESH_DUAL_CDC_LOGGING)
|
||||
// Keep the primary CDC stream exclusively framed unless the owner opts in
|
||||
// to the separate plaintext logging interface and reboots.
|
||||
_prefs.usb_logging_enabled = 0;
|
||||
@@ -1800,7 +1800,7 @@ void MyMesh::servicePendingRadioParamApply() {
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
static bool isFullCompanionBandwidth(float bw) {
|
||||
static const float supported[] = {
|
||||
7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.7f,
|
||||
@@ -2005,7 +2005,8 @@ bool MyMesh::handleLocalControlCommand(const char* command, char* reply,
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO || COMPANION_FEATURE_OTA_CLI
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
if (strcmp(command, "tempradio") == 0) {
|
||||
if (_temp_radio_set_at) {
|
||||
snprintf(reply, reply_size, "TempRadio pending: %.3f,%.2f,%u,%u",
|
||||
@@ -2048,7 +2049,9 @@ bool MyMesh::handleLocalControlCommand(const char* command, char* reply,
|
||||
scheduleNormalRadio(reply, reply_size);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if COMPANION_FEATURE_OTA_CLI
|
||||
if (strncmp(command, "ota", 3) == 0
|
||||
&& (command[3] == 0 || command[3] == ' ')) {
|
||||
char ota_reply[160] = {0};
|
||||
@@ -2056,12 +2059,13 @@ bool MyMesh::handleLocalControlCommand(const char* command, char* reply,
|
||||
snprintf(reply, reply_size, "%s", ota_reply);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
void MyMesh::serviceTempRadio() {
|
||||
const unsigned long now = _ms->getMillis();
|
||||
const bool retry_ready = !_temp_radio_retry_at
|
||||
@@ -3149,7 +3153,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
return;
|
||||
}
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
if (isTempRadioActive() || _temp_radio_set_at != 0
|
||||
|| _temp_radio_revert_at != 0) {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE);
|
||||
@@ -4217,7 +4221,7 @@ void MyMesh::resetTerminalSession() {
|
||||
|
||||
void MyMesh::printTerminalBanner(bool show_binary_stop) {
|
||||
Stream& output = terminalOutput();
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO || COMPANION_FEATURE_NETWORK_TERMINAL
|
||||
output.print("\r\n===== MeshCore Full Companion Terminal =====\r\n\r\n");
|
||||
#else
|
||||
output.print("\r\n===== MeshCore Companion Terminal =====\r\n\r\n");
|
||||
@@ -4247,7 +4251,7 @@ void MyMesh::exitTerminalMode() {
|
||||
if (_terminal_output == &Serial) _terminal_output = NULL;
|
||||
}
|
||||
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_NETWORK_TERMINAL
|
||||
bool MyMesh::enterNetworkTerminalMode(Stream& output) {
|
||||
if (_terminal_mode) return false;
|
||||
_terminal_output = &output;
|
||||
@@ -5059,7 +5063,7 @@ void MyMesh::handleTerminalCommand(char* command) {
|
||||
uint32_t effective_sleep_us = _prefs.rx_ps_sleep_us;
|
||||
uint8_t active_sf = _prefs.sf;
|
||||
float active_bw = _prefs.bw;
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
if (isTempRadioActive()) {
|
||||
active_sf = _temp_radio_sf;
|
||||
active_bw = _temp_radio_bw;
|
||||
@@ -5318,10 +5322,14 @@ void MyMesh::handleTerminalCommand(char* command) {
|
||||
terminalOutput().print(" public <text>\r\n");
|
||||
terminalOutput().print(" channels\r\n");
|
||||
terminalOutput().print(" channel <name-or-slot> <text>\r\n");
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO || COMPANION_FEATURE_OTA_CLI
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
terminalOutput().print(" tempradio [freq,bw,sf,cr,minutes]\r\n");
|
||||
terminalOutput().print(" normalradio\r\n");
|
||||
#endif
|
||||
#if COMPANION_FEATURE_OTA_CLI
|
||||
terminalOutput().print(" ota {status|ls|announce|folder|config|...}\r\n");
|
||||
#endif
|
||||
#endif
|
||||
terminalOutput().print(" ver\r\n");
|
||||
if (_terminal_mode) {
|
||||
@@ -5555,7 +5563,7 @@ void MyMesh::loop() {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
serviceTempRadio();
|
||||
#endif
|
||||
BaseChatMesh::loop();
|
||||
@@ -5568,7 +5576,7 @@ void MyMesh::loop() {
|
||||
serviceTerminalTrace();
|
||||
#endif
|
||||
if (!command_radio_apply_pending && saved_radio_apply_pending && !hasOutbound()
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
&& !_temp_radio_applied && _temp_radio_set_at == 0
|
||||
&& _temp_radio_revert_at == 0
|
||||
#endif
|
||||
@@ -5647,7 +5655,7 @@ bool MyMesh::hasPendingWork() const {
|
||||
|| (dirty_contacts_expiry != 0 && millisHasNowPassed(dirty_contacts_expiry))
|
||||
|| (emergency_client_repeat_packet != NULL
|
||||
&& millisHasNowPassed(emergency_client_repeat_send_at))
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
|| (_temp_radio_set_at != 0 && millisHasNowPassed(_temp_radio_set_at))
|
||||
|| (_temp_radio_revert_at != 0 && millisHasNowPassed(_temp_radio_revert_at))
|
||||
|| (_temp_radio_retry_at != 0 && millisHasNowPassed(_temp_radio_retry_at))
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <Arduino.h>
|
||||
#include <Mesh.h>
|
||||
#include "AbstractUITask.h"
|
||||
#include "CompanionFeatures.h"
|
||||
|
||||
/*------------ Frame Protocol --------------*/
|
||||
#define FIRMWARE_VER_CODE 13
|
||||
@@ -80,8 +81,8 @@
|
||||
#ifndef OFFLINE_QUEUE_SIZE
|
||||
#if defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM)
|
||||
#define OFFLINE_QUEUE_SIZE 512
|
||||
#elif defined(COMPANION_RADIO_FULL) || defined(ESP32_PLATFORM) \
|
||||
|| defined(NRF52_PLATFORM) || defined(RP2040_PLATFORM)
|
||||
#elif defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM) \
|
||||
|| defined(RP2040_PLATFORM)
|
||||
#define OFFLINE_QUEUE_SIZE 256
|
||||
#else
|
||||
#define OFFLINE_QUEUE_SIZE 16
|
||||
@@ -169,7 +170,7 @@ public:
|
||||
void exitTerminalMode();
|
||||
bool isTerminalMode() const { return _terminal_mode; }
|
||||
void handleTerminalCommand(char* command);
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_NETWORK_TERMINAL
|
||||
bool enterNetworkTerminalMode(Stream& output);
|
||||
void exitNetworkTerminalMode(Stream& output);
|
||||
bool isNetworkTerminalMode(const Stream& output) const;
|
||||
@@ -184,7 +185,7 @@ public:
|
||||
int getRecentlyHeard(AdvertPath dest[], int max_num);
|
||||
|
||||
protected:
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
bool isTempRadioActive() const override {
|
||||
return _temp_radio_applied && _temp_radio_revert_at != 0
|
||||
&& !millisHasNowPassed(_temp_radio_revert_at);
|
||||
@@ -357,7 +358,7 @@ private:
|
||||
void finishRadioParamApply(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t repeat);
|
||||
void cancelPendingRadioParamApply();
|
||||
void servicePendingRadioParamApply();
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
bool scheduleTempRadio(float freq, float bw, uint8_t sf, uint8_t cr,
|
||||
uint32_t timeout_mins, char* reply, size_t reply_size);
|
||||
void scheduleNormalRadio(char* reply, size_t reply_size);
|
||||
@@ -435,7 +436,7 @@ private:
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
unsigned long _usb_logging_reboot_at;
|
||||
#endif
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_TEMP_RADIO
|
||||
unsigned long _temp_radio_set_at;
|
||||
unsigned long _temp_radio_revert_at;
|
||||
unsigned long _temp_radio_retry_at;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#else
|
||||
#define COMPANION_BT_MODEM_SLEEP_AVAILABLE 0
|
||||
#endif
|
||||
#if defined(COMPANION_RADIO_FULL)
|
||||
#if COMPANION_FEATURE_MEMORY_DIAGNOSTICS
|
||||
#include <esp_heap_caps.h>
|
||||
#endif
|
||||
#endif
|
||||
@@ -80,7 +80,7 @@ MultiSerialInterface interface_manager;
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
static const char USB_TERMINAL_START_TOKEN[] = "+++MESHCORE-TERM-START";
|
||||
static const char USB_TERMINAL_STOP_TOKEN[] = "+++MESHCORE-TERM-STOP";
|
||||
#if defined(NRF52_PLATFORM) && defined(COMPANION_RADIO_FULL) && defined(OTA_FOLDER_SERIAL)
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
// motatool sends this command automatically when `serve --serial` opens the
|
||||
// port. In Binary mode it is an exact, idle-parser control sequence that
|
||||
// hands USB ownership to the host-backed mOTA source.
|
||||
@@ -291,7 +291,7 @@ static char usb_terminal_line[MAX_TRANS_UNIT * 2 + 32];
|
||||
static size_t usb_terminal_line_len = 0;
|
||||
static bool usb_terminal_discard_line = false;
|
||||
static bool usb_terminal_disconnect_armed = false;
|
||||
#if defined(NRF52_PLATFORM) && defined(COMPANION_RADIO_FULL) && defined(OTA_FOLDER_SERIAL)
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
static bool usb_mota_mode = false;
|
||||
static char usb_mota_line[32];
|
||||
static size_t usb_mota_line_len = 0;
|
||||
@@ -359,7 +359,7 @@ static void leaveUsbTerminalMode(bool acknowledge) {
|
||||
usb_terminal_disconnect_armed = false;
|
||||
}
|
||||
|
||||
#if defined(NRF52_PLATFORM) && defined(COMPANION_RADIO_FULL) && defined(OTA_FOLDER_SERIAL)
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
static void resetUsbMotaMode() {
|
||||
usb_mota_mode = false;
|
||||
usb_mota_line_len = 0;
|
||||
@@ -440,7 +440,7 @@ static void serviceUsbMota() {
|
||||
#endif
|
||||
|
||||
static void serviceUsbTerminal() {
|
||||
#if defined(NRF52_PLATFORM) && defined(COMPANION_RADIO_FULL) && defined(OTA_FOLDER_SERIAL)
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
if (usb_mota_mode) {
|
||||
serviceUsbMota();
|
||||
return;
|
||||
@@ -449,7 +449,7 @@ static void serviceUsbTerminal() {
|
||||
if (!the_mesh.isTerminalMode()) {
|
||||
if (usb_serial_interface.takeControlSequence()) {
|
||||
enterUsbTerminalMode();
|
||||
#if defined(NRF52_PLATFORM) && defined(COMPANION_RADIO_FULL) && defined(OTA_FOLDER_SERIAL)
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
} else if (usb_serial_interface.takeSecondaryControlSequence()) {
|
||||
enterUsbMotaMode();
|
||||
#endif
|
||||
@@ -746,7 +746,7 @@ void halt() {
|
||||
|
||||
static void ota_console_start() {
|
||||
ota_console_server.begin();
|
||||
#if defined(COMPANION_RADIO_FULL) && defined(ENABLE_USB_INTERFACE)
|
||||
#if COMPANION_FEATURE_NETWORK_TERMINAL
|
||||
WIFI_DEBUG_PRINTLN("Full Companion terminal listening on :%d (nc <ip> %d)",
|
||||
OTA_CONSOLE_TCP_PORT, OTA_CONSOLE_TCP_PORT);
|
||||
#else
|
||||
@@ -756,7 +756,7 @@ void halt() {
|
||||
}
|
||||
|
||||
static void ota_console_stop() {
|
||||
#if defined(COMPANION_RADIO_FULL) && defined(ENABLE_USB_INTERFACE)
|
||||
#if COMPANION_FEATURE_NETWORK_TERMINAL
|
||||
the_mesh.exitNetworkTerminalMode(ota_console_client);
|
||||
#endif
|
||||
if (ota_console_client) ota_console_client.stop();
|
||||
@@ -767,7 +767,7 @@ void halt() {
|
||||
|
||||
static void ota_console_loop() {
|
||||
if (!ota_console_client || !ota_console_client.connected()) {
|
||||
#if defined(COMPANION_RADIO_FULL) && defined(ENABLE_USB_INTERFACE)
|
||||
#if COMPANION_FEATURE_NETWORK_TERMINAL
|
||||
the_mesh.exitNetworkTerminalMode(ota_console_client);
|
||||
#endif
|
||||
WiFiClient c = ota_console_server.available();
|
||||
@@ -775,7 +775,7 @@ void halt() {
|
||||
ota_console_client = c;
|
||||
ota_console_clear_line();
|
||||
ota_console_discard_line = false;
|
||||
#if defined(COMPANION_RADIO_FULL) && defined(ENABLE_USB_INTERFACE)
|
||||
#if COMPANION_FEATURE_NETWORK_TERMINAL
|
||||
if (!the_mesh.enterNetworkTerminalMode(ota_console_client)) {
|
||||
ota_console_client.print(
|
||||
"ERROR: USB currently owns the Full Companion terminal\r\n");
|
||||
@@ -787,7 +787,7 @@ void halt() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
#if defined(COMPANION_RADIO_FULL) && defined(ENABLE_USB_INTERFACE)
|
||||
#if COMPANION_FEATURE_NETWORK_TERMINAL
|
||||
if (!the_mesh.isNetworkTerminalMode(ota_console_client)) {
|
||||
ota_console_client.print(
|
||||
"\r\nERROR: terminal ownership moved to USB; closing\r\n");
|
||||
@@ -809,7 +809,7 @@ void halt() {
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
if (ota_console_len == 0) continue; // ignore blanks / the CRLF pair
|
||||
ota_console_line[ota_console_len] = 0;
|
||||
#if defined(COMPANION_RADIO_FULL) && defined(ENABLE_USB_INTERFACE)
|
||||
#if COMPANION_FEATURE_NETWORK_TERMINAL
|
||||
if (strcmp(ota_console_line, "disconnect") == 0) {
|
||||
ota_console_client.print(" OK - disconnecting\r\n");
|
||||
the_mesh.exitNetworkTerminalMode(ota_console_client);
|
||||
@@ -1007,7 +1007,7 @@ void halt() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ESP32_PLATFORM) && defined(COMPANION_RADIO_FULL)
|
||||
#if defined(ESP32_PLATFORM) && COMPANION_FEATURE_MEMORY_DIAGNOSTICS
|
||||
static void logFullCompanionMemory(const char* stage) {
|
||||
mesh::usbLoggingPort().printf(
|
||||
"Full Companion memory %s: heap=%u largest_internal=%u psram_free=%u/%u offline_queue=%d\r\n",
|
||||
@@ -1152,15 +1152,14 @@ void setup() {
|
||||
loadCompanionWiFiCredentials();
|
||||
#endif
|
||||
|
||||
#if defined(ESP32_PLATFORM) && defined(COMPANION_RADIO_FULL)
|
||||
#if defined(ESP32_PLATFORM) && COMPANION_FEATURE_MEMORY_DIAGNOSTICS
|
||||
logFullCompanionMemory("before interfaces");
|
||||
#endif
|
||||
|
||||
// NimBLE must reserve its internal controller/host memory before WiFi starts
|
||||
// its AP and WebConfig services. Starting WiFi first can leave plenty of total
|
||||
// heap but no internal block large enough for nimble_port_init().
|
||||
#if defined(BLE_PIN_CODE) && defined(ESP32) \
|
||||
&& defined(COMPANION_RADIO_FULL) && defined(WIFI_SSID)
|
||||
#if defined(BLE_PIN_CODE) && defined(ESP32) && defined(WIFI_SSID)
|
||||
startCompanionBluetooth();
|
||||
#endif
|
||||
|
||||
@@ -1189,22 +1188,21 @@ void setup() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ESP32_PLATFORM) && defined(COMPANION_RADIO_FULL)
|
||||
#if defined(ESP32_PLATFORM) && COMPANION_FEATURE_MEMORY_DIAGNOSTICS
|
||||
logFullCompanionMemory("after WiFi start");
|
||||
#endif
|
||||
|
||||
// Full ESP32 WiFi+BLE companions started BLE above so its controller memory
|
||||
// could not be fragmented by WiFi. Other BLE companions start here.
|
||||
// ESP32 WiFi+BLE companions started BLE above so its controller memory could
|
||||
// not be fragmented by WiFi. Other BLE companions start here.
|
||||
#if defined(BLE_PIN_CODE)
|
||||
#if !(defined(ESP32) && defined(COMPANION_RADIO_FULL) \
|
||||
&& defined(WIFI_SSID))
|
||||
#if !(defined(ESP32) && defined(WIFI_SSID))
|
||||
startCompanionBluetooth();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// add usb interface
|
||||
#if defined(ENABLE_USB_INTERFACE)
|
||||
#if defined(NRF52_PLATFORM) && defined(COMPANION_RADIO_FULL) && defined(OTA_FOLDER_SERIAL)
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE
|
||||
usb_serial_interface.begin(Serial, USB_TERMINAL_START_TOKEN, USB_MOTA_START_TOKEN);
|
||||
#else
|
||||
usb_serial_interface.begin(Serial, USB_TERMINAL_START_TOKEN);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "FloodRuleEngine.h"
|
||||
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#include "RoomServerFeatures.h"
|
||||
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Mesh.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "MyMesh.h"
|
||||
#include <helpers/radiolib/RxBoostedGainDefaults.h>
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
#include <helpers/FloodFilterPolicy.h>
|
||||
#endif
|
||||
#include <helpers/radiolib/RXPowerSaving.h>
|
||||
@@ -371,7 +371,7 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
|
||||
return (int)((powf(_prefs.rx_delay_base, 0.85f - score) - 1.0f) * air_time);
|
||||
}
|
||||
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
bool MyMesh::evaluateFloodRuleTiming(const mesh::Packet* packet,
|
||||
bool& fast_track) {
|
||||
fast_track = false;
|
||||
@@ -530,14 +530,14 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
|
||||
_prefs.flood_max_advert)) {
|
||||
return false;
|
||||
}
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
if (flood_rules.shouldBlock(packet, recv_pkt_rule_match_mask,
|
||||
_ms->getMillis())) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
if (packet->isRouteFlood()) {
|
||||
if (recv_pkt_region == NULL && !recv_pkt_regionless_scope_set) {
|
||||
MESH_DEBUG_PRINTLN(
|
||||
@@ -564,7 +564,7 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
|
||||
}
|
||||
|
||||
mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) {
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
bool scope_changed = false;
|
||||
bool fast_track_scope_change = false;
|
||||
recv_pkt_regionless_scope_set = false;
|
||||
@@ -599,7 +599,7 @@ mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) {
|
||||
recv_pkt_region = NULL;
|
||||
}
|
||||
mesh::DispatcherAction action = Mesh::onRecvPacket(pkt);
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
if (scope_changed && action != ACTION_RELEASE
|
||||
&& action != ACTION_MANUAL_HOLD) {
|
||||
if (fast_track_scope_change) {
|
||||
@@ -1155,7 +1155,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
radio_apply_retry_at = 0;
|
||||
radio_apply_failures = 0;
|
||||
recv_pkt_region = NULL;
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
recv_pkt_rule_match_mask = 0;
|
||||
recv_pkt_regionless_scope_set = false;
|
||||
#endif
|
||||
@@ -1261,7 +1261,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
acl.load(_fs, self_id);
|
||||
region_map.load(_fs);
|
||||
_clock_sync.begin(_fs);
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
flood_rules.begin(_fs, ®ion_map);
|
||||
#endif
|
||||
|
||||
@@ -2115,7 +2115,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
||||
snprintf(reply, MAX_POST_TEXT_LEN, "OK");
|
||||
}
|
||||
}
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
else if (flood_rules.handleCommand(command, reply)) {
|
||||
// handled by the FULL-profile persistent flood rule engine
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#include <Mesh.h>
|
||||
#include "RoomServerFeatures.h"
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include <InternalFileSystem.h>
|
||||
@@ -139,7 +140,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks,
|
||||
NodePrefs _prefs;
|
||||
TransportKeyStore key_store;
|
||||
RegionMap region_map, temp_map;
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
FloodRuleEngine flood_rules;
|
||||
uint32_t recv_pkt_rule_match_mask;
|
||||
bool recv_pkt_regionless_scope_set;
|
||||
@@ -255,7 +256,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks,
|
||||
mesh::Packet* createSelfAdvert();
|
||||
File openAppend(const char* fname);
|
||||
int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len);
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
bool evaluateFloodRuleTiming(const mesh::Packet* packet,
|
||||
bool& fast_track);
|
||||
bool isLooped(const mesh::Packet* packet,
|
||||
@@ -278,7 +279,7 @@ protected:
|
||||
void logTxFail(mesh::Packet* pkt, int len) override;
|
||||
|
||||
int calcRxDelay(float score, uint32_t air_time) const override;
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
int calcRxDelayForPacket(const mesh::Packet* packet, float score,
|
||||
uint32_t air_time) override;
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
// Keep role capabilities independent from partition/profile labels. Existing
|
||||
// FULL recipes remain compatible, while build tooling can now select this
|
||||
// feature directly and verify that its CLI survived the link.
|
||||
#ifndef MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#define MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE 1
|
||||
#else
|
||||
#define MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE 0
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify promised firmware capabilities and emit a machine-readable manifest."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--image", required=True, type=Path)
|
||||
parser.add_argument("--output", required=True, type=Path)
|
||||
parser.add_argument("--target", required=True)
|
||||
parser.add_argument("--artifact-target")
|
||||
parser.add_argument("--platformio-env")
|
||||
parser.add_argument("--platform", required=True)
|
||||
parser.add_argument("--build-profile", required=True)
|
||||
parser.add_argument("--capability", action="append", default=[])
|
||||
parser.add_argument("--reduction", action="append", default=[])
|
||||
parser.add_argument(
|
||||
"--expect",
|
||||
action="append",
|
||||
default=[],
|
||||
metavar="CAPABILITY=TEXT",
|
||||
help="Require TEXT to be present in the linked image.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def stable_unique(values: list[str]) -> list[str]:
|
||||
return list(dict.fromkeys(value for value in values if value))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
try:
|
||||
image = args.image.read_bytes()
|
||||
except OSError as exc:
|
||||
print(f"capability check: cannot read {args.image}: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
checks = []
|
||||
malformed = False
|
||||
for expectation in args.expect:
|
||||
if "=" not in expectation:
|
||||
print(
|
||||
f"capability check: malformed --expect {expectation!r}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
malformed = True
|
||||
continue
|
||||
capability, needle = expectation.split("=", 1)
|
||||
present = bool(needle) and needle.encode("utf-8") in image
|
||||
checks.append(
|
||||
{
|
||||
"capability": capability,
|
||||
"evidence": needle,
|
||||
"present": present,
|
||||
}
|
||||
)
|
||||
|
||||
capabilities = stable_unique(
|
||||
args.capability + [check["capability"] for check in checks]
|
||||
)
|
||||
missing = [check for check in checks if not check["present"]]
|
||||
manifest = {
|
||||
"schema_version": 1,
|
||||
"target": args.target,
|
||||
"artifact_target": args.artifact_target or args.target,
|
||||
"platformio_env": args.platformio_env or args.target,
|
||||
"platform": args.platform,
|
||||
"build_profile": args.build_profile,
|
||||
"capabilities": capabilities,
|
||||
"reductions": stable_unique(args.reduction),
|
||||
"verification": checks,
|
||||
"verified": not malformed and not missing,
|
||||
}
|
||||
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = args.output.with_name(f".{args.output.name}.{os.getpid()}.tmp")
|
||||
temporary.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n")
|
||||
temporary.replace(args.output)
|
||||
|
||||
if malformed:
|
||||
return 2
|
||||
if missing:
|
||||
for check in missing:
|
||||
print(
|
||||
"capability check failed: "
|
||||
f"{check['capability']} promised but {check['evidence']!r} "
|
||||
f"is absent from {args.image}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
print(
|
||||
f"Verified {len(checks)} capability marker(s); manifest: {args.output}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,17 @@
|
||||
"""Remove a legacy final OTA undefine before PlatformIO parses build flags."""
|
||||
|
||||
Import("env") # noqa: F821
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
if os.environ.get("MESHCORE_FORCE_LORA_OTA") == "1":
|
||||
cleaned = []
|
||||
for item in env.get("BUILD_FLAGS", []): # noqa: F821
|
||||
text = re.sub(r"(?:^|\s)-U\s*ENABLE_OTA(?=\s|$)", " ", str(item))
|
||||
text = " ".join(text.split())
|
||||
if text:
|
||||
cleaned.append(text)
|
||||
env.Replace(BUILD_FLAGS=cleaned) # noqa: F821
|
||||
print("LoRa OTA overlay: removed legacy -UENABLE_OTA before flag processing")
|
||||
@@ -6,8 +6,9 @@
|
||||
#include <new>
|
||||
|
||||
#if defined(MESH_DUAL_CDC_LOGGING)
|
||||
#if !defined(COMPANION_RADIO_FULL)
|
||||
#error "MESH_DUAL_CDC_LOGGING is only supported by Full Companion"
|
||||
#if !defined(COMPANION_FEATURE_DEDICATED_USB_LOGGING) || \
|
||||
!COMPANION_FEATURE_DEDICATED_USB_LOGGING
|
||||
#error "MESH_DUAL_CDC_LOGGING requires its dedicated Companion capability"
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#define MESH_NRF52_DUAL_CDC_LOGGING 1
|
||||
|
||||
@@ -36,14 +36,14 @@ class AsyncWebServerRequest;
|
||||
class DNSServer;
|
||||
|
||||
#ifndef WEBCONFIG_AP_IDLE_TIMEOUT_MS
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if defined(MESHCORE_EXPANDED_PARTITION_PROFILE)
|
||||
#define WEBCONFIG_AP_IDLE_TIMEOUT_MS WebConfigBatch::kFullSetupApWindowMs
|
||||
#else
|
||||
#define WEBCONFIG_AP_IDLE_TIMEOUT_MS (10UL * 60UL * 1000UL)
|
||||
#endif
|
||||
#endif
|
||||
#ifndef WEBCONFIG_UNCONFIGURED_SETUP_TIMEOUT_MS
|
||||
#if defined(MESHCORE_ESP32_FULL_PROFILE)
|
||||
#if defined(MESHCORE_EXPANDED_PARTITION_PROFILE)
|
||||
#define WEBCONFIG_UNCONFIGURED_SETUP_TIMEOUT_MS WebConfigBatch::kFullSetupApWindowMs
|
||||
#else
|
||||
#define WEBCONFIG_UNCONFIGURED_SETUP_TIMEOUT_MS 0UL
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
source build.sh
|
||||
|
||||
fail() {
|
||||
echo "test_build_profiles: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Synthetic inventory: one ESP32 target qualified for expanded Full and one
|
||||
# nRF52 target which must attempt complete LoRa OTA in its current partition.
|
||||
SUPPORTED_PIO_ENVS=(
|
||||
esp_repeater
|
||||
esp_repeater_lora_ota_no_external_sensors
|
||||
nrf_repeater
|
||||
nrf_repeater_lora_ota_no_external_sensors
|
||||
nrf_qspi_repeater
|
||||
nrf_qspi_repeater_lora_ota_no_external_sensors
|
||||
nrf_sensor
|
||||
)
|
||||
PIO_ENV_PLATFORM_BY_NAME[esp_repeater]=ESP32_PLATFORM
|
||||
PIO_ENV_PLATFORM_BY_NAME[esp_repeater_lora_ota_no_external_sensors]=ESP32_PLATFORM
|
||||
PIO_ENV_PLATFORM_BY_NAME[nrf_repeater]=NRF52_PLATFORM
|
||||
PIO_ENV_PLATFORM_BY_NAME[nrf_repeater_lora_ota_no_external_sensors]=NRF52_PLATFORM
|
||||
PIO_ENV_PLATFORM_BY_NAME[nrf_qspi_repeater]=NRF52_PLATFORM
|
||||
PIO_ENV_PLATFORM_BY_NAME[nrf_qspi_repeater_lora_ota_no_external_sensors]=NRF52_PLATFORM
|
||||
PIO_ENV_PLATFORM_BY_NAME[nrf_sensor]=NRF52_PLATFORM
|
||||
PIO_ENV_QSPI_OTA_BY_NAME[nrf_qspi_repeater_lora_ota_no_external_sensors]=1
|
||||
PIO_ENV_FULL_BUILD_BY_NAME[esp_repeater]=1
|
||||
|
||||
RESOLVED_BUILD_TARGETS=(esp_repeater)
|
||||
configure_effective_build_profile build-firmware >/dev/null
|
||||
[ "$BUILD_PROFILE_EFFECTIVE" = full ] || fail "ESP32 auto did not prefer Full"
|
||||
[ "$AUTO_PREFER_FULL_BUILD" = 1 ] || fail "ESP32 Full pass was not scheduled"
|
||||
[ "$AUTO_REDUCED_FALLBACK_TARGET" = \
|
||||
esp_repeater_lora_ota_no_external_sensors ] \
|
||||
|| fail "ESP32 reduced fallback was not resolved"
|
||||
|
||||
BUILD_PROFILE_OVERRIDE=auto
|
||||
BUILD_PROFILE_EXPLICIT=0
|
||||
RESOLVED_BUILD_TARGETS=(nrf_repeater)
|
||||
configure_effective_build_profile build-firmware >/dev/null
|
||||
[ "${RESOLVED_BUILD_TARGETS[0]}" = \
|
||||
nrf_repeater_lora_ota_no_external_sensors ] \
|
||||
|| fail "nRF52 complete OTA pass did not select the OTA identity"
|
||||
[ "$AUTO_COMPLETE_FIRST_PASS" = 1 ] \
|
||||
|| fail "nRF52 complete first pass was not scheduled"
|
||||
[ "$AUTO_PUBLISH_REDUCED_SECOND_PASS" = 1 ] \
|
||||
|| fail "internal-flash nRF52 reduced second artifact was not scheduled"
|
||||
|
||||
BUILD_PROFILE_OVERRIDE=auto
|
||||
BUILD_PROFILE_EXPLICIT=0
|
||||
RESOLVED_BUILD_TARGETS=(nrf_qspi_repeater)
|
||||
configure_effective_build_profile build-firmware >/dev/null
|
||||
[ "$AUTO_COMPLETE_FIRST_PASS" = 1 ] \
|
||||
|| fail "external-storage nRF52 complete first pass was not scheduled"
|
||||
[ "$AUTO_PUBLISH_REDUCED_SECOND_PASS" = 0 ] \
|
||||
|| fail "external-storage nRF52 incorrectly scheduled a redundant artifact"
|
||||
|
||||
BUILD_PROFILE_OVERRIDE=auto
|
||||
BUILD_PROFILE_EXPLICIT=0
|
||||
RESOLVED_BUILD_TARGETS=(nrf_sensor)
|
||||
configure_effective_build_profile build-firmware >/dev/null
|
||||
[ "$BUILD_PROFILE_EFFECTIVE" = auto ] \
|
||||
|| fail "nRF52 sensor did not retain its ordinary auto profile"
|
||||
[ "$AUTO_COMPLETE_FIRST_PASS" = 0 ] \
|
||||
|| fail "non-repeater nRF52 target incorrectly scheduled OTA profile passes"
|
||||
[ "$AUTO_PUBLISH_REDUCED_SECOND_PASS" = 0 ] \
|
||||
|| fail "non-repeater nRF52 target incorrectly scheduled two artifacts"
|
||||
|
||||
calls=()
|
||||
build_firmware() {
|
||||
calls+=("$1:$BUILD_PROFILE_EFFECTIVE:$SKIP_DECLARED_REDUCTIONS:$FIRMWARE_OUTPUT_ENV_NAME")
|
||||
if [ "${#calls[@]}" -eq 1 ]; then
|
||||
return 42
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
AUTO_PREFER_FULL_BUILD=0
|
||||
AUTO_COMPLETE_FIRST_PASS=1
|
||||
AUTO_REDUCED_FALLBACK_TARGET=nrf_repeater_lora_ota_no_external_sensors
|
||||
BUILD_PROFILE_EFFECTIVE=auto
|
||||
FIRMWARE_FILENAME_INFIX=""
|
||||
SKIP_DECLARED_REDUCTIONS=0
|
||||
run_auto_two_pass_build nrf_repeater_lora_ota_no_external_sensors >/dev/null
|
||||
|
||||
[ "${#calls[@]}" -eq 2 ] || fail "size overflow did not run exactly two passes"
|
||||
[[ "${calls[0]}" == *:auto:1:* ]] || fail "pass 1 unexpectedly applied reductions"
|
||||
[[ "${calls[1]}" == *:standard:0:* ]] || fail "pass 2 did not apply standard reductions"
|
||||
|
||||
calls=()
|
||||
build_firmware() {
|
||||
calls+=("$1:$BUILD_PROFILE_EFFECTIVE:$SKIP_DECLARED_REDUCTIONS:$FIRMWARE_OUTPUT_ENV_NAME")
|
||||
return 0
|
||||
}
|
||||
|
||||
AUTO_PUBLISH_REDUCED_SECOND_PASS=1
|
||||
BUILD_PROFILE_EFFECTIVE=auto
|
||||
FIRMWARE_FILENAME_INFIX=""
|
||||
FIRMWARE_OUTPUT_ENV_NAME=""
|
||||
SKIP_DECLARED_REDUCTIONS=0
|
||||
run_auto_two_pass_build nrf_repeater_lora_ota_no_external_sensors >/dev/null
|
||||
|
||||
[ "${#calls[@]}" -eq 2 ] \
|
||||
|| fail "successful internal-flash nRF52 build did not publish both artifacts"
|
||||
[[ "${calls[0]}" == *:auto:1:nrf_repeater ]] \
|
||||
|| fail "complete nRF52 artifact did not use the feature-rich output name"
|
||||
[[ "${calls[1]}" == *:standard:0: ]] \
|
||||
|| fail "reduced nRF52 artifact did not use the reduced target name"
|
||||
|
||||
echo "test_build_profiles: OK"
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CHECKER = ROOT / "scripts" / "check_firmware_capabilities.py"
|
||||
|
||||
|
||||
class FirmwareCapabilityCheckerTest(unittest.TestCase):
|
||||
def run_checker(self, image_bytes, *extra_args):
|
||||
temp_dir = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(temp_dir.cleanup)
|
||||
directory = Path(temp_dir.name)
|
||||
image = directory / "firmware.elf"
|
||||
manifest = directory / "firmware.capabilities.json"
|
||||
image.write_bytes(image_bytes)
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(CHECKER),
|
||||
"--image",
|
||||
str(image),
|
||||
"--output",
|
||||
str(manifest),
|
||||
"--target",
|
||||
"test_target",
|
||||
"--platform",
|
||||
"ESP32_PLATFORM",
|
||||
"--build-profile",
|
||||
"auto",
|
||||
*extra_args,
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
return result, json.loads(manifest.read_text())
|
||||
|
||||
def test_writes_verified_manifest(self):
|
||||
result, manifest = self.run_checker(
|
||||
b"prefix retry.preset suffix",
|
||||
"--capability",
|
||||
"cli.retry_preset",
|
||||
"--expect",
|
||||
"cli.retry_preset=retry.preset",
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertTrue(manifest["verified"])
|
||||
self.assertEqual(manifest["build_profile"], "auto")
|
||||
|
||||
def test_fails_when_promised_marker_is_missing(self):
|
||||
result, manifest = self.run_checker(
|
||||
b"unrelated image",
|
||||
"--expect",
|
||||
"web.webconfig=start webconfig",
|
||||
)
|
||||
self.assertEqual(result.returncode, 1)
|
||||
self.assertFalse(manifest["verified"])
|
||||
self.assertIn("promised", result.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -27,6 +27,41 @@ sys.path.insert(0, os.path.join(env["PROJECT_DIR"], "tools", "mota")) # noqa: F
|
||||
import motalib as ml
|
||||
|
||||
|
||||
def _apply_forced_lora_ota_overlay() -> None:
|
||||
"""Resolve a legacy trailing ``-UENABLE_OTA`` for release overlays.
|
||||
|
||||
A few feature-rich nRF52 repeater environments are intentionally non-OTA
|
||||
and carry a final compiler undefine. PlatformIO can remove normal ``-D``
|
||||
entries through ``build_unflags``, but it leaves that ``-U`` after all
|
||||
newly supplied defines. The release builder opts into this correction only
|
||||
when it is deliberately producing the complete OTA variant.
|
||||
"""
|
||||
if os.environ.get("MESHCORE_FORCE_LORA_OTA") != "1":
|
||||
return
|
||||
|
||||
import re
|
||||
|
||||
defines = []
|
||||
enable_seen = False
|
||||
for item in env.get("CPPDEFINES", []): # noqa: F821
|
||||
name = item[0] if isinstance(item, (list, tuple)) else item
|
||||
if name == "DISABLE_LORA_OTA":
|
||||
continue
|
||||
if name == "ENABLE_OTA":
|
||||
if enable_seen:
|
||||
continue
|
||||
enable_seen = True
|
||||
defines.append(item)
|
||||
if not enable_seen:
|
||||
defines.append(("ENABLE_OTA", 1))
|
||||
env.Replace(CPPDEFINES=defines) # noqa: F821
|
||||
|
||||
cppdef_flags = str(env.get("_CPPDEFFLAGS", "")) # noqa: F821
|
||||
cppdef_flags = re.sub(r"(?:^|\s)-U\s*ENABLE_OTA(?=\s|$)", " ", cppdef_flags)
|
||||
env.Replace(_CPPDEFFLAGS=" ".join(cppdef_flags.split())) # noqa: F821
|
||||
print("LoRa OTA overlay: removed legacy -UENABLE_OTA and enabled the OTA command surface")
|
||||
|
||||
|
||||
def _ota_enabled() -> bool:
|
||||
disabled = False
|
||||
enabled = False
|
||||
@@ -239,6 +274,8 @@ def _append_endf_hex(source, target, env): # Intel-HEX path (nRF52: app f
|
||||
f"target={ident.target_id:#010x} hw='{ident.hw_id}' fw={ident.fw_version:#010x})")
|
||||
|
||||
|
||||
_apply_forced_lora_ota_overlay()
|
||||
|
||||
if _ota_enabled():
|
||||
if _is_nrf52():
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", _append_endf_hex) # noqa: F821
|
||||
|
||||
@@ -174,7 +174,12 @@ def test_canonical_bulk_matrix_omits_runtime_and_transport_aliases():
|
||||
companion = (root / "examples/companion_radio/MyMesh.cpp").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert "defined(COMPANION_RADIO_FULL) && defined(MESH_DUAL_CDC_LOGGING)" in companion
|
||||
companion_features = (
|
||||
root / "examples/companion_radio/CompanionFeatures.h"
|
||||
).read_text(encoding="utf-8")
|
||||
assert "COMPANION_FEATURE_DEDICATED_USB_LOGGING" in companion_features
|
||||
assert "defined(MESH_DUAL_CDC_LOGGING)" in companion
|
||||
assert "defined(COMPANION_RADIO_FULL)" not in companion
|
||||
assert "_prefs.usb_logging_enabled = 0" in companion
|
||||
assert 'strcmp(value, "on reboot") == 0' in companion
|
||||
assert 'strcmp(value, "off reboot") == 0' in companion
|
||||
@@ -255,7 +260,9 @@ def test_esp32_s3_full_profiles_inherit_dio_boot_mode():
|
||||
)[1].split("requires_esp32_companion_full_ota_fallback()", 1)[0]
|
||||
assert "is_esp32_dual_cdc_companion_radio_full_target" in framework_preflight
|
||||
assert 'pio pkg install -e "$pio_env_name"' in framework_preflight
|
||||
build_call = build.split('print_build_flags "$env_name"', 1)[1].split(
|
||||
build_call = build.split(
|
||||
'print_build_flags "$pio_env_name" "$env_name"', 1
|
||||
)[1].split(
|
||||
"restore_platformio_build_flags", 1
|
||||
)[0]
|
||||
assert 'prepare_esp32_dual_cdc_framework "$env_name" "$pio_env_name"' in build_call
|
||||
|
||||
Reference in new issue
Block a user