diff --git a/docs/_javascript/firmware_picker.js b/docs/_javascript/firmware_picker.js index d37b52ca1..5031d76ba 100644 --- a/docs/_javascript/firmware_picker.js +++ b/docs/_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/docs/_stylesheets/firmware_picker.css b/docs/_stylesheets/firmware_picker.css index 0db71617d..deb305bdb 100644 --- a/docs/_stylesheets/firmware_picker.css +++ b/docs/_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/docs/firmware_picker.md b/docs/firmware_picker.md index 1356d4268..3d9ddce00 100644 --- a/docs/firmware_picker.md +++ b/docs/firmware_picker.md @@ -10,6 +10,8 @@ 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. +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 @@ -22,7 +24,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.

@@ -33,6 +35,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.

+
+ + +
+
+