diff --git a/docs/_javascript/firmware_picker.js b/docs/_javascript/firmware_picker.js index 2ddabcfda..13d240c91 100644 --- a/docs/_javascript/firmware_picker.js +++ b/docs/_javascript/firmware_picker.js @@ -614,14 +614,12 @@ const sensorSuffix = (target.match(/-(?:full|reduced)$/) || [""])[0]; const baseTarget = sensorSuffix ? target.slice(0, -sensorSuffix.length) : target; const rs232Replacement = mergedRs232Targets[baseTarget]; - // Qualified RAK Full/Reduced images retain powered WisBlock GPS and - // reserve UART1 even when its bounded probe saw no receiver. The exact - // GPS-free Serial1 image remains a distinct, usable hardware choice. - const reservedRakUart1 = sensorSuffix && - /^rak_4631_repeater_bridge_rs232_serial1(?:_lora_ota_no_external_sensors)?$/.test(baseTarget); + // Recommendations prefer the combined UART2 image on RAK4631. Exact + // Serial1 files remain in raw rows for existing GPS-free installations; + // this preference does not migrate their wiring or OTA identity. // Preserve the exact sensor policy: a Full image does not replace a // missing Reduced image, or an older unsuffixed compatibility image. - const replacedRs232 = Boolean(!reservedRakUart1 && rs232Replacement && + const replacedRs232 = Boolean(rs232Replacement && [].concat(rs232Replacement).some(function (replacement) { return targets.has(replacement + sensorSuffix); })); @@ -1682,7 +1680,7 @@ if (info.rs232 && infrastructure) { const mode = chosen.mode; const uartNote = /^RAK_4631_repeater_unified_lora_ota-(?:full|reduced)$/i.test(profile.target) && profile.sensorProfile - ? "Unified RAK4631 uses UART2 for the RS-232 bridge; select it with set bridge.uart 2 while the bridge is stopped. GPS safety guards reserve UART1 even when no GPS was detected. Use the separate GPS-free Serial1 image for UART1, with no powered UART GPS or header-connected W25 flash on those pins." + ? "Unified RAK4631 uses UART2 for the RS-232 bridge; select it with set bridge.uart 2 while the bridge is stopped. GPS safety guards reserve UART1 even when no GPS was detected. Use UART2 for new installations. Legacy Serial1 downloads remain available by exact filename for existing GPS-free wiring." : /^RAK_4631_repeater_bridge_rs232_serial1(?:_lora_ota_no_external_sensors)?-(?:full|reduced)$/i.test(profile.target) ? "This GPS-free Serial1 image uses UART1. Do not fit a powered UART GPS or header-connected W25 flash on the same UART1 pins. Use the unified UART2 image when those pins are occupied." : "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."; diff --git a/docs/firmware_picker.md b/docs/firmware_picker.md index 2bfb9c8e2..7838f67db 100644 --- a/docs/firmware_picker.md +++ b/docs/firmware_picker.md @@ -28,10 +28,13 @@ 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. A GPS-free Serial1 image remains -separate because the unified image reserves UART1 for potentially powered -GPS hardware. Header-connected W25 flash also occupies UART1 pins. Ethernet -remains a separate RAK4631 hardware image. LoRa OTA still requires the matching +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 @@ -253,8 +256,9 @@ while leaving other choices open. **Clear all choices** removes the picker parameters from the URL. Changes update the current browser-history entry without reloading the page or adding a Back-button entry for every click. -For example, [RAK3401 repeater with internal storage](?hardware=RAK_3401&role=repeater&ota=lora-receiver&variant=no-external-sensors) -preselects that board, role, OTA capability, and storage profile. +For example, [RAK3401 unified Full repeater](?hardware=RAK_3401&role=repeater&ota=lora-receiver&feature=full&variant=default) +preselects that board, role, OTA capability, and Full sensor policy. Supported +internal or external storage is detected by the unified image. The query parameters are `chipFamily`, `hardwareFamily`, `hardware`, `role`, `logging`, `ota`, `mode`, `feature`, `variant`, and `install`. Values use the diff --git a/mkdocs.yml b/mkdocs.yml index be18b67c3..a680d0418 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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=20261008-rak-combined-2 + - _javascript/firmware_picker.js?v=20261008-rak-uart2-1 - _javascript/telemetry_decoder.js?v=20261007-hosted-form - _javascript/management_decoder.js - _javascript/filter_tool.js?v=20261002-hashbytes-2plus diff --git a/test/test_firmware_picker.js b/test/test_firmware_picker.js index db6b79f80..aea881dcd 100644 --- a/test/test_firmware_picker.js +++ b/test/test_firmware_picker.js @@ -1177,15 +1177,14 @@ const currentCatalog = picker.buildCatalog([ 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_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors']], + ['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 retain only combined storage/UART2 images, GPS-free UART1 and Ethernet variants'); + hardware + ' must recommend only combined storage/UART2 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, @@ -1203,11 +1202,12 @@ for (const [hardware, bases] of [ } } for (const policy of ['full', 'reduced']) { - const serial1 = currentCatalog.profiles.find(profile => profile.target === - 'RAK_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors-' + policy); - assert(serial1 && serial1.controls.gps === false, - 'The UART1 image must remain usable without the combined image\'s GPS reservation'); - assert(!picker.runtimeDirections(serial1, {mode: 'rs232'}).some(section => section.title === 'GPS')); + const target = 'RAK_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors-' + policy; + assert(!currentCatalog.profiles.some(profile => profile.target === target), + 'Normal recommendations must prefer UART2 over legacy Serial1'); + assert(currentCatalog.rows.some(row => row.target === target && row.name.startsWith(target + '-')), + 'Existing Serial1 installations must retain exact compatibility downloads'); + assert.strictEqual(currentControls.profiles[target].gps, false); } const rakBridgeBase = 'RAK_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors'; const rakPartialAssets = currentAssets.filter(file => @@ -1218,6 +1218,13 @@ const rakPartialCatalog = picker.buildCatalog([ ], currentControls); assert(rakPartialCatalog.profiles.some(profile => profile.target === rakBridgeBase + '-reduced'), 'A Full combined image must not hide the only available Reduced bridge image'); +const rakInversePartialCatalog = picker.buildCatalog([ + release(currentControls.familyTag, '2026-10-08T00:00:00Z', currentAssets.filter(file => + file.name.startsWith('RAK_4631_repeater_unified_lora_ota-reduced-') || + file.name.startsWith(rakBridgeBase + '-full-'))), +], currentControls); +assert(rakInversePartialCatalog.profiles.some(profile => profile.target === rakBridgeBase + '-full'), + 'A Reduced combined image must not hide the only available Full bridge image'); const expandedEsp32 = currentCatalog.profiles.find(profile => profile.target === 'Ebyte_EoRa-S3_Repeater-full-usb-wifi'); assert(expandedEsp32);