| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869 |
- //
- // LibreTransmitterManager.swift
- // Created by Bjørn Inge Berg on 25/02/2019.
- // Copyright © 2019 Bjørn Inge Berg. All rights reserved.
- //
- import Foundation
- import UserNotifications
- import Combine
- import UIKit
- import CoreBluetooth
- import HealthKit
- import os.log
- public protocol LibreTransmitterManagerDelegate: AnyObject {
- var queue: DispatchQueue { get }
- func startDateToFilterNewData(for: LibreTransmitterManager) -> Date?
- func cgmManager(_:LibreTransmitterManager, hasNew result: Result<[LibreGlucose], Error>)
- func overcalibration(for: LibreTransmitterManager) -> ((Double) -> (Double))?
- }
- public final class LibreTransmitterManager: LibreTransmitterDelegate {
- public typealias GlucoseArrayWithPrediction = (glucose:[LibreGlucose], prediction:[LibreGlucose])
- public lazy var logger = Logger(forType: Self.self)
- public let isOnboarded = true // No distinction between created and onboarded
- public var hasValidSensorSession: Bool {
- lastConnected != nil
- }
- public var glucoseDisplay: GlucoseDisplayable?
- public var trend: GlucoseTrend?
- public func libreManagerDidRestoreState(found peripherals: [CBPeripheral], connected to: CBPeripheral?) {
- let devicename = to?.name ?? "no device"
- let id = to?.identifier.uuidString ?? "null"
- 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)
- NotificationHelper.sendRestoredStateNotification(msg: msg)
- }
- public var batteryLevel: Double? {
- let batt = self.proxy?.metadata?.battery
- logger.debug("dabear:: LibreTransmitterManager was asked to return battery: \(batt.debugDescription)")
- //convert from 8% -> 0.8
- if let battery = proxy?.metadata?.battery {
- return Double(battery) / 100
- }
- return nil
- }
- public var cgmManagerDelegate: LibreTransmitterManagerDelegate? {
- get {
- return delegate.delegate
- }
- set {
- delegate.delegate = newValue
- }
- }
- public var delegateQueue: DispatchQueue! {
- get {
- return delegate.queue
- }
- set {
- delegate.queue = newValue
- }
- }
- public let delegate = WeakSynchronizedDelegate<LibreTransmitterManagerDelegate>()
- public var managedDataInterval: TimeInterval?
- private func getPersistedSensorDataForDebug() -> String {
- guard let data = UserDefaults.standard.queuedSensorData else {
- return "nil"
- }
- let c = self.calibrationData?.description ?? "no calibrationdata"
- return data.array.map {
- "SensorData(uuid: \"0123\".data(using: .ascii)!, bytes: \($0.bytes))!"
- }
- .joined(separator: ",\n")
- + ",\n Calibrationdata: \(c)"
- }
- public var debugDescription: String {
- return [
- "## LibreTransmitterManager",
- "Testdata: foo",
- "lastConnected: \(String(describing: lastConnected))",
- "Connection state: \(connectionState)",
- "Sensor state: \(sensorStateDescription)",
- "transmitterbattery: \(batteryString)",
- "SensorData: \(getPersistedSensorDataForDebug())",
- "Metainfo::\n\(AppMetaData.allProperties)",
- ""
- ].joined(separator: "\n")
- }
- public private(set) var lastConnected: Date?
- public private(set) var latestPrediction: LibreGlucose?
- public private(set) var latestBackfill: LibreGlucose? {
- willSet(newValue) {
- guard let newValue = newValue else {
- return
- }
- var trend: GlucoseTrend?
- let oldValue = latestBackfill
- logger.debug("dabear:: latestBackfill set, newvalue is \(newValue.description)")
- if let oldValue = oldValue {
- // 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.
- // this is because the old and new glucose values represent earlier readouts, while the trend buffer contains somewhat more jumpy (noisy) values.
- let timediff = LibreGlucose.timeDifference(oldGlucose: oldValue, newGlucose: newValue)
- logger.debug("dabear:: timediff is \(timediff)")
- let oldIsRecentEnough = timediff <= TimeInterval.minutes(15)
- trend = oldIsRecentEnough ? newValue.GetGlucoseTrend(last: oldValue) : nil
- var batteries : [(name: String, percentage: Int)]?
- if let metaData = metaData, let battery = battery {
- batteries = [(name: metaData.name, percentage: battery)]
- }
- self.glucoseDisplay = ConcreteGlucoseDisplayable(isStateValid: newValue.isStateValid, trendType: trend, isLocal: true, batteries: batteries)
- } else {
- //could consider setting this to ConcreteSensorDisplayable with trendtype GlucoseTrend.flat, but that would be kinda lying
- self.glucoseDisplay = nil
- }
- }
- }
- static public var managerIdentifier : String {
- Self.className
- }
- static public let localizedTitle = LocalizedString("Libre Bluetooth", comment: "Title for the CGMManager option")
- public init() {
- lastConnected = nil
- //let isui = (self is CGMManagerUI)
- //self.miaomiaoService = MiaomiaoService(keychainManager: keychain)
- logger.debug("dabear: LibreTransmitterManager will be created now")
- //proxy = MiaoMiaoBluetoothManager()
- proxy?.delegate = self
- }
- public func disconnect() {
- logger.debug("dabear:: LibreTransmitterManager disconnect called")
- proxy?.disconnectManually()
- proxy?.delegate = nil
- }
- deinit {
- logger.debug("dabear:: LibreTransmitterManager deinit called")
- //cleanup any references to events to this class
- disconnect()
- }
- //lazy because we don't want to scan immediately
- private lazy var proxy: LibreTransmitterProxyManager? = LibreTransmitterProxyManager()
- /*
- These properties are mostly useful for swiftui
- */
- public var transmitterInfoObservable = TransmitterInfo()
- public var sensorInfoObservable = SensorInfo()
- public var glucoseInfoObservable = GlucoseInfo()
- var longDateFormatter : DateFormatter = ({
- let df = DateFormatter()
- df.dateStyle = .long
- df.timeStyle = .long
- df.doesRelativeDateFormatting = true
- return df
- })()
- var dateFormatter : DateFormatter = ({
- let df = DateFormatter()
- df.dateStyle = .long
- df.timeStyle = .full
- df.locale = Locale.current
- return df
- })()
- //when was the libre2 direct ble update last received?
- var lastDirectUpdate : Date? = nil
- private var countTimesWithoutData: Int = 0
- }
- // MARK: - Convenience functions
- extension LibreTransmitterManager {
- func setObservables(sensorData: SensorData?, bleData: Libre2.LibreBLEResponse?, metaData: LibreTransmitterMetadata?) {
- logger.debug("dabear:: setObservables called")
- DispatchQueue.main.async {
- if let metaData=metaData {
- self.logger.debug("dabear::will set transmitterInfoObservable")
- self.transmitterInfoObservable.battery = metaData.batteryString
- self.transmitterInfoObservable.hardware = metaData.hardware
- self.transmitterInfoObservable.firmware = metaData.firmware
- self.transmitterInfoObservable.sensorType = metaData.sensorType()?.description ?? "Unknown"
- self.transmitterInfoObservable.transmitterIdentifier = metaData.macAddress ?? UserDefaults.standard.preSelectedDevice ?? "Unknown"
- }
- self.transmitterInfoObservable.connectionState = self.proxy?.connectionStateString ?? "n/a"
- self.transmitterInfoObservable.transmitterType = self.proxy?.shortTransmitterName ?? "Unknown"
- if let sensorData = sensorData {
- self.logger.debug("dabear::will set sensorInfoObservable")
- self.sensorInfoObservable.sensorAge = sensorData.humanReadableSensorAge
- self.sensorInfoObservable.sensorAgeLeft = sensorData.humanReadableTimeLeft
- self.sensorInfoObservable.sensorState = sensorData.state.description
- self.sensorInfoObservable.sensorSerial = sensorData.serialNumber
- self.glucoseInfoObservable.checksum = String(sensorData.footerCrc.byteSwapped)
- if let sensorEndTime = sensorData.sensorEndTime {
- self.sensorInfoObservable.sensorEndTime = self.dateFormatter.string(from: sensorEndTime )
- } else {
- self.sensorInfoObservable.sensorEndTime = "Unknown or ended"
- }
- } else if let bleData = bleData, let sensor = UserDefaults.standard.preSelectedSensor {
- let aday = 86_400.0 //in seconds
- var humanReadableSensorAge: String {
- let days = TimeInterval(bleData.age * 60) / aday
- return String(format: "%.2f", days) + NSLocalizedString(" day(s)", comment: "Sensor day(s)")
- }
- var maxMinutesWearTime : Int{
- sensor.maxAge
- }
- var minutesLeft: Int {
- maxMinutesWearTime - bleData.age
- }
- var humanReadableTimeLeft: String {
- let days = TimeInterval(minutesLeft * 60) / aday
- return String(format: "%.2f", days) + NSLocalizedString(" day(s)", comment: "Sensor day(s)")
- }
- //once the sensor has ended we don't know the exact date anymore
- var sensorEndTime: Date? {
- if minutesLeft <= 0 {
- return nil
- }
- // we can assume that the libre2 direct bluetooth packet is received immediately
- // after the sensor has been done a new measurement, so using Date() should be fine here
- return Date().addingTimeInterval(TimeInterval(minutes: Double(minutesLeft)))
- }
- self.sensorInfoObservable.sensorAge = humanReadableSensorAge
- self.sensorInfoObservable.sensorAgeLeft = humanReadableTimeLeft
- self.sensorInfoObservable.sensorState = "Operational"
- self.sensorInfoObservable.sensorState = "Operational"
- self.sensorInfoObservable.sensorSerial = SensorSerialNumber(withUID: sensor.uuid)?.serialNumber ?? "-"
- if let mapping = UserDefaults.standard.calibrationMapping,
- let calibration = self.calibrationData ,
- mapping.uuid == sensor.uuid && calibration.isValidForFooterWithReverseCRCs == mapping.reverseFooterCRC {
- self.glucoseInfoObservable.checksum = "\(mapping.reverseFooterCRC)"
- }
- if let sensorEndTime = sensorEndTime {
- self.sensorInfoObservable.sensorEndTime = self.dateFormatter.string(from: sensorEndTime )
- } else {
- self.sensorInfoObservable.sensorEndTime = "Unknown or ended"
- }
- }
- let formatter = QuantityFormatter()
- let preferredUnit = UserDefaults.standard.mmGlucoseUnit ?? .millimolesPerLiter
- if let d = self.latestBackfill {
- self.logger.debug("dabear::will set glucoseInfoObservable")
- formatter.setPreferredNumberFormatter(for: .millimolesPerLiter)
- self.glucoseInfoObservable.glucoseMMOL = formatter.string(from: d.quantity, for: .millimolesPerLiter) ?? "-"
- formatter.setPreferredNumberFormatter(for: .milligramsPerDeciliter)
- self.glucoseInfoObservable.glucoseMGDL = formatter.string(from: d.quantity, for: .milligramsPerDeciliter) ?? "-"
- //backward compat
- if preferredUnit == .millimolesPerLiter {
- self.glucoseInfoObservable.glucose = self.glucoseInfoObservable.glucoseMMOL
- } else if preferredUnit == .milligramsPerDeciliter {
- self.glucoseInfoObservable.glucose = self.glucoseInfoObservable.glucoseMGDL
- }
- self.glucoseInfoObservable.date = self.longDateFormatter.string(from: d.timestamp)
- }
- if let d = self.latestPrediction {
- formatter.setPreferredNumberFormatter(for: .millimolesPerLiter)
- self.glucoseInfoObservable.predictionMMOL = formatter.string(from: d.quantity, for: .millimolesPerLiter) ?? "-"
- formatter.setPreferredNumberFormatter(for: .milligramsPerDeciliter)
- self.glucoseInfoObservable.predictionMGDL = formatter.string(from: d.quantity, for: .milligramsPerDeciliter) ?? "-"
- self.glucoseInfoObservable.predictionDate = self.longDateFormatter.string(from: d.timestamp)
- }
- }
- }
- func getStartDateForFilter() -> Date?{
- var startDate: Date?
- self.delegateQueue.sync {
- startDate = self.cgmManagerDelegate?.startDateToFilterNewData(for: self) ?? self.latestBackfill?.startDate
- }
- // add one second to startdate to make this an exclusive (non overlapping) match
- return startDate?.addingTimeInterval(1)
- }
- func glucosesToSamplesFilter(_ array: [LibreGlucose], startDate: Date?) -> [LibreGlucose] {
- array
- .filterDateRange(startDate, nil)
- .filter { $0.isStateValid }
- .compactMap { $0 }
- }
- public var calibrationData: SensorData.CalibrationInfo? {
- KeychainManagerWrapper.standard.getLibreNativeCalibrationData()
- }
- }
- // MARK: - Direct bluetooth updates
- extension LibreTransmitterManager {
- public func libreSensorDidUpdate(with bleData: Libre2.LibreBLEResponse, and device: LibreTransmitterMetadata) {
- self.logger.debug("dabear:: got sensordata: \(String(describing: bleData))")
- let typeDesc = device.sensorType().debugDescription
- let now = Date()
- //only once per mins minute
- let mins = 4.5
- if let earlierplus = lastDirectUpdate?.addingTimeInterval(mins * 60), earlierplus >= now {
- logger.debug("last ble update was less than \(mins) minutes ago, aborting loop update")
- return
- }
- logger.debug("Directly connected to libresensor of type \(typeDesc). Details: \(device.description)")
- guard let mapping = UserDefaults.standard.calibrationMapping,
- let calibrationData = calibrationData,
- let sensor = UserDefaults.standard.preSelectedSensor else {
- logger.error("calibrationdata, sensor uid or mapping missing, could not continue")
- self.delegateQueue.async {
- self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(LibreError.noCalibrationData))
- }
- return
- }
- guard mapping.reverseFooterCRC == calibrationData.isValidForFooterWithReverseCRCs &&
- mapping.uuid == sensor.uuid else {
- logger.error("Calibrationdata was not correct for these bluetooth packets. This is a fatal error, we cannot calibrate without re-pairing")
- self.delegateQueue.async {
- self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(LibreError.noCalibrationData))
- }
- return
- }
- guard bleData.crcVerified else {
- self.delegateQueue.async {
- self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(LibreError.checksumValidationError))
- }
- logger.debug("did not get bledata with valid crcs")
- return
- }
- if sensor.maxAge > 0 {
- let minutesLeft = Double(sensor.maxAge - bleData.age)
- NotificationHelper.sendSensorExpireAlertIfNeeded(minutesLeft: minutesLeft)
- }
- let sensorStartDate = Date().addingTimeInterval(-1 * TimeInterval(minutes: Double(bleData.age)))
- NSLog("Libre age \(bleData.age), Start Date calculated: \(sensorStartDate)")
- // let device = self.proxy?.device
- let sortedTrends = bleData.trend.sorted{ $0.date > $1.date}
- let glucose = LibreGlucose.fromTrendMeasurements(sortedTrends, nativeCalibrationData: calibrationData, returnAll: UserDefaults.standard.mmBackfillFromTrend, sensorStartDate: sensorStartDate)
- //glucose += LibreGlucose.fromHistoryMeasurements(bleData.history, nativeCalibrationData: calibrationData)
- // while libre2 fram scans contains historymeasurements for the last 8 hours,
- // history from bledata contains just a couple of data points, so we don't bother
- /*if UserDefaults.standard.mmBackfillFromHistory {
- let sortedHistory = bleData.history.sorted{ $0.date > $1.date}
- glucose += LibreGlucose.fromHistoryMeasurements(sortedHistory, nativeCalibrationData: calibrationData)
- }*/
- var newGlucose = glucosesToSamplesFilter(glucose, startDate: getStartDateForFilter())
- if newGlucose.isEmpty {
- self.countTimesWithoutData &+= 1
- } else {
- self.latestBackfill = glucose.max { $0.startDate < $1.startDate }
- self.logger.debug("dabear:: latestbackfill set to \(self.latestBackfill.debugDescription)")
- self.countTimesWithoutData = 0
- }
- //todo: predictions also for libre2 bluetooth data
- //self.latestPrediction = prediction?.first
- var predictions: [LibreGlucose] = []
- overcalibrate(entries: &newGlucose, prediction: &predictions)
- self.setObservables(sensorData: nil, bleData: bleData, metaData: device)
- self.logger.debug("dabear:: handleGoodReading returned with \(newGlucose.count) entries")
- self.delegateQueue.async {
- var result: Result<[LibreGlucose], Error>
- // If several readings from a valid and running sensor come out empty,
- // we have (with a large degree of confidence) a sensor that has been
- // ripped off the body
- if self.countTimesWithoutData > 1 {
- result = .failure(LibreError.noValidSensorData)
- } else {
- result = .success(newGlucose)
- }
- self.cgmManagerDelegate?.cgmManager(self, hasNew: result)
- }
- lastDirectUpdate = Date()
- }
- }
- // MARK: - Bluetooth transmitter data
- extension LibreTransmitterManager {
- public func noLibreTransmitterSelected() {
- NotificationHelper.sendNoTransmitterSelectedNotification()
- }
- public func libreTransmitterDidUpdate(with sensorData: SensorData, and device: LibreTransmitterMetadata) {
- self.logger.debug("dabear:: got sensordata: \(String(describing: sensorData)), bytescount: \( sensorData.bytes.count), bytes: \(sensorData.bytes)")
- var sensorData = sensorData
- NotificationHelper.sendLowBatteryNotificationIfNeeded(device: device)
- self.setObservables(sensorData: sensorData, bleData: nil, metaData: device)
- if !sensorData.isLikelyLibre1FRAM {
- if let patchInfo = device.patchInfo, let sensorType = SensorType(patchInfo: patchInfo) {
- let needsDecryption = [SensorType.libre2, .libreUS14day].contains(sensorType)
- if needsDecryption, let uid = device.uid {
- sensorData.decrypt(patchInfo: patchInfo, uid: uid)
- }
- } else {
- logger.debug("Sensor type was incorrect, and no decryption of sensor was possible")
- self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(LibreError.encryptedSensor))
- return
- }
- }
- let typeDesc = device.sensorType().debugDescription
- logger.debug("Transmitter connected to libresensor of type \(typeDesc). Details: \(device.description)")
- tryPersistSensorData(with: sensorData)
- NotificationHelper.sendInvalidSensorNotificationIfNeeded(sensorData: sensorData)
- NotificationHelper.sendInvalidChecksumIfDeveloper(sensorData)
- guard sensorData.hasValidCRCs else {
- self.delegateQueue.async {
- self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(LibreError.checksumValidationError))
- }
- logger.debug("did not get sensordata with valid crcs")
- return
- }
- NotificationHelper.sendSensorExpireAlertIfNeeded(sensorData: sensorData)
- guard sensorData.state == .ready || sensorData.state == .starting else {
- logger.debug("dabear:: got sensordata with valid crcs, but sensor is either expired or failed")
- self.delegateQueue.async {
- self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(LibreError.expiredSensor))
- }
- return
- }
- logger.debug("dabear:: got sensordata with valid crcs, sensor was ready")
- // self.lastValidSensorData = sensorData
- self.handleGoodReading(data: sensorData) { [weak self] error, glucoseArrayWithPrediction in
- guard let self = self else {
- print("dabear:: handleGoodReading could not lock on self, aborting")
- return
- }
- if let error = error {
- self.logger.error("dabear:: handleGoodReading returned with error: \(error.errorDescription)")
- self.delegateQueue.async {
- self.cgmManagerDelegate?.cgmManager(self, hasNew: .failure(error))
- }
- return
- }
- guard let glucose = glucoseArrayWithPrediction?.glucose else {
- self.logger.debug("dabear:: handleGoodReading returned with no data")
- self.delegateQueue.async {
- self.cgmManagerDelegate?.cgmManager(self, hasNew: .success([]))
- }
- return
- }
- let prediction = glucoseArrayWithPrediction?.prediction
- // let device = self.proxy?.device
- let newGlucose = self.glucosesToSamplesFilter(glucose, startDate: self.getStartDateForFilter())
- if newGlucose.isEmpty {
- self.countTimesWithoutData &+= 1
- } else {
- self.latestBackfill = glucose.max { $0.startDate < $1.startDate }
- self.logger.debug("dabear:: latestbackfill set to \(self.latestBackfill.debugDescription)")
- self.countTimesWithoutData = 0
- }
- self.latestPrediction = prediction?.first
- //must be inside this handler as setobservables "depend" on latestbackfill
- self.setObservables(sensorData: sensorData, bleData: nil, metaData: nil)
- self.logger.debug("dabear:: handleGoodReading returned with \(newGlucose.count) entries")
- self.delegateQueue.async {
- var result: Result<[LibreGlucose], Error>
- // If several readings from a valid and running sensor come out empty,
- // we have (with a large degree of confidence) a sensor that has been
- // ripped off the body
- if self.countTimesWithoutData > 1 {
- result = .failure(LibreError.noValidSensorData)
- } else {
- result = .success(newGlucose)
- }
- self.cgmManagerDelegate?.cgmManager(self, hasNew: result)
- }
- }
- }
- private func readingToGlucose(_ data: SensorData, calibration: SensorData.CalibrationInfo) -> GlucoseArrayWithPrediction {
- var entries: [LibreGlucose] = []
- var prediction: [LibreGlucose] = []
- let predictGlucose = true
- // Increase to up to 15 to move closer to real blood sugar
- // The cost is slightly more noise on consecutive readings
- let glucosePredictionMinutes : Double = 10
- if predictGlucose {
- // We cheat here by forcing the loop to think that the predicted glucose value is the current blood sugar value.
- logger.debug("Predicting glucose value")
- if let predicted = data.predictBloodSugar(glucosePredictionMinutes){
- let currentBg = predicted.roundedGlucoseValueFromRaw2(calibrationInfo: calibration)
- let bgDate = predicted.date.addingTimeInterval(60 * -glucosePredictionMinutes)
- prediction.append(LibreGlucose(unsmoothedGlucose: currentBg, glucoseDouble: currentBg, timestamp: bgDate))
- logger.debug("Predicted glucose (not used) was: \(currentBg)")
- } else {
- logger.debug("Tried to predict glucose value but failed!")
- }
- }
- let trends = data.trendMeasurements()
- let firstTrend = trends.first?.roundedGlucoseValueFromRaw2(calibrationInfo: calibration)
- logger.debug("first trend was: \(String(describing: firstTrend))")
- entries = LibreGlucose.fromTrendMeasurements(trends, nativeCalibrationData: calibration, returnAll: UserDefaults.standard.mmBackfillFromTrend)
- if UserDefaults.standard.mmBackfillFromHistory {
- let history = data.historyMeasurements()
- entries += LibreGlucose.fromHistoryMeasurements(history, nativeCalibrationData: calibration)
- }
- overcalibrate(entries: &entries, prediction: &prediction)
- return (glucose: entries, prediction: prediction)
- }
- private func overcalibrate(entries: inout [LibreGlucose], prediction: inout [LibreGlucose]) {
- // overcalibrate
- var overcalibration: ((Double) -> (Double))? = nil
- delegateQueue.sync { overcalibration = cgmManagerDelegate?.overcalibration(for: self) }
- if let overcalibration = overcalibration {
- func overcalibrate(entries: [LibreGlucose]) -> [LibreGlucose] {
- entries.map { entry in
- var entry = entry
- entry.glucoseDouble = overcalibration(entry.glucoseDouble)
- return entry
- }
- }
- entries = overcalibrate(entries: entries)
- prediction = overcalibrate(entries: prediction)
- }
- }
- public func handleGoodReading(data: SensorData?, _ callback: @escaping (LibreError?, GlucoseArrayWithPrediction?) -> Void) {
- //only care about the once per minute readings here, historical data will not be considered
- guard let data = data else {
- callback(.noSensorData, nil)
- return
- }
- if let calibrationdata = calibrationData {
- logger.debug("dabear:: calibrationdata loaded")
- if calibrationdata.isValidForFooterWithReverseCRCs == data.footerCrc.byteSwapped {
- logger.debug("dabear:: calibrationdata correct for this sensor, returning last values")
- callback(nil, readingToGlucose(data, calibration: calibrationdata))
- return
- } else {
- logger.debug("dabear:: calibrationdata incorrect for this sensor, calibrationdata.isValidForFooterWithReverseCRCs: \(calibrationdata.isValidForFooterWithReverseCRCs), data.footerCrc.byteSwapped: \(data.footerCrc.byteSwapped)")
- }
- } else {
- logger.debug("dabear:: calibrationdata was nil")
- }
- calibrateSensor(sensordata: data) { [weak self] calibrationparams in
- do {
- try KeychainManagerWrapper.standard.setLibreNativeCalibrationData(calibrationparams)
- } catch {
- NotificationHelper.sendCalibrationNotification(.invalidCalibrationData)
- callback(.invalidCalibrationData, nil)
- return
- }
- //here we assume success, data is not changed,
- //and we trust that the remote endpoint returns correct data for the sensor
- NotificationHelper.sendCalibrationNotification(.success)
- callback(nil, self?.readingToGlucose(data, calibration: calibrationparams))
- }
- }
- //will be called on utility queue
- public func libreTransmitterStateChanged(_ state: BluetoothmanagerState) {
- DispatchQueue.main.async {
- self.transmitterInfoObservable.connectionState = self.proxy?.connectionStateString ?? "n/a"
- self.transmitterInfoObservable.transmitterType = self.proxy?.shortTransmitterName ?? "Unknown"
- }
- switch state {
- case .Connected:
- lastConnected = Date()
- case .powerOff:
- NotificationHelper.sendBluetoothPowerOffNotification()
- default:
- break
- }
- return
- }
- //will be called on utility queue
- public func libreTransmitterReceivedMessage(_ messageIdentifier: UInt16, txFlags: UInt8, payloadData: Data) {
- guard let packet = MiaoMiaoResponseState(rawValue: txFlags) else {
- // Incomplete package?
- // this would only happen if delegate is called manually with an unknown txFlags value
- // this was the case for readouts that were not yet complete
- // but that was commented out in MiaoMiaoManager.swift, see comment there:
- // "dabear-edit: don't notify on incomplete readouts"
- logger.debug("dabear:: incomplete package or unknown response state")
- return
- }
- switch packet {
- case .newSensor:
- logger.debug("dabear:: new libresensor detected")
- NotificationHelper.sendSensorChangeNotificationIfNeeded()
- NotificationCenter.default.post(name: .newSensorDetected, object: nil)
- case .noSensor:
- logger.debug("dabear:: no libresensor detected")
- NotificationHelper.sendSensorNotDetectedNotificationIfNeeded(noSensor: true)
- case .frequencyChangedResponse:
- logger.debug("dabear:: transmitter readout interval has changed!")
- default:
- //we don't care about the rest!
- break
- }
- return
- }
- func tryPersistSensorData(with sensorData: SensorData) {
- guard UserDefaults.standard.shouldPersistSensorData else {
- return
- }
- //yeah, we really really need to persist any changes right away
- var data = UserDefaults.standard.queuedSensorData ?? LimitedQueue<SensorData>()
- data.enqueue(sensorData)
- UserDefaults.standard.queuedSensorData = data
- }
- }
- // MARK: - conventience properties to access the enclosed proxy's properties
- extension LibreTransmitterManager {
- public var device: HKDevice? {
- //proxy?.OnQueue_device
- proxy?.device
- }
- static var className: String {
- String(describing: Self.self)
- }
- //cannot be called from managerQueue
- public var identifier: String {
- //proxy?.OnQueue_identifer?.uuidString ?? "n/a"
- proxy?.identifier?.uuidString ?? "n/a"
- }
- public var metaData: LibreTransmitterMetadata? {
- //proxy?.OnQueue_metadata
- proxy?.metadata
- }
- //cannot be called from managerQueue
- public var connectionState: String {
- //proxy?.connectionStateString ?? "n/a"
- proxy?.connectionStateString ?? "n/a"
- }
- //cannot be called from managerQueue
- public var sensorSerialNumber: String {
- //proxy?.OnQueue_sensorData?.serialNumber ?? "n/a"
- proxy?.sensorData?.serialNumber ?? "n/a"
- }
- public var sensorStartDate: Date? {
- proxy?.sensorData?.sensorStartTime
- }
-
-
- //cannot be called from managerQueue
- public var sensorAge: String {
- //proxy?.OnQueue_sensorData?.humanReadableSensorAge ?? "n/a"
- proxy?.sensorData?.humanReadableSensorAge ?? "n/a"
- }
- public var sensorEndTime : String {
- if let endtime = proxy?.sensorData?.sensorEndTime {
- let mydf = DateFormatter()
- mydf.dateStyle = .long
- mydf.timeStyle = .full
- mydf.locale = Locale.current
- return mydf.string(from: endtime)
- }
- return "Unknown or Ended"
- }
- public var sensorTimeLeft: String {
- //proxy?.OnQueue_sensorData?.humanReadableSensorAge ?? "n/a"
- proxy?.sensorData?.humanReadableTimeLeft ?? "n/a"
- }
- //cannot be called from managerQueue
- public var sensorFooterChecksums: String {
- //(proxy?.OnQueue_sensorData?.footerCrc.byteSwapped).map(String.init)
- (proxy?.sensorData?.footerCrc.byteSwapped).map(String.init)
- ?? "n/a"
- }
- //cannot be called from managerQueue
- public var sensorStateDescription: String {
- //proxy?.OnQueue_sensorData?.state.description ?? "n/a"
- proxy?.sensorData?.state.description ?? "n/a"
- }
- //cannot be called from managerQueue
- public var firmwareVersion: String {
- proxy?.metadata?.firmware ?? "n/a"
- }
- //cannot be called from managerQueue
- public var hardwareVersion: String {
- proxy?.metadata?.hardware ?? "n/a"
- }
- //cannot be called from managerQueue
- public var batteryString: String {
- proxy?.metadata?.batteryString ?? "n/a"
- }
- public var battery: Int? {
- proxy?.metadata?.battery
- }
- public func getDeviceType() -> String {
- proxy?.shortTransmitterName ?? "Unknown"
- }
- public func getSmallImage() -> UIImage? {
- proxy?.activePluginType?.smallImage ?? UIImage(named: "libresensor", in: Bundle.module, compatibleWith: nil)
- }
- }
|