build(nrf52): consolidate XIAO QSPI repeater images

This commit is contained in:
mikecarper committed 2026-09-21 20:54:13 -07:00
1 parent de82477184
commit 082466f71b
7 files changed
+118 -13

No files matched your search

+1
View File
@@ -216,6 +216,7 @@ jobs:
python3 -B test/test_full_logging_resume.py
python3 -B test/test_firmware_capabilities.py
bash test/test_build_profiles.sh
bash test/test_xiao_qspi_combined.sh
- name: Verify nRF52 UF2-reset CLI coverage
run: python3 -B test/test_nrf52_uf2reset_cli.py
+35 -10
View File
@@ -138,7 +138,7 @@ 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 runtime settings embedded in the firmware (not its feature set).
--build-profile <auto|standard|full>: Select feature/partition policy. Auto uses the combined Full MQTT/USB/WiFi recipe when it covers the plain infrastructure target; otherwise it first builds complete LoRa-OTA-capable firmware with a measured-size fallback. Internal-flash nRF52 repeaters and room servers also publish the reduced image for delta-staging headroom. Standard preserves portable images, including the 1.25 MiB slot; full requires the expanded ESP32 recipe.
--build-profile <auto|standard|full>: Select feature/partition policy. Auto uses the combined Full MQTT/USB/WiFi recipe when it covers the plain infrastructure target; otherwise it first builds complete LoRa-OTA-capable firmware with a measured-size fallback. Internal-flash nRF52 repeaters and room servers also publish the reduced image for delta-staging headroom; XIAO nRF52 repeaters with external-QSPI staging publish one combined image. Standard preserves portable images, including the 1.25 MiB slot; 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.
@@ -353,12 +353,16 @@ for section, options in data:
*_repeater|*_repeater_|*_repeatr|*_repeatr_|*_room_server|*_room_server_|*_room_svr|*_room_svr_) ;;
*) continue ;;
esac
# SolarXiao 30S/33S repeater and room-server roles already use matched
# external QSPI staging, so their normal full-sensor image is
# install-capable. A second lean no-external-sensors target provides no
# additional OTA capability.
# XIAO nRF52840 repeaters with raw-QSPI staging and the reserved
# bootloader scratch region keep sensors, packet logging, and LoRa OTA
# in their normal image. Do not publish a redundant reduced sibling.
if is_xiao_qspi_combined_repeater_target "$env_name"; then
continue
fi
# Leave SolarXiao room-server inventory unchanged; these roles are not
# part of the XIAO repeater consolidation.
case "${env_name,,}" in
solarxiao_30s_repeater|solarxiao_33s_repeater|solarxiao_30s_room_server|solarxiao_33s_room_server) continue ;;
solarxiao_30s_room_server|solarxiao_33s_room_server) continue ;;
esac
ota_env="${env_name%_}_lora_ota_no_external_sensors"
PIO_ENV_COMPLETE_OTA_BASE_BY_NAME["$ota_env"]="$env_name"
@@ -2080,6 +2084,22 @@ is_lora_ota_only_target() {
[[ "$target_lc" == *lora_ota* ]]
}
is_xiao_qspi_combined_repeater_target() {
local env_name=$1
[ "${PIO_ENV_PLATFORM_BY_NAME[$env_name]:-}" = "NRF52_PLATFORM" ] \
&& [ "${PIO_ENV_BOARD_BY_NAME[$env_name]:-}" = "seeed-xiao-afruitnrf52-nrf52840" ] \
&& [ "${PIO_ENV_QSPI_OTA_BY_NAME[$env_name]:-0}" = "1" ] \
&& { [[ "${env_name,,}" == *_repeater ]] \
|| [[ "${env_name,,}" == *_repeater_ ]]; }
}
is_xiao_qspi_canonical_repeater_build() {
is_xiao_qspi_combined_repeater_target "$1" \
&& [ "$BUILD_PROFILE_FOR_TARGET" = "standard" ] \
&& [ -z "$FIRMWARE_FILENAME_INFIX" ]
}
is_lora_ota_no_external_sensors_target() {
local target_lc=${1,,}
[[ "$target_lc" == *lora_ota_no_external_sensors ]]
@@ -3138,7 +3158,8 @@ record_application_expectation() {
declare_full_logging_application_contract() {
local env_name=$1
BUILD_APPLICATION_EXPECTATIONS=()
if requires_full_usb_packet_logging "$env_name"; then
if requires_full_usb_packet_logging "$env_name" \
|| is_xiao_qspi_canonical_repeater_build "$env_name"; then
# Inspect the packaged application, not an ELF debug/symbol string. The
# settings alone can exist in debug-only builds with packet logging absent.
record_application_expectation "logging.usb.packets" \
@@ -3179,7 +3200,9 @@ declare_build_capability_contract() {
if [ "$env_platform" = "NRF52_PLATFORM" ] && ! is_kiss_modem_target "$env_name"; then
# Prove the real DFU service is linked, not merely a generic OTA CLI stub.
record_build_expectation "ota.update.bluetooth" "_ZN6BLEDfu5beginEv"
if is_lora_ota_build "$env_name" && ! is_companion_build "$env_name"; then
if { is_lora_ota_build "$env_name" \
|| is_xiao_qspi_canonical_repeater_build "$env_name"; } \
&& ! is_companion_build "$env_name"; then
# nRF52 applies mOTA in its bootloader, so the ESP32 decoder marker is
# absent. The checker also verifies the packaged app's EndF/storage layout.
record_build_expectation "ota.update.lora" "invalid in-place patch geometry"
@@ -3209,7 +3232,8 @@ declare_build_capability_contract() {
fi
fi
if is_lora_ota_build "$env_name"; then
if is_lora_ota_build "$env_name" \
|| is_xiao_qspi_canonical_repeater_build "$env_name"; then
record_build_expectation "ota.cli" "OTA: status"
fi
@@ -4077,7 +4101,8 @@ write_build_capability_manifest() {
)
local item
if requires_wireless_self_update "$env_name"; then
if requires_wireless_self_update "$env_name" \
|| is_xiao_qspi_canonical_repeater_build "$env_name"; then
checker_args+=(--require-ota)
fi
if [ "$env_platform" = "ESP32_PLATFORM" ]; then
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
source build.sh
fail() { echo "test_xiao_qspi_combined: $*" >&2; exit 1; }
# Use the resolved PlatformIO recipes, not a board-name allowlist: each
# canonical XIAO repeater must retain the internal bootloader scratch bank,
# raw external-QSPI OTA staging, and packet logging in one application.
init_project_context
BUILD_PROFILE_FOR_TARGET=standard
BUILD_PROFILE_EFFECTIVE=standard
FIRMWARE_FILENAME_INFIX=""
DISABLE_DEBUG=0
PACKET_LOGGING_OVERRIDE=""
MESHDEBUG_OVERRIDE=""
MQTT_BRIDGE_OVERRIDE=""
for target in \
Xiao_nrf52_repeater \
ikoka_handheld_nrf_e22_30dbm_repeater \
ikoka_nano_nrf_22dbm_repeater \
ikoka_nano_nrf_30dbm_repeater \
ikoka_nano_nrf_33dbm_repeater \
ikoka_stick_nrf_22dbm_repeater \
ikoka_stick_nrf_30dbm_repeater \
ikoka_stick_nrf_33dbm_repeater \
solarxiao_30S_repeater \
solarxiao_33S_repeater; do
is_supported_build_env "$target" || fail "$target is missing"
is_xiao_qspi_canonical_repeater_build "$target" \
|| fail "$target lost its combined-image classification"
if is_supported_build_env "${target}_lora_ota_no_external_sensors"; then
fail "$target still publishes a redundant lean OTA sibling"
fi
if get_reduced_lora_ota_target "$target" >/dev/null; then
fail "$target still falls back to a reduced OTA sibling"
fi
pio_env_option_contains "$target" board_build.ldscript \
nrf52840_s140_v7_xiao_bootloader_ota.ld \
|| fail "$target lost its 40 KiB internal bootloader scratch reservation"
pio_env_option_contains "$target" board_upload.maximum_size 757760 \
|| fail "$target lost its internal application size limit"
pio_env_option_contains "$target" build_flags OTA_QSPI_BOOTLOADER_UPDATE=1 \
|| fail "$target lost its OTAFIX bootloader-update layout"
pio_env_option_contains "$target" build_flags OTA_QSPI_STORE=1 \
|| fail "$target lost external-QSPI OTA staging"
pio_env_option_contains "$target" build_flags MESH_PACKET_LOGGING=1 \
|| fail "$target lost packet logging"
is_lora_ota_build "$target" || fail "$target lost LoRa OTA"
BUILD_CAPABILITIES=()
BUILD_REDUCTIONS=()
BUILD_EXPECTATIONS=()
BUILD_APPLICATION_EXPECTATIONS=()
declare_build_capability_contract "$target" NRF52_PLATFORM
expectations=" ${BUILD_EXPECTATIONS[*]} "
application=" ${BUILD_APPLICATION_EXPECTATIONS[*]} "
[[ "$expectations" == *"ota.update.lora=invalid in-place patch geometry"* ]] \
|| fail "$target no longer proves its LoRa OTA receiver"
[[ "$expectations" == *"ota.cli=OTA: status"* ]] \
|| fail "$target no longer proves its OTA CLI"
[[ "$application" == *"logging.usb.packets=%s: %s, len=%d (type=%d, route=%s, payload_len=%d)"* ]] \
|| fail "$target no longer verifies packaged packet logging"
[[ "$application" == *"logging.usb.control=OK - USB logging %s (saved)"* ]] \
|| fail "$target no longer verifies the packaged USB logging control"
done
# This consolidation is specific to the external-QSPI XIAO repeater layout.
is_xiao_qspi_combined_repeater_target Xiao_nrf52_room_server \
&& fail "non-QSPI room server was incorrectly consolidated"
is_xiao_qspi_combined_repeater_target RAK_4631_repeater \
&& fail "unrelated repeater was incorrectly consolidated"
echo "test_xiao_qspi_combined: OK"
@@ -99,6 +99,7 @@ build_flags = ${ikoka_handheld_nrf.build_flags}
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
-D LORA_TX_POWER=20
-D MESH_PACKET_LOGGING=1
build_src_filter = ${ikoka_handheld_nrf.build_src_filter}
+<../examples/simple_repeater/*.cpp>
+1 -1
View File
@@ -129,7 +129,7 @@ build_flags =
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
; -D MESH_PACKET_LOGGING=1
-D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${ikoka_nano_nrf.build_src_filter}
+<../examples/simple_repeater/*.cpp>
+1 -1
View File
@@ -135,7 +135,7 @@ build_flags =
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
; -D MESH_PACKET_LOGGING=1
-D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${ikoka_stick_nrf.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+2 -1
View File
@@ -101,7 +101,7 @@ build_flags =
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
; -D MESH_PACKET_LOGGING=1
-D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Xiao_nrf52.build_src_filter}
+<../examples/simple_repeater/*.cpp>
@@ -231,6 +231,7 @@ build_flags =
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
-D MESH_PACKET_LOGGING=1
-D WITH_RS232_BRIDGE=Serial1
-D WITH_RS232_BRIDGE_UART=1
-D WITH_RS232_BRIDGE_RX=PIN_SERIAL1_RX