Merge branch 'keymindCascade' of github.com:mikecarper/MeshCore into keymindCascade
This commit is contained in:
commit
08bf174ea3
10 files changed
+5136
-95
No files matched your search
+150
-1
@@ -37,7 +37,7 @@ fail() {
|
||||
[ "$OPTION3_BUILD_WORKERS" -eq 1 ] \
|
||||
|| fail "logging matrix permits concurrent PlatformIO target builds"
|
||||
|
||||
for utility in profile_switch_t096_sx1262 xiao_s3_partition_migrator \
|
||||
for utility in profile_switch_t096_sx1262 profile_four_tx_v4_rx xiao_s3_partition_migrator \
|
||||
heltec_v4_partition_migrator_test_hold xiao_s3_partition_legacy_seed \
|
||||
esp32_4mb_partition_migrator_lora \
|
||||
heltec_v4_partition_migrator_lora_repeater \
|
||||
@@ -438,6 +438,155 @@ require(rak_usb, "build_flags", "FORCE_GPS_ALIVE")
|
||||
# replace each with that exact board's Full target.
|
||||
init_project_context >/dev/null
|
||||
|
||||
# Expanded partitions make Full the ordinary ESP32 node release. Legacy lean
|
||||
# names still address installed firmware and must remain directly buildable,
|
||||
# but cannot reappear in either the all-role or a role-specific release list.
|
||||
for resolver in resolve_all_firmwares resolve_repeater_firmwares \
|
||||
resolve_room_server_firmwares resolve_sensor_firmwares; do
|
||||
while IFS= read -r release_env; do
|
||||
[ "${PIO_ENV_PLATFORM_BY_NAME[$release_env]:-}" = ESP32_PLATFORM ] || continue
|
||||
if is_lora_ota_no_external_sensors_target "$release_env"; then
|
||||
fail "$resolver republishes reduced ESP32 alias $release_env"
|
||||
fi
|
||||
is_redundant_bulk_build_target "$release_env" \
|
||||
&& fail "$resolver republishes utility or legacy alias $release_env"
|
||||
if is_kiss_modem_target "$release_env"; then
|
||||
if is_esp32_full_release_build_target "$release_env"; then
|
||||
fail "$release_env host modem was promoted into the node Full profile"
|
||||
fi
|
||||
else
|
||||
is_esp32_full_release_build_target "$release_env" \
|
||||
|| fail "$resolver selected ESP32 node without a Full release: $release_env"
|
||||
fi
|
||||
done < <("$resolver")
|
||||
done
|
||||
|
||||
# Cover the former allowlist gaps as well as migration hardware and a named
|
||||
# Full Companion. Their ordinary release profile is Full; the older helper
|
||||
# continues to describe the narrower same-partition OTA identity contract.
|
||||
for release_env in Station_G3_ESP32_r2_repeater WHY2025_badge_repeater_ \
|
||||
GEPRC_Linkflow_900_repeater Generic_ESPNOW_repeatr \
|
||||
LilyGo_Tlora_C6_room_server_ Meshadventurer_sx1262_repeater \
|
||||
Ebyte_EoRa-S3_Repeater Heltec_ct62_sensor Station_G2_companion_radio_full; do
|
||||
is_esp32_full_release_build_target "$release_env" \
|
||||
|| fail "$release_env retained a standard ordinary ESP32 release slot"
|
||||
done
|
||||
if is_esp32_full_only_bulk_target Ebyte_EoRa-S3_Repeater; then
|
||||
fail "release consolidation incorrectly advertised same-partition EoRa migration"
|
||||
fi
|
||||
if is_esp32_full_release_build_target RAK_3401_repeater; then
|
||||
fail "nRF52 role acquired ESP32 Full-only release policy"
|
||||
fi
|
||||
|
||||
for direct_env in Heltec_v3_repeater_lora_ota_no_external_sensors \
|
||||
Ebyte_EoRa-S3_Repeater_lora_ota_no_external_sensors \
|
||||
Heltec_v3_companion_radio_usb heltec_v4_partition_migrator \
|
||||
heltec_v4_partition_migrator_lora_repeater; do
|
||||
is_supported_build_env "$direct_env" \
|
||||
|| fail "ordinary consolidation removed direct compatibility target $direct_env"
|
||||
[ -n "$(get_pio_build_env "$direct_env")" ] \
|
||||
|| fail "$direct_env no longer resolves a direct PlatformIO recipe"
|
||||
done
|
||||
(
|
||||
BATCH_BUILD_MODE=0
|
||||
SINGLE_TARGET_FULL_BUILD=0
|
||||
EXACT_IDENTITY_FULL_BUILD=0
|
||||
BUILD_PROFILE_OVERRIDE=standard
|
||||
BUILD_PROFILE_EXPLICIT=1
|
||||
RESOLVED_BUILD_TARGETS=(Ebyte_EoRa-S3_Repeater)
|
||||
configure_effective_build_profile build-firmware >/dev/null
|
||||
[ "$BUILD_PROFILE_EFFECTIVE" = standard ] \
|
||||
|| fail "explicit standard recovery profile was promoted to Full"
|
||||
[ "${RESOLVED_BUILD_TARGETS[*]}" = Ebyte_EoRa-S3_Repeater ] \
|
||||
|| fail "explicit recovery profile changed its deployed target identity"
|
||||
BUILD_PROFILE_OVERRIDE=auto
|
||||
BUILD_PROFILE_EXPLICIT=0
|
||||
RESOLVED_BUILD_TARGETS=(Heltec_v3_repeater_lora_ota_no_external_sensors)
|
||||
configure_effective_build_profile build-firmware >/dev/null
|
||||
[ "$BUILD_PROFILE_EFFECTIVE" = standard ] \
|
||||
|| fail "direct reduced compatibility request was promoted to Full"
|
||||
[ "${RESOLVED_BUILD_TARGETS[*]}" = Heltec_v3_repeater_lora_ota_no_external_sensors ] \
|
||||
|| fail "direct reduced request changed its deployed target identity"
|
||||
)
|
||||
|
||||
# Named Full Companion already selects its own combined profile. In a bulk
|
||||
# pass it must compile the real source recipe, including generated Full names,
|
||||
# without entering the infrastructure promotion path. Stop at recipe capture,
|
||||
# before any dependency install, PlatformIO invocation, or artifact collection.
|
||||
(
|
||||
BATCH_BUILD_MODE=1
|
||||
BUILD_PROFILE_EXPLICIT=0
|
||||
BUILD_PROFILE_EFFECTIVE=standard
|
||||
ESP32_FULL_BUILD=0
|
||||
FIRMWARE_VERSION=vtest-full-companion-routing
|
||||
RESUME_BUILD_OUTPUT=0
|
||||
companion_probe_marker=$(mktemp)
|
||||
trap 'rm -f "$companion_probe_marker"' EXIT
|
||||
compute_build_recipe_digest() {
|
||||
printf '%s|%s|%s\n' "$1" "$3" "$ESP32_FULL_BUILD" > "$companion_probe_marker"
|
||||
return 77
|
||||
}
|
||||
for full_companion in Heltec_v3_companion_radio_full Heltec_ct62_companion_radio_full; do
|
||||
source_recipe=$(get_pio_build_env "$full_companion")
|
||||
[ "$(get_exact_identity_full_pio_env "$full_companion")" = "$source_recipe" ] \
|
||||
|| fail "$full_companion tried to compile a logical Full name as its source recipe"
|
||||
: > "$companion_probe_marker"
|
||||
if build_firmware_one_profile "$full_companion" >/dev/null; then
|
||||
fail "$full_companion bypassed the pre-PlatformIO recipe probe"
|
||||
fi
|
||||
[ "$(cat "$companion_probe_marker")" = "$full_companion|$source_recipe|0" ] \
|
||||
|| fail "$full_companion entered infrastructure promotion or lost its real source recipe"
|
||||
done
|
||||
)
|
||||
|
||||
# Exercise actual matrix scheduling while replacing only its final build
|
||||
# dispatch. This verifies the portable pass cannot return through an old
|
||||
# allowlist gap, migration target, or later duplicate profile pass.
|
||||
(
|
||||
REQUIRE_OTA_UPDATES=0
|
||||
BATCH_BUILD_MODE=1
|
||||
BUILD_PROFILE_EXPLICIT=0
|
||||
BUILD_PROFILE_EFFECTIVE=standard
|
||||
ESP32_FULL_BUILD=0
|
||||
matrix_builds=()
|
||||
run_logged_build_targets() {
|
||||
local target
|
||||
for target in "$@"; do
|
||||
matrix_builds+=("$target:$ESP32_FULL_BUILD")
|
||||
if [ "${PIO_ENV_PLATFORM_BY_NAME[$target]:-}" = ESP32_PLATFORM ] \
|
||||
&& ! is_kiss_modem_target "$target" \
|
||||
&& ! is_companion_radio_full_target "$target"; then
|
||||
[ "$ESP32_FULL_BUILD" = 1 ] \
|
||||
|| fail "ordinary matrix scheduled a portable ESP32 node: $target"
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
run_logging_matrix_build_targets \
|
||||
Station_G3_ESP32_r2_repeater Ebyte_EoRa-S3_Repeater \
|
||||
Generic_ESPNOW_repeatr heltec_v4_room_server heltec_v4_sensor \
|
||||
Station_G2_companion_radio_full heltec_v4_kiss_modem \
|
||||
RAK_3401_repeater_lora_ota_no_external_sensors >/dev/null
|
||||
for target in Station_G3_ESP32_r2_repeater Ebyte_EoRa-S3_Repeater \
|
||||
Generic_ESPNOW_repeatr heltec_v4_room_server heltec_v4_sensor; do
|
||||
matches=0
|
||||
for call in "${matrix_builds[@]}"; do
|
||||
if [ "$call" = "$target:1" ]; then matches=$((matches + 1)); fi
|
||||
done
|
||||
[ "$matches" -eq 1 ] \
|
||||
|| fail "ordinary matrix built $target $matches times instead of one Full image"
|
||||
done
|
||||
for expected in Station_G2_companion_radio_full:0 heltec_v4_kiss_modem:0 \
|
||||
RAK_3401_repeater_lora_ota_no_external_sensors:0; do
|
||||
found=0
|
||||
for call in "${matrix_builds[@]}"; do
|
||||
if [ "$call" = "$expected" ]; then found=$((found + 1)); fi
|
||||
done
|
||||
[ "$found" -eq 1 ] \
|
||||
|| fail "matrix changed the distinct Companion/KISS/nRF52 recipe: $expected"
|
||||
done
|
||||
)
|
||||
|
||||
# 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" \
|
||||
|
||||
@@ -20,6 +20,96 @@ def record(target: str, profile: str = "full", platform: str = "ESP32_PLATFORM",
|
||||
|
||||
|
||||
class ReleaseFullSelectionTest(unittest.TestCase):
|
||||
def test_resume_discards_everyday_esp32_standard_profiles_with_or_without_full(self):
|
||||
for target in ("heltec_v4_repeater", "heltec_v4_room_server", "heltec_v4_sensor",
|
||||
"Station_G3_ESP32_r2_repeater", "Ebyte_EoRa-S3_Repeater",
|
||||
"Station_G2_companion_radio_full"):
|
||||
primary = record(target)
|
||||
for profile in ("standard", "auto", "reduced"):
|
||||
stale = record(target, profile)
|
||||
with self.subTest(target=target, profile=profile):
|
||||
self.assertEqual(select_ordinary_full_records([stale]), [])
|
||||
for inputs in ([primary, stale], [stale, primary]):
|
||||
selected = select_ordinary_full_records(inputs)
|
||||
self.assertEqual(selected, [primary])
|
||||
self.assertIs(selected[0], primary)
|
||||
|
||||
def test_reduced_esp32_identities_are_not_relabelled_as_full_releases(self):
|
||||
for base in ("heltec_v4_repeater", "Ebyte_EoRa-S3_Repeater",
|
||||
"LilyGo_Tlora_C6_room_server_"):
|
||||
primary = record(base)
|
||||
alias = base.rstrip("_") + "_lora_ota_no_external_sensors"
|
||||
for profile in ("standard", "auto", "full"):
|
||||
legacy = record(alias, profile)
|
||||
with self.subTest(base=base, profile=profile):
|
||||
self.assertEqual(select_ordinary_full_records([legacy]), [])
|
||||
self.assertEqual(select_ordinary_full_records([legacy, primary]), [primary])
|
||||
self.assertEqual(primary["manifest"]["target"], base)
|
||||
self.assertEqual(legacy["manifest"]["target"], alias)
|
||||
|
||||
def test_legacy_companion_transport_names_do_not_survive_resumed_releases(self):
|
||||
primary = record("Station_G2_companion_radio_full")
|
||||
for transport in ("usb", "ble", "wifi"):
|
||||
for profile in ("standard", "full"):
|
||||
legacy = record("Station_G2_companion_radio_" + transport, profile)
|
||||
with self.subTest(transport=transport, profile=profile):
|
||||
self.assertEqual(select_ordinary_full_records([legacy]), [])
|
||||
self.assertEqual(select_ordinary_full_records([legacy, primary]), [primary])
|
||||
terminal = record("Station_G2_terminal_chat")
|
||||
self.assertEqual(select_ordinary_full_records([terminal, primary]), [primary])
|
||||
|
||||
def test_migration_and_local_hil_utilities_stay_out_of_ordinary_release(self):
|
||||
primary = record("heltec_v4_repeater")
|
||||
for target in ("heltec_v4_partition_migrator", "Xiao_S3_WIO_partition_expander",
|
||||
"xiao_s3_partition_legacy_seed", "profile_switch_heltec_v4",
|
||||
"profile_fixed_esp32_tx", "profile_four_tx_v4_rx",
|
||||
"heltec_v4_repeater_legacy_partition_test",
|
||||
"Heltec_v3_repeater_observer_mqtt_sim"):
|
||||
for profile in ("standard", "full"):
|
||||
utility = record(target, profile)
|
||||
with self.subTest(target=target, profile=profile):
|
||||
self.assertEqual(select_ordinary_full_records([utility]), [])
|
||||
self.assertEqual(select_ordinary_full_records([utility, primary]), [primary])
|
||||
|
||||
def test_kiss_is_a_distinct_host_modem_role_and_not_a_reduced_node(self):
|
||||
companion = record("Station_G2_companion_radio_full")
|
||||
for target in ("Station_G2_kiss_modem", "nibble_screen_connect_kiss_modem_"):
|
||||
for profile in ("standard", "auto", "full"):
|
||||
modem = record(target, profile)
|
||||
with self.subTest(target=target, profile=profile):
|
||||
self.assertEqual(select_ordinary_full_records([modem]), [modem])
|
||||
self.assertCountEqual(select_ordinary_full_records([companion, modem]),
|
||||
[companion, modem])
|
||||
|
||||
def test_unverified_selected_esp32_full_image_cannot_enter_release(self):
|
||||
for target in ("heltec_v4_room_server", "Station_G2_companion_radio_full"):
|
||||
for verified in (False, None):
|
||||
image = record(target)
|
||||
image["manifest"]["verified"] = verified
|
||||
with self.subTest(target=target, verified=verified):
|
||||
with self.assertRaisesRegex(ValueError, "verified"):
|
||||
select_ordinary_full_records([image])
|
||||
|
||||
def test_exact_hardware_and_application_roles_remain_distinct_full_records(self):
|
||||
images = [record(target) for target in (
|
||||
"heltec_v4_repeater", "heltec_v4_tft_repeater", "heltec_v4_r8_repeater",
|
||||
"heltec_v4_room_server", "heltec_v4_sensor",
|
||||
"heltec_v4_2_v4_3_companion_radio_full_femon",
|
||||
"heltec_v4_tft_companion_radio_full_femon",
|
||||
)]
|
||||
for inputs in (images, list(reversed(images))):
|
||||
selected = select_ordinary_full_records(inputs)
|
||||
self.assertCountEqual(selected, images)
|
||||
self.assertEqual(len(selected), len(images))
|
||||
|
||||
def test_other_platform_profiles_and_reduced_identities_are_unchanged(self):
|
||||
for platform in ("NRF52_PLATFORM", "RP2040_PLATFORM", "STM32_PLATFORM"):
|
||||
for profile in ("standard", "auto", "full"):
|
||||
for suffix in ("", "_lora_ota_no_external_sensors"):
|
||||
image = record("other_repeater" + suffix, profile, platform)
|
||||
with self.subTest(platform=platform, profile=profile, suffix=suffix):
|
||||
self.assertEqual(select_ordinary_full_records([image]), [image])
|
||||
|
||||
def test_full_uart_source_substitution_requires_linked_driver_proof(self):
|
||||
for base in ("Heltec_v3_repeater", "Heltec_WSL3_repeater", "RAK_3112_repeater",
|
||||
"LilyGo_TLora_V2_1_1_6_repeater"):
|
||||
@@ -76,7 +166,8 @@ class ReleaseFullSelectionTest(unittest.TestCase):
|
||||
wireless = record(base + "_bridge_espnow", profile, capabilities=("bridge.espnow",))
|
||||
for inputs in ([observer, uart, wireless], [wireless, uart, observer]):
|
||||
with self.subTest(profile=profile):
|
||||
self.assertCountEqual(select_ordinary_full_records(inputs), [observer, uart])
|
||||
expected = [observer, uart] if profile == "full" else [observer]
|
||||
self.assertCountEqual(select_ordinary_full_records(inputs), expected)
|
||||
for fault in ("missing_capability", "missing_proof", "false_proof", "unverified"):
|
||||
normal = record(base, capabilities=("bridge.rs232", "bridge.espnow"))
|
||||
manifest = normal["manifest"]
|
||||
@@ -109,8 +200,12 @@ class ReleaseFullSelectionTest(unittest.TestCase):
|
||||
for profile in ("standard", "full"):
|
||||
legacy = record(base + "_bridge_espnow", profile, capabilities=("bridge.espnow",))
|
||||
with self.subTest(fault=fault, profile=profile):
|
||||
self.assertCountEqual(select_ordinary_full_records([observer, legacy]),
|
||||
[observer, legacy])
|
||||
if fault == "unverified":
|
||||
with self.assertRaisesRegex(ValueError, "verified"):
|
||||
select_ordinary_full_records([observer, legacy])
|
||||
else:
|
||||
expected = [observer, legacy] if profile == "full" else [observer]
|
||||
self.assertCountEqual(select_ordinary_full_records([observer, legacy]), expected)
|
||||
|
||||
def test_meshtower_sd_primary_filters_stale_internal_images_without_relabeling(self):
|
||||
primary = "Heltec_tower_v2_sdcard_repeater_lora_ota_no_external_sensors"
|
||||
@@ -151,7 +246,7 @@ class ReleaseFullSelectionTest(unittest.TestCase):
|
||||
variants.append(item)
|
||||
self.assertEqual(select_ordinary_full_records(variants), variants)
|
||||
|
||||
def test_resume_filters_sibling_full_images_only(self):
|
||||
def test_resume_filters_standard_and_sibling_full_images(self):
|
||||
inputs = [
|
||||
record("Heltec_v3_repeater_observer_mqtt"),
|
||||
record("Heltec_v3_repeater_bridge_espnow"),
|
||||
@@ -178,7 +273,7 @@ class ReleaseFullSelectionTest(unittest.TestCase):
|
||||
"Meshadventurer_sx1262_repeater_bridge_espnow",
|
||||
"RAK_4631_repeater",
|
||||
})
|
||||
self.assertEqual(len(selected), 9) # standard V3 and both TLora Full profiles remain
|
||||
self.assertEqual(len(selected), 8) # both TLora Full transport profiles remain
|
||||
|
||||
def test_mke_combined_repeater_filters_stale_bridge_identities_without_relabeling(self):
|
||||
primary = record("MKE_s3_repeater", capabilities=("bridge.rs232", "bridge.espnow"))
|
||||
@@ -237,12 +332,12 @@ class ReleaseFullSelectionTest(unittest.TestCase):
|
||||
with self.assertRaisesRegex(ValueError, "qualified combined repeater"):
|
||||
select_ordinary_full_records([primary])
|
||||
|
||||
def test_mke_full_contract_does_not_apply_to_other_platforms_or_standard_profiles(self):
|
||||
def test_other_platform_passthrough_and_standard_mke_is_not_a_release(self):
|
||||
other_platform = record("RAK_4631_repeater", platform="NRF52_PLATFORM")
|
||||
other_platform["manifest"].update(verified=None, capabilities=None, verification=None)
|
||||
standard_mke = record("MKE_s3_repeater", "standard")
|
||||
inputs = [other_platform, standard_mke]
|
||||
self.assertEqual(select_ordinary_full_records(inputs), inputs)
|
||||
self.assertEqual(select_ordinary_full_records(inputs), [other_platform])
|
||||
|
||||
def test_dedicated_espnow_survives_when_plain_image_lacks_the_capability(self):
|
||||
inputs = [record("heltec_v4_tft_repeater"),
|
||||
@@ -275,7 +370,12 @@ class ReleaseFullSelectionTest(unittest.TestCase):
|
||||
capabilities=("bridge.espnow",))
|
||||
for inputs in ([primary, legacy], [legacy, primary]):
|
||||
with self.subTest(profile=profile, fault=fault, primary_first=inputs[0] is primary):
|
||||
self.assertCountEqual(select_ordinary_full_records(inputs), inputs)
|
||||
if fault == "false_verified":
|
||||
with self.assertRaisesRegex(ValueError, "verified"):
|
||||
select_ordinary_full_records(inputs)
|
||||
else:
|
||||
expected = inputs if profile == "full" else [primary]
|
||||
self.assertCountEqual(select_ordinary_full_records(inputs), expected)
|
||||
|
||||
def test_stale_plain_cannot_displace_the_observer_proving_the_combined_driver(self):
|
||||
for profile in ("full", "standard"):
|
||||
@@ -324,7 +424,8 @@ class ReleaseFullSelectionTest(unittest.TestCase):
|
||||
primary = record("heltec_v4_tft_repeater", capabilities=("bridge.espnow",))
|
||||
primary["manifest"]["verified"] = False
|
||||
legacy = record("heltec_v4_tft_repeater_bridge_espnow", capabilities=("bridge.espnow",))
|
||||
self.assertEqual(select_ordinary_full_records([primary, legacy]), [primary, legacy])
|
||||
with self.assertRaisesRegex(ValueError, "verified"):
|
||||
select_ordinary_full_records([primary, legacy])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user