Skip to content
Open
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
37 changes: 13 additions & 24 deletions OmnipodKit/Bluetooth/BluetoothManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -725,8 +725,8 @@ class BluetoothManager: NSObject {
// Disconnect from all devices not in our connection list
for device in devices {
let peripheral = device.manager.peripheral
if !autoConnectIDs.contains(peripheral.identifier.uuidString) &&
(peripheral.state == .connected || peripheral.state == .connecting)
if peripheral.state == .connecting ||
(peripheral.state == .connected && !autoConnectIDs.contains(peripheral.identifier.uuidString))
{
log.default("Disconnecting from peripheral: %{public}@", peripheral)
manager.cancelPeripheralConnection(peripheral)
Expand Down Expand Up @@ -833,9 +833,15 @@ class BluetoothManager: NSObject {
manager.stopScan()
for device in devices {
let peripheral = device.manager.peripheral
if peripheral.state == .disconnected || peripheral.state == .disconnecting {
switch peripheral.state {
case .connecting where !isConnectWatchdogActive(peripheral):
log.info("discoverPods: Cancelling stale connect: %{public}@", peripheral)
manager.cancelPeripheralConnection(peripheral)
case .disconnected, .disconnecting:
log.info("discoverPods: Connecting to peripheral: %{public}@", peripheral)
timedConnect(peripheral) // pairing/discovery — an explicit connect, not auto-reconnect
timedConnect(peripheral)
default:
break
}
}
startScanning()
Expand Down Expand Up @@ -1570,30 +1576,13 @@ extension BluetoothManager: CBCentralManagerDelegate {
connectionDelegate?.omnipodLogDeviceEvent("[pairing] heard pod \(peripheral.identifier.uuidString) pairable=\(podAdvertisement.pairable) state=\(peripheral.state.rawValue)")
}
if discoveryModeEnabled && podAdvertisement.pairable {
// We've heard our target pairable pod — stop the discovery scan so it doesn't starve the
// connect (an active allowDuplicates scan wedges the connect in .connecting, which is
// what stalled pairing), then connect if it's disconnected. A watchdog-managed connect
// in flight is left alone (it's supervised and will retry itself).
// Stop the scan so it doesn't starve the connect, then connect if disconnected.
// Anything already .connecting is ours: discoverPods cancels stale connects first.
if manager.isScanning { manager.stopScan() }
if peripheral.state == .disconnected {
log.default("Connecting to pairable device %{public}@ in discovery mode", peripheral)
connectionDelegate?.omnipodLogDeviceEvent("[pairing] connecting to pairable pod \(peripheral.identifier.uuidString)")
timedConnect(peripheral) // pairing — an explicit connect, not auto-reconnect
} else if peripheral.state == .connecting && !isConnectWatchdogActive(peripheral) {
// ZOMBIE pending connect: we just HEARD this pod advertise, so it is not in a live
// connection — a stale, unsupervised connect request (e.g. from an abandoned pairing
// attempt) is pinning it in .connecting. Field failure mode: every rescan reported
// "heard pod ... state=1" and then declined to connect, so pairing never succeeded.
// Cancel the zombie and connect fresh (re-arming the watchdog) once teardown lands.
log.default("[pairing] pairable pod %{public}@ stuck in .connecting with no watchdog — cancelling zombie connect", peripheral.identifier.uuidString)
connectionDelegate?.omnipodLogDeviceEvent("[pairing] zombie connect on pairable pod — cancelling and reconnecting")
manager.cancelPeripheralConnection(peripheral)
managerQueue.asyncAfter(deadline: .now() + BluetoothManager.eagerConnectTeardownSeconds) { [weak self] in
guard let self = self, self.discoveryModeEnabled, peripheral.state != .connected else { return }
self.log.default("[pairing] reconnecting to pairable pod %{public}@ after zombie teardown", peripheral.identifier.uuidString)
self.connectionDelegate?.omnipodLogDeviceEvent("[pairing] connecting to pairable pod \(peripheral.identifier.uuidString) (post-zombie)")
self.timedConnect(peripheral)
}
timedConnect(peripheral)
}
} else if autoConnectIDs.contains(peripheral.identifier.uuidString) && peripheral.state == .disconnected {
log.debug("Reconnecting to autoconnect device")
Expand Down
11 changes: 10 additions & 1 deletion OmnipodKit/PumpManager/OmniPumpManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,16 @@ public class OmniPumpManager: RileyLinkPumpManager {
let mustProvide = request != nil
let desc = request.map { "last=\($0.lastCGMReadingDate.map { String(describing: $0) } ?? "nil") interval=\(Int($0.expectedCGMReadingInterval))s" } ?? "nil"
logDeviceCommunication("[heartbeat] pid=\(pid) setBLEHeartbeatRequest(\(desc))", type: .connection)
if self.state.podType.mayUseRileyLink {
// `mayUseRileyLink` is true for DASH as well as Eros, because DASH *can* use a RileyLink
// under the Pod Keep Alive option. That is the wrong question here: what matters is whether
// a RileyLink is actually in the loop to tick. A DASH pod on direct BLE with Pod Keep Alive
// off took the RileyLink branch, so `provideHeartbeat` was never set and the BLE pod never
// got its heartbeat request -- Loop asked for a heartbeat, we logged it, and dropped it.
// Only bites when the CGM cannot provide the heartbeat itself (a remote/networked CGM such
// as Nightscout); a BLE Dexcom masks it, which is why it went unnoticed.
let rileyLinkIsInUse = self.state.podType.isEros
|| (self.state.podType.mayUseRileyLink && self.state.podKeepAlive == .rileyLink)
if rileyLinkIsInUse {
rileyLinkDeviceProvider.timerTickEnabled =
self.state.isPumpDataStale || mustProvide || /// RL ticks needed for traditional BLE wakups
self.state.podKeepAlive == .rileyLink // RL ticks needed for PodKeepAlive rileyLink option
Expand Down