AppGroupSource.swift 3.7 KB

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