diff --git a/build_legacy.sh b/build_legacy.sh index f33279ab..d2268ef5 100755 --- a/build_legacy.sh +++ b/build_legacy.sh @@ -80,7 +80,7 @@ BUILD_BACKGROUND_HANDOFF_STATE="" BUILD_SCRIPT_LOCK_FD="" INTERACTIVE_BUILD_SELECTION=0 -ENV_VARIANT_SUFFIX_PATTERN='companion_radio_(wifi_mqtt|serial|wifi|usb|ble|full)(_ps)?(_fem(on|off))?|companion_radio_ethernet|comp_radio_usb|companion_usb|companion_ble|repeater_bridge_rs232_serial1_lora_ota_no_external_sensors|repeater_bridge_rs232_serial2_lora_ota_no_external_sensors|repeater_bridge_rs232_lora_ota_no_external_sensors|repeater_rak13302_w25q16_lora_ota|repeater_rak15001_slot_c_lora_ota|repeater_w25q16_lora_ota|repeater_lora_ota_no_external_sensors|repeater_bridge_rs232_serial1|repeater_bridge_rs232_serial2|repeater_bridge_rs232|repeater_bridge_espnow|repeater_observer_mqtt|repeater_ethernet|room_server_lora_ota_no_external_sensors|room_svr_lora_ota_no_external_sensors|room_server_observer_mqtt|room_server_ethernet|terminal_chat|room_server|room_svr|kiss_modem|sensor|repeatr|repeater' +ENV_VARIANT_SUFFIX_PATTERN='companion_radio_(wifi_mqtt|serial|wifi|usb|ble|full)(_ps)?(_fem(on|off))?|companion_radio_ethernet|comp_radio_usb|companion_usb|companion_ble|repeater_bridge_rs232_serial1_lora_ota_no_external_sensors|repeater_bridge_rs232_serial2_lora_ota_no_external_sensors|repeater_bridge_rs232_lora_ota_no_external_sensors|repeater_rak13302_w25q16_lora_ota|repeater_rak15001_slot_c_lora_ota|repeater_w25q16_lora_ota|repeater_lora_ota_no_external_sensors|repeater_bridge_rs232_serial1|repeater_bridge_rs232_serial2|repeater_bridge_rs232|repeater_bridge_espnow|repeater_observer_mqtt|repeater_ethernet|room_server_ethernet_lora_ota_no_external_sensors|room_server_lora_ota_no_external_sensors|room_svr_lora_ota_no_external_sensors|room_server_lora_ota|room_server_observer_mqtt|room_server_ethernet|terminal_chat|room_server|room_svr|kiss_modem|sensor_lora_ota_no_external_sensors|sensor|repeatr|repeater' BOARD_MODIFIER_WITHOUT_DISPLAY="_without_display" BOARD_MODIFIER_LOGGING="_logging" BOARD_MODIFIER_TFT="_tft" @@ -348,11 +348,15 @@ for section, options in data: ESP32_PLATFORM|NRF52_PLATFORM) ;; *) continue ;; esac - # Generate one lean LoRa-OTA image for each board's standalone repeater - # or room-server role. Sensor, observer, Ethernet, and bridge profiles - # stay on their explicit PlatformIO recipes. + # Generate a lean OTA image for standalone repeater/room roles and for + # nRF52 sensors. Sensor aliases retain the sensor application and core + # I2C/battery telemetry while omitting optional external driver code. + # ESP32 sensor and room roles use their expanded Full OTA image. case "${env_name,,}" in *_repeater|*_repeater_|*_repeatr|*_repeatr_|*_room_server|*_room_server_|*_room_svr|*_room_svr_) ;; + *_sensor|*_sensor_) + [ "${PIO_ENV_PLATFORM_BY_NAME[$env_name]}" = NRF52_PLATFORM ] || continue ;; + rak_4631_room_server_ethernet) ;; *) continue ;; esac # XIAO nRF52840 repeaters with raw-QSPI staging and the reserved @@ -361,8 +365,8 @@ for section, options in data: 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. + # SolarXiao room servers use the explicit QSPI OTA recipes below, which + # preserve their complete sensor set and matching bootloader layout. case "${env_name,,}" in solarxiao_30s_room_server|solarxiao_33s_room_server) continue ;; esac @@ -1420,7 +1424,7 @@ get_env_metadata() { repeater*) tag_prefix="$TAG_PREFIX_REPEATER" ;; - sensor) + sensor*) tag_prefix="$TAG_PREFIX_SENSOR" ;; *) @@ -2009,7 +2013,8 @@ print_release_firmware_targets() { get_pio_envs_ending_with_string "_room_server" for env_name in "${SUPPORTED_PIO_ENVS[@]}"; do if is_room_server_role_target "$env_name" \ - && is_lora_ota_no_external_sensors_target "$env_name"; then + && { is_lora_ota_no_external_sensors_target "$env_name" \ + || [[ "$env_name" == solarxiao_*_room_server_lora_ota ]]; }; then printf '%s\n' "$env_name" fi done @@ -2041,7 +2046,7 @@ get_pio_envs_for_variant_role() { room_server:room_server*|room_server:*_room_server*) echo "$env" ;; - sensor:sensor|sensor:*_sensor) + sensor:sensor*|sensor:*_sensor*) echo "$env" ;; kiss:kiss_modem|kiss:*_kiss_modem) @@ -2149,8 +2154,11 @@ is_rak_gps_retaining_ota_target() { # recipe and must not promise or require the WisBlock GPS provider. case "$target_lc" in rak_3401_repeater_lora_ota_no_external_sensors|\ + rak_3401_sensor_lora_ota_no_external_sensors|\ rak_4631_repeater_lora_ota_no_external_sensors|\ - rak_4631_repeater_bridge_rs232_serial2_lora_ota_no_external_sensors) + rak_4631_repeater_bridge_rs232_serial2_lora_ota_no_external_sensors|\ + rak_4631_room_server_ethernet_lora_ota_no_external_sensors|\ + rak_4631_sensor_lora_ota_no_external_sensors) return 0 ;; *) @@ -2931,7 +2939,7 @@ is_room_server_role_target() { is_sensor_role_target() { case "${1,,}" in - *_sensor|*_sensor_) return 0 ;; + *_sensor|*_sensor_|*_sensor_lora_ota_no_external_sensors) return 0 ;; *) return 1 ;; esac } @@ -3020,12 +3028,10 @@ is_lora_ota_build() { return 1 fi - # The constrained portable OTA profile is emitted for standalone repeaters - # and room servers. FULL ESP32 builds returned above also enable LoRa OTA for - # sensor, observer, and bridge roles because their expanded slots have room - # for every feature. + # The constrained OTA profile is also available for nRF52 sensors; ESP32 + # sensors use the expanded Full image returned above. case "$env_name_lc" in - *repeater*|*repeatr*|*room_server*|*room_svr*) return 0 ;; + *repeater*|*repeatr*|*room_server*|*room_svr*|*_sensor_lora_ota_no_external_sensors) return 0 ;; *) return 1 ;; esac } @@ -5112,7 +5118,8 @@ is_redundant_bulk_build_target() { # Bench fixtures and migration utilities use their dedicated PlatformIO # recipes; they are not node firmware for the release/OTA packaging matrix. case "${1,,}" in - profile_switch_*|*partition_migrator*|*partition_expander*|*_partition_legacy_seed|*_sim) + profile_switch_*|*partition_migrator*|*partition_expander*|*_partition_legacy_seed|\ + *_legacy_partition_test|*_sim) return 0 ;; esac diff --git a/docs/_javascript/firmware_picker.js b/docs/_javascript/firmware_picker.js index f6714f26..e6d813c5 100644 --- a/docs/_javascript/firmware_picker.js +++ b/docs/_javascript/firmware_picker.js @@ -54,7 +54,7 @@ const OTA_LABELS = Object.freeze({ none: "No LoRa OTA", - "lora-receiver": "LoRa OTA repeater", + "lora-receiver": "Receives LoRa OTA", "lora-source": "LoRa OTA source only (Full Companion)", }); @@ -1075,7 +1075,7 @@ ], zip: [ "Use the ZIP as the native nRF52 Serial DFU package; it is not an extra archive.", - "For a LoRa OTA repeater, install the exact-board OTAFIX bootloader first.", + "For LoRa OTA reception, install the exact-board OTAFIX bootloader first.", ], uf2: [ "Place the device in its UF2 bootloader mode and copy the UF2 to the mounted drive.", @@ -1102,7 +1102,7 @@ } if (profile.ota === "lora-receiver") { extra.push( - "This installs the LoRa OTA repeater profile. The later update package must still match the exact target and partition signature." + "This image can receive LoRa OTA updates. The later update package must still match the exact target and partition signature." ); } else if (profile.ota === "lora-source") { extra.push( diff --git a/docs/firmware_picker.md b/docs/firmware_picker.md index 21587f73..7370d794 100644 --- a/docs/firmware_picker.md +++ b/docs/firmware_picker.md @@ -162,7 +162,7 @@ from the published firmware assets. | Wi-Fi MQTT observer | Firmware connects directly to MQTT over Wi-Fi; this is not USB logging | | USB logging + Wi-Fi MQTT | Unified FULL image sends to both paths; avoid two publishers aimed at the same broker unless messages are deduplicated | | No logging | Normal standalone operation without the dedicated logging/MQTT profile | -| LoRa OTA repeater | Repeater profile that can stage an exact matching update received over LoRa | +| Receives LoRa OTA | Repeater, room server, or sensor image that can stage an exact matching update received over LoRa | | LoRa OTA source only | Full Companion serving a host-supplied update to another node without self-installing it | Connection and bridge choices depend on the selected role. Companion firmware @@ -362,15 +362,14 @@ node configuration and verify every filename suffix before flashing. ## LoRa OTA and OTAFIX -A LoRa OTA repeater build installs repeater firmware that can receive and -stage updates. A later LoRa update still needs an exact target identity, +A LoRa OTA capable build can receive and stage updates for its role. A later +LoRa update still needs an exact target identity, compatible partition signature, matching radio settings, and the correct update package. -nRF52 LoRa OTA requires an OTAFIX bootloader built for the exact board. There -is no universal bootloader file. Use the -[OTAFIX 2.4.6 for this release](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/0.11.0-OTAFIX2.4.6) -and select the hardware-matched HEX, Serial DFU ZIP, or bootloader-update UF2. +nRF52 LoRa OTA requires an OTAFIX bootloader built for the exact board and +storage layout. Select the hardware-matched HEX, Serial DFU ZIP, or +bootloader-update UF2 for the application image being installed. ## Hardware and variant names diff --git a/docs/ota_easy.md b/docs/ota_easy.md index 6efac2c1..e9136c60 100644 --- a/docs/ota_easy.md +++ b/docs/ota_easy.md @@ -95,8 +95,10 @@ payload. If any required window closes, the transfer stops making progress and c overlapping window. `build.sh` provides a `*_repeater_lora_ota_no_external_sensors` or -`*_room_server_lora_ota_no_external_sensors` build for standalone ESP32 and nRF52 -infrastructure targets that need a smaller internal update workspace. Those siblings omit selected optional environmental +`*_room_server_lora_ota_no_external_sensors` build for standalone infrastructure +targets that need a smaller internal update workspace. nRF52 sensors also have +`*_sensor_lora_ota_no_external_sensors` builds. ESP32 room servers and sensors +use their expanded Full image for LoRa OTA. These compact siblings omit selected optional environmental and ranging drivers while retaining board-native features such as displays, buttons, battery monitoring, and GPS where the target uses the GPS-preserving lean profile. RAK3401 and RAK4631 reduced builds also retain INA219, INA226, INA260, and INA3221 I2C voltage/current monitors; together these drivers cost less @@ -119,7 +121,8 @@ Selected nRF52 boards with matched external QSPI application and bootloader support can instead make the normal full-sensor repeater or room-server install-capable; those targets do not need to reserve internal flash for the downloaded container. SolarXiao 30S and 33S use this matched external-QSPI -path and therefore do not emit redundant no-external-sensors siblings. A +path: repeaters retain their existing OTA target, and room servers use the explicit +`*_room_server_lora_ota` target with the matching OTAFIX QSPI bootloader. A RAK19007 can use the same separately wired W25Q16 with either RAK4631 or RAK3401 + RAK13302: the flash consumes no WisBlock slot and GPS remains supported in slot A. This is not the RAK15001 arrangement; RAK15001 and diff --git a/docs/ota_protocol.md b/docs/ota_protocol.md index 3b45971a..a1feb757 100644 --- a/docs/ota_protocol.md +++ b/docs/ota_protocol.md @@ -133,13 +133,14 @@ artifacts, including room, sensor, and repeater roles, must fit the legacy slot limit and the target's actual app partition. The ESP32-C6 `no_external_sensors` OTA siblings are the narrow exception: the Arduino 3.x WiFi runtime cannot fit that cross-family ceiling, so those images retain their established target-specific 1920 KiB or larger A/B app layout and are checked against the actual app -partition. For standalone ESP32 and nRF52 repeater and room-server roles that -need a lean staging profile, `build.sh` also exposes an explicit -`*_lora_ota_no_external_sensors` artifact: the ordinary role remains -sensor-enabled, while that sibling trims selected optional environmental/ranging -drivers for LoRa distribution. SolarXiao 30S and 33S use matched external QSPI -staging, so their ordinary full-sensor repeater and room-server images are -already install-capable and no redundant lean sibling is generated. Integrated GPS and other +partition. ESP32 room servers and sensors use their expanded Full image for LoRa OTA. +For standalone repeater and room-server roles, and for nRF52 sensors that need a lean +staging profile, `build.sh` exposes an explicit `*_lora_ota_no_external_sensors` +artifact: the ordinary role remains sensor-enabled, while that sibling trims +selected optional environmental/ranging drivers for LoRa distribution. SolarXiao +30S and 33S use matched external QSPI staging. Their repeaters keep the existing +OTA target; their room servers use an explicit `*_room_server_lora_ota` image +with the matching OTAFIX QSPI bootloader. Integrated GPS and other board-native telemetry remain enabled where the target selects the GPS-preserving lean profile. The legacy suffix describes a driver trim, not removal of the generic I2C bus. Reduced RAK3401 and RAK4631 profiles retain INA219, INA226, INA260, and INA3221 voltage/current monitors. These are retained optional-sensor-table diff --git a/test/test_build_profiles.sh b/test/test_build_profiles.sh index 5513dc7a..135eb653 100644 --- a/test/test_build_profiles.sh +++ b/test/test_build_profiles.sh @@ -29,7 +29,10 @@ done # marker, and so its manifest never advertises impossible hardware support. for rak_target in \ RAK_3401_repeater_lora_ota_no_external_sensors \ + RAK_3401_sensor_lora_ota_no_external_sensors \ RAK_4631_repeater_lora_ota_no_external_sensors \ + RAK_4631_room_server_ethernet_lora_ota_no_external_sensors \ + RAK_4631_sensor_lora_ota_no_external_sensors \ RAK_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors \ RAK_4631_repeater_bridge_rs232_serial2_lora_ota_no_external_sensors; do is_rak_i2c_voltage_monitor_ota_target "$rak_target" \ @@ -37,7 +40,10 @@ for rak_target in \ done for rak_target in \ RAK_3401_repeater_lora_ota_no_external_sensors \ + RAK_3401_sensor_lora_ota_no_external_sensors \ RAK_4631_repeater_lora_ota_no_external_sensors \ + RAK_4631_room_server_ethernet_lora_ota_no_external_sensors \ + RAK_4631_sensor_lora_ota_no_external_sensors \ RAK_4631_repeater_bridge_rs232_serial2_lora_ota_no_external_sensors; do is_rak_gps_retaining_ota_target "$rak_target" \ || fail "$rak_target lost its compatible GPS contract" @@ -330,9 +336,19 @@ require(rak_usb, "build_flags", "FORCE_GPS_ALIVE") # replace each with that exact board's Full target. init_project_context >/dev/null -# ESP32 and nRF52 standalone room servers receive the same lean LoRa-OTA -# sibling as repeaters. Sensor roles stay on their ordinary recipe so this -# cannot accidentally become a blanket no-external-sensors policy. +# ESP32 room servers and sensors use their expanded Full image for LoRa OTA. +for full_role_env in Heltec_v3_room_server Heltec_v3_sensor; do + supports_esp32_full_build "$full_role_env" \ + || fail "$full_role_env lacks its ESP32 Full build" + ( + ESP32_FULL_BUILD=1 + is_lora_ota_build "$full_role_env" \ + || fail "$full_role_env Full image lost LoRa OTA" + ) +done + +# Standalone room servers receive a lean LoRa OTA sibling. nRF52 sensors +# receive an explicit lean sibling while their normal image keeps its sensors. while IFS='|' read -r room_env room_ota_env; do is_supported_build_env "$room_ota_env" \ || fail "$room_ota_env was not registered as a room-server LoRa OTA target" @@ -350,16 +366,57 @@ done <<'ROOM_OTA_SPECS' Heltec_v3_room_server|Heltec_v3_room_server_lora_ota_no_external_sensors RAK_3401_room_server|RAK_3401_room_server_lora_ota_no_external_sensors ROOM_OTA_SPECS -print_release_firmware_targets get-room-server-firmwares-to-build | grep -Fx \ +room_release_targets=$(print_release_firmware_targets get-room-server-firmwares-to-build) +printf '%s\n' "$room_release_targets" | grep -Fx \ RAK_3401_room_server_lora_ota_no_external_sensors >/dev/null \ || fail "room-server release resolution omitted its portable LoRa OTA target" -for sensor_ota_env in \ - Heltec_v3_sensor_lora_ota_no_external_sensors \ - RAK_3401_sensor_lora_ota_no_external_sensors; do - if is_supported_build_env "$sensor_ota_env"; then - fail "$sensor_ota_env was incorrectly generated from a sensor role" - fi +printf '%s\n' "$room_release_targets" | grep -Fx \ + solarxiao_30S_room_server_lora_ota >/dev/null \ + || fail "room-server release resolution omitted its QSPI LoRa OTA target" +for room_ota_env in solarxiao_33S_room_server_lora_ota \ + RAK_4631_room_server_ethernet_lora_ota_no_external_sensors; do + printf '%s\n' "$room_release_targets" | grep -Fx "$room_ota_env" >/dev/null \ + || fail "room-server release resolution omitted $room_ota_env" done +sensor_release_targets=$(resolve_sensor_firmwares) +while IFS='|' read -r sensor_env sensor_ota_env; do + is_supported_build_env "$sensor_ota_env" \ + || fail "$sensor_ota_env was not registered as a sensor LoRa OTA target" + [ "$(get_pio_build_env "$sensor_ota_env")" = "$sensor_env" ] \ + || fail "$sensor_ota_env did not retain its sensor PlatformIO recipe" + printf '%s\n' "$sensor_release_targets" | grep -Fx "$sensor_ota_env" >/dev/null \ + || fail "sensor release resolution omitted $sensor_ota_env" + ( + BUILD_PROFILE_FOR_TARGET=standard + ESP32_FULL_BUILD=0 + is_lora_ota_build "$sensor_ota_env" \ + || fail "$sensor_ota_env did not enable LoRa OTA" + ) +done <<'SENSOR_OTA_SPECS' +Heltec_t096_sensor|Heltec_t096_sensor_lora_ota_no_external_sensors +Heltec_t114_sensor|Heltec_t114_sensor_lora_ota_no_external_sensors +ProMicro_sensor|ProMicro_sensor_lora_ota_no_external_sensors +R1Neo_sensor|R1Neo_sensor_lora_ota_no_external_sensors +RAK_3401_sensor|RAK_3401_sensor_lora_ota_no_external_sensors +RAK_4631_sensor|RAK_4631_sensor_lora_ota_no_external_sensors +RAK_WisMesh_Tag_sensor|RAK_WisMesh_Tag_sensor_lora_ota_no_external_sensors +t1000e_sensor|t1000e_sensor_lora_ota_no_external_sensors +SENSOR_OTA_SPECS +for solar_env in solarxiao_30S_room_server_lora_ota solarxiao_33S_room_server_lora_ota; do + is_supported_build_env "$solar_env" \ + || fail "$solar_env QSPI room-server OTA target was not registered" + ( + BUILD_PROFILE_FOR_TARGET=standard + ESP32_FULL_BUILD=0 + is_lora_ota_build "$solar_env" \ + || fail "$solar_env did not enable LoRa OTA" + ) +done +is_supported_build_env RAK_4631_room_server_ethernet_lora_ota_no_external_sensors \ + || fail "RAK4631 Ethernet room server lacked its lean LoRa OTA target" +if is_supported_build_env Heltec_v3_sensor_lora_ota_no_external_sensors; then + fail "ESP32 sensors should use their Full OTA image" +fi # KISS inherits board OTA flags, not the application OTA implementation. for kiss_env in "${SUPPORTED_PIO_ENVS[@]}"; do @@ -1211,6 +1268,8 @@ is_redundant_bulk_build_target RAK_3401_terminal_chat \ || fail "RP2040 Terminal Chat did not map to USB Companion" is_redundant_bulk_build_target PicoW_terminal_chat \ || fail "Terminal Chat remained beside its matching USB Companion" +is_redundant_bulk_build_target heltec_v4_repeater_legacy_partition_test \ + || fail "legacy partition test fixture remained in bulk release builds" PIO_ENV_PLATFORM_BY_NAME[RAK_4631_companion_radio_ethernet]=NRF52_PLATFORM PIO_ENV_PLATFORM_BY_NAME[RAK_4631_companion_radio_full]=NRF52_PLATFORM diff --git a/test/test_firmware_picker.js b/test/test_firmware_picker.js index 4557b4b5..a481a104 100644 --- a/test/test_firmware_picker.js +++ b/test/test_firmware_picker.js @@ -605,7 +605,7 @@ assert.strictEqual(lora.ota, "lora-receiver"); assert.strictEqual(lora.variant, "no-external-sensors"); assert.strictEqual( picker.OTA_LABELS["lora-receiver"], - "LoRa OTA repeater" + "Receives LoRa OTA" ); assert(!Object.prototype.hasOwnProperty.call( picker.OTA_LABELS, diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 765e6bf5..65960684 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -306,6 +306,31 @@ build_flags = -D ADVERT_NAME='"SolarXiao 33S Room"' -D MANUFACTURER_STRING='"SolarXiao 33S"' +; Room-server OTA uses the same external-QSPI staging and OTAFIX linker +; contract as the corresponding SolarXiao repeater. The ordinary room image +; remains available for older bootloaders and USB/BLE recovery. +[env:solarxiao_30S_room_server_lora_ota] +extends = env:solarxiao_30S_room_server +board_build.ldscript = boards/nrf52840_s140_v7_xiao_bootloader_ota.ld +board_upload.maximum_size = 757760 +build_flags = + ${env:solarxiao_30S_room_server.build_flags} + ${nrf52_qspi_ota.build_flags} + -D OTA_QSPI_BOOTLOADER_UPDATE=1 +build_src_filter = ${env:solarxiao_30S_room_server.build_src_filter} + + + +[env:solarxiao_33S_room_server_lora_ota] +extends = env:solarxiao_33S_room_server +board_build.ldscript = boards/nrf52840_s140_v7_xiao_bootloader_ota.ld +board_upload.maximum_size = 757760 +build_flags = + ${env:solarxiao_33S_room_server.build_flags} + ${nrf52_qspi_ota.build_flags} + -D OTA_QSPI_BOOTLOADER_UPDATE=1 +build_src_filter = ${env:solarxiao_33S_room_server.build_src_filter} + + + [env:solarxiao_30S_kiss_modem] extends = solarxiao build_flags =