GlucoseStorage.swift 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import Foundation
  2. import SwiftDate
  3. import Swinject
  4. protocol GlucoseStorage {
  5. func storeGlucose(_ glucose: [BloodGlucose])
  6. func recent() -> [BloodGlucose]
  7. func syncDate() -> Date
  8. func filterTooFrequentGlucose(_ glucose: [BloodGlucose], at: Date) -> [BloodGlucose]
  9. func lastGlucoseDate() -> Date
  10. func isGlucoseFresh() -> Bool
  11. func isGlucoseNotFlat() -> Bool
  12. func nightscoutGlucoseNotUploaded() -> [BloodGlucose]
  13. var alarm: GlucoseAlarm? { get }
  14. }
  15. final class BaseGlucoseStorage: GlucoseStorage, Injectable {
  16. private let processQueue = DispatchQueue(label: "BaseGlucoseStorage.processQueue")
  17. @Injected() private var storage: FileStorage!
  18. @Injected() private var broadcaster: Broadcaster!
  19. @Injected() private var settingsManager: SettingsManager!
  20. private enum Config {
  21. static let filterTime: TimeInterval = 4.5 * 60
  22. }
  23. init(resolver: Resolver) {
  24. injectServices(resolver)
  25. }
  26. func storeGlucose(_ glucose: [BloodGlucose]) {
  27. processQueue.sync {
  28. let file = OpenAPS.Monitor.glucose
  29. self.storage.transaction { storage in
  30. storage.append(glucose, to: file, uniqBy: \.dateString)
  31. let uniqEvents = storage.retrieve(file, as: [BloodGlucose].self)?
  32. .filter { $0.dateString.addingTimeInterval(1.days.timeInterval) > Date() }
  33. .sorted { $0.dateString > $1.dateString } ?? []
  34. let glucose = Array(uniqEvents)
  35. storage.save(glucose, as: file)
  36. DispatchQueue.main.async {
  37. self.broadcaster.notify(GlucoseObserver.self, on: .main) {
  38. $0.glucoseDidUpdate(glucose.reversed())
  39. }
  40. }
  41. }
  42. }
  43. }
  44. func syncDate() -> Date {
  45. guard let events = storage.retrieve(OpenAPS.Monitor.glucose, as: [BloodGlucose].self),
  46. let recent = events.first
  47. else {
  48. return Date().addingTimeInterval(-1.days.timeInterval)
  49. }
  50. return recent.dateString
  51. }
  52. func recent() -> [BloodGlucose] {
  53. storage.retrieve(OpenAPS.Monitor.glucose, as: [BloodGlucose].self)?.reversed() ?? []
  54. }
  55. func lastGlucoseDate() -> Date {
  56. recent().last?.dateString ?? .distantPast
  57. }
  58. func isGlucoseFresh() -> Bool {
  59. Date().timeIntervalSince(lastGlucoseDate()) <= Config.filterTime
  60. }
  61. func filterTooFrequentGlucose(_ glucose: [BloodGlucose], at date: Date) -> [BloodGlucose] {
  62. var lastDate = date
  63. var filtered: [BloodGlucose] = []
  64. for entry in glucose.reversed() {
  65. guard entry.dateString.addingTimeInterval(-Config.filterTime) > lastDate else {
  66. continue
  67. }
  68. filtered.append(entry)
  69. lastDate = entry.dateString
  70. }
  71. return filtered
  72. }
  73. func isGlucoseNotFlat() -> Bool {
  74. let last3 = recent().suffix(3)
  75. guard last3.count == 3 else { return true }
  76. return Array(
  77. last3
  78. .compactMap { $0.filtered ?? 0 }
  79. .filter { $0 != 0 }
  80. .uniqued()
  81. ).count != 1
  82. }
  83. func nightscoutGlucoseNotUploaded() -> [BloodGlucose] {
  84. let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedGlucose, as: [BloodGlucose].self) ?? []
  85. let recentGlucose = recent()
  86. return Array(Set(recentGlucose).subtracting(Set(uploaded)))
  87. }
  88. var alarm: GlucoseAlarm? {
  89. guard let glucose = recent().last, glucose.dateString.addingTimeInterval(20.minutes.timeInterval) > Date(),
  90. let glucoseValue = glucose.glucose else { return nil }
  91. if Decimal(glucoseValue) < settingsManager.settings.lowGlucose {
  92. return .low
  93. }
  94. if Decimal(glucoseValue) > settingsManager.settings.highGlucose {
  95. return .high
  96. }
  97. return nil
  98. }
  99. }
  100. protocol GlucoseObserver {
  101. func glucoseDidUpdate(_ glucose: [BloodGlucose])
  102. }
  103. enum GlucoseAlarm {
  104. case high
  105. case low
  106. var displayName: String {
  107. switch self {
  108. case .high:
  109. return NSLocalizedString("LOWALERT!", comment: "LOWALERT!")
  110. case .low:
  111. return NSLocalizedString("HIGHALERT!", comment: "HIGHALERT!")
  112. }
  113. }
  114. }