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
5 changes: 3 additions & 2 deletions src/main/gatt-sidecar-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
});
});

Expand Down
28 changes: 3 additions & 25 deletions src/renderer/runtime/useReticulumRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
/**
Expand Down Expand Up @@ -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 };
Expand Down
Loading