Complete v1.17.1.1 release matrix support

This commit is contained in:
mikecarper committed 2026-08-14 21:09:14 -07:00
1 parent 8decf0ba85
commit 772607eba1
6 files changed
+4611 -4574

No files matched your search

+20
View File
@@ -1975,6 +1975,26 @@ requires_dram_limited_neighbors() {
# ESP32 MQTT observers exhaust internal DRAM, while the 256 KiB STM32WL
# targets exhaust their fixed application region because initialized table
# storage is part of the image.
#
# Classic ESP32 bridge targets only need the lower limit when the expanded
# FULL profile also enables packet logging. Keep their ordinary and
# non-logging FULL profiles at the protocol maximum.
if [ "$ESP32_FULL_BUILD" = "1" ] \
&& [ "${PACKET_LOGGING_OVERRIDE,,}" = "on" ]; then
case "${1,,}" in
generic_e22_sx1262_repeater_bridge_espnow \
|generic_e22_sx1268_repeater_bridge_espnow \
|heltec_v2_repeater_bridge_espnow \
|lilygo_tlora_v2_1_1_6_repeater_bridge_espnow \
|lilygo_tlora_v2_1_1_6_repeater_bridge_rs232 \
|meshadventurer_sx1262_repeater_bridge_espnow \
|meshadventurer_sx1268_repeater_bridge_espnow \
|tbeam_sx1262_repeater_bridge_espnow \
|tbeam_sx1276_repeater_bridge_espnow)
return 0
;;
esac
fi
case "${1,,}" in
tbeam_sx1262_repeater_observer_mqtt|tbeam_sx1262_room_server_observer_mqtt|tbeam_sx1276_repeater_observer_mqtt|tbeam_sx1276_room_server_observer_mqtt|rak_3x72_repeater|tiny_relay_repeater|wio-e5-mini_repeater|wio-e5-repeater_bridge_rs232|wio-e5_repeater) return 0 ;;
*) return 1 ;;
+20
View File
@@ -33,6 +33,26 @@ 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:
```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 \
--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
```
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.
The older PowerShell generator remains available for the historical standard
and logging source folders:
File diff suppressed because it is too large. Load diff
+247
View File
@@ -0,0 +1,247 @@
#!/usr/bin/env python3
"""Update the curated Mesh America logging catalog from a complete matrix."""
from __future__ import annotations
import argparse
import importlib.util
import json
from pathlib import Path
import sys
SCRIPT_DIR = Path(__file__).resolve().parent
COMMON_PATH = SCRIPT_DIR / "update-provider-release.py"
def load_common():
spec = importlib.util.spec_from_file_location("provider_release_common", COMMON_PATH)
if spec is None or spec.loader is None:
raise RuntimeError(f"cannot load {COMMON_PATH}")
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
common = load_common()
CONSTRAINED_LOGGING_TARGETS = {
"Tiny_Relay_repeater",
"RAK_3x72_repeater",
"wio-e5_repeater",
"wio-e5-repeater_bridge_rs232",
"wio-e5-mini_companion_radio_usb",
"wio-e5-mini_repeater",
"wio-e5-mini_sensor",
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--catalog",
type=Path,
default=SCRIPT_DIR / "keymind-cascade-logging-v1.16.0-provider.json",
)
parser.add_argument("--release-dir", type=Path, required=True)
parser.add_argument("--artifact-version", required=True)
parser.add_argument("--logging-main-tag", required=True)
parser.add_argument("--logging-utility-tag", required=True)
parser.add_argument("--full-tag", required=True)
parser.add_argument("--main-tag", required=True)
parser.add_argument("--advanced-tag", required=True)
parser.add_argument("--utility-tag", required=True)
parser.add_argument("--repo", default="mikecarper/MeshCore")
parser.add_argument("--output", type=Path)
parser.add_argument("--check-only", action="store_true")
return parser.parse_args()
def is_logging_utility(identity: str) -> bool:
lowered = identity.lower()
return any(marker in lowered for marker in ("kiss_modem", "terminal_chat", "sensor"))
def is_constrained_logging(identity: str) -> bool:
return (
identity.endswith("-logging")
and identity.removesuffix("-logging") in CONSTRAINED_LOGGING_TARGETS
)
def release_tag(identity: str, args: argparse.Namespace) -> str:
lowered = identity.lower()
if "full-logging-ota" in lowered:
return args.full_tag
if "-logging" in lowered:
return args.logging_utility_tag if is_logging_utility(identity) else args.logging_main_tag
if (
"companion_radio_full" in lowered
or "lora_ota" in lowered
or "observer_mqtt" in lowered
):
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")):
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."
)
entry_count = 0
file_count = 0
used_identities: set[str] = set()
category_counts = {
"logging-main": 0,
"logging-utility": 0,
"full-logging": 0,
"standard-main": 0,
"standard-advanced": 0,
"standard-utility": 0,
}
for device in catalog["device"]:
device_type = device["type"]
for firmware in device["firmware"]:
old_keys = list(firmware["version"])
old_version = next(iter(firmware["version"].values()))
old_notes = old_version["notes"]
identities = common.ordered_catalog_identities(firmware)
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}")
used_identities.add(identity)
paths.extend(release_files[identity])
lowered = identity.lower()
if "full-logging-ota" in lowered:
category_counts["full-logging"] += 1
elif "-logging" in lowered:
key = "logging-utility" if is_logging_utility(identity) else "logging-main"
category_counts[key] += 1
elif (
"companion_radio_full" in lowered
or "lora_ota" in lowered
or "observer_mqtt" in lowered
):
category_counts["standard-advanced"] += 1
elif is_logging_utility(identity):
category_counts["standard-utility"] += 1
else:
category_counts["standard-main"] += 1
paths.sort(key=lambda path: common.file_sort_key(path, device_type))
files = []
for path in paths:
identity = common.artifact_identity(path.name)
file_type, title = common.file_type_and_title(path, device_type)
files.append(
{
"type": file_type,
"name": path.name,
"url": common.download_url(
args.repo,
release_tag(identity, args),
path.name,
),
"title": title,
}
)
version_key = common.replacement_version_key(old_keys[0], args.artifact_version)
notes = common.replace_release_version(old_notes, display_version)
if "USA/Canada 910.525 MHz" not in notes:
notes = notes.replace(
"with Halo/Keymind retry tuning and Cascade defaults.",
"with Halo/Keymind retry tuning, Cascade defaults, and the "
"USA/Canada 910.525 MHz / SF7 / BW62.5 / CR5 preset.",
1,
)
if any(is_constrained_logging(identity) for identity in identities):
old_profile = "turns USB debug and packet logging ON."
standard_profile = (
"normal features for the selected role and hardware, with USB "
"debug/packet logging and LoRa self-update OFF."
)
replacement = (
"turns packet logging ON while keeping USB debug OFF to fit this "
"flash-constrained STM32 target."
)
if old_profile in notes:
notes = notes.replace(old_profile, replacement, 1)
elif standard_profile in notes:
notes = notes.replace(
standard_profile,
standard_profile
+ " The -logging variant turns packet logging ON while keeping "
"USB debug OFF to fit this flash-constrained STM32 target.",
1,
)
else:
raise ValueError(
"cannot update constrained logging guidance for "
f"{identities!r}"
)
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)}")
if file_count == 0:
raise ValueError("catalog update produced no files")
print(
json.dumps(
{
"catalog_entries": entry_count,
"catalog_files": file_count,
"release_identities_used": len(used_identities),
"identity_categories": category_counts,
},
sort_keys=True,
)
)
return catalog
def main() -> int:
args = parse_args()
output = args.output or args.catalog
try:
if not args.artifact_version.startswith("v"):
raise ValueError("--artifact-version must start with 'v'")
with args.catalog.open(encoding="utf-8") as stream:
catalog = json.load(stream)
release_files = common.index_release_files(args.release_dir, args.artifact_version)
updated = update_catalog(catalog, release_files, args)
if not args.check_only:
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(
json.dumps(updated, ensure_ascii=True, indent=2) + "\n",
encoding="ascii",
)
print(f"Wrote {output}")
except (OSError, ValueError, KeyError, json.JSONDecodeError) as exc:
print(f"error: {exc}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())
+6
View File
@@ -129,6 +129,12 @@ def hardware_id_for_env(env_name: str) -> str:
r"[_-](?:repeater|repeatr|room_server|room_svr|sensor|terminal_chat|kiss_modem|"
r"companion_radio|companion|comp_radio)(?=[_-]|$)", env_name, re.IGNORECASE)
family = (env_name[:role.start()] if role else env_name).strip("_-") or env_name.strip("_-")
# Match the explicit MOTA_HW_ID shared by every role in these variant
# families. The environment spelling is not always the physical-board tag.
family = {
"Heltec_t114_without_display": "Heltec_t114",
"RAK_4631": "RAK4631",
}.get(family, family)
if len(family) <= 32:
return family
suffix = hashlib.sha256(family.encode()).hexdigest()[:8]
+6 -2
View File
@@ -52,8 +52,12 @@ def test_ota_target_generation_honors_explicit_disable():
def test_hardware_id_for_env():
assert ml.hardware_id_for_env("RAK_4631_repeater") == "RAK_4631"
assert ml.hardware_id_for_env("RAK_4631_companion_radio_usb") == "RAK_4631"
assert ml.hardware_id_for_env("RAK_4631_repeater") == "RAK4631"
assert ml.hardware_id_for_env("RAK_4631_companion_radio_usb") == "RAK4631"
assert (
ml.hardware_id_for_env("Heltec_t114_without_display_repeater")
== "Heltec_t114"
)
assert ml.hardware_id_for_env("ThinkNode_M2_Repeater_bridge_espnow") == "ThinkNode_M2"
assert ml.hardware_id_for_env("wio-e5-repeater_bridge_rs232") == "wio-e5"
long_env = "ikoka_handheld_nrf_e22_30dbm_096_rotated_room_server"