From 32226bfed956995e3b424e3bdbe371a1fff02fa3 Mon Sep 17 00:00:00 2001 From: mikecarper Date: Sun, 6 Sep 2026 18:38:33 +0000 Subject: [PATCH] deploy: 840458c0e631433e2cc8e746c0f71a5905aa20cb --- _javascript/firmware_picker.js | 66 ++++++++++++++++++++++++++++++-- _stylesheets/firmware_picker.css | 14 +++++++ firmware_picker/index.html | 17 +++++++- search/search_index.json | 2 +- 4 files changed, 93 insertions(+), 6 deletions(-) diff --git a/_javascript/firmware_picker.js b/_javascript/firmware_picker.js index d37b52ca..5031d76b 100644 --- a/_javascript/firmware_picker.js +++ b/_javascript/firmware_picker.js @@ -11,6 +11,7 @@ ]); const FILTER_FIELDS = Object.freeze([ + "chipFamily", "hardwareFamily", "hardware", "role", @@ -23,6 +24,14 @@ const FACET_FIELDS = Object.freeze(FILTER_FIELDS.concat(["install"])); + const CHIP_FAMILY_LABELS = Object.freeze({ + esp32: "ESP32", + nrf52: "nRF52", + rp2040: "RP2040", + stm32: "STM32", + unknown: "Other / unspecified", + }); + const ROLE_LABELS = Object.freeze({ companion: "Companion", repeater: "Repeater", @@ -701,6 +710,27 @@ ); }); + // Use release-bound platform metadata, never a filename extension (UF2 + // is used by both nRF52 and RP2040). Legacy images may inherit the chip + // family, but no runtime capabilities, from the same exact hardware. + profiles.forEach(function (profile) { + const platform = profile.controls && profile.controls.platform; + profile.chipFamily = platform + ? platform.replace(/_PLATFORM$/, "").toLowerCase() : ""; + }); + const knownChips = new Map(); + profiles.forEach(function (profile) { + if (!profile.chipFamily) return; + if (!knownChips.has(profile.hardware)) knownChips.set(profile.hardware, new Set()); + knownChips.get(profile.hardware).add(profile.chipFamily); + }); + profiles.forEach(function (profile) { + const chips = knownChips.get(profile.hardware); + if (!profile.chipFamily) { + profile.chipFamily = chips && chips.size === 1 ? Array.from(chips)[0] : "unknown"; + } + }); + return { releaseSet: releaseSet, rows: firmwareRows, @@ -807,6 +837,7 @@ } function labelFor(field, value) { + if (field === "chipFamily") return CHIP_FAMILY_LABELS[value] || value; if (field === "hardwareFamily") return humanizeHardware(value); if (field === "hardware") return humanizeHardware(value); if (field === "role") return ROLE_LABELS[value] || value; @@ -1209,6 +1240,7 @@ }); let catalog = { releaseSet: null, rows: [], profiles: [] }; const filters = {}; + let automaticChipFamily = false; const groupPrefix = "firmware-picker-" + (++pickerInstanceCount); function matchingProfiles(ignoredField) { @@ -1220,6 +1252,7 @@ function setSelectOptions(select, field, values, selected) { select.replaceChildren(); const placeholders = { + chipFamily: "Any chip family — skip this filter", hardwareFamily: "Any hardware", hardware: "Choose a hardware variant", mode: "Any connection / mode", @@ -1279,11 +1312,16 @@ } function valuesForField(field) { + const ignored = field === "chipFamily" ? ["hardwareFamily", "hardware"] + : field === "hardwareFamily" ? ["hardware"] : []; + if (automaticChipFamily && (field === "hardwareFamily" || field === "hardware")) { + ignored.push("chipFamily"); + } return facetValues( catalog.profiles, filters, field, - field === "hardwareFamily" ? ["hardware"] : [] + ignored ); } @@ -1315,7 +1353,12 @@ } function refreshFacets() { + if (automaticChipFamily) filters.chipFamily = ""; stabilizeFilters(); + if (automaticChipFamily && filters.hardwareFamily) { + const chips = uniqueValues(matchingProfiles("chipFamily"), "chipFamily"); + filters.chipFamily = chips.length === 1 ? chips[0] : ""; + } FACET_FIELDS.forEach(function (field) { setControlOptions(field, valuesForField(field)); }); @@ -1325,6 +1368,9 @@ ); hardwareVariantControl.hidden = !filters.hardwareFamily || hardwareVariants.length <= 1; + const chipSummary = root.querySelector('[data-role="chip-family-summary"]'); + if (chipSummary) chipSummary.textContent = "Optional: chip family" + + (filters.chipFamily ? " — " + labelFor("chipFamily", filters.chipFamily) : ""); render(); } @@ -1333,12 +1379,12 @@ missing.hidden = true; resultList.replaceChildren(); const missingFields = FACET_FIELDS.filter(function (field) { - return !filters[field]; + return field !== "chipFamily" && !filters[field]; }); const matches = matchingProfiles(); if (missingFields.length) { - if (missingFields.length === FACET_FIELDS.length) { + if (missingFields.length === FACET_FIELDS.length - 1 && !filters.chipFamily) { status.textContent = "Pick options in any order. " + matches.length + " compatible configurations are available."; } else { @@ -1437,6 +1483,19 @@ const target = event.target; const field = target.dataset.choiceField || target.dataset.field; if (!FACET_FIELDS.includes(field)) return; + if (field === "chipFamily") { + automaticChipFamily = false; + // A deliberate family switch takes precedence over an old board. + if (target.value && !matchingProfiles("chipFamily").some(function (profile) { + return profile.chipFamily === target.value; + })) { + filters.hardwareFamily = ""; + filters.hardware = ""; + } + } + if (field === "hardwareFamily" || field === "hardware") { + automaticChipFamily = true; + } if (field === "hardwareFamily") filters.hardware = ""; filters[field] = target.value; refreshFacets(); @@ -1444,6 +1503,7 @@ form.addEventListener("reset", function (event) { event.preventDefault(); if (!catalog.profiles.length) return; + automaticChipFamily = false; FACET_FIELDS.forEach(function (field) { filters[field] = ""; }); diff --git a/_stylesheets/firmware_picker.css b/_stylesheets/firmware_picker.css index 0db71617..deb305bd 100644 --- a/_stylesheets/firmware_picker.css +++ b/_stylesheets/firmware_picker.css @@ -43,6 +43,20 @@ color: var(--md-default-fg-color--light); } +.firmware-picker-chip-family { + border-bottom: 1px solid var(--picker-border); + padding-bottom: 0.75rem; +} + +.firmware-picker-chip-family summary { + cursor: pointer; + font-weight: 600; +} + +.firmware-picker-chip-family p { + color: var(--md-default-fg-color--light); +} + .firmware-picker-control { min-width: 0; margin: 0; diff --git a/firmware_picker/index.html b/firmware_picker/index.html index 4388f2d0..e60cf041 100644 --- a/firmware_picker/index.html +++ b/firmware_picker/index.html @@ -1708,7 +1708,9 @@ MQTT and GPS show their app/WebConfig steps instead. See The USB web console works with the default ASCII terminal on Full Companion and infrastructure roles.

Pick the choices in any order. Every selection narrows all the other controls -to firmware combinations that were actually built in the current release set.

+to firmware combinations that were actually built in the current release set. +The optional chip-family filter (ESP32, nRF52, RP2040, or STM32) can narrow the +hardware list first. You can skip it: picking hardware fills it in automatically.

The picker reads public release metadata from GitHub. It does not upload device information. Hardware names, target names, and download links come directly from the published firmware assets.

@@ -1719,7 +1721,7 @@ from the published firmware assets.

For a new installation, choose the exact board and role, prefer a FULL / complete profile when it is available, and select - Erase & fresh install (merged .bin). Narrower profiles + Full install / layout migration (merged .bin). Narrower profiles remain available when their reduced transport or feature set is intentional.

@@ -1730,6 +1732,17 @@ from the published firmware assets.

+
+ Optional: chip family +

Skip this if you know your board. Picking hardware selects its chip family automatically. Choose Any to clear this filter.

+
+ + +
+
+