Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ Conventional Commits (`feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, `test:`).
- **Runtime:** `useReticulumRuntime`, `reticulumSession.ts`, `reticulumIngest.ts`; connect starts sidecar, not `ConnectionDriver` RF. Sidecar RRC: `rrc_codec` / `rrc_link` / `rrc_session` / `api/rrc.rs`
- **Diagnostics:** `ReticulumDiagnosticEngine.ts` (Reticulum-native rows; no LoRa hop-goblin semantics)
- **No Noble/MQTT** for Reticulum's own connections (sidecar owns BLE RNode via `btleplug`); gate UI with `hasReticulumInterfaceConfig` / `hasReticulumNetworkPanel` / `ProtocolCapabilities`. On macOS/Windows, connecting a Reticulum BLE RNode may still **suspend/yield Noble** so it does not contend with the sidecar's BLE scan — see **Multi-protocol BLE** below.
- **Multi-protocol BLE:** Meshtastic, MeshCore, and Reticulum (BLE Peer + `ble://` RNode) may connect to **different** BLE devices at once on all platforms. Coexistence: `ble-coexistence-coordinator.ts` (peripheral MAC registry + scan-only mutex); Linux mesh uses Web Bluetooth + sidecar `btleplug`. Same MAC rejected; scans serialized—never disconnect unrelated GATT for scans. **Reticulum BLE RNode** on macOS/Windows may **suspend Noble** (`suspendNobleForReticulumBleConnect`, `reticulum-ble-rnode-config.ts`, `reticulumNobleBleYield.ts`, `useReticulumNobleBleYieldWatcher`); while yield holds the scan, Noble connect is rejected; post-grace yield stops re-contending; disconnect timeout fails closed (releases scan). Sidecar may latch **`bleBondRemoved`** for stale OS bonds — Forget/re-pair. Release dispatches `mesh-client:nobleBleYieldReleased` for Meshtastic/MeshCore reconnect.
- **Multi-protocol BLE:** Meshtastic, MeshCore, and Reticulum (BLE Peer + `ble://` RNode) may connect to **different** BLE devices at once on all platforms. Coexistence: `ble-coexistence-coordinator.ts` (peripheral MAC registry + scan-only mutex); Linux mesh uses Web Bluetooth + sidecar `btleplug`. Same MAC rejected; scans serialized—never disconnect unrelated GATT for scans. **Reticulum BLE RNode** on macOS/Windows may **suspend Noble** (`suspendNobleForReticulumBleConnect`, `reticulum-ble-rnode-config.ts`, `reticulumNobleBleYield.ts`, `useReticulumNobleBleYieldWatcher`); while yield holds the scan, Noble connect is rejected; post-grace yield stops re-contending (**~60s** grace aligns with OS passkey window); disconnect timeout fails closed (releases scan). Sidecar may latch **`bleBondRemoved`** (stale OS bonds) or **`blePairingTimedOut`** (passkey not entered) — Forget/re-pair; Admin Start pairing shows PIN in-panel over USB (not radio display; never Meshtastic `123456`). Release dispatches `mesh-client:nobleBleYieldReleased` for Meshtastic/MeshCore reconnect.
- **Docs:** [docs/reticulum.md](docs/reticulum.md), [docs/reticulum-sidecar-ipc.md](docs/reticulum-sidecar-ipc.md)

### Diagnostics
Expand Down
2 changes: 1 addition & 1 deletion docs/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ SVG force-directed graph of nodes within direct reach (hops 0–1 from the conne

On the **Reticulum** protocol tab, the **Diagnostics** panel includes a **Reticulum interface config** section (below continuous ping). It audits the sidecar rnsd config against the live RNS interface list and surfaces actionable repairs.

Runtime interface-issue rows from the sidecar latch (`interfaceIssueAlert`) are also folded into Diagnostics via `ReticulumDiagnosticEngine` — including TCP connect failures, TX queue drops, link-delivery timeouts, transport saturation, and **`bleBondRemoved`** (stale OS Bluetooth bond for an RNode; Forget/re-pair).
Runtime interface-issue rows from the sidecar latch (`interfaceIssueAlert`) are also folded into Diagnostics via `ReticulumDiagnosticEngine` — including TCP connect failures, TX queue drops, link-delivery timeouts, transport saturation, **`bleBondRemoved`** (stale OS Bluetooth bond for an RNode; Forget/re-pair), and **`blePairingTimedOut`** (OS passkey not entered within the TX-read window).

| Issue kind | Typical cause | In-app action |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
Expand Down
2 changes: 1 addition & 1 deletion docs/reticulum-sidecar-ipc.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ Renderer calls `electronAPI.reticulum.*`; main process proxies to this API (sand
| `reticulum:revealInFolder` | Reveal a path in the OS file manager when it matches an rncp picker allowlist |
| `reticulum:onEvent` / `onStatus` | WS events and sidecar status |

`getStatus` / `onStatus` may include `interfaceIssueAlert` (TCP connect failures, TX queue drops, link-delivery timeouts, transport saturation / slow queries, **`bleBondRemoved`** stale RNode bonds). Per-entry latch timestamps use a **5-minute** stale window (`RETICULUM_INTERFACE_ISSUE_ALERT_STALE_MS`). Connection syncs **enabled** interface names via `syncInterfaceIssueScope` so disabling or removing an interface clears that name immediately and rejects re-latch from lagging log lines. Stopping the stack (or unexpected process exit) clears the tracker.
`getStatus` / `onStatus` may include `interfaceIssueAlert` (TCP connect failures, TX queue drops, link-delivery timeouts, transport saturation / slow queries, **`bleBondRemoved`** stale RNode bonds, **`blePairingTimedOut`** OS passkey / TX-read timeouts). Per-entry latch timestamps use a **5-minute** stale window (`RETICULUM_INTERFACE_ISSUE_ALERT_STALE_MS`). Connection syncs **enabled** interface names via `syncInterfaceIssueScope` so disabling or removing an interface clears that name immediately and rejects re-latch from lagging log lines. Stopping the stack (or unexpected process exit) clears the tracker.

**`propagation_sync` WebSocket payload:** `{ active: boolean, progress: number, message: string | null }`. Progress uses 0–100 (Establishing ≈10, Offering ≈25, …, Complete ≈100). Sticky success after HaveAll emits `active:false, progress:100`; cancel/stall/failure emit `active:false, progress:0` (and must not emit a trailing 100). Sync `POST /api/v1/propagation/sync` may return `PROPAGATION_IDENTITY_UNKNOWN`, `PROPAGATION_TARGET_NOT_PN`, `PROPAGATION_PEERING_STAMP_FAILED`, or `LOCAL_PROPAGATION_SYNC_UNSUPPORTED`.

Expand Down
2 changes: 2 additions & 0 deletions docs/reticulum.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ When multiple enabled local RNode interfaces are connected, the interface list s

**Stale BLE bond:** Sidecar may latch `bleBondRemoved` when the peer dropped pairing information while the OS still shows Paired. Connection / Diagnostics surface Forget-and-re-pair copy — forget the RNode in System Settings → Bluetooth, start pairing on the radio, restart the stack, enter the new PIN.

**Pairing timeout:** Sidecar may latch `blePairingTimedOut` when the OS passkey was not entered within ~60s. Admin **Start pairing** shows the PIN in the Admin panel over USB (radio display may stay blank); do not use Meshtastic’s `123456` default. The RNode need not appear in System Settings before mesh-client connects.

**Bulk migration:** **Network → Config import** (merge or replace), or import from standard system paths (see [Config import paths](#config-import-paths-system)).

### Config audit and repair
Expand Down
16 changes: 15 additions & 1 deletion docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -1065,11 +1065,25 @@ Unrecognized codes pass through unchanged.

**Fix**:

1. Wait up to ~30s after stack start for the BLE RNode to connect (Connection tab interface status **up** / **online**).
1. Wait up to ~**60s** after stack start for the BLE RNode to connect (Connection tab interface status **up** / **online**) — that matches the OS passkey window.
2. Stop the Reticulum stack if you need immediate Meshtastic/MeshCore BLE access.
3. Ensure you are on a current build with paired yield/release (`reticulumNobleBleYield.ts`, `useReticulumNobleBleYieldWatcher`, `ble-coexistence-coordinator.assertCanConnect`).
4. Check Device logs for `[BleCoexistence]` and `[useReticulumNobleBleYieldWatcher]`.

### Reticulum BLE RNode pairing fails (wrong PIN / no PIN on display / not in macOS list)

**Symptoms**: BLE RNode stays offline; logs show `peripheral.connect() ok` then `[pair] TX read err` / `BLE pairing in progress` / `BLE pairing timed out`; Connection may show a pairing-timed-out sidecar alert. The RNode does not appear under System Settings → Bluetooth. Admin **Start pairing** does not put a code on the radio display.

**Cause**: RNode generates a **new** 6-digit PIN each pairing — there is **no** default. Users sometimes enter **123456** (Meshtastic’s fixed default). Admin **Start pairing** shows the PIN in the **Admin Bluetooth panel over USB** (radio display often stays blank). Discovery uses the sidecar BLE scan (`ble://…`), not the macOS Settings device list. The OS passkey dialog appears when the stack triggers SMP (TX-char read).

**Fix**:

1. Stop the stack (or disable the BLE RNode) so reconnect does not thrash while you prepare.
2. Forget any half-paired RNode in System Settings → Bluetooth.
3. Get a real PIN: USB → Admin → Bluetooth → **Start pairing** (watch the **Admin panel**, not the radio screen), **or** ~7 s button hold on display boards for an on-screen PIN.
4. Start the stack **once**, enter that PIN in the OS dialog within ~60 seconds — never `123456`.
5. The device may only show as Paired in System Settings **after** a successful bond.

### Reticulum BLE RNode bond is stale (OS still shows Paired)

**Symptoms**: Connection / Diagnostics show a BLE bond-stale banner for an RNode interface; Connect fails; System Settings still lists the device as Paired.
Expand Down
2 changes: 2 additions & 0 deletions reticulum-sidecar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Apply overlays (required for `rns-stack` until upstream merges):
./scripts/apply-rsReticulum-packet-tap.sh
./scripts/apply-rsReticulum-auto-beacon-utun.sh
./scripts/apply-rsReticulum-link-client-nomad.sh
./scripts/apply-rsReticulum-rnode-tcp-activity-keepalive.sh
Comment thread
coderabbitai[bot] marked this conversation as resolved.
./scripts/apply-rsReticulum-ble-rnode-pairing-transition-debounce.sh
./scripts/apply-rsLXMF-propagation-sync-peering.sh
```

Expand Down
86 changes: 86 additions & 0 deletions reticulum-sidecar/patches/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,92 @@ git -C /tmp/rsReticulum-patch-test diff \

When [ratspeak/rsReticulum#14](https://github.com/ratspeak/rsReticulum/pull/14) merges, remove this patch and drop the apply step from `clone-ratspeak-stack.sh` / `ensure-rsReticulum-patches.sh`.

## rsReticulum-rnode-tcp-activity-keepalive.patch

Port Python `RNodeInterface` / `TCPConnection.ACTIVITY_KEEPALIVE` (3.5s idle → `detect()`): Wi‑Fi/TCP RNodes otherwise close the socket at ~`ACTIVITY_TIMEOUT` (6s), causing mesh-client / rnsd-rs up/down flaps.

| Field | Value |
| ----- | ----- |
| **Base commit** | `4095022` (`ratspeak/rsReticulum` `main` tip when generated; also applies on pin `6d2b28475321bc15c8f60796513d8878b47ed3ab`) |
| **Upstream PR** | https://github.com/ratspeak/rsReticulum/pull/15 |

**Modifies (1 file):**

- `crates/rns-interface/src/rnode.rs` — TCP activity keepalive constants + write-loop `detect()` on idle

### Apply locally

From mesh-client repo root (sibling `../rsReticulum` required):

```bash
./scripts/apply-rsReticulum-rnode-tcp-activity-keepalive.sh
```

Apply after the other rsReticulum overlays when rebuilding a pinned checkout:

```bash
./scripts/apply-rsReticulum-packet-tap.sh
./scripts/apply-rsReticulum-auto-beacon-utun.sh
./scripts/apply-rsReticulum-link-client-nomad.sh
./scripts/apply-rsReticulum-rnode-tcp-activity-keepalive.sh
```

### Regenerate

```bash
cd ../rsReticulum
git fetch origin
git diff origin/main...HEAD -- crates/rns-interface/src/rnode.rs \
> ../mesh-client/reticulum-sidecar/patches/rsReticulum-rnode-tcp-activity-keepalive.patch
```

### Sunset

When [ratspeak/rsReticulum#15](https://github.com/ratspeak/rsReticulum/pull/15) merges, remove this patch and drop the apply step from `clone-ratspeak-stack.sh` / `ensure-rsReticulum-patches.sh`.

## rsReticulum-ble-rnode-pairing-transition-debounce.patch

Debounce BLE RNode reconnect after mid-SMP disconnect (`BLE pairing in progress`): wait **30s** instead of **1s** so macOS/Windows OS passkey dialogs are not re-fired while the user enters the PIN.

| Field | Value |
| ----- | ----- |
| **Base commit** | applies on current `rsReticulum` tip used for mesh-client overlays (also intended for pin `6d2b28475321bc15c8f60796513d8878b47ed3ab`) |
| **Upstream PR** | none yet (mesh-client overlay) |

**Modifies (1 file):**

- `crates/rns-interface/src/ble_rnode.rs` — `PAIRING_TRANSITION_RETRY_WAIT = 30`

### Apply locally

From mesh-client repo root (sibling `../rsReticulum` required):

```bash
./scripts/apply-rsReticulum-ble-rnode-pairing-transition-debounce.sh
```

Apply after the other rsReticulum overlays when rebuilding a pinned checkout:

```bash
./scripts/apply-rsReticulum-packet-tap.sh
./scripts/apply-rsReticulum-auto-beacon-utun.sh
./scripts/apply-rsReticulum-link-client-nomad.sh
./scripts/apply-rsReticulum-rnode-tcp-activity-keepalive.sh
./scripts/apply-rsReticulum-ble-rnode-pairing-transition-debounce.sh
```

### Regenerate

```bash
cd ../rsReticulum
git diff -- crates/rns-interface/src/ble_rnode.rs \
> ../mesh-client/reticulum-sidecar/patches/rsReticulum-ble-rnode-pairing-transition-debounce.patch
```

### Sunset

When upstream ships an equivalent debounce (or a passkey-window pause), remove this patch and drop the apply step from `clone-ratspeak-stack.sh` / `ensure-rsReticulum-patches.sh`.

## rsLXMF-propagation-sync-peering.patch

LinkIdentify + peering stamp before LXMF `/offer`, plus `set_local_identity` / `configure_peering` / `last_offer_error` / `last_finished_ok` on `PropagationSyncTask` so mesh-client can complete remote PN sync and distinguish HaveAll success from Failed after Complete→Idle cleanup.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
diff --git a/crates/rns-interface/src/ble_rnode.rs b/crates/rns-interface/src/ble_rnode.rs
index 3bbc7c5..e742172 100644
--- a/crates/rns-interface/src/ble_rnode.rs
+++ b/crates/rns-interface/src/ble_rnode.rs
@@ -43,6 +43,10 @@ pub const NUS_TX_CHAR_UUID: Uuid = Uuid::from_u128(0x6E400003_B5A3_F393_E0A9_E50
const RECONNECT_WAIT: u64 = 5;
/// Capped below TCP's 300s — a BLE radio is either in range or not.
const RECONNECT_WAIT_MAX: u64 = 120;
+/// After a mid-SMP disconnect (`BLE pairing in progress`), wait before
+/// reconnecting so the OS passkey dialog is not re-fired every second while
+/// the user is typing the PIN (mesh-client macOS pairing UX).
+const PAIRING_TRANSITION_RETRY_WAIT: u64 = 30;
/// `None` retries forever; teardown goes via `stop_ble_rnode_interface`.
const MAX_RECONNECT_TRIES: Option<usize> = None;
const SCAN_TIMEOUT: u64 = 3;
@@ -1272,7 +1276,11 @@ pub async fn spawn_ble_rnode_interface(
Ok(c) => c,
Err(e) => {
let pairing_transition = is_pairing_transition_error(&e);
- let retry_wait = if pairing_transition { 1 } else { backoff };
+ let retry_wait = if pairing_transition {
+ PAIRING_TRANSITION_RETRY_WAIT
+ } else {
+ backoff
+ };
tracing::warn!(name = %log_name, error = %e, "BLE RNode connect failed");
ble_diag(format!(
"[ble] connect_rnode err: {e} — retrying in {retry_wait}s (attempt {})",
Loading