PumpOps.swift 5.5 KB

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