mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 16:52:22 +00:00
Put firmware selections first and show contextual board notes
This commit is contained in:
1 parent
3d539b0a8d
commit
ff78f8df7e
6 files changed
+324
-105
No files matched your search
@@ -1502,6 +1502,68 @@
|
||||
: { url: fallback, label: "Check partition-expansion availability" };
|
||||
}
|
||||
|
||||
function boardNotes(profile) {
|
||||
if (!profile || typeof profile.target !== "string") return [];
|
||||
const identity = parseTargetProfile(profile.target);
|
||||
// Match the exact result, not its broad hardware-family filter. A TFT,
|
||||
// legacy UART or different role must not inherit another image's claims.
|
||||
if (identity.hardware !== profile.hardware || identity.role !== profile.role) return [];
|
||||
const target = profile.target;
|
||||
const info = profile.controls || {};
|
||||
const notes = [];
|
||||
const rak = /^RAK_(3401|4631)_repeater_unified_lora_ota(?:-(?:full|reduced)(?:-ota)?)?$/i.exec(target);
|
||||
const lora = info.updateRequirements && info.updateRequirements.lora;
|
||||
if (rak && info.platform === "NRF52_PLATFORM" && profile.ota === "lora-receiver" &&
|
||||
["full", "reduced"].includes(profile.sensorProfile) && lora &&
|
||||
lora.storage === "adaptive_internal_or_external_qspi") {
|
||||
notes.push("This unified repeater chooses supported internal or external OTA storage automatically. Full and Reduced select optional sensor drivers, not a storage module. Exact external-flash wiring and the matching OTAFIX bootloader are still required.");
|
||||
notes.push("Internal flash accepts in-place deltas only; supported external QSPI storage accepts full images and in-place deltas. The update package must match the installed target and storage layout.");
|
||||
if (rak[1] === "4631" && info.rs232 === true && info.gps === true) {
|
||||
notes.push("Use UART2 for new RS-232 installations. UART1 remains reserved for potentially powered GPS hardware, and header-connected W25 flash also occupies its pins. Legacy GPS-free Serial1 images remain exact-filename compatibility downloads for existing wiring; selecting the unified image does not migrate wiring or OTA identity.");
|
||||
notes.push("RAK4631 Ethernet requires its separate exact hardware image; it is not a storage choice in this unified repeater.");
|
||||
}
|
||||
}
|
||||
if (/^RAK_4631_repeater_bridge_rs232_serial1(?:_lora_ota_no_external_sensors)?(?:-(?:full|reduced))?$/i.test(target) &&
|
||||
info.platform === "NRF52_PLATFORM" && info.rs232 === true && info.gps === false) {
|
||||
notes.push("This legacy GPS-free Serial1 bridge uses UART1. Do not connect powered UART GPS or header-connected W25 flash to the same pins. Prefer the unified UART2 image for new wiring, but changing images does not migrate an existing installation's wiring or OTA identity.");
|
||||
}
|
||||
if (/^RAK_4631_repeater_ethernet(?:-(?:full|reduced)(?:-ota)?)?$/i.test(target) &&
|
||||
info.platform === "NRF52_PLATFORM" && profile.mode === "ethernet") {
|
||||
notes.push("This is the separate RAK4631 Ethernet hardware image, not the unified internal/external-storage repeater. Use its exact Ethernet hardware and update target.");
|
||||
}
|
||||
if (/^LilyGo_TLora_V2_1_1_6_repeater(?:_observer_mqtt_?)?-full-(?:usb-wifi|logging)$/i.test(target) &&
|
||||
info.platform === "ESP32_PLATFORM" && profile.feature === "full" && info.espnowBridge === true) {
|
||||
const observer = /_observer_mqtt/i.test(target);
|
||||
if (!observer && info.rs232 === true && info.mqtt === false) {
|
||||
notes.push("T-LoRa V2.1 retains two RAM-safe Full images: UART plus ESP-NOW, or MQTT plus ESP-NOW. This image is the UART/RS-232 choice; it does not include direct Wi-Fi MQTT. Select the separate MQTT observer image if that is needed.");
|
||||
} else if (observer && info.mqtt === true && info.rs232 === false) {
|
||||
notes.push("T-LoRa V2.1 retains two RAM-safe Full images: UART plus ESP-NOW, or MQTT plus ESP-NOW. This image is the MQTT observer choice; it does not include the UART/RS-232 bridge. Select the separate UART image if that is needed.");
|
||||
}
|
||||
}
|
||||
if (/^(?:Heltec_v3|Heltec_WSL3|RAK_3112)_repeater(?:_observer_mqtt_?)?-full-usb-wifi$/i.test(target) &&
|
||||
info.platform === "ESP32_PLATFORM" && profile.feature === "full" &&
|
||||
info.rs232 === true && info.mqtt === true && info.espnowBridge === true) {
|
||||
notes.push("This Full MQTT repeater also retains independent RS-232 and ESP-NOW bridges. Use the separate restore-settings controls for each transport; the UART is not MQTT's historical bridge.enabled alias.");
|
||||
}
|
||||
const v4 = /^heltec_v4(?:_3)?(?:_expansionkit)?(?:_r8)?(?:_tft)?$/i.test(profile.hardware);
|
||||
if (v4 && info.platform === "ESP32_PLATFORM" && info.primaryEspnow === false) {
|
||||
if (isFullCompanion(profile) && /^heltec_v4(?:_[a-z0-9]+)*_companion_radio_full(?:_fem(?:on|off))?$/i.test(target)) {
|
||||
notes.push("This Full Companion is LoRa-primary, not the LoRa/ESP-NOW repeater gateway role. For a gateway, choose this exact board's Full repeater when its ESP-NOW bridge is offered.");
|
||||
} else if (profile.role === "repeater" && profile.feature === "full" && info.espnowBridge === true &&
|
||||
/^heltec_v4(?:_[a-z0-9]+)*_repeater(?:_observer_mqtt_?)?-full-(?:usb-wifi|logging)$/i.test(target)) {
|
||||
notes.push("This Full repeater keeps LoRa as its primary mesh radio and can bridge to ESP-NOW when that runtime bridge is enabled. Use the matching restore-settings controls below; the Full Companion is a different role, not the repeater gateway.");
|
||||
}
|
||||
}
|
||||
if (profile.hardware === "wio-e5" && profile.role === "repeater" &&
|
||||
(!info.platform || info.platform === "STM32_PLATFORM") &&
|
||||
/^wio-e5(?:_|-)repeater(?:_bridge_rs232)?$/i.test(target)) {
|
||||
// These exact historical recipes are intentionally separate; do not
|
||||
// infer the RS-232 capability for an ordinary or Mini Companion image.
|
||||
notes.push("Wio-E5 is a flash-capacity exception with separate ordinary repeater and RS-232 bridge images. For RS-232 wiring, choose the exact bridge image rather than assuming the ordinary repeater includes that bridge.");
|
||||
}
|
||||
return notes;
|
||||
}
|
||||
|
||||
function installSteps(profile, kind) {
|
||||
const common = [
|
||||
"Verify that the hardware name and every displayed variant match the physical board.",
|
||||
@@ -1962,6 +2024,17 @@
|
||||
}
|
||||
card.appendChild(actions);
|
||||
|
||||
const notes = boardNotes(profile);
|
||||
if (notes.length) {
|
||||
const details = createElement("details");
|
||||
details.className = "firmware-picker-board-notes";
|
||||
details.appendChild(createElement("summary", "Board notes"));
|
||||
notes.forEach(function (text) {
|
||||
details.appendChild(createElement("p", text));
|
||||
});
|
||||
card.appendChild(details);
|
||||
}
|
||||
|
||||
const steps = createElement("div");
|
||||
steps.className = "firmware-picker-steps";
|
||||
steps.appendChild(createElement("strong", "What to do"));
|
||||
@@ -2582,6 +2655,8 @@
|
||||
runtimeDirections: runtimeDirections,
|
||||
runtimePanelDirections: runtimePanelDirections,
|
||||
renderRuntimeDirections: renderRuntimeDirections,
|
||||
renderProfileCard: renderProfileCard,
|
||||
boardNotes: boardNotes,
|
||||
installSteps: installSteps,
|
||||
migrationReleaseUrl: migrationReleaseUrl,
|
||||
migrationPackageForProfile: migrationPackageForProfile,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
.firmware-picker-intro,
|
||||
.firmware-picker-result,
|
||||
.firmware-picker-missing,
|
||||
.firmware-picker-help,
|
||||
.firmware-picker-board-notes,
|
||||
.firmware-asset-browser {
|
||||
border: 1px solid var(--picker-border);
|
||||
border-radius: 0.7rem;
|
||||
@@ -338,6 +340,13 @@
|
||||
margin-top: 1.2rem;
|
||||
}
|
||||
|
||||
.firmware-picker-help,
|
||||
.firmware-picker-board-notes {
|
||||
margin-top: 1.2rem;
|
||||
}
|
||||
|
||||
.firmware-picker-help summary,
|
||||
.firmware-picker-board-notes summary,
|
||||
.firmware-asset-browser summary {
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
|
||||
+83
-103
@@ -4,75 +4,7 @@
|
||||
|
||||
# Firmware picker
|
||||
|
||||
Each result now includes **Restore your settings after flashing**. Its commands
|
||||
follow the selected logging mode and the exact image's verified role/hardware
|
||||
controls. Click On, Off, or Check to view and copy the commands; Companion
|
||||
MQTT and GPS show their app/WebConfig steps instead. See
|
||||
[feature switches by role](role_feature_switches.md) for the full reference.
|
||||
The [USB web console](https://flasher.meshcore.io/console) works with the
|
||||
default ASCII terminal on USB Companion and infrastructure roles.
|
||||
|
||||
Pick the choices in any order. The controls stay limited to compatible
|
||||
firmware combinations that were actually built in the current release set.
|
||||
The result count stays visible while you scroll through the filters on a phone.
|
||||
Once the exact board and role are selected, the picker skips logging,
|
||||
connection, and profile choices that all select the same firmware. Logging
|
||||
settings move into **Restore your settings after flashing** beneath the files.
|
||||
Choices that select different images remain visible.
|
||||
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.
|
||||
Chip family and hardware are the only dropdowns. All remaining choices use
|
||||
buttons. **Firmware profile** combines OTA support, Full/standard, and sensor
|
||||
or storage differences when a choice between different images is needed.
|
||||
|
||||
RAK3401 and RAK4631 repeaters use one unified image per sensor policy for
|
||||
supported internal and external OTA storage. **Full** and **Reduced** choose
|
||||
optional sensor drivers; they do not select a storage module. The unified
|
||||
RAK4631 image also includes RS-232 on UART2, which is recommended for new
|
||||
installations. The unified image reserves UART1 for potentially powered GPS
|
||||
hardware; header-connected W25 flash also occupies UART1 pins. Legacy GPS-free
|
||||
Serial1 downloads remain available through exact-filename search for existing
|
||||
wiring, but are omitted from normal choices when the matching unified sensor
|
||||
profile is available. Ethernet remains a separate RAK4631 hardware image.
|
||||
LoRa OTA still requires the matching
|
||||
OTAFIX bootloader and a package compatible with the installed target.
|
||||
|
||||
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.
|
||||
|
||||
<div class="firmware-picker" data-firmware-picker data-release-repo="mikecarper/MeshCore" data-controls-url="../_data/firmware_controls.json?v=1.17.1.9-88c85108" data-bootloaders-url="../_data/bootloader_manifest.json?v=20261002-1" data-share-url="https://mikecarper.github.io/MeshCore/p/">
|
||||
<div class="firmware-picker-intro" role="note">
|
||||
<strong>Current release set</strong>
|
||||
<p data-role="release-set">Loading release information...</p>
|
||||
<p>
|
||||
For a new installation, choose the exact board and role, and select
|
||||
<strong>Full install / layout migration (merged .bin)</strong>. Current ESP32
|
||||
releases combine the supported features in Full; use runtime settings to
|
||||
choose the active transports. Where separate profiles remain, prefer
|
||||
<strong>FULL / complete</strong> when it meets your update and hardware
|
||||
requirements. Other chip families retain their qualified profiles.
|
||||
</p>
|
||||
<p>
|
||||
Upgrading an existing ESP32 infrastructure node from a smaller partition
|
||||
layout? The selected Full result links directly to its exact board and
|
||||
role partition-expansion ZIP when published. Follow its included README
|
||||
to check the installed OTA target and source layout for a supported Wi-Fi
|
||||
or LoRa route. If no unique matching ZIP is available, the picker links
|
||||
to the utility release to check availability. USB installation uses the matching
|
||||
merged image instead. An app-only .bin cannot change the partition table.
|
||||
If the installed firmware supports it, run <code>get storage.layout</code>
|
||||
to see the live flash size and OTA slots. Firmware version alone does not
|
||||
prove the installed layout.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="firmware-picker-order-note">
|
||||
Pick in any order. Use <strong>Any</strong> to clear one choice, or clear
|
||||
everything with the button below. The address bar updates with your choices;
|
||||
use <strong>Copy link to settings</strong> for a shorter link to the same selection.
|
||||
</p>
|
||||
|
||||
<form class="firmware-picker-form" data-role="form">
|
||||
<div class="firmware-picker-feedback firmware-picker-wide" data-role="filter-feedback" hidden>
|
||||
<div aria-live="polite" aria-atomic="true">
|
||||
@@ -82,6 +14,27 @@ from the published firmware assets.
|
||||
<a data-role="view-results" hidden>View files</a>
|
||||
</div>
|
||||
|
||||
<div class="firmware-picker-control firmware-picker-select-control">
|
||||
<label for="firmware-picker-hardware-family">Hardware</label>
|
||||
<select id="firmware-picker-hardware-family" data-field="hardwareFamily" disabled>
|
||||
<option value="">Loading hardware...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<fieldset class="firmware-picker-control firmware-picker-radio-control" data-radio-field="role" disabled>
|
||||
<legend>Firmware role</legend>
|
||||
<div class="firmware-picker-radio-options" data-field="role">
|
||||
Loading roles...
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="firmware-picker-control firmware-picker-radio-control firmware-picker-wide" data-radio-field="hardware" data-role="hardware-variant-control" disabled hidden>
|
||||
<legend>Hardware variant</legend>
|
||||
<div class="firmware-picker-radio-options" data-field="hardware">
|
||||
Choose hardware first
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<details class="firmware-picker-chip-family firmware-picker-wide">
|
||||
<summary data-role="chip-family-summary">Optional: chip family</summary>
|
||||
<p id="firmware-picker-chip-help">Skip this if you know your board. Picking hardware selects its chip family automatically. Choose Any to clear this filter.</p>
|
||||
@@ -93,20 +46,6 @@ from the published firmware assets.
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div class="firmware-picker-control firmware-picker-select-control">
|
||||
<label for="firmware-picker-hardware-family">Hardware</label>
|
||||
<select id="firmware-picker-hardware-family" data-field="hardwareFamily" disabled>
|
||||
<option value="">Loading hardware...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<fieldset class="firmware-picker-control firmware-picker-radio-control firmware-picker-wide" data-radio-field="hardware" data-role="hardware-variant-control" disabled hidden>
|
||||
<legend>Hardware variant</legend>
|
||||
<div class="firmware-picker-radio-options" data-field="hardware">
|
||||
Choose hardware first
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="firmware-picker-control firmware-picker-radio-control firmware-picker-wide" data-radio-field="install" disabled>
|
||||
<legend>Install operation</legend>
|
||||
<div class="firmware-picker-radio-options" data-field="install">
|
||||
@@ -114,13 +53,6 @@ from the published firmware assets.
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="firmware-picker-control firmware-picker-radio-control" data-radio-field="role" disabled>
|
||||
<legend>Firmware role</legend>
|
||||
<div class="firmware-picker-radio-options" data-field="role">
|
||||
Loading roles...
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="firmware-picker-control firmware-picker-radio-control" data-radio-field="logging" disabled>
|
||||
<legend>Logging / MQTT</legend>
|
||||
<div class="firmware-picker-radio-options" data-field="logging">
|
||||
@@ -151,6 +83,11 @@ from the published firmware assets.
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="firmware-picker-intro" role="note">
|
||||
<strong>Current release set</strong>
|
||||
<p data-role="release-set">Loading release information...</p>
|
||||
</div>
|
||||
|
||||
<section class="firmware-picker-result" data-role="bootloader" aria-live="polite" hidden>
|
||||
<h2 data-role="bootloader-title">nRF52 bootloader</h2>
|
||||
<div data-role="bootloader-content"></div>
|
||||
@@ -173,6 +110,27 @@ from the published firmware assets.
|
||||
<a href="https://github.com/mikecarper/MeshCore/releases">Browse all firmware releases</a>
|
||||
</section>
|
||||
|
||||
<details class="firmware-picker-help">
|
||||
<summary>Picker help</summary>
|
||||
<p>
|
||||
Pick in any order. Use <strong>Any</strong> to clear one choice, or
|
||||
<strong>Clear all choices</strong> to start over. Use
|
||||
<strong>Copy link to settings</strong> to share your selection.
|
||||
</p>
|
||||
<p>
|
||||
Choices stay limited to compatible builds. Picking hardware selects its
|
||||
chip family automatically. Filters disappear when they select the same
|
||||
file; runtime options appear under
|
||||
<strong>Restore your settings after flashing</strong> instead.
|
||||
Installation instructions and board-specific guidance follow your download.
|
||||
</p>
|
||||
<p>
|
||||
The picker reads public release metadata from GitHub. It does not upload
|
||||
device information. Hardware names, targets, and download links come from
|
||||
the published firmware assets.
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details class="firmware-asset-browser">
|
||||
<summary>Advanced: search current release filenames</summary>
|
||||
<p>
|
||||
@@ -189,6 +147,35 @@ from the published firmware assets.
|
||||
|
||||
## What the choices mean
|
||||
|
||||
Pick the choices in any order. The controls stay limited to compatible
|
||||
firmware combinations that were actually built in the current release set.
|
||||
The result count stays visible while you scroll through the filters on a phone.
|
||||
Once the exact board and role are selected, the picker skips logging,
|
||||
connection, and profile choices that all select the same firmware. Logging
|
||||
settings move into **Restore your settings after flashing** beneath the files.
|
||||
Choices that select different images remain visible.
|
||||
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.
|
||||
Chip family and hardware are the only dropdowns. All remaining choices use
|
||||
buttons. **Firmware profile** combines OTA support, Full/standard, and sensor
|
||||
or storage differences when a choice between different images is needed.
|
||||
|
||||
Use **Any** to clear one choice, or **Clear all choices** to start over.
|
||||
The address bar updates with your choices; use **Copy link to settings** for a
|
||||
shorter link to the same selection.
|
||||
|
||||
Each result includes **Restore your settings after flashing**. Its commands
|
||||
follow the selected logging mode and the exact image's verified role/hardware
|
||||
controls. Click On, Off, or Check to view and copy the commands; Companion
|
||||
MQTT and GPS show their app/WebConfig steps instead. See
|
||||
[feature switches by role](role_feature_switches.md) for the full reference.
|
||||
The [USB web console](https://flasher.meshcore.io/console) works with the
|
||||
default ASCII terminal on USB Companion and infrastructure roles.
|
||||
|
||||
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.
|
||||
|
||||
| Choice | Use |
|
||||
| --- | --- |
|
||||
| Companion | A phone, computer, or host application controls the radio |
|
||||
@@ -210,9 +197,8 @@ Connection and bridge choices depend on the selected role. Companion firmware
|
||||
may offer Full, combined USB + Bluetooth, Bluetooth, USB, Wi-Fi, serial, or
|
||||
Ethernet transports.
|
||||
Normal repeater firmware includes runtime-controlled RS-232 support where the
|
||||
board has room; use `set bridge.enabled on` after configuring `bridge.uart` and
|
||||
`bridge.baud`. The Wio-E5 remains the capacity exception and offers a separate
|
||||
RS-232 image. Choose Wi-Fi MQTT in the result's saved logging settings when
|
||||
board has room; use the selected image's runtime directions to configure it.
|
||||
Choose Wi-Fi MQTT in the result's saved logging settings when
|
||||
it shares the Full image; separate MQTT images retain a **Logging / MQTT**
|
||||
filter. Full ESP32 LoRa repeaters include runtime
|
||||
ESP-NOW when capacity permits; dedicated ESP-NOW images remain for measured
|
||||
@@ -229,11 +215,9 @@ supported, `set mqtt.enabled on|off` for MQTT. Newly combined ordinary
|
||||
repeaters start with ESP-NOW off until explicitly enabled, including the first
|
||||
upgrade from unmarked legacy preferences. Later reboots preserve that setting.
|
||||
On combined RS-232 boards without MQTT, `bridge.enabled` controls the UART.
|
||||
Full Heltec V3, WSL3 and RAK3112 MQTT images also retain RS-232;
|
||||
use `set rs232.enabled on|off` and `get rs232.running` for their independent
|
||||
UART. T-LoRa V2.1 keeps two Full choices due to its internal RAM limit:
|
||||
the normal repeater has UART plus ESP-NOW, and the observer has MQTT plus
|
||||
ESP-NOW. MQTT and ESP-NOW use one 2.4 GHz radio, so when both are enabled the ESP-NOW bridge channel must
|
||||
Use the selected download's board notes and runtime controls for images with
|
||||
independent UART, MQTT, and ESP-NOW services.
|
||||
MQTT and ESP-NOW use one 2.4 GHz radio, so when both are enabled the ESP-NOW bridge channel must
|
||||
match the connected WiFi access point's fixed channel. ESP-NOW-only mode does
|
||||
not require WiFi credentials. Its runtime
|
||||
`bridge.format` setting chooses the peer protocol: `wrapped` (the
|
||||
@@ -242,10 +226,6 @@ backward-compatible bridge-to-bridge default using `bridge.secret`) or `raw`
|
||||
`SenseCapIndicator-ESPNow`, and other primary-ESP-NOW nodes). This is one
|
||||
firmware choice, not two board images. Match `bridge.channel` to the primary
|
||||
nodes' `espnow.channel` before selecting `set bridge.format raw`.
|
||||
For Heltec V4 specifically, `companion_radio_full` is still a LoRa-primary
|
||||
Companion; choose the combined `heltec_v4_repeater` Full firmware and enable
|
||||
ESP-NOW to make that board the LoRa/ESP-NOW gateway. Use its exact merged artifact when
|
||||
changing roles or partition layouts.
|
||||
|
||||
## Share a selection
|
||||
|
||||
@@ -288,8 +268,8 @@ Current ESP32 ordinary releases use one Full / complete image per exact board
|
||||
and role. Full keeps the supported feature set and CLI; runtime switches select
|
||||
USB, Bluetooth, Wi-Fi, logging, and other qualified features. Separate portable,
|
||||
minimal, and single-transport ESP32 images are installation/recovery recipes,
|
||||
not additional ordinary release choices. The documented T-LoRa UART/MQTT RAM
|
||||
split remains two Full images. Other chip families retain their qualified
|
||||
not additional ordinary release choices. Board-specific capacity exceptions
|
||||
are shown with the selected download. Other chip families retain their qualified
|
||||
capacity and sensor-profile choices.
|
||||
|
||||
Moving an older ESP32 installation to an expanded Full layout requires a
|
||||
|
||||
+2
-2
@@ -120,7 +120,7 @@ extra_css:
|
||||
- _stylesheets/notification_builder.css?v=20261006-channel9
|
||||
- https://unpkg.com/leaflet@1.9.4/dist/leaflet.css
|
||||
- _stylesheets/extra.css
|
||||
- _stylesheets/firmware_picker.css?v=20261001-feedback-1
|
||||
- _stylesheets/firmware_picker.css?v=20261013-pick-first-1
|
||||
- _stylesheets/telemetry_decoder.css
|
||||
- _stylesheets/management_decoder.css
|
||||
- _stylesheets/filter_tool.css
|
||||
@@ -131,7 +131,7 @@ extra_javascript:
|
||||
- _javascript/hosted_doc_links.js?v=20261007-1
|
||||
- _javascript/notification_builder.js?v=20261006-channel9
|
||||
- https://unpkg.com/leaflet@1.9.4/dist/leaflet.js
|
||||
- _javascript/firmware_picker.js?v=20261012-short-picker-1
|
||||
- _javascript/firmware_picker.js?v=20261013-board-notes-1
|
||||
- _javascript/telemetry_decoder.js?v=20261007-hosted-form
|
||||
- _javascript/management_decoder.js
|
||||
- _javascript/filter_tool.js?v=20261002-hashbytes-2plus
|
||||
|
||||
@@ -2127,6 +2127,94 @@ assert.strictEqual(JSON.stringify(feedbackCatalog), feedbackSnapshot);
|
||||
assert.deepStrictEqual(simpleFilters, {...feedbackFilters, hardwareFamily: 'Heltec_v3'});
|
||||
console.log('same-image selectors skip redundant steps while distinct profiles and install routes stay explicit');
|
||||
|
||||
// Board-specific help belongs to exact qualified results, never to a broad
|
||||
// family filter or an unrelated role which happens to share the board name.
|
||||
const boardText = profile => picker.boardNotes(profile).join('\n');
|
||||
const boardProfile = target => currentCatalog.profiles.find(profile => profile.target === target);
|
||||
assert.deepStrictEqual(picker.boardNotes(null), []);
|
||||
assert.deepStrictEqual(picker.boardNotes({}), []);
|
||||
for (const board of ['RAK_3401', 'RAK_4631']) {
|
||||
for (const policy of ['full', 'reduced']) {
|
||||
const profile = boardProfile(board + '_repeater_unified_lora_ota-' + policy);
|
||||
assert(profile);
|
||||
const before = JSON.stringify(profile);
|
||||
const notes = boardText(profile);
|
||||
assert(notes.includes('storage automatically'));
|
||||
assert(notes.includes('sensor drivers, not a storage module'));
|
||||
assert(notes.includes('Internal flash accepts in-place deltas only'));
|
||||
assert(notes.includes('matching OTAFIX bootloader'));
|
||||
if (board === 'RAK_4631') {
|
||||
assert(notes.includes('UART2') && notes.includes('UART1') && notes.includes('W25'));
|
||||
assert(notes.includes('Legacy GPS-free Serial1') && notes.includes('Ethernet'));
|
||||
} else {
|
||||
assert(!notes.includes('UART') && !notes.includes('Ethernet'));
|
||||
}
|
||||
assert.strictEqual(JSON.stringify(profile), before, 'boardNotes must not mutate release metadata');
|
||||
for (const change of [
|
||||
{controls: undefined}, {ota: 'none'}, {sensorProfile: undefined},
|
||||
{hardware: 'RAK_4631_ethernet'}, {role: 'companion'},
|
||||
{controls: {...profile.controls, platform: 'ESP32_PLATFORM'}},
|
||||
{controls: {...profile.controls, updateRequirements: undefined}},
|
||||
{controls: {...profile.controls, updateRequirements: {lora: {storage: 'external_only'}}}},
|
||||
]) {
|
||||
assert.deepStrictEqual(picker.boardNotes({...profile, ...change}), [],
|
||||
'Unqualified adaptive-storage metadata must not create a storage promise');
|
||||
}
|
||||
}
|
||||
}
|
||||
const boardRak = boardProfile('RAK_4631_repeater_unified_lora_ota-full');
|
||||
assert(!boardText({...boardRak, controls: {...boardRak.controls, rs232: false}}).includes('UART2'));
|
||||
assert(!boardText({...boardRak, controls: {...boardRak.controls, gps: false}}).includes('UART2'));
|
||||
const boardLegacy = rakPartialCatalog.profiles.find(profile => profile.target === rakBridgeBase + '-reduced');
|
||||
assert(boardText(boardLegacy).includes('legacy GPS-free Serial1'));
|
||||
assert(!boardText(boardLegacy).includes('storage automatically'));
|
||||
assert.deepStrictEqual(picker.boardNotes({...boardLegacy,
|
||||
controls: {...boardLegacy.controls, gps: true}}), []);
|
||||
const boardEthernet = boardProfile('RAK_4631_repeater_ethernet-full');
|
||||
assert(boardText(boardEthernet).includes('separate RAK4631 Ethernet hardware image'));
|
||||
assert(!boardText(boardEthernet).includes('storage automatically'));
|
||||
assert(!boardText(boardEthernet).includes('Use UART2'));
|
||||
for (const [profile, selected] of [[tloraNormalProfile, 'UART/RS-232 choice'],
|
||||
[tloraObserverProfile, 'MQTT observer choice']]) {
|
||||
assert(boardText(profile).includes('two RAM-safe Full images'));
|
||||
assert(boardText(profile).includes(selected));
|
||||
for (const change of [{controls: undefined}, {feature: 'standard'},
|
||||
{controls: {...profile.controls, espnowBridge: false}},
|
||||
{controls: {...profile.controls, rs232: true, mqtt: true}},
|
||||
{target: profile.target.replace('V2_1_1_6', 'V2_0'), hardware: 'LilyGo_TLora_V2_0'}]) {
|
||||
assert.deepStrictEqual(picker.boardNotes({...profile, ...change}), []);
|
||||
}
|
||||
}
|
||||
assert(boardText(boardProfile('LilyGo_TLora_V2_1_1_6_repeater-full-logging'))
|
||||
.includes('UART/RS-232 choice'), 'Current UART-only logging recipe must retain the RAM warning');
|
||||
for (const profile of tripleCatalog.profiles) {
|
||||
assert(boardText(profile).includes('independent RS-232 and ESP-NOW'));
|
||||
assert.deepStrictEqual(picker.boardNotes({...profile,
|
||||
controls: {...profile.controls, rs232: false}}), []);
|
||||
}
|
||||
for (const profile of currentCatalog.profiles.filter(profile =>
|
||||
/^heltec_v4(?:_[a-z0-9]+)*_companion_radio_full/i.test(profile.target))) {
|
||||
assert(boardText(profile).includes('Full Companion is LoRa-primary'), profile.target);
|
||||
assert(!boardText(profile).includes('can bridge to ESP-NOW'));
|
||||
assert.deepStrictEqual(picker.boardNotes({...profile, controls: undefined}), []);
|
||||
assert.deepStrictEqual(picker.boardNotes({...profile,
|
||||
controls: {...profile.controls, primaryEspnow: true}}), []);
|
||||
}
|
||||
const boardV4Gateway = boardProfile('heltec_v4_repeater-full-usb-wifi');
|
||||
assert(boardText(boardV4Gateway).includes('can bridge to ESP-NOW'));
|
||||
assert.deepStrictEqual(picker.boardNotes({...boardV4Gateway,
|
||||
controls: {...boardV4Gateway.controls, espnowBridge: false}}), []);
|
||||
assert.deepStrictEqual(picker.boardNotes({...boardV4Gateway, role: 'companion'}), []);
|
||||
assert(boardText(wio).includes('flash-capacity exception'));
|
||||
assert(boardText(picker.parseTargetProfile('wio-e5_repeater')).includes('separate ordinary repeater'));
|
||||
for (const target of ['wio-e5_companion_radio_usb', 'wio-e5-mini_companion_radio_usb',
|
||||
'wio-e5-mini_repeater', 'wio-e5-dev_repeater', 'RAK_4631_companion_radio_full',
|
||||
'RAK_3401_sensor', 't1000e_companion_radio_full', 'heltec_v4_companion_radio_usb']) {
|
||||
assert.deepStrictEqual(picker.boardNotes(picker.parseTargetProfile(target)), [], target);
|
||||
}
|
||||
assert.deepStrictEqual(picker.boardNotes({...wio, controls: {platform: 'ESP32_PLATFORM'}}), []);
|
||||
console.log('exact-board notes qualify adaptive storage, UART wiring, RAM splits and gateway roles');
|
||||
|
||||
// Logging choices remain available beneath the download. Linked choices
|
||||
// preselect their exact commands; an unselected logging mode shows off steps.
|
||||
for (const logging of ['none', 'usb', 'wifi', 'both']) {
|
||||
@@ -2178,6 +2266,24 @@ global.document = {
|
||||
createTextNode: text => new RuntimeElement('#text', text),
|
||||
};
|
||||
try {
|
||||
const boardContainer = new RuntimeElement('section');
|
||||
picker.renderProfileCard(boardContainer, boardRak, boardRak.files[0], 'uf2', {}, {});
|
||||
const boardCard = boardContainer.children[0];
|
||||
const boardDetails = boardCard.children.find(node => node.className === 'firmware-picker-board-notes');
|
||||
assert(boardDetails && boardDetails.tagName === 'DETAILS');
|
||||
assert.strictEqual(!!boardDetails.open, false, 'Board notes start collapsed');
|
||||
assert.strictEqual(boardDetails.children[0].tagName, 'SUMMARY');
|
||||
assert.strictEqual(boardDetails.children[0].textContent, 'Board notes');
|
||||
assert(boardCard.children.indexOf(boardDetails) > boardCard.children.findIndex(node =>
|
||||
node.className === 'firmware-picker-actions'), 'Exact download actions stay ahead of board notes');
|
||||
assert(boardCard.children.indexOf(boardDetails) < boardCard.children.findIndex(node =>
|
||||
node.className === 'firmware-picker-steps'), 'Board notes precede installation steps');
|
||||
assert.strictEqual(boardDetails.children.filter(node => node.tagName === 'P').length,
|
||||
picker.boardNotes(boardRak).length);
|
||||
const noNotesContainer = new RuntimeElement('section');
|
||||
picker.renderProfileCard(noNotesContainer, companionEsp32, companionEsp32.files[0], 'bin', {}, {});
|
||||
assert(!noNotesContainer.children[0].children.some(node =>
|
||||
node.className === 'firmware-picker-board-notes'), 'Unaffected boards must not get an empty notes panel');
|
||||
const card = new RuntimeElement('article');
|
||||
const changes = [];
|
||||
picker.renderRuntimeDirections(card, observer, {logging: 'wifi'}, {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Offline picker packaging preserves qualified full/reduced OTA choices."""
|
||||
import importlib.util
|
||||
from html.parser import HTMLParser
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
@@ -12,7 +13,54 @@ PACKAGE = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(PACKAGE)
|
||||
|
||||
|
||||
class PickerMarkup(HTMLParser):
|
||||
def __init__(self, text):
|
||||
super().__init__()
|
||||
self.tags = []
|
||||
self.feed(text)
|
||||
|
||||
def matching(self, attribute, value):
|
||||
return [(index, tag, attrs) for index, (tag, attrs) in enumerate(self.tags)
|
||||
if attrs.get(attribute) == value]
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
self.tags.append((tag, dict(attrs)))
|
||||
|
||||
|
||||
class OfflinePickerPackageTests(unittest.TestCase):
|
||||
def assert_picker_first(self, text):
|
||||
markup = PickerMarkup(text)
|
||||
form = markup.matching('data-role', 'form')
|
||||
release = markup.matching('data-role', 'release-set')
|
||||
help_panels = markup.matching('class', 'firmware-picker-help')
|
||||
self.assertEqual(len(form), 1)
|
||||
self.assertEqual(len(release), 1)
|
||||
self.assertEqual(len(help_panels), 1)
|
||||
help_index, help_tag, help_attrs = help_panels[0]
|
||||
self.assertEqual(help_tag, 'details')
|
||||
self.assertNotIn('open', help_attrs)
|
||||
self.assertLess(form[0][0], release[0][0])
|
||||
self.assertLess(markup.matching('data-role', 'result')[0][0], help_index)
|
||||
fields = [(attrs.get('data-field'), index) for index, (_, attrs)
|
||||
in enumerate(markup.tags) if 'data-field' in attrs]
|
||||
self.assertEqual(fields[0][0], 'hardwareFamily')
|
||||
field_positions = dict(fields)
|
||||
self.assertLess(field_positions['role'], field_positions['install'])
|
||||
self.assertTrue({'chipFamily', 'hardwareFamily', 'hardware', 'install',
|
||||
'role', 'logging', 'firmwareProfile', 'mode'}.issubset(
|
||||
{field for field, _ in fields}))
|
||||
for _, attrs in markup.tags:
|
||||
for described_by in attrs.get('aria-describedby', '').split():
|
||||
self.assertEqual(len(markup.matching('id', described_by)), 1)
|
||||
|
||||
def test_hosted_page_starts_with_choices_not_instructions(self):
|
||||
source = (ROOT / 'docs/firmware_picker.md').read_text(encoding='utf-8')
|
||||
title_end = source.index('# Firmware picker') + len('# Firmware picker')
|
||||
picker_start = source.index('<div class="firmware-picker"')
|
||||
self.assertFalse(source[title_end:picker_start].strip())
|
||||
self.assert_picker_first(source)
|
||||
self.assertNotIn('The unified\nRAK4631 image', source)
|
||||
|
||||
def test_sensor_policy_pairs_and_individual_downloads_are_retained(self):
|
||||
source = 'a' * 40
|
||||
family = 'v1.17.1.7-dev-aaaaaaaa'
|
||||
@@ -30,6 +78,7 @@ class OfflinePickerPackageTests(unittest.TestCase):
|
||||
uploader={'token': 'not part of the picker'}))
|
||||
releases = [dict(name=family, tag_name=family, assets=assets, authorization='private')]
|
||||
packaged = PACKAGE.package(releases, controls)
|
||||
self.assert_picker_first(packaged)
|
||||
match = re.search(r'<script type="application/json" id="firmware-picker-data">(.*?)</script>', packaged)
|
||||
embedded = json.loads(match[1])
|
||||
self.assertEqual(embedded['controls'], controls)
|
||||
|
||||
Reference in new issue
Block a user