diff --git a/scripts/generate_mesh_america_release.py b/scripts/generate_mesh_america_release.py index 91f2d6c67..af1087652 100644 --- a/scripts/generate_mesh_america_release.py +++ b/scripts/generate_mesh_america_release.py @@ -41,7 +41,9 @@ process.stdout.write(JSON.stringify(Object.fromEntries( def file_entry(path, platform, tag, repo): - if path.name.endswith("-merged.bin"): + if platform == "noflash": + kind, title = "download", path.suffix[1:].upper() + " download" + elif path.name.endswith("-merged.bin"): kind, title = "flash-wipe", "USB full install (bootloader + partitions + app)" elif path.suffix == ".bin" and platform == "esp32": kind, title = "flash-update", "App update (matching partition layout only)" @@ -119,10 +121,13 @@ def generate(catalog, plan, family, records, profiles, repo): platform = PLATFORMS[row["platform"]] indexes = by_identity.get(target) or by_identity.get(row["target"]) if not indexes: - indexes = by_hardware.get((platform, parsed["hardware"]), set()) + indexes = set(by_hardware.get((platform, parsed["hardware"]), set())) + # A download-only card is an installer restriction, not a chip. + # Retain that restriction for new profiles of the same hardware. + indexes.update(by_hardware.get(("noflash", parsed["hardware"]), set())) if len(indexes) != 1: raise ValueError(f"new profile needs an exact hardware card mapping: {target}") - if any(catalog["device"][i]["type"] != platform for i in indexes): + if any(catalog["device"][i]["type"] not in (platform, "noflash") for i in indexes): raise ValueError("firmware platform differs from its hardware card") role, title = {"repeater": ("repeater", "Repeater"), "room": ("roomServer", "Room Server"), @@ -168,12 +173,16 @@ def generate(catalog, plan, family, records, profiles, repo): "CAPABILITIES - " + url + quote(cap), "MEMORY - " + url + quote(memory), f"SOURCE - https://github.com/{repo}/tree/{row.get('source_commit', plan['source'])}", f"RELEASE - https://github.com/{repo}/releases/tag/{tag}"] - entry = {"role": role, "title": title, "subTitle": target + " - " + profile, - "version": {family: {"notes": "\n\n".join(notes), - "files": [file_entry(path, platform, tag, repo) for path in sorted(files, - key=lambda p: (not p.name.endswith('-merged.bin'), p.suffix != '.bin', p.name))]}}} for index in sorted(indexes): - result["device"][index]["firmware"].append(copy.deepcopy(entry)) + install_platform = catalog["device"][index]["type"] + card_notes = list(notes) + if install_platform == "noflash": + card_notes.append("MESH AMERICA - Downloads only. In-app flashing is not qualified for this hardware card.") + entry = {"role": role, "title": title, "subTitle": target + " - " + profile, + "version": {family: {"notes": "\n\n".join(card_notes), + "files": [file_entry(path, install_platform, tag, repo) for path in sorted(files, + key=lambda p: (not p.name.endswith('-merged.bin'), p.suffix != '.bin', p.name))]}}} + result["device"][index]["firmware"].append(entry) result["device"] = [device for device in result["device"] if device["firmware"]] for device in result["device"]: device["firmware"].sort(key=lambda entry: (entry["role"], entry["subTitle"])) diff --git a/test/test_release_publication.py b/test/test_release_publication.py index 1a2678ccb..1231066e9 100644 --- a/test/test_release_publication.py +++ b/test/test_release_publication.py @@ -112,6 +112,34 @@ class ReleasePublicationTest(unittest.TestCase): catalogs.generate(self.template(), {"version": "1.17.1.9", "source": SOURCE, "groups": []}, FAMILY, [(row, FAMILY, files)], catalogs.target_profiles(targets), "mikecarper/MeshCore") + def test_catalog_retains_download_only_cards_for_new_sensor_profiles(self): + with tempfile.TemporaryDirectory() as temp: + directory = Path(temp) + self.stage(directory) + plan, family, records = catalogs.read_stage(directory) + template = self.template() + template["device"][1]["type"] = "noflash" + targets = {row["artifact_target"] for row, _, _ in records} | { + "Station_G2_companion_radio_full", "Station_G2_repeater", "RAK_4631_repeater"} + result = catalogs.generate(template, plan, family, records, + catalogs.target_profiles(targets), "mikecarper/MeshCore") + device = result["device"][1] + self.assertEqual(device["type"], "noflash") + self.assertEqual(len(device["firmware"]), 2) + for firmware in device["firmware"]: + version = firmware["version"][FAMILY] + self.assertIn("Downloads only", version["notes"]) + self.assertTrue(all(file["type"] == "download" for file in version["files"])) + # Even a merged ESP32 image must not enable a download-only card. + for name in ("example-merged.bin", "example.bin", "example.zip", "example.uf2"): + self.assertEqual(catalogs.file_entry(Path(name), "noflash", FAMILY, + "mikecarper/MeshCore")["type"], "download") + # Real flasher/chip mismatches still fail instead of being guessed. + template["device"][1]["type"] = "esp32" + with self.assertRaisesRegex(ValueError, "exact hardware card|platform differs"): + catalogs.generate(template, plan, family, records, + catalogs.target_profiles(targets), "mikecarper/MeshCore") + def test_stable_publication_notes_and_assets_are_ascii(self): with tempfile.TemporaryDirectory() as temp: base = Path(temp)