| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- //
- // TransmitterManager.swift
- // Loop
- //
- // Copyright © 2017 LoopKit Authors. All rights reserved.
- //
- import os.log
- import HealthKit
- public protocol TransmitterManagerDelegate: AnyObject {
- func transmitterManager(_ manager: TransmitterManager, didRead glucose: [Glucose])
- func transmitterManager(_ manager: TransmitterManager, didFailWith error: Error)
- }
- public struct TransmitterManagerState: Equatable {
- public static let version = 1
- public var transmitterID: String
- public var passiveModeEnabled: Bool = true
-
- public var shouldSyncToRemoteService: Bool
- public init(transmitterID: String, shouldSyncToRemoteService: Bool = false) {
- self.transmitterID = transmitterID
- self.shouldSyncToRemoteService = shouldSyncToRemoteService
- }
- }
- public protocol TransmitterManagerObserver: AnyObject {
- func transmitterManagerDidUpdateLatestReading(_ manager: TransmitterManager)
- }
- public class TransmitterManager: TransmitterDelegate {
- public weak var delegate: TransmitterManagerDelegate?
- private var state: TransmitterManagerState
- private let observers = WeakSynchronizedSet<TransmitterManagerObserver>()
- public var hasValidSensorSession: Bool {
- // TODO: we should decode and persist transmitter session state
- return !state.transmitterID.isEmpty
- }
- public required init(state: TransmitterManagerState) {
- self.state = state
- self.transmitter = Transmitter(id: state.transmitterID, passiveModeEnabled: state.passiveModeEnabled)
- self.transmitter.delegate = self
- }
- public var shouldSyncToRemoteService: Bool {
- get {
- return state.shouldSyncToRemoteService
- }
- set {
- self.state.shouldSyncToRemoteService = newValue
- notifyDelegateOfStateChange()
- }
- }
- private(set) public var latestConnection: Date? {
- get {
- return lockedLatestConnection.value
- }
- set {
- lockedLatestConnection.value = newValue
- }
- }
- private let lockedLatestConnection: Locked<Date?> = Locked(nil)
- public let transmitter: Transmitter
- let log = OSLog(category: "TransmitterManager")
- public var providesBLEHeartbeat: Bool {
- return dataIsFresh
- }
- private(set) public var latestReading: Glucose? {
- get {
- return lockedLatestReading.value
- }
- set {
- lockedLatestReading.value = newValue
- }
- }
- private let lockedLatestReading: Locked<Glucose?> = Locked(nil)
- private var dataIsFresh: Bool {
- guard let latestGlucose = latestReading,
- latestGlucose.readDate > Date(timeIntervalSinceNow: .minutes(-4.5)) else {
- return false
- }
- return true
- }
- // public func fetchNewDataIfNeeded(_ completion: @escaping (CGMReadingResult) -> Void) {
- // // Ensure our transmitter connection is active
- // transmitter.resumeScanning()
- //
- // // If our last glucose was less than 4.5 minutes ago, don't fetch.
- // guard !dataIsFresh else {
- // completion(.noData)
- // return
- // }
- //
- // log.default("Fetching new glucose from Share because last reading is %{public}.1f minutes old", latestReading?.readDate.timeIntervalSinceNow.minutes ?? 0)
- // }
- public var debugDescription: String {
- return [
- "## \(String(describing: type(of: self)))",
- "latestReading: \(String(describing: latestReading))",
- "latestConnection: \(String(describing: latestConnection))",
- "dataIsFresh: \(dataIsFresh)",
- "providesBLEHeartbeat: \(providesBLEHeartbeat)",
- "observers.count: \(observers.cleanupDeallocatedElements().count)",
- String(reflecting: transmitter),
- ].joined(separator: "\n")
- }
- // private func updateDelegate(with result: CGMReadingResult) {
- // if let manager = self as? CGMManager {
- // shareManager.delegate.notify { (delegate) in
- // delegate?.cgmManager(manager, hasNew: result)
- // }
- // }
- //
- // notifyObserversOfLatestReading()
- // }
-
- private func notifyDelegateOfStateChange() {
- // if let manager = self as? CGMManager {
- // shareManager.delegate.notify { (delegate) in
- // delegate?.cgmManagerDidUpdateState(manager)
- // }
- // }
- }
- // MARK: - TransmitterDelegate
- public func transmitterDidConnect(_ transmitter: Transmitter) {
- log.default("%{public}@", #function)
- latestConnection = Date()
- // logDeviceCommunication("Connected", type: .connection)
- }
- public func transmitter(_ transmitter: Transmitter, didError error: Error) {
- log.error("%{public}@: %{public}@", #function, String(describing: error))
- // updateDelegate(with: .error(error))
- // logDeviceCommunication("Error: \(error)", type: .error)
- }
- public func transmitter(_ transmitter: Transmitter, didRead glucose: Glucose) {
- guard glucose != latestReading else {
- delegate?.transmitterManager(self, didRead: [])
- // updateDelegate(with: .noData)
- return
- }
- latestReading = glucose
- // logDeviceCommunication("New reading: \(glucose.readDate)", type: .receive)
- guard glucose.state.hasReliableGlucose else {
- log.default("%{public}@: Unreliable glucose: %{public}@", #function, String(describing: glucose.state))
- delegate?.transmitterManager(self, didFailWith: CalibrationError.unreliableState(glucose.state))
- // updateDelegate(with: .error(CalibrationError.unreliableState(glucose.state)))
- return
- }
-
- guard glucose.glucose != nil else {
- delegate?.transmitterManager(self, didRead: [])
- // updateDelegate(with: .noData)
- return
- }
- log.default("%{public}@: New glucose", #function)
- delegate?.transmitterManager(self, didRead: [glucose])
- // updateDelegate(with: .newData([
- // NewGlucoseSample(
- // date: glucose.readDate,
- // quantity: quantity,
- // trend: glucose.trendType,
- // isDisplayOnly: glucose.isDisplayOnly,
- // wasUserEntered: glucose.isDisplayOnly,
- // syncIdentifier: glucose.syncIdentifier,
- // device: device
- // )
- // ]))
- }
- public func transmitter(_ transmitter: Transmitter, didReadBackfill glucose: [Glucose]) {
- let samples = glucose.filter { glucose -> Bool in
- guard glucose != latestReading, glucose.state.hasReliableGlucose, glucose.glucose != nil else {
- return false
- }
- return true
- }
- delegate?.transmitterManager(self, didRead: samples)
- //
- // guard samples.count > 0 else {
- // return
- // }
- // updateDelegate(with: .newData(samples))
- // logDeviceCommunication("New backfill: \(String(describing: samples.first?.date))", type: .receive)
- }
- public func transmitter(_ transmitter: Transmitter, didReadUnknownData data: Data) {
- log.error("Unknown sensor data: %{public}@", data.hexadecimalString)
- // This can be used for protocol discovery, but isn't necessary for normal operation
- // logDeviceCommunication("Unknown sensor data: \(data.hexadecimalString)", type: .error)
- }
- }
- // MARK: - Observer management
- extension TransmitterManager {
- public func addObserver(_ observer: TransmitterManagerObserver, queue: DispatchQueue) {
- observers.insert(observer, queue: queue)
- }
- public func removeObserver(_ observer: TransmitterManagerObserver) {
- observers.removeElement(observer)
- }
- private func notifyObserversOfLatestReading() {
- observers.forEach { (observer) in
- observer.transmitterManagerDidUpdateLatestReading(self)
- }
- }
- }
- public class G5CGMManager: TransmitterManager {
- public let managerIdentifier: String = "DexG5Transmitter"
- public let localizedTitle = LocalizedString("Dexcom G5", comment: "CGM display title")
- public let isOnboarded = true // No distinction between created and onboarded
- public var appURL: URL? {
- return URL(string: "dexcomcgm://")
- }
- public var device: HKDevice? {
- return HKDevice(
- name: "CGMBLEKit",
- manufacturer: "Dexcom",
- model: "G5 Mobile",
- hardwareVersion: nil,
- firmwareVersion: nil,
- softwareVersion: String(CGMBLEKitVersionNumber),
- localIdentifier: nil,
- udiDeviceIdentifier: "00386270000002"
- )
- }
-
- // func logDeviceCommunication(_ message: String, type: DeviceLogEntryType = .send) {
- // self.cgmManagerDelegate?.deviceManager(self, logEventForDeviceIdentifier: transmitter.ID, type: type, message: message, completion: nil)
- // }
-
- }
- public class G6CGMManager: TransmitterManager {
- public let managerIdentifier: String = "DexG6Transmitter"
- public let localizedTitle = LocalizedString("Dexcom G6", comment: "CGM display title")
- public let isOnboarded = true // No distinction between created and onboarded
- public var appURL: URL? {
- return nil
- }
- public var device: HKDevice? {
- return HKDevice(
- name: "CGMBLEKit",
- manufacturer: "Dexcom",
- model: "G6",
- hardwareVersion: nil,
- firmwareVersion: nil,
- softwareVersion: String(CGMBLEKitVersionNumber),
- localIdentifier: nil,
- udiDeviceIdentifier: "00386270000385"
- )
- }
-
- // func logDeviceCommunication(_ message: String, type: DeviceLogEntryType = .send) {
- // self.cgmManagerDelegate?.deviceManager(self, logEventForDeviceIdentifier: transmitter.ID, type: type, message: message, completion: nil)
- // }
- }
- enum CalibrationError: Error {
- case unreliableState(CalibrationState)
- }
- extension CalibrationError: LocalizedError {
- var errorDescription: String? {
- switch self {
- case .unreliableState:
- return LocalizedString("Glucose data is unavailable", comment: "Error description for unreliable state")
- }
- }
- var failureReason: String? {
- switch self {
- case .unreliableState(let state):
- return state.localizedDescription
- }
- }
- }
- extension CalibrationState {
- public var localizedDescription: String {
- switch self {
- case .known(let state):
- switch state {
- case .needCalibration7, .needCalibration14, .needFirstInitialCalibration, .needSecondInitialCalibration, .calibrationError8, .calibrationError9, .calibrationError10, .calibrationError13:
- return LocalizedString("Sensor needs calibration", comment: "The description of sensor calibration state when sensor needs calibration.")
- case .ok:
- return LocalizedString("Sensor calibration is OK", comment: "The description of sensor calibration state when sensor calibration is ok.")
- case .stopped, .sensorFailure11, .sensorFailure12, .sessionFailure15, .sessionFailure16, .sessionFailure17:
- return LocalizedString("Sensor is stopped", comment: "The description of sensor calibration state when sensor sensor is stopped.")
- case .warmup, .questionMarks:
- return LocalizedString("Sensor is warming up", comment: "The description of sensor calibration state when sensor sensor is warming up.")
- }
- case .unknown(let rawValue):
- return String(format: LocalizedString("Sensor is in unknown state %1$d", comment: "The description of sensor calibration state when raw value is unknown. (1: missing data details)"), rawValue)
- }
- }
- }
|