Publish canonical Full partition-migration targets

This commit is contained in:
mikecarper committed 2026-09-19 09:28:04 -07:00
1 parent e48792e178
commit a21d6046f6
4 files changed
+184 -9

No files matched your search

+130 -9
View File
@@ -2387,23 +2387,25 @@ get_unified_full_infrastructure_target() {
return 1
}
# A same-partition FULL release keeps the logical target's mOTA identity while
# it may compile the matching observer recipe for its MQTT/TLS/ESP-NOW sources.
# That lets a deployed ordinary role update to the complete image without
# treating an observer environment name as a different target. If a board has
# no matching observer recipe, its own source tree remains the Full build base.
# A canonical FULL release keeps the logical target's mOTA identity while it
# may compile the matching observer recipe for its MQTT/TLS/ESP-NOW sources.
# Same-partition targets can receive that image through mOTA. Partition-
# migration targets require their merged image to be flashed once first, but
# then use this same normal-target identity for every later FULL update. If a
# board has no matching observer recipe, its own source tree remains the Full
# build base.
get_exact_identity_full_pio_env() {
local env_name=$1
local candidate=""
is_esp32_full_only_bulk_target "$env_name" || {
is_esp32_canonical_full_release_target "$env_name" || {
printf '%s\n' "$env_name"
return 0
}
candidate=$(get_unified_full_infrastructure_target "$env_name") || candidate=""
if [ -n "$candidate" ] \
&& is_esp32_full_only_bulk_target "$candidate" \
&& supports_esp32_full_build "$candidate" \
&& [ "${PIO_ENV_BOARD_BY_NAME[$candidate]:-}" = "${PIO_ENV_BOARD_BY_NAME[$env_name]:-}" ]; then
printf '%s\n' "$candidate"
else
@@ -3056,6 +3058,34 @@ is_esp32_full_only_bulk_target() {
return 1
}
# These normal role identities still ship a portable legacy image for installed
# nodes, but also need a canonical FULL artifact for the one-time wired layout
# migration. They are deliberately separate from the FULL-only list above:
# their deployed 0x140000 slots differ from the expanded table, so LoRa mOTA
# must not try to cross this boundary. The matching merged image is the
# migration package; after it is flashed, later normal-target FULL packages
# use the same logical mOTA identity.
is_esp32_partition_migration_full_target() {
local env_name=${1,,}
[ "${PIO_ENV_PLATFORM_BY_NAME[$1]:-}" = "ESP32_PLATFORM" ] || return 1
case "$env_name" in
lilygo_t3s3_sx1262_repeater|lilygo_t3s3_sx1262_room_server|\
lilygo_t3s3_sx1276_repeater|lilygo_t3s3_sx1276_room_server|\
station_g2_repeater|station_g2_room_server|\
thinknode_m2_repeater|thinknode_m2_room_server)
return 0
;;
esac
return 1
}
is_esp32_canonical_full_release_target() {
is_esp32_full_only_bulk_target "$1" \
|| is_esp32_partition_migration_full_target "$1"
}
requires_esp32_full_cli_profile() {
local env_name=$1
@@ -3116,6 +3146,15 @@ declare_build_capability_contract() {
declare_full_logging_application_contract "$env_name"
record_build_capability "profile.${BUILD_PROFILE_FOR_TARGET}"
if [ "$env_platform" = "ESP32_PLATFORM" ] \
&& [ "$BUILD_PROFILE_FOR_TARGET" = "full" ]; then
if is_esp32_full_only_bulk_target "$env_name"; then
record_build_capability "release.full.same_partition_successor"
elif is_esp32_partition_migration_full_target "$env_name"; then
record_build_capability "release.full.partition_migration_usb"
fi
fi
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"
@@ -4369,10 +4408,14 @@ build_firmware() {
fi
if [ "$ESP32_FULL_BUILD" = "1" ] \
&& is_esp32_full_only_bulk_target "$env_name"; then
&& is_esp32_canonical_full_release_target "$env_name"; then
exact_identity_full_pio_env=$(get_exact_identity_full_pio_env "$env_name")
if [ "$exact_identity_full_pio_env" != "$pio_env_name" ]; then
echo "FULL ${env_name} retains its mOTA identity while compiling observer feature base ${exact_identity_full_pio_env}."
if is_esp32_partition_migration_full_target "$env_name"; then
echo "Partition-migration FULL ${env_name} retains its mOTA identity while compiling observer feature base ${exact_identity_full_pio_env}."
else
echo "FULL ${env_name} retains its mOTA identity while compiling observer feature base ${exact_identity_full_pio_env}."
fi
pio_env_name=$exact_identity_full_pio_env
fi
fi
@@ -5690,6 +5733,14 @@ run_full_esp32_profile() {
if [ -z "$full_target" ] || ! supports_esp32_full_build "$full_target"; then
continue
fi
# The canonical normal-target FULL artifact was already emitted by the
# partition-migration pass. Do not rebuild it as an identical fallback.
# Observer identities are intentionally not skipped: existing observer
# nodes still need their exact legacy mOTA asset during the transition.
if [ "${PARTITION_MIGRATION_FULL_PROFILE_ACTIVE:-0}" = "1" ] \
&& is_esp32_partition_migration_full_target "$full_target"; then
continue
fi
# The logging matrix has already emitted these approved targets under
# their exact environment names. Do not replace that identity with a
# generic MQTT/unified sibling in this legacy resolution pass.
@@ -5777,6 +5828,60 @@ run_full_only_esp32_profile() {
return 0
}
run_partition_migration_full_esp32_profile() {
local targets=("$@")
local target
local pass_status=0
local build_status=0
local BUILD_PROFILE_EFFECTIVE=full
local -a migration_targets=()
local -A seen_migration_targets=()
local original_meshdebug_override=$MESHDEBUG_OVERRIDE
local original_packet_logging_override=$PACKET_LOGGING_OVERRIDE
local original_mqtt_bridge_override=$MQTT_BRIDGE_OVERRIDE
local original_mqtt_debug_override=$MQTT_DEBUG_OVERRIDE
local original_firmware_filename_infix=$FIRMWARE_FILENAME_INFIX
local original_esp32_full_build=$ESP32_FULL_BUILD
for target in "${targets[@]}"; do
if ! is_esp32_partition_migration_full_target "$target" \
|| [ -n "${seen_migration_targets[$target]+x}" ]; then
continue
fi
migration_targets+=("$target")
seen_migration_targets["$target"]=1
done
if [ ${#migration_targets[@]} -eq 0 ]; then
return 0
fi
echo "Partition-migration FULL pass: building ${#migration_targets[@]} canonical ESP32 target(s)."
echo "Each normal-target FULL artifact keeps its mOTA identity after its matching merged image is flashed once; legacy 1.25 MiB artifacts remain published during the transition."
MESHDEBUG_OVERRIDE=off
PACKET_LOGGING_OVERRIDE=on
MQTT_BRIDGE_OVERRIDE=off
MQTT_DEBUG_OVERRIDE=off
FIRMWARE_FILENAME_INFIX=full-logging
ESP32_FULL_BUILD=1
run_logged_build_targets "${migration_targets[@]}"
pass_status=$?
if [ "$pass_status" -eq 130 ]; then
build_status=130
elif [ "$pass_status" -ne 0 ]; then
build_status=1
fi
MESHDEBUG_OVERRIDE=$original_meshdebug_override
PACKET_LOGGING_OVERRIDE=$original_packet_logging_override
MQTT_BRIDGE_OVERRIDE=$original_mqtt_bridge_override
MQTT_DEBUG_OVERRIDE=$original_mqtt_debug_override
FIRMWARE_FILENAME_INFIX=$original_firmware_filename_infix
ESP32_FULL_BUILD=$original_esp32_full_build
return "$build_status"
}
run_full_esp32_build_targets() {
local profile_mode=$1
shift
@@ -5831,12 +5936,14 @@ run_logging_matrix_build_targets() {
local original_profile_build_workers=$PROFILE_BUILD_WORKERS
local full_only_standard_skip_count=0
local full_only_exact_count=0
local partition_migration_full_count=0
local merged_usb_logging_count=0
local constrained_merged_logging_count=0
local build_status=0
local pass_status=0
local DEFER_ESP32_PORTABLE_OVERFLOW_TO_FULL=0
local FULL_ONLY_EXACT_PROFILE_ACTIVE=0
local PARTITION_MIGRATION_FULL_PROFILE_ACTIVE=0
if [ ${#targets[@]} -eq 0 ]; then
echo "No build targets resolved."
@@ -5889,6 +5996,14 @@ run_logging_matrix_build_targets() {
if [ "$full_only_exact_count" -gt 0 ]; then
echo "Publishing ${full_only_exact_count} audited ESP32 target(s) as their exact-identity FULL release only; explicit --standard remains available for recovery."
fi
for target in "${targets[@]}"; do
if is_esp32_partition_migration_full_target "$target"; then
partition_migration_full_count=$((partition_migration_full_count + 1))
fi
done
if [ "$partition_migration_full_count" -gt 0 ]; then
echo "Publishing ${partition_migration_full_count} canonical ESP32 FULL migration target(s) alongside their legacy portable artifacts; flash each matching merged image once before mOTA can use FULL."
fi
ESP32_FULL_BUILD=0
MESHDEBUG_OVERRIDE=""
PACKET_LOGGING_OVERRIDE=""
@@ -5908,6 +6023,12 @@ run_logging_matrix_build_targets() {
if [ "$pass_status" -eq 130 ]; then return 130; fi
if [ "$pass_status" -ne 0 ]; then build_status=1; fi
run_partition_migration_full_esp32_profile "${targets[@]}"
pass_status=$?
if [ "$pass_status" -eq 130 ]; then return 130; fi
if [ "$pass_status" -ne 0 ]; then build_status=1; fi
PARTITION_MIGRATION_FULL_PROFILE_ACTIVE=1
FULL_ONLY_EXACT_PROFILE_ACTIVE=1
run_full_esp32_profile "FULL unified pass" "unified" "${targets[@]}"
pass_status=$?
+5
View File
@@ -55,6 +55,11 @@ portable application slot, remain available and remain in the standard matrix
pass. Select `--build-profile standard` to retain the original target and
partition contract. Moving from a plain target to its MQTT sibling changes the
LoRa OTA target identity; existing nodes need an appropriate wired migration.
For the normal-role partition migrations, the matrix also publishes a
canonical Full image under the normal target identity. Install its matching
merged image over USB once; mOTA rejects the incompatible partition signature
until that migration is complete, then later Full updates use the normal
target ID. The portable image remains available while deployed nodes move.
The default `cascade` settings profile enables device and LoRa RX power saving;
saved preferences take precedence, and active USB/network services can prevent
device sleep. `--profile default` instead uses the upstream power-saving defaults.
+11
View File
@@ -420,6 +420,17 @@ The former observer-named Full image remains available through exact filename
search for deployed devices that still use that mOTA identity; it is not shown
as a second recommended configuration.
Some normal ESP32 roles begin in the legacy 1.25 MiB dual-OTA layout but have
a canonical Full image with a larger partition table. Their Full image keeps
the ordinary target identity for future mOTA updates, but the first install is
always its exact `-merged.bin` over USB: LoRa mOTA intentionally rejects a
package whose partition signature differs from the running layout. During this
transition the portable artifact remains published for already-installed
nodes. The current migration set is LilyGo T3S3 SX1262/SX1276 repeater and
room-server roles, Station G2 repeater and room-server roles, and ThinkNode M2
repeater and room-server roles. Station G2 moves to two 6.25 MiB slots;
the 4 MiB T3S3 and ThinkNode M2 boards move to two 1984 KiB slots.
## Maintaining the runtime directions
The online picker and downloadable HTML use the same command renderer.
@@ -65,12 +65,35 @@ for target in LilyGo_TLora_V2_1_1_6_repeater_observer_mqtt_ \
fi
done
# Partition-changing normal roles publish a canonical Full image alongside
# their portable compatibility image. They must never be mistaken for the
# same-partition policy, which is the only policy allowed to install through
# the automatic mOTA target alias.
for target in LilyGo_T3S3_sx1262_repeater \
LilyGo_T3S3_sx1262_room_server \
LilyGo_T3S3_sx1276_repeater \
LilyGo_T3S3_sx1276_room_server \
Station_G2_repeater Station_G2_room_server \
ThinkNode_M2_Repeater ThinkNode_M2_room_server; do
is_esp32_partition_migration_full_target "$target" \
|| fail "missed partition-migration Full target $target"
if is_esp32_full_only_bulk_target "$target"; then
fail "partition-migration target was incorrectly made FULL-only: $target"
fi
done
[ "$(get_exact_identity_full_pio_env heltec_v4_r8_repeater)" \
= heltec_v4_r8_repeater_observer_mqtt ] \
|| fail "V4 R8 Full lost its observer feature base"
[ "$(get_exact_identity_full_pio_env heltec_v4_r8_sensor)" \
= heltec_v4_r8_sensor ] \
|| fail "V4 R8 sensor should retain its own feature base"
[ "$(get_exact_identity_full_pio_env Station_G2_repeater)" \
= Station_G2_repeater_observer_mqtt ] \
|| fail "Station G2 migration Full lost its observer feature base"
[ "$(get_exact_identity_full_pio_env LilyGo_T3S3_sx1262_room_server)" \
= LilyGo_T3S3_sx1262_room_server_observer_mqtt ] \
|| fail "T3S3 migration Full lost its observer feature base"
[ "$(get_exact_identity_full_migration_target heltec_v4_r8_repeater_observer_mqtt)" \
= heltec_v4_r8_repeater ] \
|| fail "legacy V4 R8 observer lacks its canonical migration target"
@@ -80,6 +103,9 @@ fi
if get_exact_identity_full_migration_target heltec_v4_r8_repeater_observer_mqtt_sim >/dev/null; then
fail "simulated observer target unexpectedly received a migration alias"
fi
if get_exact_identity_full_migration_target Station_G2_repeater_observer_mqtt >/dev/null; then
fail "partition-changing Station G2 observer unexpectedly received an mOTA alias"
fi
# Portable images and exact reduced OTA identities must not be consolidated.
for target in heltec_v4_r8_repeater heltec_v4_r8_room_server \
@@ -231,6 +257,18 @@ done
|| fail "matrix did not build exact FULL-only identities"
)
(
calls=()
run_logged_build_targets() {
calls+=("$BUILD_PROFILE_EFFECTIVE:$ESP32_FULL_BUILD:$FIRMWARE_FILENAME_INFIX:$*")
}
run_partition_migration_full_esp32_profile \
Station_G2_repeater Station_G2_room_server Heltec_v3_repeater >/dev/null
[ "${#calls[@]}" -eq 1 ] || fail "partition-migration pass emitted the wrong number of builds"
[ "${calls[0]}" = 'full:1:full-logging:Station_G2_repeater Station_G2_room_server' ] \
|| fail "partition-migration pass lost canonical normal identities"
)
# The default build settings retain power saving; this does not overwrite
# preferences already stored on a device.
[ "$FIRMWARE_PROFILE_OVERRIDE" = cascade ] || fail "default profile changed"