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
106 changes: 82 additions & 24 deletions CGMBLEKit/BluetoothManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ protocol BluetoothManagerDelegate: class {
Tells the delegate that the bluetooth manager has finished connecting to and discovering all required services of its peripheral, or that it failed to do so

- parameter manager: The bluetooth manager
- parameter peripheralManager: The peripheral manager
- parameter error: An error describing why bluetooth setup failed
*/
func bluetoothManager(_ manager: BluetoothManager, isReadyWithError error: Error?)
func bluetoothManager(_ manager: BluetoothManager, peripheralManager: PeripheralManager, isReadyWithError error: Error?)

/**
Asks the delegate whether the discovered or restored peripheral should be connected
Expand Down Expand Up @@ -49,14 +50,24 @@ protocol BluetoothManagerDelegate: class {

class BluetoothManager: NSObject {

var stayConnected = true
var stayConnected: Bool {
get {
return lockedStayConnected.value
}
set {
lockedStayConnected.value = newValue
}
}
private let lockedStayConnected: Locked<Bool> = Locked(true)

weak var delegate: BluetoothManagerDelegate?

private let log = OSLog(category: "BluetoothManager")

/// Isolated to `managerQueue`
private var manager: CBCentralManager! = nil

/// Isolated to `managerQueue`
private var peripheral: CBPeripheral? {
get {
return peripheralManager?.peripheral
Expand All @@ -79,9 +90,18 @@ class BluetoothManager: NSObject {
}
}

var peripheralIdentifier: UUID?
var peripheralIdentifier: UUID? {
get {
return lockedPeripheralIdentifier.value
}
set {
lockedPeripheralIdentifier.value = newValue
}
}
private let lockedPeripheralIdentifier: Locked<UUID?> = Locked(nil)

var peripheralManager: PeripheralManager? {
/// Isolated to `managerQueue`
private var peripheralManager: PeripheralManager? {
didSet {
oldValue?.delegate = nil
peripheralManager?.delegate = self
Expand All @@ -90,19 +110,45 @@ class BluetoothManager: NSObject {
}
}

// MARK: - GCD Management
// MARK: - Synchronization

private let managerQueue = DispatchQueue(label: "com.loudnate.CGMBLEKit.bluetoothManagerQueue", qos: .utility)

override init() {
super.init()

manager = CBCentralManager(delegate: self, queue: managerQueue, options: [CBCentralManagerOptionRestoreIdentifierKey: "com.loudnate.CGMBLEKit"])
managerQueue.sync {
self.manager = CBCentralManager(delegate: self, queue: managerQueue, options: [CBCentralManagerOptionRestoreIdentifierKey: "com.loudnate.CGMBLEKit"])
}
}

// MARK: - Actions

func scanForPeripheral() {
dispatchPrecondition(condition: .notOnQueue(managerQueue))

managerQueue.sync {
self.managerQueue_scanForPeripheral()
}
}

func disconnect() {
dispatchPrecondition(condition: .notOnQueue(managerQueue))

managerQueue.sync {
if manager.isScanning {
manager.stopScan()
}

if let peripheral = peripheral {
manager.cancelPeripheralConnection(peripheral)
}
}
}

private func managerQueue_scanForPeripheral() {
dispatchPrecondition(condition: .onQueue(managerQueue))

guard manager.state == .poweredOn else {
return
}
Expand All @@ -119,7 +165,9 @@ class BluetoothManager: NSObject {
} else if let peripheral = manager.retrieveConnectedPeripherals(withServices: [
TransmitterServiceUUID.advertisement.cbUUID,
TransmitterServiceUUID.cgmService.cbUUID
]).first, delegate == nil || delegate!.bluetoothManager(self, shouldConnectPeripheral: peripheral) {
]).first,
delegate == nil || delegate!.bluetoothManager(self, shouldConnectPeripheral: peripheral)
{
log.debug("Found system-connected peripheral: %{public}@", peripheral.identifier.uuidString)
self.peripheral = peripheral
self.manager.connect(peripheral)
Expand All @@ -133,16 +181,6 @@ class BluetoothManager: NSObject {
}
}

func disconnect() {
if manager.isScanning {
manager.stopScan()
}

if let peripheral = peripheral {
manager.cancelPeripheralConnection(peripheral)
}
}

/**

Persistent connections don't seem to work with the transmitter shutoff: The OS won't re-wake the
Expand All @@ -162,19 +200,27 @@ class BluetoothManager: NSObject {
// MARK: - Accessors

var isScanning: Bool {
return manager.isScanning
dispatchPrecondition(condition: .notOnQueue(managerQueue))

var isScanning = false
managerQueue.sync {
isScanning = manager.isScanning
}
return isScanning
}
}


extension BluetoothManager: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
dispatchPrecondition(condition: .onQueue(managerQueue))

peripheralManager?.centralManagerDidUpdateState(central)
log.info("%{public}@: %{public}@", #function, String(describing: central.state.rawValue))

switch central.state {
case .poweredOn:
scanForPeripheral()
managerQueue_scanForPeripheral()
case .resetting, .poweredOff, .unauthorized, .unknown, .unsupported:
if central.isScanning {
central.stopScan()
Expand All @@ -183,6 +229,8 @@ extension BluetoothManager: CBCentralManagerDelegate {
}

func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
dispatchPrecondition(condition: .onQueue(managerQueue))

if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] {
for peripheral in peripherals {
if delegate == nil || delegate!.bluetoothManager(self, shouldConnectPeripheral: peripheral) {
Expand All @@ -194,6 +242,8 @@ extension BluetoothManager: CBCentralManagerDelegate {
}

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
dispatchPrecondition(condition: .onQueue(managerQueue))

log.info("%{public}@: %{public}@", #function, peripheral)
if delegate == nil || delegate!.bluetoothManager(self, shouldConnectPeripheral: peripheral) {
self.peripheral = peripheral
Expand All @@ -205,23 +255,29 @@ extension BluetoothManager: CBCentralManagerDelegate {
}

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
dispatchPrecondition(condition: .onQueue(managerQueue))

log.info("%{public}@: %{public}@", #function, peripheral)
if central.isScanning {
central.stopScan()
}

peripheralManager?.centralManager(central, didConnect: peripheral)

if case .poweredOn = manager.state, case .connected = peripheral.state {
self.delegate?.bluetoothManager(self, isReadyWithError: nil)
if case .poweredOn = manager.state, case .connected = peripheral.state, let peripheralManager = peripheralManager {
self.delegate?.bluetoothManager(self, peripheralManager: peripheralManager, isReadyWithError: nil)
}
}

func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
dispatchPrecondition(condition: .onQueue(managerQueue))

// Ignore errors indicating the peripheral disconnected remotely, as that's expected behavior
if let error = error as NSError?, CBError(_nsError: error).code != .peripheralDisconnected {
log.error("%{public}@: %{public}@", #function, error)
self.delegate?.bluetoothManager(self, isReadyWithError: error)
if let peripheralManager = peripheralManager {
self.delegate?.bluetoothManager(self, peripheralManager: peripheralManager, isReadyWithError: error)
}
}

if stayConnected {
Expand All @@ -230,8 +286,10 @@ extension BluetoothManager: CBCentralManagerDelegate {
}

func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
if let error = error {
self.delegate?.bluetoothManager(self, isReadyWithError: error)
dispatchPrecondition(condition: .onQueue(managerQueue))

if let error = error, let peripheralManager = peripheralManager {
self.delegate?.bluetoothManager(self, peripheralManager: peripheralManager, isReadyWithError: error)
}

if stayConnected {
Expand Down
44 changes: 38 additions & 6 deletions CGMBLEKit/PeripheralManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class PeripheralManager: NSObject {
oldValue.delegate = nil
peripheral.delegate = self

queue.async {
queue.sync {
self.needsConfiguration = true
}
}
Expand All @@ -52,7 +52,13 @@ class PeripheralManager: NSObject {
// Confined to `queue`
private var needsConfiguration = true

weak var delegate: PeripheralManagerDelegate?
weak var delegate: PeripheralManagerDelegate? {
didSet {
queue.sync {
needsConfiguration = true
}
}
}

init(peripheral: CBPeripheral, configuration: Configuration, centralManager: CBCentralManager) {
self.peripheral = peripheral
Expand Down Expand Up @@ -101,16 +107,28 @@ extension PeripheralManager {
func configureAndRun(_ block: @escaping (_ manager: PeripheralManager) -> Void) -> (() -> Void) {
return {
if !self.needsConfiguration && self.peripheral.services == nil {
self.log.debug("Configured peripheral has no services. Reconfiguring…")
self.log.error("Configured peripheral has no services. Reconfiguring…")
}

if self.needsConfiguration || self.peripheral.services == nil {
do {
try self.applyConfiguration()
try self.delegate?.completeConfiguration(for: self)
self.needsConfiguration = false
self.log.default("Peripheral configuration completed")
} catch let error {
self.log.debug("Error applying configuration: %@", String(describing: error))
self.log.error("Error applying peripheral configuration: %@", String(describing: error))
// Will retry
}

do {
if let delegate = self.delegate {
try delegate.completeConfiguration(for: self)
self.log.default("Delegate configuration completed")
self.needsConfiguration = false
} else {
self.log.error("No delegate set configured")
}
} catch let error {
self.log.error("Error applying delegate configuration: %@", String(describing: error))
// Will retry
}
}
Expand Down Expand Up @@ -433,3 +451,17 @@ extension PeripheralManager: CBCentralManagerDelegate {
}
}
}


extension PeripheralManager {
public override var debugDescription: String {
var items = [
"## PeripheralManager",
"peripheral: \(peripheral)",
]
queue.sync {
items.append("needsConfiguration: \(needsConfiguration)")
}
return items.joined(separator: "\n")
}
}
4 changes: 2 additions & 2 deletions CGMBLEKit/Transmitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,15 @@ public final class Transmitter: BluetoothManagerDelegate {

// MARK: - BluetoothManagerDelegate

func bluetoothManager(_ manager: BluetoothManager, isReadyWithError error: Error?) {
func bluetoothManager(_ manager: BluetoothManager, peripheralManager: PeripheralManager, isReadyWithError error: Error?) {
if let error = error {
delegateQueue.async {
self.delegate?.transmitter(self, didError: error)
}
return
}

manager.peripheralManager?.perform { (peripheral) in
peripheralManager.perform { (peripheral) in
if self.passiveModeEnabled {
self.log.debug("Listening for control commands in passive mode")
do {
Expand Down
4 changes: 2 additions & 2 deletions Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github "LoopKit/LoopKit" "0f12e7a973625be59ea617166fea7bd2e0f6d729"
github "LoopKit/dexcom-share-client-swift" "81f6539a0e66fa9bcf34cfb92f60720741536f8a"
github "LoopKit/LoopKit" "1dc7b3ec4b7c6bb9225545f5d8fe599bfe783a5c"
github "LoopKit/dexcom-share-client-swift" "27fd38c28dcb16093ddf660c2b6b84ae34733352"