PumpOps.swift 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. //
  2. // PumpOps.swift
  3. // RileyLink
  4. //
  5. // Created by Pete Schwamb on 3/14/16.
  6. // Copyright © 2016 Pete Schwamb. All rights reserved.
  7. //
  8. import Foundation
  9. import RileyLinkKit
  10. import RileyLinkBLEKit
  11. import os.log
  12. import LoopKit
  13. public protocol PumpOpsDelegate: AnyObject, CommsLogger {
  14. func pumpOps(_ pumpOps: PumpOps, didChange state: PumpState)
  15. }
  16. public protocol PumpOps {
  17. func runSession(withName name: String, using device: RileyLinkDevice, _ block: @escaping (_ session: PumpOpsSession) -> Void)
  18. }
  19. extension PumpOps {
  20. public func runSession(withName name: String, usingSelector deviceSelector: @escaping (_ completion: @escaping (_ device: RileyLinkDevice?) -> Void) -> Void, _ block: @escaping (_ session: PumpOpsSession?) -> Void) {
  21. deviceSelector { (device) in
  22. guard let device = device else {
  23. block(nil)
  24. return
  25. }
  26. self.runSession(withName: name, using: device, block)
  27. }
  28. }
  29. }
  30. public class MinimedPumpOps: PumpOps {
  31. private let log = OSLog(category: "PumpOps")
  32. private let pumpSettings: PumpSettings
  33. private let pumpState: Locked<PumpState>
  34. private let configuredDevices: Locked<Set<UUID>> = Locked(Set())
  35. // Isolated to RileyLinkDeviceManager.sessionQueue
  36. private var sessionDevice: RileyLinkDevice?
  37. private weak var delegate: PumpOpsDelegate?
  38. public init(pumpSettings: PumpSettings, pumpState: PumpState?, delegate: PumpOpsDelegate?) {
  39. self.pumpSettings = pumpSettings
  40. self.delegate = delegate
  41. if let pumpState = pumpState {
  42. self.pumpState = Locked(pumpState)
  43. } else {
  44. let pumpState = PumpState()
  45. self.pumpState = Locked(pumpState)
  46. self.delegate?.pumpOps(self, didChange: pumpState)
  47. }
  48. }
  49. public func runSession(withName name: String, using device: RileyLinkDevice, _ block: @escaping (_ session: PumpOpsSession) -> Void) {
  50. device.runSession(withName: name) { (commandSession) in
  51. let minimedPumpMessageSender = MinimedPumpMessageSender(commandSession: commandSession, commsLogger: self.delegate)
  52. let session = PumpOpsSession(settings: self.pumpSettings, pumpState: self.pumpState.value, messageSender: minimedPumpMessageSender, delegate: self)
  53. self.sessionDevice = device
  54. if !commandSession.firmwareVersion.isUnknown {
  55. self.configureDevice(device, with: session)
  56. } else {
  57. self.log.error("Skipping device configuration due to unknown firmware version")
  58. }
  59. block(session)
  60. self.sessionDevice = nil
  61. }
  62. }
  63. // Must be called from within the RileyLinkDevice sessionQueue
  64. private func configureDevice(_ device: RileyLinkDevice, with session: PumpOpsSession) {
  65. guard !self.configuredDevices.value.contains(device.peripheralIdentifier) else {
  66. return
  67. }
  68. log.default("Configuring RileyLinkDevice: %{public}@", String(describing: device.deviceURI))
  69. do {
  70. _ = try session.configureRadio(for: pumpSettings.pumpRegion, frequency: pumpState.value.lastValidFrequency)
  71. } catch let error {
  72. // Ignore the error and let the block run anyway
  73. log.error("Error configuring device: %{public}@", String(describing: error))
  74. return
  75. }
  76. NotificationCenter.default.post(name: .DeviceRadioConfigDidChange, object: device)
  77. NotificationCenter.default.addObserver(self, selector: #selector(deviceRadioConfigDidChange(_:)), name: .DeviceRadioConfigDidChange, object: device)
  78. NotificationCenter.default.addObserver(self, selector: #selector(deviceRadioConfigDidChange(_:)), name: .DeviceConnectionStateDidChange, object: device)
  79. _ = configuredDevices.mutate { (value) in
  80. value.insert(device.peripheralIdentifier)
  81. }
  82. }
  83. @objc private func deviceRadioConfigDidChange(_ note: Notification) {
  84. guard let device = note.object as? RileyLinkDevice else {
  85. return
  86. }
  87. NotificationCenter.default.removeObserver(self, name: .DeviceRadioConfigDidChange, object: device)
  88. NotificationCenter.default.removeObserver(self, name: .DeviceConnectionStateDidChange, object: device)
  89. _ = configuredDevices.mutate { (value) in
  90. value.remove(device.peripheralIdentifier)
  91. }
  92. }
  93. }
  94. // Delivered on RileyLinkDeviceManager.sessionQueue
  95. extension MinimedPumpOps: PumpOpsSessionDelegate {
  96. public func pumpOpsSessionDidChangeRadioConfig(_ session: PumpOpsSession) {
  97. if let sessionDevice = self.sessionDevice {
  98. self.configuredDevices.value = [sessionDevice.peripheralIdentifier]
  99. }
  100. }
  101. public func pumpOpsSession(_ session: PumpOpsSession, didChange state: PumpState) {
  102. self.pumpState.value = state
  103. delegate?.pumpOps(self, didChange: state)
  104. NotificationCenter.default.post(
  105. name: .PumpOpsStateDidChange,
  106. object: self,
  107. userInfo: [MinimedPumpOps.notificationPumpStateKey: pumpState]
  108. )
  109. }
  110. }
  111. extension MinimedPumpOps: CustomDebugStringConvertible {
  112. public var debugDescription: String {
  113. return [
  114. "### PumpOps",
  115. "pumpSettings: \(String(reflecting: pumpSettings))",
  116. "pumpState: \(String(reflecting: pumpState.value))",
  117. "configuredDevices: \(configuredDevices.value.map({ $0.uuidString }))",
  118. ].joined(separator: "\n")
  119. }
  120. }
  121. /// Provide a notification contract that clients can use to inform RileyLink UI of changes to PumpOps.PumpState
  122. extension MinimedPumpOps {
  123. public static let notificationPumpStateKey = "com.rileylink.RileyLinkKit.PumpOps.PumpState"
  124. }
  125. extension Notification.Name {
  126. public static let PumpOpsStateDidChange = Notification.Name(rawValue: "com.rileylink.RileyLinkKit.PumpOpsStateDidChange")
  127. }