LibreGlucose.swift 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. //
  2. // MiaomiaoClient.h
  3. // MiaomiaoClient
  4. //
  5. import Foundation
  6. import HealthKit
  7. import os.log
  8. fileprivate var logger = Logger(forType: "LibreGlucose")
  9. public struct LibreGlucose {
  10. public let unsmoothedGlucose: Double
  11. public var glucoseDouble: Double
  12. public var error = [MeasurementError.OK]
  13. public var glucose: UInt16 {
  14. UInt16(glucoseDouble.rounded())
  15. }
  16. //trend is deprecated here, it should only be calculated once in latestbackfill
  17. //public var trend: UInt8
  18. public var timestamp: Date
  19. //public let collector: String?
  20. public var sensorStartDate: Date? = nil
  21. public static func timeDifference(oldGlucose: LibreGlucose, newGlucose: LibreGlucose) -> TimeInterval {
  22. newGlucose.startDate.timeIntervalSince(oldGlucose.startDate)
  23. }
  24. public var syncId: String {
  25. "\(Int(self.startDate.timeIntervalSince1970))\(self.unsmoothedGlucose)"
  26. }
  27. public var isStateValid: Bool {
  28. // We know that the official libre algorithm doesn't produce values
  29. // below 39. However, both the raw sensor contents and the derived algorithm
  30. // 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
  31. glucose >= 1
  32. }
  33. public func GetGlucoseTrend(last: Self) -> GlucoseTrend {
  34. Self.GetGlucoseTrend(current: self, last: last)
  35. }
  36. }
  37. extension LibreGlucose: GlucoseValue {
  38. public var startDate: Date {
  39. timestamp
  40. }
  41. public var quantity: HKQuantity {
  42. .init(unit: .milligramsPerDeciliter, doubleValue: glucoseDouble)
  43. }
  44. }
  45. extension LibreGlucose {
  46. public var description: String {
  47. guard let glucoseUnit = UserDefaults.standard.mmGlucoseUnit, let formatter = LibreGlucose.dynamicFormatter, let formatted = formatter.string(from: self.quantity, for: glucoseUnit) else {
  48. logger.debug("dabear:: glucose unit was not recognized, aborting")
  49. return "Unknown"
  50. }
  51. return formatted
  52. }
  53. private static var glucoseFormatterMgdl: QuantityFormatter = {
  54. let formatter = QuantityFormatter()
  55. formatter.setPreferredNumberFormatter(for: HKUnit.milligramsPerDeciliter)
  56. return formatter
  57. }()
  58. private static var glucoseFormatterMmol: QuantityFormatter = {
  59. let formatter = QuantityFormatter()
  60. formatter.setPreferredNumberFormatter(for: HKUnit.millimolesPerLiter)
  61. return formatter
  62. }()
  63. public static var dynamicFormatter: QuantityFormatter? {
  64. guard let glucoseUnit = UserDefaults.standard.mmGlucoseUnit else {
  65. logger.debug("dabear:: glucose unit was not recognized, aborting")
  66. return nil
  67. }
  68. return (glucoseUnit == HKUnit.milligramsPerDeciliter ? glucoseFormatterMgdl : glucoseFormatterMmol)
  69. }
  70. public static func glucoseDiffDesc(oldValue: Self, newValue: Self) -> String {
  71. guard let glucoseUnit = UserDefaults.standard.mmGlucoseUnit else {
  72. logger.debug("dabear:: glucose unit was not recognized, aborting")
  73. return "Unknown"
  74. }
  75. var stringValue = [String]()
  76. var diff = newValue.glucoseDouble - oldValue.glucoseDouble
  77. let sign = diff < 0 ? "-" : "+"
  78. if diff == 0 {
  79. stringValue.append( "\(sign) 0")
  80. } else {
  81. diff = abs(diff)
  82. let asObj = LibreGlucose(
  83. unsmoothedGlucose: diff,
  84. glucoseDouble: diff,
  85. timestamp: Date())
  86. if let formatted = dynamicFormatter?.string(from: asObj.quantity, for: glucoseUnit) {
  87. stringValue.append( "\(sign) \(formatted)")
  88. }
  89. }
  90. return stringValue.joined(separator: ",")
  91. }
  92. }
  93. extension LibreGlucose {
  94. static func calculateSlope(current: Self, last: Self) -> Double {
  95. if current.timestamp == last.timestamp {
  96. return 0.0
  97. }
  98. let _curr = Double(current.timestamp.timeIntervalSince1970 * 1_000)
  99. let _last = Double(last.timestamp.timeIntervalSince1970 * 1_000)
  100. return (Double(last.unsmoothedGlucose) - Double(current.unsmoothedGlucose)) / (_last - _curr)
  101. }
  102. static func calculateSlopeByMinute(current: Self, last: Self) -> Double {
  103. return calculateSlope(current: current, last: last) * 60_000
  104. }
  105. static func GetGlucoseTrend(current: Self?, last: Self?) -> GlucoseTrend {
  106. guard let current = current, let last = last else {
  107. return .flat
  108. }
  109. let s = calculateSlopeByMinute(current: current, last: last)
  110. switch s {
  111. case _ where s <= (-3.5):
  112. return .downDownDown
  113. case _ where s <= (-2):
  114. return .downDown
  115. case _ where s <= (-1):
  116. return .down
  117. case _ where s <= (1):
  118. return .flat
  119. case _ where s <= (2):
  120. return .up
  121. case _ where s <= (3.5):
  122. return .upUp
  123. case _ where s <= (40):
  124. return .flat //flat is the new (tm) "unknown"!
  125. default:
  126. return .flat
  127. }
  128. }
  129. }
  130. extension LibreGlucose {
  131. static func fromHistoryMeasurements(_ measurements: [Measurement], nativeCalibrationData: SensorData.CalibrationInfo) -> [LibreGlucose] {
  132. var arr = [LibreGlucose]()
  133. for historical in measurements {
  134. let glucose = LibreGlucose(
  135. //unsmoothedGlucose: historical.temperatureAlgorithmGlucose,
  136. //glucoseDouble: historical.temperatureAlgorithmGlucose,
  137. unsmoothedGlucose: historical.roundedGlucoseValueFromRaw2(calibrationInfo: nativeCalibrationData),
  138. glucoseDouble: historical.roundedGlucoseValueFromRaw2(calibrationInfo: nativeCalibrationData),
  139. error: historical.error,
  140. timestamp: historical.date)
  141. if glucose.glucoseDouble > 0 {
  142. arr.append(glucose)
  143. }
  144. }
  145. return arr
  146. }
  147. static func fromTrendMeasurements(_ measurements: [Measurement], nativeCalibrationData: SensorData.CalibrationInfo, returnAll: Bool,
  148. sensorStartDate: Date? = nil) -> [LibreGlucose] {
  149. var arr = [LibreGlucose]()
  150. var shouldSmoothGlucose = true
  151. for trend in measurements {
  152. // trend arrows on each libreglucose value is not needed
  153. // instead we calculate it once when latestbackfill is set, which in turn sets
  154. // the sensordisplayable property
  155. let glucose = LibreGlucose(
  156. //unsmoothedGlucose: trend.temperatureAlgorithmGlucose,
  157. unsmoothedGlucose: trend.roundedGlucoseValueFromRaw2(calibrationInfo: nativeCalibrationData),
  158. glucoseDouble: 0.0,
  159. error: trend.error,
  160. timestamp: trend.date,
  161. sensorStartDate: sensorStartDate)
  162. // if sensor is ripped off body while transmitter is attached, values below 1 might be created
  163. if glucose.unsmoothedGlucose > 0 && glucose.unsmoothedGlucose <= 500 {
  164. arr.append(glucose)
  165. }
  166. // Just for expliciticity, if one of the values are 0,
  167. // then the rest of the values should not be smoothed
  168. if glucose.unsmoothedGlucose <= 0 {
  169. shouldSmoothGlucose = false
  170. }
  171. }
  172. if shouldSmoothGlucose {
  173. arr = CalculateSmothedData5Points(origtrends: arr)
  174. } else {
  175. for i in 0 ..< arr.count {
  176. arr[i].glucoseDouble = arr[i].unsmoothedGlucose
  177. }
  178. }
  179. if !returnAll, let first = arr.first {
  180. return [first]
  181. }
  182. return arr
  183. }
  184. }