mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 22:42:25 +00:00
Select registered OTA slots and verify LoRa staging geometry
This commit is contained in:
1 parent
c55022b001
commit
1bc900c33e
3 files changed
+36
-33
No files matched your search
@@ -152,14 +152,6 @@ esp_partition_t rawPartition(uint32_t address, uint32_t size, const char* label)
|
||||
return part;
|
||||
}
|
||||
|
||||
esp_partition_t rawAppPartition(uint32_t address, uint32_t size,
|
||||
esp_partition_subtype_t subtype, const char* label) {
|
||||
esp_partition_t part = rawPartition(address, size, label);
|
||||
part.type = ESP_PARTITION_TYPE_APP;
|
||||
part.subtype = subtype;
|
||||
return part;
|
||||
}
|
||||
|
||||
bool rangesOverlap(uint32_t first_address, uint32_t first_size,
|
||||
uint32_t second_address, uint32_t second_size) {
|
||||
const uint64_t first_end = static_cast<uint64_t>(first_address) + first_size;
|
||||
@@ -398,7 +390,7 @@ bool stageResumeSlot(uint8_t slot) {
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool resumeLegacyOtaReceiver(const migration::TargetPlan& plan) {
|
||||
bool resumeLegacyOtaReceiver() {
|
||||
Preferences migration_nvs;
|
||||
if (!migration_nvs.begin(kMigrationNvsNamespace, false)) {
|
||||
strcpy(status_text, "Could not read old LoRa application slot");
|
||||
@@ -410,12 +402,12 @@ bool resumeLegacyOtaReceiver(const migration::TargetPlan& plan) {
|
||||
strcpy(status_text, "Expanded layout ready; no LoRa resume record");
|
||||
return false;
|
||||
}
|
||||
const esp_partition_t old_app = slot == 0
|
||||
? rawAppPartition(plan.layout.app0_address, plan.layout.app0_size,
|
||||
ESP_PARTITION_SUBTYPE_APP_OTA_0, "app0")
|
||||
: rawAppPartition(plan.layout.app1_address, plan.layout.app1_size,
|
||||
ESP_PARTITION_SUBTYPE_APP_OTA_1, "app1");
|
||||
const esp_err_t selected = esp_ota_set_boot_partition(&old_app);
|
||||
const esp_partition_t* old_app = esp_partition_find_first(
|
||||
ESP_PARTITION_TYPE_APP,
|
||||
slot == 0 ? ESP_PARTITION_SUBTYPE_APP_OTA_0 : ESP_PARTITION_SUBTYPE_APP_OTA_1,
|
||||
nullptr);
|
||||
const esp_err_t selected = old_app
|
||||
? esp_ota_set_boot_partition(old_app) : ESP_ERR_NOT_FOUND;
|
||||
if (selected != ESP_OK) {
|
||||
snprintf(status_text, sizeof(status_text), "Could not resume old LoRa app: %s",
|
||||
errName(selected));
|
||||
@@ -569,20 +561,16 @@ void runMigration() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// The OTA-select data records a slot identity. Select a descriptor for the
|
||||
// bridge's expanded slot before replacing the table so the next boot runs
|
||||
// the bridge at the correct address under the new layout.
|
||||
// The OTA-select data records a slot identity. Select the bridge's currently
|
||||
// registered legacy slot; after the table change, that slot identity points
|
||||
// at the verified bridge copy under the expanded layout.
|
||||
#if defined(MESHCORE_MIGRATION_RESUME_OTA)
|
||||
const bool bridge_in_app0 = resume.bridge_slot == 0;
|
||||
#else
|
||||
const bool bridge_in_app0 = true;
|
||||
#endif
|
||||
const esp_partition_t target_bridge = bridge_in_app0
|
||||
? rawAppPartition(plan->layout.app0_address, plan->layout.app0_size,
|
||||
ESP_PARTITION_SUBTYPE_APP_OTA_0, "app0")
|
||||
: rawAppPartition(plan->layout.app1_address, plan->layout.app1_size,
|
||||
ESP_PARTITION_SUBTYPE_APP_OTA_1, "app1");
|
||||
const esp_err_t select_result = esp_ota_set_boot_partition(&target_bridge);
|
||||
const esp_err_t select_result = esp_ota_set_boot_partition(
|
||||
bridge_in_app0 ? refs.app0 : refs.app1);
|
||||
if (select_result != ESP_OK) {
|
||||
snprintf(status_text, sizeof(status_text), "Could not select bridge slot: %s",
|
||||
errName(select_result));
|
||||
@@ -700,8 +688,7 @@ void setup() {
|
||||
if (restoreStagedIdentity()) {
|
||||
strcpy(status_text, "Expanded layout ready");
|
||||
#if defined(MESHCORE_MIGRATION_RESUME_OTA)
|
||||
const migration::TargetPlan* plan = migration::targetForFlash(flash_bytes);
|
||||
if (plan) resumeLegacyOtaReceiver(*plan);
|
||||
resumeLegacyOtaReceiver();
|
||||
#endif
|
||||
}
|
||||
} else if (findPartitions(refs, geometry)
|
||||
|
||||
@@ -61,13 +61,17 @@ def partition_entries(table: bytes) -> dict[str, tuple[int, int]]:
|
||||
return entries
|
||||
|
||||
|
||||
def mota_full(image: bytes, identity: FwIdent) -> bytes:
|
||||
def mota_full(image: bytes, identity: FwIdent, block_size: int) -> bytes:
|
||||
# Device-side seeders keep at most 1024 Merkle leaves in 4 KiB scratch.
|
||||
# The bridge fits 1 KiB blocks; a roughly 2 MiB Full image needs 2 KiB.
|
||||
if (len(image) + block_size - 1) // block_size > 1024:
|
||||
raise ValueError("mOTA image exceeds the device seeder's 1024-block limit")
|
||||
manifest = build_manifest(
|
||||
target_id=identity.target_id,
|
||||
fw_version=identity.fw_version,
|
||||
image_size=len(image),
|
||||
payload=image,
|
||||
block_size=1024,
|
||||
block_size=block_size,
|
||||
image_hash=hashlib.sha256(image).digest(),
|
||||
codec_id=CODEC_FULL,
|
||||
is_full=True,
|
||||
@@ -80,6 +84,16 @@ def mota_full(image: bytes, identity: FwIdent) -> bytes:
|
||||
return package
|
||||
|
||||
|
||||
def check_esp32_stage(package: bytes, image: bytes, slot_bytes: int) -> None:
|
||||
# Mirror the ESP32 FULL staging geometry: the payload is written at the
|
||||
# start of the inactive app, with metadata in aligned sectors at its end.
|
||||
metadata_bytes = len(package) - len(image) - 5
|
||||
metadata_flush = (metadata_bytes + 5 + 4095) & ~4095
|
||||
if (len(package) > slot_bytes or metadata_flush > 65536
|
||||
or len(image) > slot_bytes - metadata_flush):
|
||||
raise ValueError("mOTA image cannot be staged in the inactive ESP32 slot")
|
||||
|
||||
|
||||
def readme(board: str, spec: dict, version: str, source: str) -> str:
|
||||
return f"""# {board} in-place partition migration
|
||||
|
||||
@@ -166,12 +180,14 @@ def package_board(name: str, spec: dict, build_dir: Path, output_dir: Path,
|
||||
|
||||
files = {
|
||||
"wifi-bridge.bin": wifi_bridge,
|
||||
"lora-bridge.mota": mota_full(lora_bridge, bridge_ident),
|
||||
"lora-bridge.mota": mota_full(lora_bridge, bridge_ident, 1024),
|
||||
"full-application.bin": full_image,
|
||||
"full-application.mota": mota_full(full_image, full_ident),
|
||||
"full-application.mota": mota_full(full_image, full_ident, 2048),
|
||||
"target-partitions.bin": table,
|
||||
"capabilities.json": capability_path.read_bytes(),
|
||||
}
|
||||
check_esp32_stage(files["lora-bridge.mota"], lora_bridge, LEGACY_SLOT_BYTES)
|
||||
check_esp32_stage(files["full-application.mota"], full_image, slot_bytes)
|
||||
files["README.md"] = readme(name, spec, version, source).encode()
|
||||
manifest = {
|
||||
"board": name,
|
||||
|
||||
@@ -54,13 +54,13 @@ class HeltecV4PartitionMigratorTest(unittest.TestCase):
|
||||
migration_body = source[source.index("void runMigration()") :]
|
||||
self.assertLess(migration_body.index("stageLegacyIdentity()"),
|
||||
migration_body.index("copyAndVerify(*running"))
|
||||
self.assertLess(migration_body.index("esp_ota_set_boot_partition(&target_bridge)"),
|
||||
self.assertLess(migration_body.index("esp_ota_set_boot_partition(\n bridge_in_app0 ? refs.app0 : refs.app1)"),
|
||||
migration_body.index("publishExpandedPartitionTable(*plan)"))
|
||||
setup_body = source[source.index("void setup()") :]
|
||||
self.assertIn("restoreStagedIdentity()", setup_body)
|
||||
self.assertIn("resumeLegacyOtaReceiver(*plan)", setup_body)
|
||||
self.assertIn("resumeLegacyOtaReceiver()", setup_body)
|
||||
self.assertLess(setup_body.index("restoreStagedIdentity()"),
|
||||
setup_body.index("resumeLegacyOtaReceiver(*plan)"))
|
||||
setup_body.index("resumeLegacyOtaReceiver()"))
|
||||
self.assertIn("validLegacyOtaReceiver(*old_receiver)", migration_body)
|
||||
self.assertIn("copyAndVerify(*refs.app1", migration_body)
|
||||
self.assertIn("stageResumeSlot(resume.resume_slot)", migration_body)
|
||||
|
||||
Reference in new issue
Block a user