Enable LoRa OTA room and nRF52 sensor variants

This commit is contained in:
mikecarper committed 2026-09-26 09:58:43 -07:00
1 parent df19760c85
commit f215792162
8 files changed
+142 -48

No files matched your search

+24 -17
View File
@@ -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
+3 -3
View File
@@ -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(
+6 -7
View File
@@ -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
+6 -3
View File
@@ -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
+8 -7
View File
@@ -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
+69 -10
View File
@@ -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
+1 -1
View File
@@ -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,
+25
View File
@@ -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}
+<helpers/ota/*.cpp>
[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}
+<helpers/ota/*.cpp>
[env:solarxiao_30S_kiss_modem]
extends = solarxiao
build_flags =