Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -368,7 +368,7 @@ Do not change behavior guarded by `meshcoreZeroHopRepeaterWorkingState.test.ts`
| Repeater CLI danger / auto-ping | `meshcoreRepeaterCliDanger.ts`, `RepeatersPanel.tsx` (`ensureCliRoutePrimed`); `repeatersPanel.cliMultiHopHint` |
| Room vs repeater LoginFail | `meshcoreRoomLoginRpc.ts` (fail fast) vs `meshcoreRepeaterLoginRpc.ts` + `meshcoreRepeaterPrefixPushRpc.ts` (wait for LoginSuccess) |
| Renderer hung after wake | `rendererHeartbeatWatchdog.ts`, `useRendererHeartbeat`; visible stall + export `mainLiveness`; [troubleshooting](docs/troubleshooting.md#macos-sleep--wake-and-auto-reconnect) — quit fully if no `[usePowerRecovery]` after resume watchdog |
| MeshCore TCP mid-init peer FIN | `useMeshcoreRuntime` initConn / `meshcore:tcp-*`; [troubleshooting](docs/troubleshooting.md#meshcore-tcp-connect-stuck-or-reconnect-loop-on-softapopenhop) |
| MeshCore TCP mid-init peer FIN | `useMeshcoreRuntime` initConn / `meshcore:tcp-*`; [troubleshooting](docs/troubleshooting.md#meshcore-tcp-connect-stuck-or-reconnect-loop-on-openhop) |

| Chat hop pills missing | MeshCore: `meshcoreCompanionRxPathLenToHopCount` / `MeshCoreProtocol` / `meshcoreRawPacketCorrelate` / `meshcoreIngest`; Meshtastic: `meshtasticRfHops.ts` (`viaMqtt` / `hopStart===0` omit by design) |
| Meshtastic SDK routing console noise | `meshtasticSdkRoutingErrorConsoleHook.ts`, `meshtasticSdkRoutingErrorLog.ts` |
Expand Down
2 changes: 1 addition & 1 deletion docs/meshcore-meshtastic-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Shared UI gates use `ProtocolCapabilities` in [`src/renderer/lib/radio/BaseRadio
| Log analyzer | `LogPanel` → **Analyze** (`logAnalyzer.ts`, protocol-aware) | Same shared UI | **App** (implemented) |
| Room servers (BBS) | Not applicable | **Rooms** tab: login/post/admin CLI; optional **Remember password** (`app_settings`); **Auto-sync** periodic re-login while radio connected ([`meshcoreRoomSyncScheduler.ts`](../src/renderer/lib/meshcoreRoomSyncScheduler.ts), [`useMeshcoreRuntime.ts`](../src/renderer/runtime/useMeshcoreRuntime.ts)); RF-only (not MQTT) | **App** (MeshCore-only) |
| Repeater admin passwords | Not applicable | Per-repeater **Remember** (`meshcoreRepeaterCredential:<nodeId>` in `app_settings`); shared factory [`meshcorePerNodeCredentialStorage.ts`](../src/renderer/lib/meshcorePerNodeCredentialStorage.ts) with [`meshcoreRepeaterCredentialStorage.ts`](../src/renderer/lib/meshcoreRepeaterCredentialStorage.ts) / [`meshcoreRoomCredentialStorage.ts`](../src/renderer/lib/meshcoreRoomCredentialStorage.ts); [`useMeshcoreRepeaterRemoteAuth.tsx`](../src/renderer/hooks/useMeshcoreRepeaterRemoteAuth.tsx), [`MeshcoreRepeaterPasswordControls.tsx`](../src/renderer/components/MeshcoreRepeaterPasswordControls.tsx); Repeaters sidebar **Saved repeater passwords** + Forget | **App** (MeshCore-only) |
| MsgWaiting background drain | Not applicable | Event 131 silent drain ([`meshcoreWaitingMessagesDrain.ts`](../src/renderer/lib/meshcoreWaitingMessagesDrain.ts)); **header status indicator** (queued backlog and active sync on any protocol tab; **paused/deferred** only on MeshCore tab); manual **Sync now** with determinate progress in the header indicator | **App** (MeshCore-only) |
| MsgWaiting background drain | Not applicable | Event 131 silent drain ([`meshcoreWaitingMessagesDrain.ts`](../src/renderer/lib/meshcoreWaitingMessagesDrain.ts)): bulk `getWaitingMessages` first (header **X / Y**), `syncNextMessage` fallback on timeout without disconnect (**Fetched N…**); **header status indicator** (queued backlog and active sync on any protocol tab; **paused/deferred** only on MeshCore tab); manual **Sync now** with determinate progress | **App** (MeshCore-only) |

## MeshCore: Room servers

Expand Down
18 changes: 9 additions & 9 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ The top-level **`legend`** explains that ids like `offline-meshcore` are **inter
- `uiStoreIdentityId` — bucket Chat and Nodes read from.
- `identitySplit: true` while transport is connected — **suspicious** (live ingress and UI may disagree).
- `ui.chatPanelFrozen` + `frozenMessageCount` lagging `liveResolvedMessageCount` — **legacy snapshots only** (current builds always emit `chatPanelFrozen: false`; the freeze path was removed). Ignore unless analyzing an older export.
- `ui.waitingMessagesSilentDrainActive` / `ui.waitingMessagesDrainDeferred` — MeshCore incremental drain in progress or paused behind admin/trace (serial may show small batches). UI: **header status indicator** (queued backlog visible on any protocol tab; **active sync spinner and paused/deferred** state only on the MeshCore tab), not Chat/Rooms panel strips.
- `ui.waitingMessagesSilentDrainActive` / `ui.waitingMessagesDrainDeferred` — MeshCore waiting-message drain in progress or paused behind admin/trace. Auto-drain prefers bulk `getWaitingMessages` (header shows **X / Y** when the radio returns a queue); on bulk timeout it falls back to one-at-a-time `syncNextMessage` (header shows **Fetched N…**, no fake total). Serial may still feel batchy. UI: **header status indicator** (queued backlog visible on any protocol tab; **active sync spinner and paused/deferred** state only on the MeshCore tab), not Chat/Rooms panel strips.
- `meshcoreContactPathDiagnostics` — redacted MeshCore contact rows with `pubKeyPrefixHex` (12 hex chars), `hopsAway`, and best known `bestPathBytes` / `bestPathHopCount` from SQLite path history (useful for ping/no-route reports).

**Meshtastic-only extension** (under `meshtastic` bucket):
Expand Down Expand Up @@ -607,7 +607,7 @@ IPv6 addresses work for Meshtastic Wi‑Fi, MeshCore TCP, and Reticulum RNode Wi

### Connection panel Link quality (TCP) shows "—" or unexpected latency

**Cause:** For **Meshtastic WiFi/TCP** and **MeshCore TCP/IP SoftAP**, the Connection panel signal bars reflect **live-session responsiveness** — an EWMA of write→first-data delay on the already-open TCP socket — not a separate connect probe. Bars may show **"—"** until traffic has produced a sample, or after ~2 minutes without a completed sample (covers idle heartbeat gaps). Meshtastic **WiFi/HTTP** still uses a `/json/report` RTT probe (separate from the TCP session). Reticulum hub/RMAP rows still use a short-lived TCP connect probe (different risk profile).
**Cause:** For **Meshtastic WiFi/TCP** and **MeshCore TCP/IP OpenHop**, the Connection panel signal bars reflect **live-session responsiveness** — an EWMA of write→first-data delay on the already-open TCP socket — not a separate connect probe. Bars may show **"—"** until traffic has produced a sample, or after ~2 minutes without a completed sample (covers idle heartbeat gaps). Meshtastic **WiFi/HTTP** still uses a `/json/report` RTT probe (separate from the TCP session). Reticulum hub/RMAP rows still use a short-lived TCP connect probe (different risk profile).

**Why not a second TCP connect?** Probing the same `host:port` as the live session every few seconds can RST ESP32/lwIP-class devices (see PR discussion around competing connections).

Expand Down Expand Up @@ -788,9 +788,9 @@ When the Meshtastic SDK logs a routing / queue failure, mesh-client intercepts m

## MeshCore

### MeshCore TCP connect stuck or reconnect loop on SoftAP/OpenHop
### MeshCore TCP connect stuck or reconnect loop on OpenHop

**Symptoms**: TCP connect stuck on **Connecting**; empty/stale nodes; reconnect thrash on SoftAP / OpenHop / pyMC companions; log lines like `[IPC] meshcore:tcp socket closed … readableEnded=true` during `[useMeshcoreRuntime] initConn getContacts`.
**Symptoms**: TCP connect stuck on **Connecting**; empty/stale nodes; reconnect thrash on OpenHop / pyMC companions; log lines like `[IPC] meshcore:tcp socket closed … readableEnded=true` during `[useMeshcoreRuntime] initConn getContacts`.

**Cause**: Companion closes TCP mid-handshake or after the contacts dump. Older builds thrashed reconnect before contacts were latched.

Expand Down Expand Up @@ -824,7 +824,7 @@ Startup maintenance can delete stale MeshCore contacts by age. Important details
**Common causes**:

- **Large contact/repeater lists (1,000+)** — list tabs virtualize rows, but USB serial still serializes companion RPCs; prefer **Nodes → search** for one repeater instead of scrolling the full Repeaters table.
- **Queued public messages (Sync now)** — MsgWaiting backlog is drained **incrementally in the background** after connect and when the radio pushes event 131 (including after you send). The **header status indicator** (queued backlog and active sync on any protocol tab; **paused/deferred** state only on the MeshCore tab) shows silent auto-drain or deferred drain behind repeater admin/trace work. The determinate progress state appears when you click **Sync now** and the radio confirms a non-empty queue. Large backlogs may take a minute on manual sync; wait for the indicator to finish before switching tabs during heavy sync.
- **Queued public messages (Sync now)** — MsgWaiting backlog is drained in the background after connect and when the radio pushes event 131 (including after you send). Auto-drain prefers a bulk `getWaitingMessages` pull (header shows **Syncing X / Y…**); if that times out it falls back to one-at-a-time `syncNextMessage` without disconnecting (header shows **Fetched N…**). The **header status indicator** (queued backlog and active sync on any protocol tab; **paused/deferred** state only on the MeshCore tab) shows silent auto-drain or deferred drain behind repeater admin/trace work. Manual **Sync now** still uses bulk with determinate progress. Wait for the indicator to finish before switching tabs during heavy sync.
- **Multi-hop repeater RPCs** (Neighbors, Status, telemetry) share one serialized USB serial queue. Retrying rapidly or querying distant repeaters (8+ hops) can block the link for up to **120 seconds** per request; queued pings up to **180s** each. **Load more** on a neighbor list is another full Neighbors RPC (~120s) — prefer it over re-clicking **Neighbors** (which replaces the first page). Page request size is 50, but firmware reply buffers often return fewer rows.
- **Concurrent Ping + Status** — MeshCore allows only **one traceroute at a time** on the RF link; multiple pings are queued serially. Status/Neighbors/Sensors wait for an in-progress ping to finish before using the companion queue (see [Serialized traceroutes](meshcore-meshtastic-parity.md#serialized-traceroutes-protocol-requirement)).

Expand Down Expand Up @@ -1619,18 +1619,18 @@ Chat used a freeze-on-leave snapshot: `messagesForUnread` stayed live for badges

**Cause**

The companion radio queues public messages behind a **single serialized USB serial lane** shared with repeater admin, init RPCs, and MsgWaiting drains. Older builds bulk-fetched the whole queue before updating the UI.
The companion radio queues public messages behind a **single serialized USB serial lane** shared with repeater admin, init RPCs, and MsgWaiting drains. Auto-drain tries bulk `getWaitingMessages` first (shorter silent timeout on serial); on timeout it falls back to `syncNextMessage` without tearing down the link.

**In-app status**

The **header status indicator** (queued backlog and active sync on any protocol tab; **paused/deferred** state only on the MeshCore tab) shows silent auto-drain or deferred drain behind admin/trace work. On serial, messages may arrive in small batches without a Chat/Rooms panel banner.
The **header status indicator** (queued backlog and active sync on any protocol tab; **paused/deferred** state only on the MeshCore tab) shows silent auto-drain (**X / Y** on bulk success, **Fetched N…** on fallback) or deferred drain behind admin/trace work. On serial, messages may still arrive in small batches without a Chat/Rooms panel banner.

**Fix / workaround**

1. Pause repeater **Status / Neighbors / ping** while monitoring live chat on serial.
2. Prefer **BLE** or **TCP** when available for lower-latency chat.
2. Prefer **BLE** or **TCP** (including OpenHop) when available for lower-latency chat.
3. If drains stall, **Disconnect → Connect** or quit and reopen after repeated timeouts in the log.
4. Use **Sync now** from the **header waiting-messages indicator** for a large backlog (determinate progress in the header tooltip/status).
4. Use **Sync now** from the **header waiting-messages indicator** for a large backlog (determinate **X / Y** progress in the header tooltip/status). Auto-drain now also shows progress when bulk succeeds.

### Chat or Rooms: scroll jumps when switching tabs

Expand Down
4 changes: 2 additions & 2 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ async function ensureTakServerManager(): Promise<TakServerManager> {

/** Max bytes per MeshCore TCP IPC write (DoS guard). */
const MESHCORE_TCP_WRITE_MAX_BYTES = 256 * 1024;
/** Cap per-chunk IPC fan-out from SoftAP/companion TCP reads (align with write max). */
/** Cap per-chunk IPC fan-out from OpenHop/companion TCP reads (align with write max). */
const MESHCORE_TCP_DATA_MAX_BYTES = MESHCORE_TCP_WRITE_MAX_BYTES;
/** Min node ID for MeshCore chat stub nodes (derived from meshcoreUtils). */
const MESHCORE_CHAT_STUB_ID_MIN = 0xa0000000 >>> 0;
Expand Down Expand Up @@ -6148,7 +6148,7 @@ ipcMain.handle('meshcore:tcp-connect', (event, host: string, port: number) => {
const socketHost = formatHostForSocket(host);
const socket = new net.Socket();
// MeshCore Open / official companion TCP clients use TCP_NODELAY; Node defaults can
// Nagle-batch small companion RPCs and SoftAP/OpenHop peers often FIN mid-init.
// Nagle-batch small companion RPCs and OpenHop peers often FIN mid-init.
socket.setNoDelay(true);
socket.setKeepAlive(true, MESHCORE_TCP_KEEPALIVE_INITIAL_DELAY_MS);
meshcoreTcpSocket = socket;
Expand Down
10 changes: 5 additions & 5 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import { ConnectIcon } from '@/renderer/lib/icons/connectIcon';
import { MqttGlobeIcon } from '@/renderer/lib/icons/connectionIcons';
import { ICON_MD } from '@/renderer/lib/icons/iconClass';
import { useIconTrigger } from '@/renderer/lib/icons/iconMotionContext';
import { isMeshcoreTcpSoftApDeadAccepted } from '@/renderer/lib/meshcore/meshcoreTcpInitBurst';
import { isMeshcoreTcpOpenHopDeadAccepted } from '@/renderer/lib/meshcore/meshcoreTcpInitBurst';
import {
meshcoreConfiguredChannelIndexSet,
meshcoreConfiguredChatChannels,
Expand Down Expand Up @@ -2510,11 +2510,11 @@ function AppContent() {
if (!meshcoreIdentityId || !connectionDriver.getHandle(meshcoreIdentityId)) return;

const sendScheduledAdvert = () => {
// SoftAP/OpenHop: configured session may have a dead TCP bridge after contacts FIN.
// OpenHop: configured session may have a dead TCP bridge after contacts FIN.
// Flood advert would tcp-write-fail and thrash reconnect.
const softAp = isMeshcoreTcpSoftApDeadAccepted();
if (softAp) {
console.debug('[App] auto flood advert skipped (SoftAP dead bridge)');
const openHop = isMeshcoreTcpOpenHopDeadAccepted();
if (openHop) {
console.debug('[App] auto flood advert skipped (OpenHop dead bridge)');
return;
}
const action =
Expand Down
Loading