BluetoothManager.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. //
  2. // BluetoothManager.swift
  3. // xDripG5
  4. //
  5. // Created by Nathan Racklyeft on 10/1/15.
  6. // Copyright © 2015 Nathan Racklyeft. All rights reserved.
  7. //
  8. import CoreBluetooth
  9. import Foundation
  10. import os.log
  11. protocol BluetoothManagerDelegate: AnyObject {
  12. /**
  13. 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
  14. - parameter manager: The bluetooth manager
  15. - parameter peripheralManager: The peripheral manager
  16. - parameter error: An error describing why bluetooth setup failed
  17. */
  18. func bluetoothManager(_ manager: BluetoothManager, peripheralManager: PeripheralManager, isReadyWithError error: Error?)
  19. /**
  20. Asks the delegate whether the discovered or restored peripheral should be connected
  21. - parameter manager: The bluetooth manager
  22. - parameter peripheral: The found peripheral
  23. - returns: True if the peripheral should connect
  24. */
  25. func bluetoothManager(_ manager: BluetoothManager, shouldConnectPeripheral peripheral: CBPeripheral) -> Bool
  26. /// Informs the delegate that the bluetooth manager received new data in the control characteristic
  27. ///
  28. /// - Parameters:
  29. /// - manager: The bluetooth manager
  30. /// - peripheralManager: The peripheral manager
  31. /// - response: The data received on the control characteristic
  32. func bluetoothManager(_ manager: BluetoothManager, peripheralManager: PeripheralManager, didReceiveControlResponse response: Data)
  33. /// Informs the delegate that the bluetooth manager received new data in the backfill characteristic
  34. ///
  35. /// - Parameters:
  36. /// - manager: The bluetooth manager
  37. /// - response: The data received on the backfill characteristic
  38. func bluetoothManager(_ manager: BluetoothManager, didReceiveBackfillResponse response: Data)
  39. /// Informs the delegate that the bluetooth manager received new data in the authentication characteristic
  40. ///
  41. /// - Parameters:
  42. /// - manager: The bluetooth manager
  43. /// - peripheralManager: The peripheral manager
  44. /// - response: The data received on the authentication characteristic
  45. func bluetoothManager(_ manager: BluetoothManager, peripheralManager: PeripheralManager, didReceiveAuthenticationResponse response: Data)
  46. }
  47. class BluetoothManager: NSObject {
  48. var stayConnected: Bool {
  49. get {
  50. return lockedStayConnected.value
  51. }
  52. set {
  53. lockedStayConnected.value = newValue
  54. }
  55. }
  56. private let lockedStayConnected: Locked<Bool> = Locked(true)
  57. weak var delegate: BluetoothManagerDelegate?
  58. private let log = OSLog(category: "BluetoothManager")
  59. /// Isolated to `managerQueue`
  60. private var manager: CBCentralManager! = nil
  61. /// Isolated to `managerQueue`
  62. private var peripheral: CBPeripheral? {
  63. get {
  64. return peripheralManager?.peripheral
  65. }
  66. set {
  67. guard let peripheral = newValue else {
  68. peripheralManager = nil
  69. return
  70. }
  71. if let peripheralManager = peripheralManager {
  72. peripheralManager.peripheral = peripheral
  73. } else {
  74. peripheralManager = PeripheralManager(
  75. peripheral: peripheral,
  76. configuration: .dexcomG5,
  77. centralManager: manager
  78. )
  79. }
  80. }
  81. }
  82. var peripheralIdentifier: UUID? {
  83. get {
  84. return lockedPeripheralIdentifier.value
  85. }
  86. set {
  87. lockedPeripheralIdentifier.value = newValue
  88. }
  89. }
  90. private let lockedPeripheralIdentifier: Locked<UUID?> = Locked(nil)
  91. /// Isolated to `managerQueue`
  92. private var peripheralManager: PeripheralManager? {
  93. didSet {
  94. oldValue?.delegate = nil
  95. peripheralManager?.delegate = self
  96. peripheralIdentifier = peripheralManager?.peripheral.identifier
  97. }
  98. }
  99. // MARK: - Synchronization
  100. private let managerQueue = DispatchQueue(label: "com.loudnate.CGMBLEKit.bluetoothManagerQueue", qos: .unspecified)
  101. override init() {
  102. super.init()
  103. managerQueue.sync {
  104. self.manager = CBCentralManager(delegate: self, queue: managerQueue, options: [CBCentralManagerOptionRestoreIdentifierKey: "com.loudnate.CGMBLEKit"])
  105. }
  106. }
  107. // MARK: - Actions
  108. func scanForPeripheral() {
  109. dispatchPrecondition(condition: .notOnQueue(managerQueue))
  110. managerQueue.sync {
  111. self.managerQueue_scanForPeripheral()
  112. }
  113. }
  114. func disconnect() {
  115. dispatchPrecondition(condition: .notOnQueue(managerQueue))
  116. managerQueue.sync {
  117. if manager.isScanning {
  118. manager.stopScan()
  119. }
  120. if let peripheral = peripheral {
  121. manager.cancelPeripheralConnection(peripheral)
  122. }
  123. }
  124. }
  125. private func managerQueue_scanForPeripheral() {
  126. dispatchPrecondition(condition: .onQueue(managerQueue))
  127. guard manager.state == .poweredOn else {
  128. return
  129. }
  130. let currentState = peripheral?.state ?? .disconnected
  131. guard currentState != .connected else {
  132. return
  133. }
  134. if let peripheralID = peripheralIdentifier, let peripheral = manager.retrievePeripherals(withIdentifiers: [peripheralID]).first {
  135. log.debug("Re-connecting to known peripheral %{public}@", peripheral.identifier.uuidString)
  136. self.peripheral = peripheral
  137. self.manager.connect(peripheral)
  138. } else if let peripheral = manager.retrieveConnectedPeripherals(withServices: [
  139. TransmitterServiceUUID.advertisement.cbUUID,
  140. TransmitterServiceUUID.cgmService.cbUUID
  141. ]).first,
  142. delegate == nil || delegate!.bluetoothManager(self, shouldConnectPeripheral: peripheral)
  143. {
  144. log.debug("Found system-connected peripheral: %{public}@", peripheral.identifier.uuidString)
  145. self.peripheral = peripheral
  146. self.manager.connect(peripheral)
  147. } else {
  148. log.debug("Scanning for peripherals")
  149. manager.scanForPeripherals(withServices: [
  150. TransmitterServiceUUID.advertisement.cbUUID
  151. ],
  152. options: nil
  153. )
  154. }
  155. }
  156. /**
  157. Persistent connections don't seem to work with the transmitter shutoff: The OS won't re-wake the
  158. app unless it's scanning.
  159. The sleep gives the transmitter time to shut down, but keeps the app running.
  160. */
  161. fileprivate func scanAfterDelay() {
  162. DispatchQueue.global(qos: .utility).async {
  163. Thread.sleep(forTimeInterval: 2)
  164. self.scanForPeripheral()
  165. }
  166. }
  167. // MARK: - Accessors
  168. var isScanning: Bool {
  169. dispatchPrecondition(condition: .notOnQueue(managerQueue))
  170. var isScanning = false
  171. managerQueue.sync {
  172. isScanning = manager.isScanning
  173. }
  174. return isScanning
  175. }
  176. override var debugDescription: String {
  177. return [
  178. "## BluetoothManager",
  179. peripheralManager.map(String.init(reflecting:)) ?? "No peripheral",
  180. ].joined(separator: "\n")
  181. }
  182. }
  183. extension BluetoothManager: CBCentralManagerDelegate {
  184. func centralManagerDidUpdateState(_ central: CBCentralManager) {
  185. dispatchPrecondition(condition: .onQueue(managerQueue))
  186. peripheralManager?.centralManagerDidUpdateState(central)
  187. log.default("%{public}@: %{public}@", #function, String(describing: central.state.rawValue))
  188. switch central.state {
  189. case .poweredOn:
  190. managerQueue_scanForPeripheral()
  191. case .resetting, .poweredOff, .unauthorized, .unknown, .unsupported:
  192. fallthrough
  193. @unknown default:
  194. if central.isScanning {
  195. central.stopScan()
  196. }
  197. }
  198. }
  199. func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
  200. dispatchPrecondition(condition: .onQueue(managerQueue))
  201. if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] {
  202. for peripheral in peripherals {
  203. if delegate == nil || delegate!.bluetoothManager(self, shouldConnectPeripheral: peripheral) {
  204. log.default("Restoring peripheral from state: %{public}@", peripheral.identifier.uuidString)
  205. self.peripheral = peripheral
  206. }
  207. }
  208. }
  209. }
  210. func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
  211. dispatchPrecondition(condition: .onQueue(managerQueue))
  212. log.info("%{public}@: %{public}@", #function, peripheral)
  213. if delegate == nil || delegate!.bluetoothManager(self, shouldConnectPeripheral: peripheral) {
  214. self.peripheral = peripheral
  215. central.connect(peripheral, options: nil)
  216. central.stopScan()
  217. }
  218. }
  219. func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
  220. dispatchPrecondition(condition: .onQueue(managerQueue))
  221. log.default("%{public}@: %{public}@", #function, peripheral)
  222. if central.isScanning {
  223. central.stopScan()
  224. }
  225. peripheralManager?.centralManager(central, didConnect: peripheral)
  226. if case .poweredOn = manager.state, case .connected = peripheral.state, let peripheralManager = peripheralManager {
  227. self.delegate?.bluetoothManager(self, peripheralManager: peripheralManager, isReadyWithError: nil)
  228. }
  229. }
  230. func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
  231. dispatchPrecondition(condition: .onQueue(managerQueue))
  232. log.default("%{public}@: %{public}@", #function, peripheral)
  233. // Ignore errors indicating the peripheral disconnected remotely, as that's expected behavior
  234. if let error = error as NSError?, CBError(_nsError: error).code != .peripheralDisconnected {
  235. log.error("%{public}@: %{public}@", #function, error)
  236. if let peripheralManager = peripheralManager {
  237. self.delegate?.bluetoothManager(self, peripheralManager: peripheralManager, isReadyWithError: error)
  238. }
  239. }
  240. if stayConnected {
  241. scanAfterDelay()
  242. }
  243. }
  244. func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
  245. dispatchPrecondition(condition: .onQueue(managerQueue))
  246. log.error("%{public}@: %{public}@", #function, String(describing: error))
  247. if let error = error, let peripheralManager = peripheralManager {
  248. self.delegate?.bluetoothManager(self, peripheralManager: peripheralManager, isReadyWithError: error)
  249. }
  250. if stayConnected {
  251. scanAfterDelay()
  252. }
  253. }
  254. }
  255. extension BluetoothManager: PeripheralManagerDelegate {
  256. func peripheralManager(_ manager: PeripheralManager, didReadRSSI RSSI: NSNumber, error: Error?) {
  257. }
  258. func peripheralManagerDidUpdateName(_ manager: PeripheralManager) {
  259. }
  260. func completeConfiguration(for manager: PeripheralManager) throws {
  261. }
  262. func peripheralManager(_ manager: PeripheralManager, didUpdateValueFor characteristic: CBCharacteristic) {
  263. guard let value = characteristic.value else {
  264. return
  265. }
  266. switch CGMServiceCharacteristicUUID(rawValue: characteristic.uuid.uuidString.uppercased()) {
  267. case .none, .communication?:
  268. return
  269. case .control?:
  270. self.delegate?.bluetoothManager(self, peripheralManager: manager, didReceiveControlResponse: value)
  271. case .backfill?:
  272. self.delegate?.bluetoothManager(self, didReceiveBackfillResponse: value)
  273. case .authentication?:
  274. self.delegate?.bluetoothManager(self, peripheralManager: manager, didReceiveAuthenticationResponse: value)
  275. }
  276. }
  277. }