From 86fd8db41e1bf749e297f104690b5268a59bb0c0 Mon Sep 17 00:00:00 2001 From: Joey Stanford Date: Mon, 21 Sep 2026 07:46:17 -0600 Subject: [PATCH] fix(reticulum): stop blocking LoRa BLE while healthy RNode is online Exclusive GATT dispose belongs only to bleBondRemoved / LTK recovery. Holding it for every online BLE RNode blocked MeshCore coexistence after #1034. --- src/main/gatt-sidecar-proxy.ts | 5 ++-- ...ticulumRuntime.reconnect-hardening.test.ts | 4 +++ src/renderer/runtime/useReticulumRuntime.ts | 28 ++----------------- 3 files changed, 10 insertions(+), 27 deletions(-) diff --git a/src/main/gatt-sidecar-proxy.ts b/src/main/gatt-sidecar-proxy.ts index ce2f092ed..0c061b04f 100644 --- a/src/main/gatt-sidecar-proxy.ts +++ b/src/main/gatt-sidecar-proxy.ts @@ -105,8 +105,9 @@ export class GattSidecarProxy extends EventEmitter { } /** - * Block LoRa GATT scan/connect while RNode recovers from CoreBluetooth Peer-removed - * or while an RNode BLE link is online (macOS cannot safely host two CBCentralManagers). + * Block LoRa GATT scan/connect while RNode recovers from CoreBluetooth Peer-removed / + * LTK desync (exclusive dual-central pause). Healthy online RNode must NOT hold this — + * MeshCore/Meshtastic BLE coexist on different MACs in the same sidecar. * Auto-clears after 10 minutes so a stuck latch cannot brick MeshCore/Meshtastic forever. */ setRnodeBondRecoveryExclusive(active: boolean): void { diff --git a/src/renderer/runtime/useReticulumRuntime.reconnect-hardening.test.ts b/src/renderer/runtime/useReticulumRuntime.reconnect-hardening.test.ts index f64e123f4..7d3c78675 100644 --- a/src/renderer/runtime/useReticulumRuntime.reconnect-hardening.test.ts +++ b/src/renderer/runtime/useReticulumRuntime.reconnect-hardening.test.ts @@ -69,6 +69,10 @@ describe('useReticulumRuntime reconnect hardening (regression)', () => { ); // Bond-recovery path may pause LoRa GATT; keep it out of connect(). expect(SOURCE).toMatch(/bleBondRemoved[\s\S]*?releaseGattBleCentral\(\)/); + // Healthy online BLE RNode must not permanently exclusive-hold LoRa GATT + // (that blocked MeshCore BLE coexistence after #1034). + expect(SOURCE).not.toContain('rnodeBleOnlineLoRaHoldRef'); + expect(SOURCE).not.toMatch(/isReticulumBleRnodeOnline[\s\S]*?releaseGattBleCentral/); }); }); diff --git a/src/renderer/runtime/useReticulumRuntime.ts b/src/renderer/runtime/useReticulumRuntime.ts index 4a05d0fe1..682da6e9b 100644 --- a/src/renderer/runtime/useReticulumRuntime.ts +++ b/src/renderer/runtime/useReticulumRuntime.ts @@ -59,10 +59,7 @@ import { } from '@/renderer/lib/reticulum/reticulumAnnounceIfaceAttribution'; import { cacheReticulumInboundAttachment } from '@/renderer/lib/reticulum/reticulumAttachmentCache'; import { cacheReticulumInboundAudio } from '@/renderer/lib/reticulum/reticulumAudioAttachmentCache'; -import { - isReticulumBleRnodeInterfaceRow, - isReticulumBleRnodeOnline, -} from '@/renderer/lib/reticulum/reticulumBleAdapterConflict'; +import { isReticulumBleRnodeInterfaceRow } from '@/renderer/lib/reticulum/reticulumBleAdapterConflict'; import { prepareReticulumBleRnodeConnect, releaseReticulumBleRnodeConnect, @@ -323,12 +320,12 @@ export function useReticulumRuntime(): ProtocolRuntime { const suppressReconnectRef = useRef(false); /** Set on power-suspend when an enabled BLE RNode was configured — wake must not reuseIfRunning. */ const powerSuspendHadBleRnodeRef = useRef(false); - /** True while we are holding LoRa GATT exclusive for an online BLE RNode. */ - const rnodeBleOnlineLoRaHoldRef = useRef(false); /** * True once this recovery episode has run releaseGattBleCentral + scan-lease hold. * Distinct from {@link getReticulumBleBondDesyncActive}: `BleLtkDesync` can set the * shared flag before `onStatus` arrives, which must not skip the hold setup. + * Exclusive LoRa GATT dispose is only for bleBondRemoved / LTK desync recovery — not + * while a healthy BLE RNode is online (that blocked MeshCore/Meshtastic BLE coexistence). */ const bondRecoveryHoldAppliedRef = useRef(false); /** @@ -2329,25 +2326,6 @@ export function useReticulumRuntime(): ProtocolRuntime { if (newlyOnlineBle.length > 0) { void clearReticulumBleBondIssuesForOnlineInterfaces(newlyOnlineBle); } - // macOS: two CBCentralManagers in one sidecar can invalidate the RNode bond. - // While any BLE RNode is online, keep LoRa GATT disposed/exclusive. - const rnodeBleOnline = interfaces.some((row) => isReticulumBleRnodeOnline(row)); - if (rnodeBleOnline) { - rnodeBleOnlineLoRaHoldRef.current = true; - void window.electronAPI.releaseGattBleCentral().catch((e: unknown) => { - console.debug( - '[useReticulumRuntime] releaseGattBleCentral while RNode BLE online ' + - errLikeToLogString(e), - ); - }); - } else if (rnodeBleOnlineLoRaHoldRef.current && !getReticulumBleBondDesyncActive()) { - rnodeBleOnlineLoRaHoldRef.current = false; - void window.electronAPI.clearGattBondRecoveryExclusive().catch((e: unknown) => { - console.debug( - '[useReticulumRuntime] clearGattBondRecoveryExclusive ' + errLikeToLogString(e), - ); - }); - } const queueAgg = aggregateReticulumLocalRfTxQueue(interfaces); setQueueStatus(queueAgg); const health = { interfaces, osSerialPorts };