fix(picker): recommend unified RAK repeater profiles

This commit is contained in:
mikecarper committed 2026-10-08 01:34:07 -07:00
1 parent 4bbd613417
commit c07d16e398
4 files changed
+90 -16

No files matched your search

+27 -15
View File
@@ -482,6 +482,11 @@
if (/^RAK_(?:3401|4631)_repeater_unified_lora_ota(?:-(?:full|reduced)(?:-ota)?)?$/i.test(target)) {
variant = "default";
}
if (/^RAK_4631_repeater_ethernet(?:-(?:full|reduced)(?:-ota)?)?$/i.test(target)) {
// The RAK13800 Ethernet image is a real hardware choice, separate from
// adaptive storage. Label it consistently for both sensor policies.
variant = "ethernet";
}
if (sourceHardware === "Station_G2_logging") {
variant = variant === "default"
? "rx-boosted"
@@ -571,11 +576,13 @@
heltec_t096_repeater_bridge_rs232_lora_ota_no_external_sensors:
"heltec_t096_repeater_lora_ota_no_external_sensors",
rak_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors:
"rak_4631_repeater_lora_ota_no_external_sensors",
["rak_4631_repeater_unified_lora_ota", "rak_4631_repeater_lora_ota_no_external_sensors"],
rak_4631_repeater_bridge_rs232_serial2_lora_ota_no_external_sensors:
"rak_4631_repeater_lora_ota_no_external_sensors",
rak_4631_repeater_bridge_rs232_serial1: "rak_4631_repeater",
rak_4631_repeater_bridge_rs232_serial2: "rak_4631_repeater",
["rak_4631_repeater_unified_lora_ota", "rak_4631_repeater_lora_ota_no_external_sensors"],
rak_4631_repeater_bridge_rs232_serial1:
["rak_4631_repeater_unified_lora_ota", "rak_4631_repeater"],
rak_4631_repeater_bridge_rs232_serial2:
["rak_4631_repeater_unified_lora_ota", "rak_4631_repeater"],
promicro_repeater_bridge_rs232_serial1: "promicro_repeater",
heltec_t114_without_display_repeater_bridge_rs232:
"heltec_t114_without_display_repeater",
@@ -603,11 +610,16 @@
combinedUsbBleKeys.has(key);
const replacedTerminal = profile.role === "terminal" &&
terminalReplacementKeys.has(key);
const rs232Replacement = mergedRs232Targets[
String(profile.target || "").toLowerCase()
];
const target = String(profile.target || "").toLowerCase();
const sensorSuffix = (target.match(/-(?:full|reduced)$/) || [""])[0];
const baseTarget = sensorSuffix ? target.slice(0, -sensorSuffix.length) : target;
const rs232Replacement = mergedRs232Targets[baseTarget];
// Preserve the exact sensor policy: a Full image does not replace a
// missing Reduced image, or an older unsuffixed compatibility image.
const replacedRs232 = Boolean(rs232Replacement &&
targets.has(rs232Replacement));
[].concat(rs232Replacement).some(function (replacement) {
return targets.has(replacement + sensorSuffix);
}));
return !(replacedAttachedTransport && fullKeys.has(key)) &&
!replacedByUsbBle && !replacedTerminal && !replacedRs232;
});
@@ -644,8 +656,8 @@
}));
return (profiles || []).filter(function (profile) {
const target = String(profile && profile.target || "").toLowerCase();
const match = /^rak_(3401|4631)_repeater_(?:lora_ota_no_external_sensors|w25q16_lora_ota|rak15001_slot_c_lora_ota|rak13302_w25q16_lora_ota)$/.exec(target);
return !match || !targets.has("rak_" + match[1] + "_repeater_unified_lora_ota");
const match = /^rak_(3401|4631)_repeater_(?:lora_ota_no_external_sensors|w25q16_lora_ota|rak15001_slot_c_lora_ota|rak13302_w25q16_lora_ota)(-(?:full|reduced))?$/.exec(target);
return !match || !targets.has("rak_" + match[1] + "_repeater_unified_lora_ota" + (match[2] || ""));
});
}
@@ -668,10 +680,7 @@
function applyMergedRak4631RepeaterCapabilities(profiles) {
return (profiles || []).map(function (profile) {
const target = String(profile && profile.target || "").toLowerCase();
if (
target !== "rak_4631_repeater" &&
target !== "rak_4631_repeater_lora_ota_no_external_sensors"
) {
if (!/^rak_4631_repeater(?:_lora_ota_no_external_sensors|_unified_lora_ota)?(?:-(?:full|reduced))?$/.test(target)) {
return profile;
}
@@ -1667,6 +1676,9 @@
if (profile.role === "repeater") toggle("Repeat mesh traffic", "set repeat", "get repeat");
if (info.rs232 && infrastructure) {
const mode = chosen.mode;
const uartNote = /^RAK_4631_repeater_unified_lora_ota-(?:full|reduced)$/i.test(profile.target) && profile.sensorProfile
? "Use the UART and pin map for this exact board. Unified RAK4631 supports set bridge.uart 1 or set bridge.uart 2 while the bridge is stopped. UART1 pauses UART GPS while the bridge runs and restores it when stopped."
: "Use the UART and pin map for this exact board. Canonical GPS-enabled RAK4631 uses UART2; select it with set bridge.uart 2 while the bridge is stopped. UART1 needs a compatible GPS-free image.";
// MQTT images keep the historical bridge.enabled ESP-NOW alias.
// Their independently saved UART uses the transport-specific controls.
const uartSetting = info.mqtt ? "rs232.enabled" : "bridge.enabled";
@@ -1677,7 +1689,7 @@
{ label: "Set baud", commands: ["set " + uartSetting + " off", "set bridge.baud 115200", "set " + uartSetting + " on"] },
].sort(function (a, b) {
return mode === "standard" ? Number(b.label === "Off") - Number(a.label === "Off") : 0;
}), "Use the UART and pin map for this exact board. Canonical GPS-enabled RAK4631 uses UART2; select it with set bridge.uart 2 while the bridge is stopped. UART1 needs a compatible GPS-free image.");
}), uartNote);
}
if (info.espnowBridge && infrastructure) {
// Combined images need the transport-specific switch: bridge.enabled
+7
View File
@@ -25,6 +25,13 @@ 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 runtime RS-232 UART selection. 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.
+1 -1
View File
@@ -127,7 +127,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=20261006-simplify-1
- _javascript/firmware_picker.js?v=20261008-rak-combined-1
- _javascript/telemetry_decoder.js?v=20261007-hosted-form
- _javascript/management_decoder.js
- _javascript/filter_tool.js?v=20261002-hashbytes-2plus
+55
View File
@@ -1014,6 +1014,27 @@ const rakLegacyCatalog = picker.buildCatalog([
release(family, "2026-09-25T00:00:00Z", rakStorageAssets.slice(1, 4).concat(rakStorageAssets.slice(5))),
]);
assert.strictEqual(rakLegacyCatalog.profiles.length, 5);
// Sensor-policy suffixes must not resurrect the storage-specific compatibility
// images. A Full successor cannot replace a missing Reduced successor.
const rakPolicyTargets = rakStorageTargets.flatMap(target =>
['full', 'reduced'].map(policy => target + '-' + policy));
const rakPolicyAssets = rakPolicyTargets.map(target =>
asset(target + '-ota-' + family + '.uf2'));
const rakPolicyCatalog = picker.buildCatalog([
release(family, '2026-10-08T00:00:00Z', rakPolicyAssets),
]);
assert.deepStrictEqual(rakPolicyCatalog.profiles.map(item => item.target).sort(),
['RAK_3401', 'RAK_4631'].flatMap(board =>
['full', 'reduced'].map(policy => board + '_repeater_unified_lora_ota-' + policy)).sort());
assert.deepStrictEqual(rakPolicyCatalog.rows.map(item => item.target).sort(), rakPolicyTargets.sort(),
'All exact legacy OTA identities must remain in the raw catalog');
const rakMissingReducedCatalog = picker.buildCatalog([
release(family, '2026-10-08T00:00:00Z', rakPolicyAssets.filter(file =>
!file.name.includes('_unified_lora_ota-reduced-'))),
]);
assert.deepStrictEqual(rakMissingReducedCatalog.profiles.map(item => item.target).sort(),
rakPolicyTargets.filter(target => target.endsWith('_unified_lora_ota-full') ||
(target.endsWith('-reduced') && !target.includes('_unified_lora_ota-'))).sort());
assert.strictEqual(picker.formatBytes(2097152), "2.00 MiB");
assert.strictEqual(
picker.parseFirmwareAsset(
@@ -1155,6 +1176,40 @@ const currentCatalog = picker.buildCatalog([
], currentControls);
assert.strictEqual(currentCatalog.rows.length, currentAssets.length);
assert(currentCatalog.profiles.every(profile => profile.controls && profile.chipFamily !== 'unknown'));
for (const [hardware, bases] of [
['RAK_4631', ['RAK_4631_repeater_unified_lora_ota', 'RAK_4631_repeater_ethernet']],
['RAK_3401', ['RAK_3401_repeater_unified_lora_ota']],
]) {
const context = {hardware, role: 'repeater'};
const expected = bases.flatMap(base => ['full', 'reduced'].map(policy => base + '-' + policy));
const visible = currentCatalog.profiles.filter(profile => picker.profileMatchesFacets(profile, context));
assert.deepStrictEqual(visible.map(profile => profile.target).sort(), expected.sort(),
hardware + ' must recommend only combined storage/bridge images and genuine Ethernet variants');
const choices = picker.firmwareProfileChoices(currentCatalog.profiles, context);
assert.strictEqual(choices.length, expected.length, hardware + ' must have one choice per real image');
assert.strictEqual(new Set(choices.map(choice => choice.label)).size, choices.length,
hardware + ' must not show duplicate profile labels');
for (const profile of visible.filter(item => item.target.includes('_unified_lora_ota-'))) {
assert.deepStrictEqual(picker.profileFieldValues(profile, 'mode'),
hardware === 'RAK_4631' ? ['standard', 'rs232'] : ['standard']);
if (hardware === 'RAK_4631') {
const bridge = picker.runtimeDirections(profile, {mode: 'rs232'})
.find(section => section.title.startsWith('RS232 bridge'));
assert(bridge && bridge.note.includes('set bridge.uart 1') && bridge.note.includes('pauses UART GPS'),
'Both combined sensor policies must explain runtime UART selection and GPS sharing');
assert(!bridge.note.includes('GPS-free image'));
}
}
}
const rakBridgeBase = 'RAK_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors';
const rakPartialAssets = currentAssets.filter(file =>
file.name.startsWith('RAK_4631_repeater_unified_lora_ota-full-') ||
file.name.startsWith(rakBridgeBase + '-reduced-'));
const rakPartialCatalog = picker.buildCatalog([
release(currentControls.familyTag, '2026-10-08T00:00:00Z', rakPartialAssets),
], currentControls);
assert(rakPartialCatalog.profiles.some(profile => profile.target === rakBridgeBase + '-reduced'),
'A Full combined image must not hide the only available Reduced bridge image');
const expandedEsp32 = currentCatalog.profiles.find(profile =>
profile.target === 'Ebyte_EoRa-S3_Repeater-full-usb-wifi');
assert(expandedEsp32);