CGMManager.swift 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. //
  2. // CGMManager.swift
  3. // Loop
  4. //
  5. // Copyright © 2017 LoopKit Authors. All rights reserved.
  6. //
  7. import HealthKit
  8. /// Describes the result of CGM manager operations to fetch and report sensor readings.
  9. ///
  10. /// - noData: No new data was available or retrieved
  11. /// - unreliableData: New glucose data was received, but is not reliable enough to use for therapy
  12. /// - newData: New glucose data was received and stored
  13. /// - error: An error occurred while receiving or store data
  14. public enum CGMReadingResult {
  15. case noData
  16. case unreliableData
  17. case newData([NewGlucoseSample])
  18. case error(Error)
  19. }
  20. public struct CGMManagerStatus: Equatable {
  21. // Return false if no sensor active, or in a state where no future data is expected without user intervention
  22. public var hasValidSensorSession: Bool
  23. public var lastCommunicationDate: Date?
  24. public var device: HKDevice?
  25. public init(hasValidSensorSession: Bool, lastCommunicationDate: Date? = nil, device: HKDevice?) {
  26. self.hasValidSensorSession = hasValidSensorSession
  27. self.lastCommunicationDate = lastCommunicationDate
  28. self.device = device
  29. }
  30. }
  31. extension CGMManagerStatus: Codable {
  32. public init(from decoder: Decoder) throws {
  33. let container = try decoder.container(keyedBy: CodingKeys.self)
  34. self.hasValidSensorSession = try container.decode(Bool.self, forKey: .hasValidSensorSession)
  35. self.lastCommunicationDate = try container.decodeIfPresent(Date.self, forKey: .lastCommunicationDate)
  36. self.device = try container.decodeIfPresent(CodableDevice.self, forKey: .device)?.device
  37. }
  38. public func encode(to encoder: Encoder) throws {
  39. var container = encoder.container(keyedBy: CodingKeys.self)
  40. try container.encode(hasValidSensorSession, forKey: .hasValidSensorSession)
  41. try container.encodeIfPresent(lastCommunicationDate, forKey: .lastCommunicationDate)
  42. try container.encodeIfPresent(device.map { CodableDevice($0) }, forKey: .device)
  43. }
  44. private enum CodingKeys: String, CodingKey {
  45. case hasValidSensorSession
  46. case lastCommunicationDate
  47. case device
  48. }
  49. }
  50. public protocol CGMManagerStatusObserver: AnyObject {
  51. /// Notifies observers of changes in CGMManagerStatus
  52. ///
  53. /// - Parameter manager: The manager instance
  54. /// - Parameter status: The new, updated status. Status includes properties associated with the manager, transmitter, or sensor,
  55. /// that are not part of an individual sensor reading.
  56. func cgmManager(_ manager: CGMManager, didUpdate status: CGMManagerStatus)
  57. }
  58. public protocol CGMManagerDelegate: DeviceManagerDelegate, CGMManagerStatusObserver {
  59. /// Asks the delegate for a date with which to filter incoming glucose data
  60. ///
  61. /// - Parameter manager: The manager instance
  62. /// - Returns: The date data occuring on or after which should be kept
  63. func startDateToFilterNewData(for manager: CGMManager) -> Date?
  64. /// Informs the delegate that the device has updated with a new result
  65. ///
  66. /// - Parameters:
  67. /// - manager: The manager instance
  68. /// - result: The result of the update
  69. func cgmManager(_ manager: CGMManager, hasNew readingResult: CGMReadingResult) -> Void
  70. /// Informs the delegate that the manager is deactivating and should be deleted
  71. ///
  72. /// - Parameter manager: The manager instance
  73. func cgmManagerWantsDeletion(_ manager: CGMManager)
  74. /// Informs the delegate that the manager has updated its state and should be persisted.
  75. ///
  76. /// - Parameter manager: The manager instance
  77. func cgmManagerDidUpdateState(_ manager: CGMManager)
  78. /// Asks the delegate for credential store prefix to avoid namespace conflicts
  79. ///
  80. /// - Parameter manager: The manager instance
  81. /// - Returns: The unique prefix for the credential store
  82. func credentialStoragePrefix(for manager: CGMManager) -> String
  83. }
  84. public protocol CGMManager: DeviceManager {
  85. var cgmManagerDelegate: CGMManagerDelegate? { get set }
  86. var appURL: URL? { get }
  87. /// Whether the device is capable of waking the app
  88. var providesBLEHeartbeat: Bool { get }
  89. /// The length of time to keep samples in HealthKit before removal. Return nil to never remove samples.
  90. var managedDataInterval: TimeInterval? { get }
  91. /// The length of time to delay until storing samples into HealthKit. Return 0 for no delay.
  92. static var healthKitStorageDelay: TimeInterval { get }
  93. var shouldSyncToRemoteService: Bool { get }
  94. var glucoseDisplay: GlucoseDisplayable? { get }
  95. /// The current status of the cgm manager
  96. var cgmManagerStatus: CGMManagerStatus { get }
  97. /// The queue on which CGMManagerDelegate methods are called
  98. /// Setting to nil resets to a default provided by the manager
  99. var delegateQueue: DispatchQueue! { get set }
  100. /// Implementations of this function must call the `completion` block, with the appropriate `CGMReadingResult`
  101. /// according to the current available data.
  102. /// - If there is new unreliable data, return `.unreliableData`
  103. /// - If there is no new data and the current data is unreliable, return `.unreliableData`
  104. /// - If there is new reliable data, return `.newData` with the data samples
  105. /// - If there is no new data and the current data is reliable, return `.noData`
  106. /// - If there is an error, return `.error` with the appropriate error.
  107. ///
  108. /// - Parameters:
  109. /// - completion: A closure called when operation has completed
  110. func fetchNewDataIfNeeded(_ completion: @escaping (CGMReadingResult) -> Void) -> Void
  111. /// Adds an observer of changes in CGMManagerStatus
  112. ///
  113. /// Observers are held by weak reference.
  114. ///
  115. /// - Parameters:
  116. /// - observer: The observing object
  117. /// - queue: The queue on which the observer methods should be called
  118. func addStatusObserver(_ observer: CGMManagerStatusObserver, queue: DispatchQueue)
  119. /// Removes an observer of changes in CGMManagerStatus
  120. ///
  121. /// Since observers are held weakly, calling this method is not required when the observer is deallocated
  122. ///
  123. /// - Parameter observer: The observing object
  124. func removeStatusObserver(_ observer: CGMManagerStatusObserver)
  125. /// Requests that the manager completes its deletion process
  126. ///
  127. /// - Parameter completion: Action to take after the CGM manager is deleted
  128. func delete(completion: @escaping () -> Void)
  129. }
  130. public extension CGMManager {
  131. var appURL: URL? {
  132. return nil
  133. }
  134. /// Default is no delay to store samples in HealthKit
  135. static var healthKitStorageDelay: TimeInterval { 0 }
  136. /// Convenience wrapper for notifying the delegate of deletion on the delegate queue
  137. ///
  138. /// - Parameters:
  139. /// - completion: A closure called from the delegate queue after the delegate is called
  140. func notifyDelegateOfDeletion(completion: @escaping () -> Void) {
  141. delegateQueue.async {
  142. self.cgmManagerDelegate?.cgmManagerWantsDeletion(self)
  143. completion()
  144. }
  145. }
  146. func addStatusObserver(_ observer: CGMManagerStatusObserver, queue: DispatchQueue) {
  147. // optional since a CGM manager may not support status observers
  148. }
  149. func removeStatusObserver(_ observer: CGMManagerStatusObserver) {
  150. // optional since a CGM manager may not support status observers
  151. }
  152. /// Override this default behaviour if the CGM Manager needs to complete tasks before being deleted
  153. func delete(completion: @escaping () -> Void) {
  154. notifyDelegateOfDeletion(completion: completion)
  155. }
  156. }