Unify OTA picker capabilities
This commit is contained in:
1 parent
c6f1f50305
commit
142d166252
6 files changed
+95
-35
No files matched your search
@@ -270,8 +270,9 @@ for section, options in data:
|
||||
|
||||
# Keep each ordinary repeater environment feature-rich (including external
|
||||
# sensors), and expose a separately named no-external-sensors OTA build for
|
||||
# every ESP32/nRF52 repeater role. These two platforms have a complete apply
|
||||
# path; RP2040 and STM32 do not yet have the required bootloader/apply path.
|
||||
# ESP32/nRF52 repeaters that need a lean staging profile. These two platforms
|
||||
# have a complete apply path; RP2040 and STM32 do not yet have the required
|
||||
# bootloader/apply path.
|
||||
local env_name ota_env full_env usb_env ble_env
|
||||
local -a base_envs=("${SUPPORTED_PIO_ENVS[@]}")
|
||||
for env_name in "${base_envs[@]}"; do
|
||||
@@ -286,6 +287,12 @@ for section, options in data:
|
||||
*_repeater|*_repeater_|*_repeatr|*_repeatr_) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
# SolarXiao 30S/33S repeaters already use matched external QSPI staging,
|
||||
# so their normal full-sensor image is install-capable. A second lean
|
||||
# no-external-sensors target provides no additional OTA capability.
|
||||
case "${env_name,,}" in
|
||||
solarxiao_30s_repeater|solarxiao_33s_repeater) continue ;;
|
||||
esac
|
||||
ota_env="${env_name%_}_lora_ota_no_external_sensors"
|
||||
if [ -n "${PIO_ENV_PLATFORM_BY_NAME[$ota_env]+x}" ]; then
|
||||
continue
|
||||
|
||||
@@ -40,10 +40,9 @@
|
||||
});
|
||||
|
||||
const OTA_LABELS = Object.freeze({
|
||||
none: "No explicit OTA profile",
|
||||
"ota-enabled": "OTA-enabled profile",
|
||||
"lora-receiver": "LoRa OTA receiver",
|
||||
"lora-source": "LoRa OTA source (Full Companion)",
|
||||
none: "No LoRa OTA",
|
||||
"lora-receiver": "LoRa OTA enabled / receiver",
|
||||
"lora-source": "LoRa OTA source only (Full Companion)",
|
||||
});
|
||||
|
||||
const MODE_LABELS = Object.freeze({
|
||||
@@ -84,11 +83,8 @@
|
||||
// Generated Full Companion artifact name. It uses the standard Heltec V4
|
||||
// board definition and auto-detects the V4.2 and V4.3 FEM hardware.
|
||||
"heltec_v4_2_v4_3": "heltec_v4",
|
||||
// These target prefixes select a radio profile on otherwise identical
|
||||
// hardware. Keep that distinction in the firmware variant facet instead.
|
||||
"heltec_v4_3": "heltec_v4",
|
||||
"heltec_v4_3_expansionkit_tft": "heltec_v4_expansionkit_tft",
|
||||
"heltec_v4_3_tft": "heltec_v4_tft",
|
||||
// These Station target prefixes select a firmware/radio profile on the
|
||||
// same physical hardware.
|
||||
"Station_G2_logging": "Station_G2",
|
||||
"Station_G3_ESP32_logging": "Station_G3_ESP32",
|
||||
});
|
||||
@@ -100,6 +96,14 @@
|
||||
"Station_G3_ESP32_logging",
|
||||
]);
|
||||
|
||||
const HIDDEN_PROFILE_TARGETS = Object.freeze([
|
||||
// SolarXiao repeaters already stage full-sensor LoRa OTA images in their
|
||||
// matched external QSPI flash. Older releases contain redundant lean
|
||||
// siblings; keep those files searchable without recommending them.
|
||||
"solarxiao_30S_repeater_lora_ota_no_external_sensors",
|
||||
"solarxiao_33S_repeater_lora_ota_no_external_sensors",
|
||||
]);
|
||||
|
||||
let pickerInstanceCount = 0;
|
||||
|
||||
function isSafeGithubUrl(value) {
|
||||
@@ -368,6 +372,9 @@
|
||||
}
|
||||
|
||||
const knownLabels = {
|
||||
"heltec_v4_3": "V4.3",
|
||||
"heltec_v4_3_expansionkit_tft": "V4.3 + expansion kit + TFT",
|
||||
"heltec_v4_3_tft": "V4.3 + TFT",
|
||||
"heltec_v4_expansionkit": "Expansion kit",
|
||||
"heltec_v4_expansionkit_tft": "Expansion kit + TFT",
|
||||
"heltec_v4_r8": "R8",
|
||||
@@ -427,7 +434,7 @@
|
||||
|
||||
const profiles = Array.from(grouped.values()).map(function (profile) {
|
||||
profile.ota = profile.explicitOta ||
|
||||
(profile.otaPackaged ? "ota-enabled" : "none");
|
||||
(profile.otaPackaged ? "lora-receiver" : "none");
|
||||
profile.installKinds = INSTALL_ORDER.filter(function (kind) {
|
||||
return Boolean(canonicalAsset(profile.files, kind));
|
||||
});
|
||||
@@ -436,7 +443,8 @@
|
||||
});
|
||||
return profile;
|
||||
}).filter(function (profile) {
|
||||
return !HIDDEN_PROFILE_HARDWARE.includes(profile.sourceHardware);
|
||||
return !HIDDEN_PROFILE_HARDWARE.includes(profile.sourceHardware) &&
|
||||
!HIDDEN_PROFILE_TARGETS.includes(profile.target);
|
||||
}).sort(function (a, b) {
|
||||
return a.target.localeCompare(b.target, undefined, {
|
||||
numeric: true,
|
||||
@@ -559,7 +567,7 @@
|
||||
function optionSort(field, a, b) {
|
||||
const roleOrder = ["companion", "repeater", "room", "sensor", "terminal", "kiss", "other"];
|
||||
const loggingOrder = ["none", "usb", "wifi"];
|
||||
const otaOrder = ["none", "ota-enabled", "lora-receiver", "lora-source"];
|
||||
const otaOrder = ["none", "lora-receiver", "lora-source"];
|
||||
const featureOrder = ["standard", "full"];
|
||||
const modeOrder = ["standard", "full", "ble", "usb", "wifi", "serial", "ethernet", "mqtt", "espnow", "rs232"];
|
||||
const orders = {
|
||||
|
||||
@@ -135,9 +135,8 @@ from the published firmware assets.
|
||||
| USB logging / USB-connected MQTT | Node remains attached to a computer over a data-capable USB cable |
|
||||
| Wi-Fi MQTT observer | Firmware connects directly to MQTT over Wi-Fi; this is not USB logging |
|
||||
| No logging | Normal standalone operation without the dedicated logging/MQTT profile |
|
||||
| LoRa OTA receiver | Repeater profile that can stage an exact matching update received over LoRa |
|
||||
| LoRa OTA source | Full Companion serving a host-supplied update to another node |
|
||||
| OTA-enabled profile | Build uses an OTA-capable application/partition profile, but is not necessarily an explicit LoRa receiver |
|
||||
| LoRa OTA enabled / receiver | Install-capable profile that can stage an exact matching update received over LoRa |
|
||||
| LoRa OTA source only | Full Companion serving a host-supplied update to another node without self-installing it |
|
||||
|
||||
Connection and bridge choices depend on the selected role. Companion firmware
|
||||
may offer Full, Bluetooth, USB, Wi-Fi, serial, or Ethernet transports.
|
||||
|
||||
+4
-2
@@ -74,8 +74,10 @@ RAK13302 radio's BUSY/DIO1 lines. The RAK4631 OTA repeater retains GPS except
|
||||
for its Serial1 RS232 bridge, which uses the same UART. Selected nRF52 boards with matched external
|
||||
QSPI application and bootloader support can instead make the normal full-sensor
|
||||
repeater install-capable; those targets do not need to reserve internal flash
|
||||
for the downloaded container. Other normal repeaters can still serve as
|
||||
intermediate relays but cannot necessarily install an update themselves. ESP32
|
||||
for the downloaded container. SolarXiao 30S and 33S use this matched external-QSPI
|
||||
path and therefore do not emit redundant no-external-sensors siblings. Other
|
||||
normal repeaters can still serve as intermediate relays but cannot necessarily
|
||||
install an update themselves. ESP32
|
||||
`-ota-` siblings also retain the lightweight browser WiFi uploader (`start
|
||||
ota`), the complete CLI, and a 254-entry neighbor table. RP2040 and STM32
|
||||
repeaters do not currently have a safe self-apply path, but current repeater
|
||||
|
||||
@@ -125,9 +125,12 @@ artifacts, including room, sensor, and repeater roles, must fit the legacy slot
|
||||
limit and the target's actual app partition. The ESP32-C6 `no_external_sensors` OTA siblings are the narrow
|
||||
exception: the Arduino 3.x WiFi runtime cannot fit that cross-family ceiling, so those images retain their
|
||||
established target-specific 1920 KiB or larger A/B app layout and are checked against the actual app
|
||||
partition. For every standalone ESP32 and nRF52 repeater, `build.sh` also exposes an explicit
|
||||
`*_lora_ota_no_external_sensors` artifact: the ordinary repeater remains sensor-enabled, while that sibling
|
||||
disables optional external environmental-sensor drivers for LoRa distribution. Integrated GPS and other
|
||||
partition. For standalone ESP32 and nRF52 repeaters that need a lean staging
|
||||
profile, `build.sh` also exposes an explicit `*_lora_ota_no_external_sensors`
|
||||
artifact: the ordinary repeater remains sensor-enabled, while that sibling
|
||||
disables optional external environmental-sensor drivers for LoRa distribution. SolarXiao 30S and 33S use
|
||||
matched external QSPI staging, so their ordinary full-sensor repeater is already install-capable and no
|
||||
redundant lean sibling is generated. Integrated GPS and other
|
||||
board-native telemetry remain enabled where the target selects the GPS-preserving lean profile. The RAK3401
|
||||
OTA repeater also retains RAK12501 GPS support; install that module in sensor slot A because slot D conflicts
|
||||
with the RAK13302 radio's BUSY/DIO1 lines. RAK4631 OTA repeaters retain GPS except for the Serial1 RS232
|
||||
|
||||
@@ -44,6 +44,8 @@ const releases = [
|
||||
asset("Station_G2_repeater-" + family + "-deadbee.bin"),
|
||||
asset("Station_G2_repeater-" + family + "-merged.bin"),
|
||||
asset("Station_G2_repeater-" + family + ".bin"),
|
||||
asset("solarxiao_33S_repeater-ota-" + family + ".uf2"),
|
||||
asset("solarxiao_33S_repeater-ota-" + family + ".zip"),
|
||||
asset("wio-e5-repeater_bridge_rs232-" + family + ".hex"),
|
||||
]),
|
||||
release("utility-" + family, "2026-08-23T12:00:04Z", [
|
||||
@@ -63,6 +65,14 @@ const releases = [
|
||||
"Station_G2_repeater_lora_ota_no_external_sensors-ota-" +
|
||||
family + ".bin"
|
||||
),
|
||||
asset(
|
||||
"solarxiao_33S_repeater_lora_ota_no_external_sensors-ota-" +
|
||||
family + ".uf2"
|
||||
),
|
||||
asset(
|
||||
"solarxiao_33S_repeater_lora_ota_no_external_sensors-ota-" +
|
||||
family + ".zip"
|
||||
),
|
||||
]),
|
||||
release("full-profiles-" + family, "2026-08-23T12:00:01Z", [
|
||||
asset(
|
||||
@@ -96,8 +106,8 @@ assert(!releaseSet.releases.some(function (item) {
|
||||
|
||||
const catalog = picker.buildCatalog(releases);
|
||||
assert.strictEqual(catalog.releaseSet.familyTag, family);
|
||||
assert.strictEqual(catalog.profiles.length, 11);
|
||||
assert.strictEqual(catalog.rows.length, 25);
|
||||
assert.strictEqual(catalog.profiles.length, 12);
|
||||
assert.strictEqual(catalog.rows.length, 29);
|
||||
|
||||
function profile(target) {
|
||||
const found = catalog.profiles.find(function (item) {
|
||||
@@ -153,23 +163,23 @@ assert.strictEqual(
|
||||
);
|
||||
assert.strictEqual(
|
||||
picker.canonicalHardware("heltec_v4_3_tft"),
|
||||
"heltec_v4_tft"
|
||||
"heltec_v4_3_tft"
|
||||
);
|
||||
assert.strictEqual(
|
||||
picker.canonicalHardware("heltec_v4_3_expansionkit_tft"),
|
||||
"heltec_v4_expansionkit_tft"
|
||||
"heltec_v4_3_expansionkit_tft"
|
||||
);
|
||||
|
||||
const heltecV4FemOff = picker.parseTargetProfile(
|
||||
"heltec_v4_3_companion_radio_ble_femoff"
|
||||
);
|
||||
assert.strictEqual(heltecV4FemOff.hardware, "heltec_v4");
|
||||
assert.strictEqual(heltecV4FemOff.hardware, "heltec_v4_3");
|
||||
assert.strictEqual(heltecV4FemOff.variant, "femoff");
|
||||
|
||||
const heltecV4TftFemOff = picker.parseTargetProfile(
|
||||
"heltec_v4_3_tft_companion_radio_wifi_femoff"
|
||||
);
|
||||
assert.strictEqual(heltecV4TftFemOff.hardware, "heltec_v4_tft");
|
||||
assert.strictEqual(heltecV4TftFemOff.hardware, "heltec_v4_3_tft");
|
||||
assert.strictEqual(heltecV4TftFemOff.variant, "femoff");
|
||||
|
||||
const g2RxBoosted = profile("Station_G2_logging_repeater");
|
||||
@@ -204,20 +214,53 @@ assert.strictEqual(
|
||||
|
||||
const fullLogging = profile("Station_G2_repeater-full-logging");
|
||||
assert.strictEqual(fullLogging.logging, "usb");
|
||||
assert.strictEqual(fullLogging.ota, "ota-enabled");
|
||||
assert.strictEqual(fullLogging.ota, "lora-receiver");
|
||||
assert.strictEqual(fullLogging.feature, "full");
|
||||
assert.strictEqual(fullLogging.variant, "default");
|
||||
|
||||
const mqtt = profile("Station_G2_repeater_observer_mqtt-full");
|
||||
assert.strictEqual(mqtt.logging, "wifi");
|
||||
assert.strictEqual(mqtt.mode, "mqtt");
|
||||
assert.strictEqual(mqtt.ota, "ota-enabled");
|
||||
assert.strictEqual(mqtt.ota, "lora-receiver");
|
||||
|
||||
const lora = profile(
|
||||
"Station_G2_repeater_lora_ota_no_external_sensors"
|
||||
);
|
||||
assert.strictEqual(lora.ota, "lora-receiver");
|
||||
assert.strictEqual(lora.variant, "no-external-sensors");
|
||||
assert.strictEqual(
|
||||
picker.OTA_LABELS["lora-receiver"],
|
||||
"LoRa OTA enabled / receiver"
|
||||
);
|
||||
assert(!Object.prototype.hasOwnProperty.call(
|
||||
picker.OTA_LABELS,
|
||||
"ota-enabled"
|
||||
));
|
||||
assert(!picker.uniqueValues(catalog.profiles, "ota").includes("ota-enabled"));
|
||||
|
||||
const solarXiao = profile("solarxiao_33S_repeater");
|
||||
assert.strictEqual(solarXiao.ota, "lora-receiver");
|
||||
assert.strictEqual(solarXiao.variant, "default");
|
||||
assert(!catalog.profiles.some(function (item) {
|
||||
return item.target ===
|
||||
"solarxiao_33S_repeater_lora_ota_no_external_sensors";
|
||||
}));
|
||||
assert(catalog.rows.some(function (item) {
|
||||
return item.target ===
|
||||
"solarxiao_33S_repeater_lora_ota_no_external_sensors";
|
||||
}));
|
||||
assert.deepStrictEqual(
|
||||
picker.facetValues(
|
||||
catalog.profiles,
|
||||
{
|
||||
hardware: "solarxiao_33S",
|
||||
role: "repeater",
|
||||
logging: "none",
|
||||
},
|
||||
"ota"
|
||||
),
|
||||
["lora-receiver"]
|
||||
);
|
||||
|
||||
const wio = profile("wio-e5-repeater_bridge_rs232");
|
||||
assert.strictEqual(wio.hardware, "wio-e5");
|
||||
@@ -269,11 +312,8 @@ assert.strictEqual(
|
||||
"Base / standard (V4.2 / V4.3 auto-detect)"
|
||||
);
|
||||
assert.strictEqual(
|
||||
picker.humanizeHardwareVariant(
|
||||
picker.canonicalHardware("heltec_v4_3_tft"),
|
||||
"heltec_v4"
|
||||
),
|
||||
"TFT"
|
||||
picker.humanizeHardwareVariant("heltec_v4_3_tft", "heltec_v4"),
|
||||
"V4.3 + TFT"
|
||||
);
|
||||
const groupedHardwareProfiles = [
|
||||
{
|
||||
@@ -353,6 +393,7 @@ assert.deepStrictEqual(
|
||||
"RAK_4631",
|
||||
"Station_G2",
|
||||
"Station_G3_ESP32",
|
||||
"solarxiao_33S",
|
||||
"wio-e5",
|
||||
].sort()
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user