Update Mesh America Companion catalogs for 1.17.1.2

This commit is contained in:
mikecarper committed 2026-08-17 00:16:04 -07:00
1 parent c9652754cf
commit 23e91b792e
5 files changed
+2253 -2115

No files matched your search

+25 -18
View File
@@ -16,16 +16,18 @@ Provider name: Keymind Cascade Logging
Catalog URL: https://raw.githubusercontent.com/mikecarper/MeshCore/keymindCascade/mesh-america/keymind-cascade-logging-v1.16.0-provider.json
```
The standard catalog currently tracks the v1.17.1.1 Cascade/USA release. Update
its curated device and role choices from a completed all-target release with:
The standard catalog currently keeps unaffected infrastructure on v1.17.1.1
and tracks the corrective v1.17.1.2 release for every Companion role. Update
only those Companion choices from the completed corrective matrix with:
```text
python3 mesh-america/update-provider-release.py \
--release-dir /path/to/release \
--artifact-version v1.17.1.1-759a35fc \
--main-tag v1.17.1.1-halo-keymind-cascade-759a35fc \
--advanced-tag lora-ota-v1.17.1.1-halo-keymind-cascade-759a35fc \
--utility-tag kiss-v1.17.1.1-halo-keymind-cascade-759a35fc
--artifact-version v1.17.1.2-c9652754 \
--main-tag v1.17.1.2-halo-keymind-cascade-c9652754 \
--advanced-tag lora-ota-v1.17.1.2-halo-keymind-cascade-c9652754 \
--utility-tag kiss-v1.17.1.1-halo-keymind-cascade-759a35fc \
--companion-only
```
The updater preserves the catalog's curated device names, role choices, and
@@ -33,25 +35,30 @@ hardware guidance; validates every referenced artifact; routes URLs to the
correct GitHub release page; and replaces legacy FULL MQTT choices with the
matching portable MQTT observer builds.
The logging catalog tracks the diagnostic and expanded-partition profiles in
the supplemental v1.17.1.1 release pages. Update it from the same completed
matrix with:
The logging catalog likewise keeps unaffected roles on v1.17.1.1 while its
Companion, Full Companion, and expanded Companion logging profiles track the
v1.17.1.2 correction. Logging builds are for a USB-connected MQTT/logging host;
they are not the direct on-device Wi-Fi MQTT bridge. Update only the Companion
choices from the same completed matrix with:
```text
python3 mesh-america/update-logging-provider-release.py \
--release-dir /path/to/release \
--artifact-version v1.17.1.1-759a35fc \
--logging-main-tag logging-v1.17.1.1-halo-keymind-cascade-759a35fc \
--artifact-version v1.17.1.2-c9652754 \
--logging-main-tag logging-v1.17.1.2-halo-keymind-cascade-c9652754 \
--logging-utility-tag logging-utility-v1.17.1.1-halo-keymind-cascade-759a35fc \
--full-tag full-profiles-v1.17.1.1-halo-keymind-cascade-759a35fc \
--main-tag v1.17.1.1-halo-keymind-cascade-759a35fc \
--advanced-tag lora-ota-v1.17.1.1-halo-keymind-cascade-759a35fc \
--utility-tag kiss-v1.17.1.1-halo-keymind-cascade-759a35fc
--full-tag full-profiles-v1.17.1.2-halo-keymind-cascade-c9652754 \
--main-tag v1.17.1.2-halo-keymind-cascade-c9652754 \
--advanced-tag lora-ota-v1.17.1.2-halo-keymind-cascade-c9652754 \
--utility-tag kiss-v1.17.1.1-halo-keymind-cascade-759a35fc \
--companion-only
```
This updater preserves the logging catalog's curated role and hardware
guidance, validates all 648 referenced firmware identities, and selects the
release page that owns each file.
In Companion-only mode, each updater preserves every unaffected catalog entry,
validates every selected replacement artifact, and adds the one-time
power-saving migration guidance. Without `--companion-only`, the existing
all-role behavior remains available for a future complete matrix release; the
logging updater then validates all 648 referenced firmware identities.
The older PowerShell generator remains available for the historical standard
and logging source folders:
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+64 -17
View File
@@ -54,6 +54,14 @@ def parse_args() -> argparse.Namespace:
parser.add_argument("--repo", default="mikecarper/MeshCore")
parser.add_argument("--output", type=Path)
parser.add_argument("--check-only", action="store_true")
parser.add_argument(
"--companion-only",
action="store_true",
help=(
"update only Companion roles, preserving the versions and files for "
"all unaffected roles"
),
)
return parser.parse_args()
@@ -83,23 +91,37 @@ def release_tag(identity: str, args: argparse.Namespace) -> str:
return args.advanced_tag
if is_logging_utility(identity):
return args.utility_tag
if any(marker in lowered for marker in ("companion", "repeater", "room_server")):
if any(
marker in lowered
for marker in ("companion", "comp_radio", "repeater", "room_server")
):
return args.main_tag
return args.utility_tag
def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: argparse.Namespace) -> dict:
display_version = args.artifact_version.rsplit("-", 1)[0]
catalog["description"] = (
f"Keymind Cascade MeshCore {display_version} firmware with Halo/Keymind "
"retry tuning, Cascade defaults, and the USA/Canada 910.525 MHz / SF7 / "
"BW62.5 / CR5 preset. This catalog contains packet-logging builds with "
"USB debug enabled except on seven flash-constrained STM32 targets, "
"selected non-logging utility and portable MQTT observer builds, "
"and expanded-partition FULL USB-logging LoRa-OTA builds. Host software "
"can consume the USB serial log and publish it separately. Open Release "
"notes for role, hardware, installation, and partition requirements."
)
if args.companion_only:
catalog["description"] = (
"Keymind Cascade MeshCore v1.17.1.1 infrastructure firmware plus "
f"corrected {display_version} Companion firmware, with Halo/Keymind "
"retry tuning, Cascade defaults, and the USA/Canada 910.525 MHz / "
"SF7 / BW62.5 / CR5 preset. Companion device power saving is enabled "
"by default and migrated on once in the corrected release. Logging "
"profiles are intended for a USB-connected MQTT/logging host. "
"Unaffected roles remain on v1.17.1.1."
)
else:
catalog["description"] = (
f"Keymind Cascade MeshCore {display_version} firmware with Halo/Keymind "
"retry tuning, Cascade defaults, and the USA/Canada 910.525 MHz / SF7 / "
"BW62.5 / CR5 preset. This catalog contains packet-logging builds with "
"USB debug enabled except on seven flash-constrained STM32 targets, "
"selected non-logging utility and portable MQTT observer builds, "
"and expanded-partition FULL USB-logging LoRa-OTA builds. Host software "
"can consume the USB serial log and publish it separately. Open Release "
"notes for role, hardware, installation, and partition requirements."
)
entry_count = 0
file_count = 0
@@ -116,6 +138,8 @@ def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: ar
for device in catalog["device"]:
device_type = device["type"]
for firmware in device["firmware"]:
if args.companion_only and not firmware["role"].startswith("companion"):
continue
old_keys = list(firmware["version"])
old_version = next(iter(firmware["version"].values()))
old_notes = old_version["notes"]
@@ -123,7 +147,7 @@ def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: ar
paths: list[Path] = []
for identity in identities:
if identity not in release_files:
raise ValueError(f"no v1.17.1.1 artifact matches {identity!r}")
raise ValueError(f"no release artifact matches {identity!r}")
used_identities.add(identity)
paths.extend(release_files[identity])
@@ -181,7 +205,9 @@ def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: ar
"turns packet logging ON while keeping USB debug OFF to fit this "
"flash-constrained STM32 target."
)
if old_profile in notes:
if replacement in notes:
pass
elif old_profile in notes:
notes = notes.replace(old_profile, replacement, 1)
elif standard_profile in notes:
notes = notes.replace(
@@ -196,14 +222,34 @@ def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: ar
"cannot update constrained logging guidance for "
f"{identities!r}"
)
if firmware["role"].startswith("companion"):
notes = common.append_companion_power_saving_note(
notes, display_version
)
if any("-logging" in identity.lower() for identity in identities):
logging_note = (
"LOGGING USE - This USB packet-logging build is for a "
"USB-connected MQTT/logging host that reads the serial stream "
"and publishes it. It is not the direct on-device Wi-Fi MQTT "
"bridge."
)
if logging_note not in notes:
notes += "\n\n" + logging_note
firmware["version"] = {version_key: {"notes": notes, "files": files}}
entry_count += 1
file_count += len(files)
if entry_count != 596:
raise ValueError(f"expected 596 curated firmware entries, found {entry_count}")
if len(used_identities) != 648:
raise ValueError(f"expected 648 curated identities, found {len(used_identities)}")
expected_entries = 129 if args.companion_only else 596
expected_identities = None if args.companion_only else 648
if entry_count != expected_entries:
raise ValueError(
f"expected {expected_entries} curated firmware entries, found {entry_count}"
)
if expected_identities is not None and len(used_identities) != expected_identities:
raise ValueError(
f"expected {expected_identities} curated identities, "
f"found {len(used_identities)}"
)
if file_count == 0:
raise ValueError("catalog update produced no files")
print(
@@ -213,6 +259,7 @@ def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: ar
"catalog_files": file_count,
"release_identities_used": len(used_identities),
"identity_categories": category_counts,
"update_mode": "companion-only" if args.companion_only else "all",
},
sort_keys=True,
)
+94 -10
View File
@@ -36,6 +36,15 @@ LEGACY_PORTABLE_CEILING_EXCEPTIONS = {
"t_beam_s3_supreme_sx1262_repeater_observer_mqtt",
}
LEGACY_COMPANION_IDENTITY_ALIASES = {
# The short-lived install-capable LoRa-OTA Wi-Fi Companion profile was
# replaced by the ordinary Wi-Fi Companion plus the safer, serve-only Full
# Companion profile. Keep the curated role available without claiming that
# the replacement can LoRa-install firmware onto itself.
"LilyGo_TBeam_1W_companion_radio_wifi-ota":
"LilyGo_TBeam_1W_companion_radio_wifi",
}
def parse_args() -> argparse.Namespace:
script_dir = Path(__file__).resolve().parent
@@ -62,6 +71,14 @@ def parse_args() -> argparse.Namespace:
action="store_true",
help="validate and report without writing the output catalog",
)
parser.add_argument(
"--companion-only",
action="store_true",
help=(
"update only Companion roles, preserving the versions and files for "
"all unaffected roles"
),
)
return parser.parse_args()
@@ -116,6 +133,10 @@ def resolve_release_identity(
if old_identity in release_files:
return old_identity, False
alias = LEGACY_COMPANION_IDENTITY_ALIASES.get(old_identity)
if alias is not None and alias in release_files:
return alias, False
# The previous catalog used expanded-partition FULL MQTT images. The new
# standard matrix emits a portable MQTT observer under the same target name
# without the build-only "-full-ota" filename marker.
@@ -128,6 +149,25 @@ def resolve_release_identity(
raise ValueError(f"no new release artifact matches catalog target {old_identity!r}")
def replace_legacy_companion_ota_notes(notes: str) -> str:
paragraphs: list[str] = []
for paragraph in notes.split("\n\n"):
if paragraph.startswith("PROFILE ") and "LoRa OTA companion:" in paragraph:
paragraphs.append(
"PROFILE - Standard Wi-Fi Companion: keeps the normal Wi-Fi and "
"USB Companion interfaces with LoRa self-update off. Use Full "
"Companion when the complete transports and host-backed LoRa-OTA "
"seeding tools are needed."
)
elif paragraph.startswith("LORA OTA "):
continue
elif paragraph.startswith("SELECTION "):
paragraphs.append(paragraph.replace(", OTA.", "."))
else:
paragraphs.append(paragraph)
return "\n\n".join(paragraphs)
def release_tag_for_identity(identity: str, args: argparse.Namespace) -> str:
lowered = identity.lower()
if (
@@ -144,6 +184,7 @@ def release_tag_for_identity(identity: str, args: argparse.Namespace) -> str:
return args.utility_tag
if (
"companion" in lowered
or "comp_radio" in lowered
or "repeater" in lowered
or "room_server" in lowered
):
@@ -217,6 +258,21 @@ def append_partition_warning(notes: str, identities: list[str]) -> str:
return notes + "\n\n" + warning
def append_companion_power_saving_note(notes: str, display_version: str) -> str:
paragraph = (
f"POWER SAVING - {display_version} enables Companion device power saving "
"by default. On first boot it migrates the saved Companion setting to on "
"once, repairing devices that carried the regressed off value. A later "
"explicit `powersaving off` choice remains persistent. This controls "
"MCU/GPS idle saving and is separate from LoRa receive power saving "
"(RXPS). On nRF52, an active USB data-host connection can intentionally "
"keep the device awake; USB power from a charger alone does not."
)
if paragraph in notes:
return notes
return notes + "\n\n" + paragraph
def observer_notes(
role: str, display_version: str, identities: list[str], old_notes: str
) -> str:
@@ -270,16 +326,27 @@ def observer_notes(
def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: argparse.Namespace) -> dict:
display_version = args.artifact_version.rsplit("-", 1)[0]
catalog["description"] = (
f"Keymind Cascade MeshCore {display_version} firmware with Halo/Keymind "
"retry tuning, Cascade defaults, and the USA/Canada 910.525 MHz / SF7 / "
"BW62.5 / CR5 preset. This catalog contains standard builds, Full "
"Companion builds, compact LoRa-OTA builds, and portable MQTT observer "
"builds with USB debug/packet logging off. On nRF52, Full Companion "
"replaces separate BLE and USB choices; on ESP32, Full Companion is "
"offered next to the BLE/USB variants. Open Release notes for role, "
"hardware, installation, and partition requirements."
)
if args.companion_only:
catalog["description"] = (
"Keymind Cascade MeshCore v1.17.1.1 infrastructure firmware plus "
f"corrected {display_version} Companion firmware, with Halo/Keymind "
"retry tuning, Cascade defaults, and the USA/Canada 910.525 MHz / "
"SF7 / BW62.5 / CR5 preset. Companion device power saving is enabled "
"by default and migrated on once in the corrected release. Unaffected "
"roles remain on v1.17.1.1. Open Release notes for role, hardware, "
"installation, and partition requirements."
)
else:
catalog["description"] = (
f"Keymind Cascade MeshCore {display_version} firmware with Halo/Keymind "
"retry tuning, Cascade defaults, and the USA/Canada 910.525 MHz / SF7 / "
"BW62.5 / CR5 preset. This catalog contains standard builds, Full "
"Companion builds, compact LoRa-OTA builds, and portable MQTT observer "
"builds with USB debug/packet logging off. On nRF52, Full Companion "
"replaces separate BLE and USB choices; on ESP32, Full Companion is "
"offered next to the BLE/USB variants. Open Release notes for role, "
"hardware, installation, and partition requirements."
)
updated_entries = 0
updated_files = 0
@@ -289,13 +356,20 @@ def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: ar
for device in catalog["device"]:
device_type = device["type"]
for firmware in device["firmware"]:
if args.companion_only and not firmware["role"].startswith("companion"):
continue
old_keys = list(firmware["version"])
old_notes = next(iter(firmware["version"].values()))["notes"]
resolved_identities: list[str] = []
converted_observer = False
converted_legacy_companion_ota = False
for old_identity in ordered_catalog_identities(firmware):
identity, converted = resolve_release_identity(old_identity, release_files)
converted_legacy_companion_ota = (
converted_legacy_companion_ota
or old_identity in LEGACY_COMPANION_IDENTITY_ALIASES
)
if identity not in resolved_identities:
resolved_identities.append(identity)
converted_observer = (
@@ -337,12 +411,21 @@ def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: ar
else:
version_key = replacement_version_key(old_keys[0], args.artifact_version)
notes = replace_release_version(old_notes, display_version)
if converted_legacy_companion_ota:
notes = replace_legacy_companion_ota_notes(notes)
notes = append_partition_warning(notes, resolved_identities)
if firmware["role"].startswith("companion"):
notes = append_companion_power_saving_note(notes, display_version)
firmware["version"] = {version_key: {"notes": notes, "files": files}}
updated_entries += 1
updated_files += len(files)
if args.companion_only and updated_entries != 216:
raise ValueError(
f"expected 216 curated Companion entries, found {updated_entries}"
)
if updated_entries == 0 or updated_files == 0:
raise ValueError("catalog update produced no firmware entries")
@@ -353,6 +436,7 @@ def update_catalog(catalog: dict, release_files: dict[str, list[Path]], args: ar
"catalog_files": updated_files,
"observer_entries": observer_entries,
"release_identities_used": len(used_identities),
"update_mode": "companion-only" if args.companion_only else "all",
},
sort_keys=True,
)