LibreTransmitterManager.swift 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. //
  2. // LibreTransmitterManager.swift
  3. // Created by Bjørn Inge Berg on 25/02/2019.
  4. // Copyright © 2019 Bjørn Inge Berg. All rights reserved.
  5. //
  6. import Foundation
  7. import UserNotifications
  8. import Combine
  9. import UIKit
  10. import CoreBluetooth
  11. import HealthKit
  12. import os.log
  13. public protocol LibreTransmitterManagerDelegate: AnyObject {
  14. var queue: DispatchQueue { get }
  15. func startDateToFilterNewData(for: LibreTransmitterManager) -> Date?
  16. func cgmManager(_:LibreTransmitterManager, hasNew result: Result<[LibreGlucose], Error>)
  17. func overcalibration(for: LibreTransmitterManager) -> ((Double) -> (Double))?
  18. }
  19. public final class LibreTransmitterManager: LibreTransmitterDelegate {
  20. public typealias GlucoseArrayWithPrediction = (glucose:[LibreGlucose], prediction:[LibreGlucose])
  21. public lazy var logger = Logger(forType: Self.self)
  22. public let isOnboarded = true // No distinction between created and onboarded
  23. public var hasValidSensorSession: Bool {
  24. lastConnected != nil
  25. }
  26. public var glucoseDisplay: GlucoseDisplayable?
  27. public var trend: GlucoseTrend?
  28. public func libreManagerDidRestoreState(found peripherals: [CBPeripheral], connected to: CBPeripheral?) {
  29. let devicename = to?.name ?? "no device"
  30. let id = to?.identifier.uuidString ?? "null"
  31. let msg = String(format: NSLocalizedString("Bluetooth State restored (APS restarted?). Found %d peripherals, and connected to %@ with identifier %@", comment: "Restored state message"), peripherals.count, devicename, id)
  32. NotificationHelper.sendRestoredStateNotification(msg: msg)
  33. }
  34. public var batteryLevel: Double? {
  35. let batt = self.proxy?.metadata?.battery
  36. logger.debug("dabear:: LibreTransmitterManager was asked to return battery: \(batt.debugDescription)")
  37. //convert from 8% -> 0.8
  38. if let battery = proxy?.metadata?.battery {
  39. return Double(battery) / 100
  40. }
  41. return nil
  42. }
  43. public var cgmManagerDelegate: LibreTransmitterManagerDelegate? {
  44. get {
  45. return delegate.delegate
  46. }
  47. set {
  48. delegate.delegate = newValue
  49. }
  50. }
  51. public var delegateQueue: DispatchQueue! {
  52. get {
  53. return delegate.queue
  54. }
  55. set {
  56. delegate.queue = newValue
  57. }
  58. }
  59. public let delegate = WeakSynchronizedDelegate<LibreTransmitterManagerDelegate>()
  60. public var managedDataInterval: TimeInterval?
  61. private func getPersistedSensorDataForDebug() -> String {
  62. guard let data = UserDefaults.standard.queuedSensorData else {
  63. return "nil"
  64. }
  65. let c = self.calibrationData?.description ?? "no calibrationdata"
  66. return data.array.map {
  67. "SensorData(uuid: \"0123\".data(using: .ascii)!, bytes: \($0.bytes))!"
  68. }
  69. .joined(separator: ",\n")
  70. + ",\n Calibrationdata: \(c)"
  71. }
  72. public var debugDescription: String {
  73. return [
  74. "## LibreTransmitterManager",
  75. "Testdata: foo",
  76. "lastConnected: \(String(describing: lastConnected))",
  77. "Connection state: \(connectionState)",
  78. "Sensor state: \(sensorStateDescription)",
  79. "transmitterbattery: \(batteryString)",
  80. "SensorData: \(getPersistedSensorDataForDebug())",
  81. "Metainfo::\n\(AppMetaData.allProperties)",
  82. ""
  83. ].joined(separator: "\n")
  84. }
  85. public private(set) var lastConnected: Date?
  86. public private(set) var latestPrediction: LibreGlucose?
  87. public private(set) var latestBackfill: LibreGlucose? {
  88. willSet(newValue) {
  89. guard let newValue = newValue else {
  90. return
  91. }
  92. var trend: GlucoseTrend?
  93. let oldValue = latestBackfill
  94. logger.debug("dabear:: latestBackfill set, newvalue is \(newValue.description)")
  95. if let oldValue = oldValue {
  96. // the idea here is to use the diff between the old and the new glucose to calculate slope and direction, rather than using trend from the glucose value.
  97. // this is because the old and new glucose values represent earlier readouts, while the trend buffer contains somewhat more jumpy (noisy) values.
  98. let timediff = LibreGlucose.timeDifference(oldGlucose: oldValue, newGlucose: newValue)
  99. logger.debug("dabear:: timediff is \(timediff)")
  100. let oldIsRecentEnough = timediff <= TimeInterval.minutes(15)
  101. trend = oldIsRecentEnough ? newValue.GetGlucoseTrend(last: oldValue) : nil
  102. var batteries : [(name: String, percentage: Int)]?
  103. if let metaData = metaData, let battery = battery {
  104. batteries = [(name: metaData.name, percentage: battery)]
  105. }
  106. self.glucoseDisplay = ConcreteGlucoseDisplayable(isStateValid: newValue.isStateValid, trendType: trend, isLocal: true, batteries: batteries)
  107. } else {
  108. //could consider setting this to ConcreteSensorDisplayable with trendtype GlucoseTrend.flat, but that would be kinda lying
  109. self.glucoseDisplay = nil
  110. }
  111. }
  112. }
  113. static public var managerIdentifier : String {
  114. Self.className
  115. }
  116. static public let localizedTitle = LocalizedString("Libre Bluetooth", comment: "Title for the CGMManager option")
  117. public init() {
  118. lastConnected = nil
  119. //let isui = (self is CGMManagerUI)
  120. //self.miaomiaoService = MiaomiaoService(keychainManager: keychain)
  121. logger.debug("dabear: LibreTransmitterManager will be created now")
  122. //proxy = MiaoMiaoBluetoothManager()
  123. proxy?.delegate = self
  124. }
  125. public func disconnect() {
  126. logger.debug("dabear:: LibreTransmitterManager disconnect called")
  127. proxy?.disconnectManually()
  128. proxy?.delegate = nil
  129. }
  130. deinit {
  131. logger.debug("dabear:: LibreTransmitterManager deinit called")
  132. //cleanup any references to events to this class
  133. disconnect()
  134. }
  135. //lazy because we don't want to scan immediately
  136. private lazy var proxy: LibreTransmitterProxyManager? = LibreTransmitterProxyManager()
  137. /*
  138. These properties are mostly useful for swiftui
  139. */
  140. public var transmitterInfoObservable = TransmitterInfo()
  141. public var sensorInfoObservable = SensorInfo()
  142. public var glucoseInfoObservable = GlucoseInfo()
  143. var longDateFormatter : DateFormatter = ({
  144. let df = DateFormatter()
  145. df.dateStyle = .long
  146. df.timeStyle = .long
  147. df.doesRelativeDateFormatting = true
  148. return df
  149. })()
  150. var dateFormatter : DateFormatter = ({
  151. let df = DateFormatter()
  152. df.dateStyle = .long
  153. df.timeStyle = .full
  154. df.locale = Locale.current
  155. return df
  156. })()
  157. //when was the libre2 direct ble update last received?
  158. var lastDirectUpdate : Date? = nil
  159. private var countTimesWithoutData: Int = 0
  160. }
  161. // MARK: - Convenience functions
  162. extension LibreTransmitterManager {
  163. func setObservables(sensorData: SensorData?, bleData: Libre2.LibreBLEResponse?, metaData: LibreTransmitterMetadata?) {
  164. logger.debug("dabear:: setObservables called")
  165. DispatchQueue.main.async {
  166. if let metaData=metaData {
  167. self.logger.debug("dabear::will set transmitterInfoObservable")
  168. self.transmitterInfoObservable.battery = metaData.batteryString
  169. self.transmitterInfoObservable.hardware = metaData.hardware
  170. self.transmitterInfoObservable.firmware = metaData.firmware
  171. self.transmitterInfoObservable.sensorType = metaData.sensorType()?.description ?? "Unknown"
  172. self.transmitterInfoObservable.transmitterIdentifier = metaData.macAddress ?? UserDefaults.standard.preSelectedDevice ?? "Unknown"
  173. }
  174. self.transmitterInfoObservable.connectionState = self.proxy?.connectionStateString ?? "n/a"
  175. self.transmitterInfoObservable.transmitterType = self.proxy?.shortTransmitterName ?? "Unknown"
  176. if let sensorData = sensorData {
  177. self.logger.debug("dabear::will set sensorInfoObservable")
  178. self.sensorInfoObservable.sensorAge = sensorData.humanReadableSensorAge
  179. self.sensorInfoObservable.sensorAgeLeft = sensorData.humanReadableTimeLeft
  180. self.sensorInfoObservable.sensorState = sensorData.state.description
  181. self.sensorInfoObservable.sensorSerial = sensorData.serialNumber
  182. self.glucoseInfoObservable.checksum = String(sensorData.footerCrc.byteSwapped)
  183. if let sensorEndTime = sensorData.sensorEndTime {
  184. self.sensorInfoObservable.sensorEndTime = self.dateFormatter.string(from: sensorEndTime )
  185. } else {
  186. self.sensorInfoObservable.sensorEndTime = "Unknown or ended"
  187. }
  188. } else if let bleData = bleData, let sensor = UserDefaults.standard.preSelectedSensor {
  189. let aday = 86_400.0 //in seconds
  190. var humanReadableSensorAge: String {
  191. let days = TimeInterval(bleData.age * 60) / aday
  192. return String(format: "%.2f", days) + NSLocalizedString(" day(s)", comment: "Sensor day(s)")
  193. }
  194. var maxMinutesWearTime : Int{
  195. sensor.maxAge
  196. }
  197. var minutesLeft: Int {
  198. maxMinutesWearTime - bleData.age
  199. }
  200. var humanReadableTimeLeft: String {
  201. let days = TimeInterval(minutesLeft * 60) / aday
  202. return String(format: "%.2f", days) + NSLocalizedString(" day(s)", comment: "Sensor day(s)")
  203. }
  204. //once the sensor has ended we don't know the exact date anymore
  205. var sensorEndTime: Date? {
  206. if minutesLeft <= 0 {
  207. return nil
  208. }
  209. // we can assume that the libre2 direct bluetooth packet is received immediately
  210. // after the sensor has been done a new measurement, so using Date() should be fine here
  211. return Date().addingTimeInterval(TimeInterval(minutes: Double(minutesLeft)))
  212. }
  213. self.sensorInfoObservable.sensorAge = humanReadableSensorAge
  214. self.sensorInfoObservable.sensorAgeLeft = humanReadableTimeLeft
  215. self.sensorInfoObservable.sensorState = "Operational"
  216. self.sensorInfoObservable.sensorState = "Operational"
  217. self.sensorInfoObservable.sensorSerial = SensorSerialNumber(withUID: sensor.uuid)?.serialNumber ?? "-"
  218. if let mapping = UserDefaults.standard.calibrationMapping,
  219. let calibration = self.calibrationData ,
  220. mapping.uuid == sensor.uuid && calibration.isValidForFooterWithReverseCRCs == mapping.reverseFooterCRC {
  221. self.glucoseInfoObservable.checksum = "\(mapping.reverseFooterCRC)"
  222. }
  223. if let sensorEndTime = sensorEndTime {
  224. self.sensorInfoObservable.sensorEndTime = self.dateFormatter.string(from: sensorEndTime )
  225. } else {
  226. self.sensorInfoObservable.sensorEndTime = "Unknown or ended"
  227. }
  228. }
  229. let formatter = QuantityFormatter()
  230. let preferredUnit = UserDefaults.standard.mmGlucoseUnit ?? .millimolesPerLiter
  231. if let d = self.latestBackfill {
  232. self.logger.debug("dabear::will set glucoseInfoObservable")
  233. formatter.setPreferredNumberFormatter(for: .millimolesPerLiter)
  234. self.glucoseInfoObservable.glucoseMMOL = formatter.string(from: d.quantity, for: .millimolesPerLiter) ?? "-"
  235. formatter.setPreferredNumberFormatter(for: .milligramsPerDeciliter)
  236. self.glucoseInfoObservable.glucoseMGDL = formatter.string(from: d.quantity, for: .milligramsPerDeciliter) ?? "-"
  237. //backward compat
  238. if preferredUnit == .millimolesPerLiter {
  239. self.glucoseInfoObservable.glucose = self.glucoseInfoObservable.glucoseMMOL
  240. } else if preferredUnit == .milligramsPerDeciliter {
  241. self.glucoseInfoObservable.glucose = self.glucoseInfoObservable.glucoseMGDL
  242. }
  243. self.glucoseInfoObservable.date = self.longDateFormatter.string(from: d.timestamp)
  244. }
  245. if let d = self.latestPrediction {
  246. formatter.setPreferredNumberFormatter(for: .millimolesPerLiter)
  247. self.glucoseInfoObservable.predictionMMOL = formatter.string(from: d.quantity, for: .millimolesPerLiter) ?? "-"
  248. formatter.setPreferredNumberFormatter(for: .milligramsPerDeciliter)
  249. self.glucoseInfoObservable.predictionMGDL = formatter.string(from: d.quantity, for: .milligramsPerDeciliter) ?? "-"
  250. self.glucoseInfoObservable.predictionDate = self.longDateFormatter.string(from: d.timestamp)
  251. }
  252. }
  253. }
  254. func getStartDateForFilter() -> Date?{
  255. var startDate: Date?
  256. self.delegateQueue.sync {
  257. startDate = self.cgmManagerDelegate?.startDateToFilterNewData(for: self) ?? self.latestBackfill?.startDate
  258. }
  259. // add one second to startdate to make this an exclusive (non overlapping) match
  260. return startDate?.addingTimeInterval(1)
  261. }
  262. func glucosesToSamplesFilter(_ array: [LibreGlucose], startDate: Date?) -> [LibreGlucose] {
  263. array
  264. .filterDateRange(startDate, nil)
  265. .filter { $0.isStateValid }
  266. .compactMap { $0 }
  267. }
  268. public var calibrationData: SensorData.CalibrationInfo? {
  269. KeychainManagerWrapper.standard.getLibreNativeCalibrationData()
  270. }
  271. }
  272. // MARK: - Direct bluetooth updates
  273. extension LibreTransmitterManager {
  274. public func libreSensorDidUpdate(with bleData: Libre2.LibreBLEResponse, and device: LibreTransmitterMetadata) {
  275. self.logger.debug("dabear:: got sensordata: \(String(describing: bleData))")
  276. let typeDesc = device.sensorType().debugDescription
  277. let now = Date()
  278. //only once per mins minute
  279. let mins = 4.5
  280. if let earlierplus = lastDirectUpdate?.addingTimeInterval(mins * 60), earlierplus >= now {
  281. logger.debug("last ble update was less than \(mins) minutes ago, aborting loop update")
  282. return
  283. }
  284. logger.debug("Directly connected to libresensor of type \(typeDesc). Details: \(device.description)")
  285. guard let mapping = UserDefaults.standard.calibrationMapping,
  286. let calibrationData = calibrationData,
  287. let sensor = UserDefaults.standard.preSelectedSensor else {
  288. logger.error("calibrationdata, sensor uid or mapping missing, could not continue")
  289. self.delegateQueue.async {
  290. self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(LibreError.noCalibrationData))
  291. }
  292. return
  293. }
  294. guard mapping.reverseFooterCRC == calibrationData.isValidForFooterWithReverseCRCs &&
  295. mapping.uuid == sensor.uuid else {
  296. logger.error("Calibrationdata was not correct for these bluetooth packets. This is a fatal error, we cannot calibrate without re-pairing")
  297. self.delegateQueue.async {
  298. self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(LibreError.noCalibrationData))
  299. }
  300. return
  301. }
  302. guard bleData.crcVerified else {
  303. self.delegateQueue.async {
  304. self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(LibreError.checksumValidationError))
  305. }
  306. logger.debug("did not get bledata with valid crcs")
  307. return
  308. }
  309. if sensor.maxAge > 0 {
  310. let minutesLeft = Double(sensor.maxAge - bleData.age)
  311. NotificationHelper.sendSensorExpireAlertIfNeeded(minutesLeft: minutesLeft)
  312. }
  313. let sensorStartDate = Date().addingTimeInterval(-1 * TimeInterval(minutes: Double(bleData.age)))
  314. NSLog("Libre age \(bleData.age), Start Date calculated: \(sensorStartDate)")
  315. // let device = self.proxy?.device
  316. let sortedTrends = bleData.trend.sorted{ $0.date > $1.date}
  317. let glucose = LibreGlucose.fromTrendMeasurements(sortedTrends, nativeCalibrationData: calibrationData, returnAll: UserDefaults.standard.mmBackfillFromTrend, sensorStartDate: sensorStartDate)
  318. //glucose += LibreGlucose.fromHistoryMeasurements(bleData.history, nativeCalibrationData: calibrationData)
  319. // while libre2 fram scans contains historymeasurements for the last 8 hours,
  320. // history from bledata contains just a couple of data points, so we don't bother
  321. /*if UserDefaults.standard.mmBackfillFromHistory {
  322. let sortedHistory = bleData.history.sorted{ $0.date > $1.date}
  323. glucose += LibreGlucose.fromHistoryMeasurements(sortedHistory, nativeCalibrationData: calibrationData)
  324. }*/
  325. var newGlucose = glucosesToSamplesFilter(glucose, startDate: getStartDateForFilter())
  326. if newGlucose.isEmpty {
  327. self.countTimesWithoutData &+= 1
  328. } else {
  329. self.latestBackfill = glucose.max { $0.startDate < $1.startDate }
  330. self.logger.debug("dabear:: latestbackfill set to \(self.latestBackfill.debugDescription)")
  331. self.countTimesWithoutData = 0
  332. }
  333. //todo: predictions also for libre2 bluetooth data
  334. //self.latestPrediction = prediction?.first
  335. var predictions: [LibreGlucose] = []
  336. overcalibrate(entries: &newGlucose, prediction: &predictions)
  337. self.setObservables(sensorData: nil, bleData: bleData, metaData: device)
  338. self.logger.debug("dabear:: handleGoodReading returned with \(newGlucose.count) entries")
  339. self.delegateQueue.async {
  340. var result: Result<[LibreGlucose], Error>
  341. // If several readings from a valid and running sensor come out empty,
  342. // we have (with a large degree of confidence) a sensor that has been
  343. // ripped off the body
  344. if self.countTimesWithoutData > 1 {
  345. result = .failure(LibreError.noValidSensorData)
  346. } else {
  347. result = .success(newGlucose)
  348. }
  349. self.cgmManagerDelegate?.cgmManager(self, hasNew: result)
  350. }
  351. lastDirectUpdate = Date()
  352. }
  353. }
  354. // MARK: - Bluetooth transmitter data
  355. extension LibreTransmitterManager {
  356. public func noLibreTransmitterSelected() {
  357. NotificationHelper.sendNoTransmitterSelectedNotification()
  358. }
  359. public func libreTransmitterDidUpdate(with sensorData: SensorData, and device: LibreTransmitterMetadata) {
  360. self.logger.debug("dabear:: got sensordata: \(String(describing: sensorData)), bytescount: \( sensorData.bytes.count), bytes: \(sensorData.bytes)")
  361. var sensorData = sensorData
  362. NotificationHelper.sendLowBatteryNotificationIfNeeded(device: device)
  363. self.setObservables(sensorData: sensorData, bleData: nil, metaData: device)
  364. if !sensorData.isLikelyLibre1FRAM {
  365. if let patchInfo = device.patchInfo, let sensorType = SensorType(patchInfo: patchInfo) {
  366. let needsDecryption = [SensorType.libre2, .libreUS14day].contains(sensorType)
  367. if needsDecryption, let uid = device.uid {
  368. sensorData.decrypt(patchInfo: patchInfo, uid: uid)
  369. }
  370. } else {
  371. logger.debug("Sensor type was incorrect, and no decryption of sensor was possible")
  372. self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(LibreError.encryptedSensor))
  373. return
  374. }
  375. }
  376. let typeDesc = device.sensorType().debugDescription
  377. logger.debug("Transmitter connected to libresensor of type \(typeDesc). Details: \(device.description)")
  378. tryPersistSensorData(with: sensorData)
  379. NotificationHelper.sendInvalidSensorNotificationIfNeeded(sensorData: sensorData)
  380. NotificationHelper.sendInvalidChecksumIfDeveloper(sensorData)
  381. guard sensorData.hasValidCRCs else {
  382. self.delegateQueue.async {
  383. self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(LibreError.checksumValidationError))
  384. }
  385. logger.debug("did not get sensordata with valid crcs")
  386. return
  387. }
  388. NotificationHelper.sendSensorExpireAlertIfNeeded(sensorData: sensorData)
  389. guard sensorData.state == .ready || sensorData.state == .starting else {
  390. logger.debug("dabear:: got sensordata with valid crcs, but sensor is either expired or failed")
  391. self.delegateQueue.async {
  392. self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(LibreError.expiredSensor))
  393. }
  394. return
  395. }
  396. logger.debug("dabear:: got sensordata with valid crcs, sensor was ready")
  397. // self.lastValidSensorData = sensorData
  398. self.handleGoodReading(data: sensorData) { [weak self] error, glucoseArrayWithPrediction in
  399. guard let self = self else {
  400. print("dabear:: handleGoodReading could not lock on self, aborting")
  401. return
  402. }
  403. if let error = error {
  404. self.logger.error("dabear:: handleGoodReading returned with error: \(error.errorDescription)")
  405. self.delegateQueue.async {
  406. self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(error))
  407. }
  408. return
  409. }
  410. guard let glucose = glucoseArrayWithPrediction?.glucose else {
  411. self.logger.debug("dabear:: handleGoodReading returned with no data")
  412. self.delegateQueue.async {
  413. self.cgmManagerDelegate?.cgmManager(self, hasNew: .success([]))
  414. }
  415. return
  416. }
  417. let prediction = glucoseArrayWithPrediction?.prediction
  418. // let device = self.proxy?.device
  419. let newGlucose = self.glucosesToSamplesFilter(glucose, startDate: self.getStartDateForFilter())
  420. if newGlucose.isEmpty {
  421. self.countTimesWithoutData &+= 1
  422. } else {
  423. self.latestBackfill = glucose.max { $0.startDate < $1.startDate }
  424. self.logger.debug("dabear:: latestbackfill set to \(self.latestBackfill.debugDescription)")
  425. self.countTimesWithoutData = 0
  426. }
  427. self.latestPrediction = prediction?.first
  428. //must be inside this handler as setobservables "depend" on latestbackfill
  429. self.setObservables(sensorData: sensorData, bleData: nil, metaData: nil)
  430. self.logger.debug("dabear:: handleGoodReading returned with \(newGlucose.count) entries")
  431. self.delegateQueue.async {
  432. var result: Result<[LibreGlucose], Error>
  433. // If several readings from a valid and running sensor come out empty,
  434. // we have (with a large degree of confidence) a sensor that has been
  435. // ripped off the body
  436. if self.countTimesWithoutData > 1 {
  437. result = .failure(LibreError.noValidSensorData)
  438. } else {
  439. result = .success(newGlucose)
  440. }
  441. self.cgmManagerDelegate?.cgmManager(self, hasNew: result)
  442. }
  443. }
  444. }
  445. private func readingToGlucose(_ data: SensorData, calibration: SensorData.CalibrationInfo) -> GlucoseArrayWithPrediction {
  446. var entries: [LibreGlucose] = []
  447. var prediction: [LibreGlucose] = []
  448. let predictGlucose = true
  449. // Increase to up to 15 to move closer to real blood sugar
  450. // The cost is slightly more noise on consecutive readings
  451. let glucosePredictionMinutes : Double = 10
  452. if predictGlucose {
  453. // We cheat here by forcing the loop to think that the predicted glucose value is the current blood sugar value.
  454. logger.debug("Predicting glucose value")
  455. if let predicted = data.predictBloodSugar(glucosePredictionMinutes){
  456. let currentBg = predicted.roundedGlucoseValueFromRaw2(calibrationInfo: calibration)
  457. let bgDate = predicted.date.addingTimeInterval(60 * -glucosePredictionMinutes)
  458. prediction.append(LibreGlucose(unsmoothedGlucose: currentBg, glucoseDouble: currentBg, timestamp: bgDate))
  459. logger.debug("Predicted glucose (not used) was: \(currentBg)")
  460. } else {
  461. logger.debug("Tried to predict glucose value but failed!")
  462. }
  463. }
  464. let trends = data.trendMeasurements()
  465. let firstTrend = trends.first?.roundedGlucoseValueFromRaw2(calibrationInfo: calibration)
  466. logger.debug("first trend was: \(String(describing: firstTrend))")
  467. entries = LibreGlucose.fromTrendMeasurements(trends, nativeCalibrationData: calibration, returnAll: UserDefaults.standard.mmBackfillFromTrend)
  468. if UserDefaults.standard.mmBackfillFromHistory {
  469. let history = data.historyMeasurements()
  470. entries += LibreGlucose.fromHistoryMeasurements(history, nativeCalibrationData: calibration)
  471. }
  472. overcalibrate(entries: &entries, prediction: &prediction)
  473. return (glucose: entries, prediction: prediction)
  474. }
  475. private func overcalibrate(entries: inout [LibreGlucose], prediction: inout [LibreGlucose]) {
  476. // overcalibrate
  477. var overcalibration: ((Double) -> (Double))? = nil
  478. delegateQueue.sync { overcalibration = cgmManagerDelegate?.overcalibration(for: self) }
  479. if let overcalibration = overcalibration {
  480. func overcalibrate(entries: [LibreGlucose]) -> [LibreGlucose] {
  481. entries.map { entry in
  482. var entry = entry
  483. entry.glucoseDouble = overcalibration(entry.glucoseDouble)
  484. return entry
  485. }
  486. }
  487. entries = overcalibrate(entries: entries)
  488. prediction = overcalibrate(entries: prediction)
  489. }
  490. }
  491. public func handleGoodReading(data: SensorData?, _ callback: @escaping (LibreError?, GlucoseArrayWithPrediction?) -> Void) {
  492. //only care about the once per minute readings here, historical data will not be considered
  493. guard let data = data else {
  494. callback(.noSensorData, nil)
  495. return
  496. }
  497. if let calibrationdata = calibrationData {
  498. logger.debug("dabear:: calibrationdata loaded")
  499. if calibrationdata.isValidForFooterWithReverseCRCs == data.footerCrc.byteSwapped {
  500. logger.debug("dabear:: calibrationdata correct for this sensor, returning last values")
  501. callback(nil, readingToGlucose(data, calibration: calibrationdata))
  502. return
  503. } else {
  504. logger.debug("dabear:: calibrationdata incorrect for this sensor, calibrationdata.isValidForFooterWithReverseCRCs: \(calibrationdata.isValidForFooterWithReverseCRCs), data.footerCrc.byteSwapped: \(data.footerCrc.byteSwapped)")
  505. }
  506. } else {
  507. logger.debug("dabear:: calibrationdata was nil")
  508. }
  509. calibrateSensor(sensordata: data) { [weak self] calibrationparams in
  510. do {
  511. try KeychainManagerWrapper.standard.setLibreNativeCalibrationData(calibrationparams)
  512. } catch {
  513. NotificationHelper.sendCalibrationNotification(.invalidCalibrationData)
  514. callback(.invalidCalibrationData, nil)
  515. return
  516. }
  517. //here we assume success, data is not changed,
  518. //and we trust that the remote endpoint returns correct data for the sensor
  519. NotificationHelper.sendCalibrationNotification(.success)
  520. callback(nil, self?.readingToGlucose(data, calibration: calibrationparams))
  521. }
  522. }
  523. //will be called on utility queue
  524. public func libreTransmitterStateChanged(_ state: BluetoothmanagerState) {
  525. DispatchQueue.main.async {
  526. self.transmitterInfoObservable.connectionState = self.proxy?.connectionStateString ?? "n/a"
  527. self.transmitterInfoObservable.transmitterType = self.proxy?.shortTransmitterName ?? "Unknown"
  528. }
  529. switch state {
  530. case .Connected:
  531. lastConnected = Date()
  532. case .powerOff:
  533. NotificationHelper.sendBluetoothPowerOffNotification()
  534. default:
  535. break
  536. }
  537. return
  538. }
  539. //will be called on utility queue
  540. public func libreTransmitterReceivedMessage(_ messageIdentifier: UInt16, txFlags: UInt8, payloadData: Data) {
  541. guard let packet = MiaoMiaoResponseState(rawValue: txFlags) else {
  542. // Incomplete package?
  543. // this would only happen if delegate is called manually with an unknown txFlags value
  544. // this was the case for readouts that were not yet complete
  545. // but that was commented out in MiaoMiaoManager.swift, see comment there:
  546. // "dabear-edit: don't notify on incomplete readouts"
  547. logger.debug("dabear:: incomplete package or unknown response state")
  548. return
  549. }
  550. switch packet {
  551. case .newSensor:
  552. logger.debug("dabear:: new libresensor detected")
  553. NotificationHelper.sendSensorChangeNotificationIfNeeded()
  554. NotificationCenter.default.post(name: .newSensorDetected, object: nil)
  555. case .noSensor:
  556. logger.debug("dabear:: no libresensor detected")
  557. NotificationHelper.sendSensorNotDetectedNotificationIfNeeded(noSensor: true)
  558. case .frequencyChangedResponse:
  559. logger.debug("dabear:: transmitter readout interval has changed!")
  560. default:
  561. //we don't care about the rest!
  562. break
  563. }
  564. return
  565. }
  566. func tryPersistSensorData(with sensorData: SensorData) {
  567. guard UserDefaults.standard.shouldPersistSensorData else {
  568. return
  569. }
  570. //yeah, we really really need to persist any changes right away
  571. var data = UserDefaults.standard.queuedSensorData ?? LimitedQueue<SensorData>()
  572. data.enqueue(sensorData)
  573. UserDefaults.standard.queuedSensorData = data
  574. }
  575. }
  576. // MARK: - conventience properties to access the enclosed proxy's properties
  577. extension LibreTransmitterManager {
  578. public var device: HKDevice? {
  579. //proxy?.OnQueue_device
  580. proxy?.device
  581. }
  582. static var className: String {
  583. String(describing: Self.self)
  584. }
  585. //cannot be called from managerQueue
  586. public var identifier: String {
  587. //proxy?.OnQueue_identifer?.uuidString ?? "n/a"
  588. proxy?.identifier?.uuidString ?? "n/a"
  589. }
  590. public var metaData: LibreTransmitterMetadata? {
  591. //proxy?.OnQueue_metadata
  592. proxy?.metadata
  593. }
  594. //cannot be called from managerQueue
  595. public var connectionState: String {
  596. //proxy?.connectionStateString ?? "n/a"
  597. proxy?.connectionStateString ?? "n/a"
  598. }
  599. //cannot be called from managerQueue
  600. public var sensorSerialNumber: String {
  601. //proxy?.OnQueue_sensorData?.serialNumber ?? "n/a"
  602. proxy?.sensorData?.serialNumber ?? "n/a"
  603. }
  604. public var sensorStartDate: Date? {
  605. proxy?.sensorData?.sensorStartTime
  606. }
  607. //cannot be called from managerQueue
  608. public var sensorAge: String {
  609. //proxy?.OnQueue_sensorData?.humanReadableSensorAge ?? "n/a"
  610. proxy?.sensorData?.humanReadableSensorAge ?? "n/a"
  611. }
  612. public var sensorEndTime : String {
  613. if let endtime = proxy?.sensorData?.sensorEndTime {
  614. let mydf = DateFormatter()
  615. mydf.dateStyle = .long
  616. mydf.timeStyle = .full
  617. mydf.locale = Locale.current
  618. return mydf.string(from: endtime)
  619. }
  620. return "Unknown or Ended"
  621. }
  622. public var sensorTimeLeft: String {
  623. //proxy?.OnQueue_sensorData?.humanReadableSensorAge ?? "n/a"
  624. proxy?.sensorData?.humanReadableTimeLeft ?? "n/a"
  625. }
  626. //cannot be called from managerQueue
  627. public var sensorFooterChecksums: String {
  628. //(proxy?.OnQueue_sensorData?.footerCrc.byteSwapped).map(String.init)
  629. (proxy?.sensorData?.footerCrc.byteSwapped).map(String.init)
  630. ?? "n/a"
  631. }
  632. //cannot be called from managerQueue
  633. public var sensorStateDescription: String {
  634. //proxy?.OnQueue_sensorData?.state.description ?? "n/a"
  635. proxy?.sensorData?.state.description ?? "n/a"
  636. }
  637. //cannot be called from managerQueue
  638. public var firmwareVersion: String {
  639. proxy?.metadata?.firmware ?? "n/a"
  640. }
  641. //cannot be called from managerQueue
  642. public var hardwareVersion: String {
  643. proxy?.metadata?.hardware ?? "n/a"
  644. }
  645. //cannot be called from managerQueue
  646. public var batteryString: String {
  647. proxy?.metadata?.batteryString ?? "n/a"
  648. }
  649. public var battery: Int? {
  650. proxy?.metadata?.battery
  651. }
  652. public func getDeviceType() -> String {
  653. proxy?.shortTransmitterName ?? "Unknown"
  654. }
  655. public func getSmallImage() -> UIImage? {
  656. proxy?.activePluginType?.smallImage ?? UIImage(named: "libresensor", in: Bundle.module, compatibleWith: nil)
  657. }
  658. }