diff --git a/docs/_javascript/firmware_picker.js b/docs/_javascript/firmware_picker.js index 0b66da375..2ddabcfda 100644 --- a/docs/_javascript/firmware_picker.js +++ b/docs/_javascript/firmware_picker.js @@ -614,9 +614,14 @@ 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); // 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 && + const replacedRs232 = Boolean(!reservedRakUart1 && rs232Replacement && [].concat(rs232Replacement).some(function (replacement) { return targets.has(replacement + sensorSuffix); })); @@ -1677,7 +1682,9 @@ 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." + ? "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." + : /^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."; // MQTT images keep the historical bridge.enabled ESP-NOW alias. // Their independently saved UART uses the transport-specific controls. diff --git a/docs/firmware_picker.md b/docs/firmware_picker.md index 085b5339d..2bfb9c8e2 100644 --- a/docs/firmware_picker.md +++ b/docs/firmware_picker.md @@ -28,8 +28,10 @@ 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 +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 OTAFIX bootloader and a package compatible with the installed target. The picker reads public release metadata from GitHub. It does not upload device diff --git a/mkdocs.yml b/mkdocs.yml index a149186f4..be18b67c3 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-1 + - _javascript/firmware_picker.js?v=20261008-rak-combined-2 - _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 a3e00272f..db6b79f80 100644 --- a/test/test_firmware_picker.js +++ b/test/test_firmware_picker.js @@ -1177,14 +1177,15 @@ 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', ['RAK_4631_repeater_unified_lora_ota', 'RAK_4631_repeater_ethernet', + 'RAK_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors']], ['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'); + hardware + ' must retain only combined storage/UART2 images, GPS-free UART1 and 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, @@ -1195,12 +1196,19 @@ for (const [hardware, bases] of [ 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')); + assert(bridge && bridge.note.includes('set bridge.uart 2') && bridge.note.includes('reserve UART1'), + 'Both combined sensor policies must explain the powered-GPS UART1 restriction'); + assert(!bridge.note.includes('pauses UART GPS')); } } } +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 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-') ||