AppGroupSource.swift 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import Combine
  2. import Foundation
  3. import LoopKit
  4. import LoopKitUI
  5. public extension GlucoseTrend {
  6. var direction: String {
  7. switch self {
  8. case .upUpUp:
  9. return "DoubleUp"
  10. case .upUp:
  11. return "SingleUp"
  12. case .up:
  13. return "FortyFiveUp"
  14. case .flat:
  15. return "Flat"
  16. case .down:
  17. return "FortyFiveDown"
  18. case .downDown:
  19. return "SingleDown"
  20. case .downDownDown:
  21. return "DoubleDown"
  22. }
  23. }
  24. }
  25. struct AppGroupSource: GlucoseSource {
  26. var cgmManager: CGMManagerUI?
  27. var glucoseManager: FetchGlucoseManager?
  28. let from: String
  29. var cgmType: CGMType
  30. func fetch(_ heartbeat: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> {
  31. guard let suiteName = Bundle.main.appGroupSuiteName,
  32. let sharedDefaults = UserDefaults(suiteName: suiteName)
  33. else {
  34. return Just([]).eraseToAnyPublisher()
  35. }
  36. return Just(fetchLastBGs(60, sharedDefaults, heartbeat)).eraseToAnyPublisher()
  37. }
  38. func fetchIfNeeded() -> AnyPublisher<[BloodGlucose], Never> {
  39. fetch(nil)
  40. }
  41. private func fetchLastBGs(_ count: Int, _ sharedDefaults: UserDefaults, _ heartbeat: DispatchTimer?) -> [BloodGlucose] {
  42. guard let sharedData = sharedDefaults.data(forKey: "latestReadings") else {
  43. return []
  44. }
  45. HeartBeatManager.shared.checkCGMBluetoothTransmitter(sharedUserDefaults: sharedDefaults, heartbeat: heartbeat)
  46. debug(.deviceManager, "APPGROUP : START FETCH LAST BG ")
  47. let decoded = try? JSONSerialization.jsonObject(with: sharedData, options: [])
  48. guard let sgvs = decoded as? [AnyObject] else {
  49. return []
  50. }
  51. var results: [BloodGlucose] = []
  52. for sgv in sgvs.prefix(count) {
  53. guard
  54. let glucose = sgv["Value"] as? Int,
  55. let timestamp = sgv["DT"] as? String,
  56. let date = parseDate(timestamp)
  57. else { continue }
  58. var direction: String?
  59. // Dexcom changed the format of trend in 2021 so we accept both String/Int types
  60. if let directionString = sgv["direction"] as? String {
  61. direction = directionString
  62. } else if let intTrend = sgv["trend"] as? Int {
  63. direction = GlucoseTrend(rawValue: intTrend)?.direction
  64. } else if let intTrend = sgv["Trend"] as? Int {
  65. direction = GlucoseTrend(rawValue: intTrend)?.direction
  66. } else if let stringTrend = sgv["trend"] as? String, let intTrend = Int(stringTrend) {
  67. direction = GlucoseTrend(rawValue: intTrend)?.direction
  68. }
  69. guard let direction = direction else { continue }
  70. if let from = sgv["from"] as? String {
  71. guard from == self.from else { continue }
  72. }
  73. results.append(
  74. BloodGlucose(
  75. sgv: glucose,
  76. direction: BloodGlucose.Direction(rawValue: direction),
  77. date: Decimal(Int(date.timeIntervalSince1970 * 1000)),
  78. dateString: date,
  79. unfiltered: Decimal(glucose),
  80. filtered: nil,
  81. noise: nil,
  82. glucose: glucose,
  83. type: "sgv"
  84. )
  85. )
  86. }
  87. return results
  88. }
  89. private func parseDate(_ timestamp: String) -> Date? {
  90. // timestamp looks like "/Date(1462404576000)/"
  91. guard let re = try? NSRegularExpression(pattern: "\\((.*)\\)"),
  92. let match = re.firstMatch(in: timestamp, range: NSMakeRange(0, timestamp.count))
  93. else {
  94. return nil
  95. }
  96. let matchRange = match.range(at: 1)
  97. let epoch = Double((timestamp as NSString).substring(with: matchRange))! / 1000
  98. return Date(timeIntervalSince1970: epoch)
  99. }
  100. func sourceInfo() -> [String: Any]? {
  101. [GlucoseSourceKey.description.rawValue: "Group ID: \(Bundle.main.appGroupSuiteName ?? "Not set"))"]
  102. }
  103. }
  104. public extension Bundle {
  105. var appGroupSuiteName: String? {
  106. object(forInfoDictionaryKey: "AppGroupID") as? String
  107. }
  108. }