diff --git a/_javascript/firmware_picker.js b/_javascript/firmware_picker.js index 6dfb9afc2..f59b0c923 100644 --- a/_javascript/firmware_picker.js +++ b/_javascript/firmware_picker.js @@ -658,6 +658,42 @@ }); } + function stationG3SharedSlotProfile(profile) { + const controls = profile && profile.controls; + return Boolean(controls && controls.platform === "ESP32_PLATFORM" && + controls.radioSlotAuto === true && controls.radioSlotFamily === "sx1262" && + /^[0-9a-f]{40}$/.test(controls.radioSlotSource || "") && + Array.isArray(profile.files) && profile.files.length > 0 && + profile.files.every(function (file) { + const hash = String(file.name || "").match(/-([0-9a-f]{8,40})(?:-merged)?\.(?:bin|hex|uf2|zip)$/i); + return hash && controls.radioSlotSource.startsWith(hash[1].toLowerCase()); + })); + } + + function hardwareChoiceLabel(field, value, profiles, family) { + const matches = (profiles || []).filter(function (profile) { + return profile[field] === value; + }); + if (value === "Station_G3_ESP32" && matches.length > 0 && + matches.every(function (profile) { + return profile.hardware === "Station_G3_ESP32" && stationG3SharedSlotProfile(profile); + })) { + return "Station G3 ESP32 (automatic slot detection)"; + } + return field === "hardware" && family + ? humanizeHardwareVariant(value, family) : humanizeHardware(value); + } + + function omitStationG3SlotDuplicates(profiles) { + const successors = new Set((profiles || []).filter(stationG3SharedSlotProfile) + .map(function (profile) { return profile.target; })); + return (profiles || []).filter(function (profile) { + const target = String(profile.target || ""); + const match = /^Station_G3_ESP32_r2_(repeater(?:_observer_mqtt)?|room_server(?:_observer_mqtt)?|sensor)((?:-(?:full|reduced|usb|wifi|logging|ota))*)$/.exec(target); + return !match || !successors.has("Station_G3_ESP32_" + match[1] + match[2]); + }); + } + function omitRakStorageProfilesWhenUnified(profiles) { const targets = new Set((profiles || []).map(function (profile) { return String(profile && profile.target || "").toLowerCase(); @@ -972,7 +1008,9 @@ return { releaseSet: releaseSet, rows: firmwareRows, - profiles: profiles, + // Old releases have no shared-slot proof and retain both choices. Raw + // assets remain searchable even when a new exact successor replaces R2. + profiles: omitStationG3SlotDuplicates(profiles), }; } @@ -1285,6 +1323,18 @@ FACET_FIELDS.forEach(function (field) { requested[field] = params.get(field) || ""; }); + if (!profiles.some(function (profile) { + return profile.hardware === "Station_G3_ESP32_r2" && + (!requested.role || profile.role === requested.role); + }) && + profiles.some(function (profile) { + return profile.hardware === "Station_G3_ESP32" && stationG3SharedSlotProfile(profile) && + (!requested.role || profile.role === requested.role); + })) { + ["hardware", "hardwareFamily"].forEach(function (field) { + if (requested[field] === "Station_G3_ESP32_r2") requested[field] = "Station_G3_ESP32"; + }); + } // Handwritten links may specify just the exact hardware variant. if (requested.hardware && !requested.hardwareFamily) { const profile = profiles.find(function (item) { @@ -1511,6 +1561,9 @@ const target = profile.target; const info = profile.controls || {}; const notes = []; + if (identity.hardware === "Station_G3_ESP32" && stationG3SharedSlotProfile(profile)) { + notes.push("Automatically selects physical radio slot 1 or 2. Use this same image for either slot."); + } 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" && @@ -1976,7 +2029,7 @@ card.className = "firmware-picker-card"; card.appendChild(createElement( "h3", - humanizeHardware(profile.hardware) + " - " + + hardwareChoiceLabel("hardware", profile.hardware, [profile]) + " - " + (ROLE_LABELS[profile.role] || profile.role) )); @@ -2140,6 +2193,8 @@ function setSelectOptions(select, field, values, selected) { select.replaceChildren(); + const hardwareProfiles = field === "hardware" || field === "hardwareFamily" + ? matchingProfiles(field) : []; const placeholders = { chipFamily: "Any chip family - skip this filter", hardwareFamily: "Any hardware", @@ -2153,8 +2208,8 @@ values.slice().sort(function (a, b) { return optionSort(field, a, b); }).forEach(function (value) { - const label = field === "hardware" - ? humanizeHardwareVariant(value, filters.hardwareFamily) + const label = field === "hardware" || field === "hardwareFamily" + ? hardwareChoiceLabel(field, value, hardwareProfiles, filters.hardwareFamily) : labelFor(field, value, filters.hardware || filters.hardwareFamily); const option = createElement("option", label); option.value = value; @@ -2165,12 +2220,14 @@ } function setRadioOptions(container, field, values, selected) { + const hardwareProfiles = field === "hardware" || field === "hardwareFamily" + ? matchingProfiles(field) : []; const options = [{ value: "", label: "Any" }].concat( values.slice().sort(function (a, b) { return optionSort(field, a, b); }).map(function (value) { - return { value: value, label: field === "hardware" - ? humanizeHardwareVariant(value, filters.hardwareFamily) + return { value: value, label: field === "hardware" || field === "hardwareFamily" + ? hardwareChoiceLabel(field, value, hardwareProfiles, filters.hardwareFamily) : labelFor(field, value, filters.hardware || filters.hardwareFamily) }; }) ); @@ -2627,6 +2684,7 @@ omitTransportsReplacedByFull: omitTransportsReplacedByFull, omitLegacyObserverCompatibilityProfiles: omitLegacyObserverCompatibilityProfiles, + omitStationG3SlotDuplicates: omitStationG3SlotDuplicates, omitTransportsReplacedByDualCdcFull: omitTransportsReplacedByFull, omitNrf52TransportsReplacedByFull: @@ -2663,6 +2721,7 @@ migrationAssetForProfile: migrationAssetForProfile, migrationLink: migrationLink, humanizeHardware: humanizeHardware, + hardwareChoiceLabel: hardwareChoiceLabel, humanizeVariant: humanizeVariant, labelFor: labelFor, formatBytes: formatBytes, diff --git a/cli_commands/index.html b/cli_commands/index.html index a4bb0f49f..eeed56cda 100644 --- a/cli_commands/index.html +++ b/cli_commands/index.html @@ -2360,6 +2360,17 @@ + + +
  • + + + + Select the Station G3 physical radio slot + + + +
  • @@ -5286,6 +5297,17 @@ + + +
  • + + + + Select the Station G3 physical radio slot + + + +
  • @@ -6879,7 +6901,9 @@ RX-only mode, cross-transmission policy, and optional radio preambles.

    for mt- channels and the explicit send mt.nodeinfo <channel slot> command. Chat remains plain text. A separate GPS-free NodeInfo is sent after the first successful chat transmission per used channel/identity per boot; the explicit -command can also request one. MT heard-repeat retries use the Companion's +command can also request one. Automatic metadata waits for a 250 ms gap after +the latest successful MT transmission; explicit requests bypass that gap. +MT heard-repeat retries use the Companion's flood.retry.count, with the metadata cap and flood.retry.advert gate for NodeInfo.

    See carrier-wave RF checks for cw on [seconds], @@ -8603,6 +8627,30 @@ Station G2/G3 targets default to off.

    - Select an operating level and SX1262 transmit power that comply with local RF limits and the Station G3 power-supply requirements. - The setting is saved immediately, but on Station G3 the level is applied to the hardware at the start of the next transmit, so that the PA supply rail is never re-targeted while the PA is being driven. get reports the configured state, which may lead the hardware until the node next transmits.


    +

    Select the Station G3 physical radio slot

    +
    get radio.slot
    +set radio.slot auto
    +set radio.slot 1
    +set radio.slot 2
    +reboot
    +
    +

    Station G3 SX1262 builds support both physical RF sockets in the same image. +The default, auto, probes both sockets at boot without transmitting. If both +work, it prefers the last successfully used socket; on a fresh installation, +it prefers socket 1. A forced 1 or 2 never silently uses the other socket.

    +

    get radio.slot reports the saved selection, active socket, detected sockets, +and whether a reboot is needed. Changes are saved immediately but applied only +after reboot, never during a transmission. The selector does not change keys, +node settings, or the time-shared radio2 profile. The legacy _r2 build name +means physical socket 2, not a motherboard revision; both build names now +support this selector.

    +

    If no usable socket is found, startup waits with both LNAs disabled instead of +reboot-looping. A limited USB recovery console accepts !g3 get radio.slot, +!g3 set radio.slot auto|1|2, !g3 reboot, and !g3 help. The !g3 prefix is +required in this recovery console; it is not the normal running CLI. KISS +firmware uses binary slot commands during normal operation, documented in the +KISS protocol.

    +

    System

    View or change this node's name

    Usage: @@ -9219,6 +9267,16 @@ the BLE/WiFi drivers, ESP-NOW, GPS UART, logging, and USB host sessions are inactive and no work is pending. Compiling those transports into Full does not itself prevent sleep. Turning off BLE advertising alone leaves its controller running and therefore retains idle behavior rather than manual light sleep.

    +

    An active radio2 requires continuous software scanning. With power saving on, +ESP32 Companion, Repeater, Room and Sensor builds do not insert the ordinary +1 ms idle delay while dual-radio scanning is active. This avoids delaying +sub-millisecond scan visits; it does not make active dual-radio scanning a sleep mode.

    +

    The verified Meshnology W12 layout enables the LR2021 radio's DC-DC regulator +after LoRa and Morse receive initialization, including recovery. This setting +is independent of powersaving and does not remove radio features. Other +LR2021 layouts retain their existing regulator behavior unless their recipe +explicitly enables LR2021_USE_DCDC after confirming the required inductor; +the X1 is not opted in without that hardware confirmation.


    View or set the reboot interval (Repeater and room server)

    Usage: diff --git a/kiss_modem_protocol/index.html b/kiss_modem_protocol/index.html index 1ff2d6c55..71177b4f1 100644 --- a/kiss_modem_protocol/index.html +++ b/kiss_modem_protocol/index.html @@ -1958,6 +1958,17 @@ + + +

  • + + + + Station G3 physical radio slot + + + +
  • @@ -2867,6 +2878,17 @@ +
  • + +
  • + + + + Station G3 physical radio slot + + + +
  • @@ -3483,6 +3505,16 @@ Clients that need temporary-lease state can poll GetTempRadio2.

    0x20 - + +GetRadioSlot +0x22 +- (Station G3 only) + + +SetRadioSlot +0x23 +Mode (1): 0=auto, 1=slot 1, 2=slot 2 (Station G3 only) +

    Response Sub-commands (TNC to Host)

    @@ -3622,6 +3654,11 @@ Clients that need temporary-lease state can poll GetTempRadio2.

    Saved radio2 parameters + protocol (14) +RadioSlot +0xA2 +Saved mode, active slot, detected mask, reboot needed (4 bytes) + + OK 0xF0 - @@ -3688,8 +3725,27 @@ Clients that need temporary-lease state can poll GetTempRadio2.

    0x07 Radio TX busy, or host output queue full + +SaveFailed +0x08 +Setting could not be saved; running slot unchanged + +

    Station G3 physical radio slot

    +

    GetRadioSlot takes no data and replies with four bytes: saved mode (0 auto, +1 or 2 forced socket), active socket (0 unavailable, 1 or 2), detected +socket mask (bit 0 = socket 1, bit 1 = socket 2), and reboot-needed flag (0 or +1). This selects the physical SX1262 daughterboard socket, not the logical +radio2 PHY profile. Other boards return UnknownCmd.

    +

    SetRadioSlot requires exactly one mode byte (0–2). An OK means the mode +was saved; the running socket stays unchanged until a separate Reboot command +or power cycle. Radio settings and identity are untouched. A failed save returns +SaveFailed, with no change to the running socket.

    +

    If no usable socket is found during boot, the normal KISS modem has not started. +Use the separate USB recovery console: !g3 get radio.slot, +!g3 set radio.slot auto, then !g3 reboot. Recovery accepts only these +prefixed management commands (and !g3 help), never KISS packet payloads.

    Unsolicited Events

    The TNC sends these SetHardware frames without a preceding request:

    TxDone (0xF8): Sent after radio transmission completes. Contains a single byte: 0x01 for success, 0x00 for failure. Delivery to the host may be delayed under serial backpressure but is not dropped.

    diff --git a/nrf52_power_management/index.html b/nrf52_power_management/index.html index 096dfea15..2f697b582 100644 --- a/nrf52_power_management/index.html +++ b/nrf52_power_management/index.html @@ -1665,6 +1665,28 @@