| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- //
- // MiaomiaoClient.h
- // MiaomiaoClient
- //
- import Foundation
- import HealthKit
- import os.log
- fileprivate var logger = Logger(forType: "LibreGlucose")
- public struct LibreGlucose {
- public let unsmoothedGlucose: Double
- public var glucoseDouble: Double
- public var error = [MeasurementError.OK]
- public var glucose: UInt16 {
- UInt16(glucoseDouble.rounded())
- }
- //trend is deprecated here, it should only be calculated once in latestbackfill
- //public var trend: UInt8
- public var timestamp: Date
- //public let collector: String?
- public var sensorStartDate: Date? = nil
- public static func timeDifference(oldGlucose: LibreGlucose, newGlucose: LibreGlucose) -> TimeInterval {
- newGlucose.startDate.timeIntervalSince(oldGlucose.startDate)
- }
- public var syncId: String {
- "\(Int(self.startDate.timeIntervalSince1970))\(self.unsmoothedGlucose)"
- }
- public var isStateValid: Bool {
- // We know that the official libre algorithm doesn't produce values
- // below 39. However, both the raw sensor contents and the derived algorithm
- // supports values down to 0 without issues. A bit uncertain if nightscout and loop will work with values below 1, so we restrict this to 1
- glucose >= 1
- }
- public func GetGlucoseTrend(last: Self) -> GlucoseTrend {
- Self.GetGlucoseTrend(current: self, last: last)
- }
- }
- extension LibreGlucose: GlucoseValue {
- public var startDate: Date {
- timestamp
- }
- public var quantity: HKQuantity {
- .init(unit: .milligramsPerDeciliter, doubleValue: glucoseDouble)
- }
- }
- extension LibreGlucose {
- public var description: String {
- guard let glucoseUnit = UserDefaults.standard.mmGlucoseUnit, let formatter = LibreGlucose.dynamicFormatter, let formatted = formatter.string(from: self.quantity, for: glucoseUnit) else {
- logger.debug("dabear:: glucose unit was not recognized, aborting")
- return "Unknown"
- }
- return formatted
- }
- private static var glucoseFormatterMgdl: QuantityFormatter = {
- let formatter = QuantityFormatter()
- formatter.setPreferredNumberFormatter(for: HKUnit.milligramsPerDeciliter)
- return formatter
- }()
- private static var glucoseFormatterMmol: QuantityFormatter = {
- let formatter = QuantityFormatter()
- formatter.setPreferredNumberFormatter(for: HKUnit.millimolesPerLiter)
- return formatter
- }()
- public static var dynamicFormatter: QuantityFormatter? {
- guard let glucoseUnit = UserDefaults.standard.mmGlucoseUnit else {
- logger.debug("dabear:: glucose unit was not recognized, aborting")
- return nil
- }
- return (glucoseUnit == HKUnit.milligramsPerDeciliter ? glucoseFormatterMgdl : glucoseFormatterMmol)
- }
- public static func glucoseDiffDesc(oldValue: Self, newValue: Self) -> String {
- guard let glucoseUnit = UserDefaults.standard.mmGlucoseUnit else {
- logger.debug("dabear:: glucose unit was not recognized, aborting")
- return "Unknown"
- }
- var stringValue = [String]()
- var diff = newValue.glucoseDouble - oldValue.glucoseDouble
- let sign = diff < 0 ? "-" : "+"
- if diff == 0 {
- stringValue.append( "\(sign) 0")
- } else {
- diff = abs(diff)
- let asObj = LibreGlucose(
- unsmoothedGlucose: diff,
- glucoseDouble: diff,
- timestamp: Date())
- if let formatted = dynamicFormatter?.string(from: asObj.quantity, for: glucoseUnit) {
- stringValue.append( "\(sign) \(formatted)")
- }
- }
- return stringValue.joined(separator: ",")
- }
- }
- extension LibreGlucose {
- static func calculateSlope(current: Self, last: Self) -> Double {
- if current.timestamp == last.timestamp {
- return 0.0
- }
- let _curr = Double(current.timestamp.timeIntervalSince1970 * 1_000)
- let _last = Double(last.timestamp.timeIntervalSince1970 * 1_000)
- return (Double(last.unsmoothedGlucose) - Double(current.unsmoothedGlucose)) / (_last - _curr)
- }
- static func calculateSlopeByMinute(current: Self, last: Self) -> Double {
- return calculateSlope(current: current, last: last) * 60_000
- }
- static func GetGlucoseTrend(current: Self?, last: Self?) -> GlucoseTrend {
- guard let current = current, let last = last else {
- return .flat
- }
- let s = calculateSlopeByMinute(current: current, last: last)
- switch s {
- case _ where s <= (-3.5):
- return .downDownDown
- case _ where s <= (-2):
- return .downDown
- case _ where s <= (-1):
- return .down
- case _ where s <= (1):
- return .flat
- case _ where s <= (2):
- return .up
- case _ where s <= (3.5):
- return .upUp
- case _ where s <= (40):
- return .flat //flat is the new (tm) "unknown"!
- default:
- return .flat
- }
- }
- }
- extension LibreGlucose {
- static func fromHistoryMeasurements(_ measurements: [Measurement], nativeCalibrationData: SensorData.CalibrationInfo) -> [LibreGlucose] {
- var arr = [LibreGlucose]()
- for historical in measurements {
- let glucose = LibreGlucose(
- //unsmoothedGlucose: historical.temperatureAlgorithmGlucose,
- //glucoseDouble: historical.temperatureAlgorithmGlucose,
- unsmoothedGlucose: historical.roundedGlucoseValueFromRaw2(calibrationInfo: nativeCalibrationData),
- glucoseDouble: historical.roundedGlucoseValueFromRaw2(calibrationInfo: nativeCalibrationData),
- error: historical.error,
- timestamp: historical.date)
- if glucose.glucoseDouble > 0 {
- arr.append(glucose)
- }
- }
- return arr
- }
- static func fromTrendMeasurements(_ measurements: [Measurement], nativeCalibrationData: SensorData.CalibrationInfo, returnAll: Bool,
- sensorStartDate: Date? = nil) -> [LibreGlucose] {
- var arr = [LibreGlucose]()
- var shouldSmoothGlucose = true
- for trend in measurements {
- // trend arrows on each libreglucose value is not needed
- // instead we calculate it once when latestbackfill is set, which in turn sets
- // the sensordisplayable property
- let glucose = LibreGlucose(
- //unsmoothedGlucose: trend.temperatureAlgorithmGlucose,
- unsmoothedGlucose: trend.roundedGlucoseValueFromRaw2(calibrationInfo: nativeCalibrationData),
- glucoseDouble: 0.0,
- error: trend.error,
- timestamp: trend.date,
- sensorStartDate: sensorStartDate)
- // if sensor is ripped off body while transmitter is attached, values below 1 might be created
- if glucose.unsmoothedGlucose > 0 && glucose.unsmoothedGlucose <= 500 {
- arr.append(glucose)
- }
- // Just for expliciticity, if one of the values are 0,
- // then the rest of the values should not be smoothed
- if glucose.unsmoothedGlucose <= 0 {
- shouldSmoothGlucose = false
- }
- }
- if shouldSmoothGlucose {
- arr = CalculateSmothedData5Points(origtrends: arr)
- } else {
- for i in 0 ..< arr.count {
- arr[i].glucoseDouble = arr[i].unsmoothedGlucose
- }
- }
- if !returnAll, let first = arr.first {
- return [first]
- }
- return arr
- }
- }
|