diff --git a/xdrip/Extensions/UserDefaults.swift b/xdrip/Extensions/UserDefaults.swift index a2c8443f..8f1f5905 100644 --- a/xdrip/Extensions/UserDefaults.swift +++ b/xdrip/Extensions/UserDefaults.swift @@ -288,6 +288,11 @@ extension UserDefaults { /// case smooth libre values case smoothLibreValues = "smoothLibreValues" + /// to create artificial delay in readings stored in sharedUserDefaults for loop. Minutes - so that Loop receives more smoothed values. + /// + /// Default value 0, if used then recommende value between 4 and 9 + case loopDelay = "loopDelay" + /// used for Libre data parsing - only for Libre 1 or Libre 2 read via transmitter, ie full NFC block case previousRawLibreValues = "previousRawLibreValues" @@ -396,6 +401,18 @@ extension UserDefaults { set(newValue, forKey: Key.notificationInterval.rawValue) } } + + /// to create artificial delay in readings stored in sharedUserDefaults for loop. Minutes - so that Loop receives more smoothed values. + /// + /// Default value 0, if used then recommende value between 4 and 9 + @objc dynamic var loopDelay: Int { + get { + return integer(forKey: Key.loopDelay.rawValue) + } + set { + set(newValue, forKey: Key.loopDelay.rawValue) + } + } /// should reading be shown in app badge yes or no @objc dynamic var showReadingInAppBadge: Bool { diff --git a/xdrip/Managers/Loop/LoopManager.swift b/xdrip/Managers/Loop/LoopManager.swift index 17c954fd..c8efff1e 100644 --- a/xdrip/Managers/Loop/LoopManager.swift +++ b/xdrip/Managers/Loop/LoopManager.swift @@ -44,13 +44,24 @@ public class LoopManager:NSObject { guard let sharedUserDefaults = sharedUserDefaults else {return} // get last readings with calculated value - let lastReadings = bgReadingsAccessor.getLatestBgReadings(limit: ConstantsShareWithLoop.maxReadingsToShareWithLoop, fromDate: UserDefaults.standard.timeStampLatestLoopSharedBgReading, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false) + var lastReadings = bgReadingsAccessor.getLatestBgReadings(limit: ConstantsShareWithLoop.maxReadingsToShareWithLoop, fromDate: UserDefaults.standard.timeStampLatestLoopSharedBgReading, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false) + + // if needed, remove readings less than loopDelay minutes old + if UserDefaults.standard.loopDelay > 0 { + + while lastReadings.count > 0 && lastReadings[0].timeStamp.addingTimeInterval(TimeInterval(minutes: Double(UserDefaults.standard.loopDelay))) > Date() { + + lastReadings.remove(at: 0) + + } + + } // if there's no readings, then no further processing if lastReadings.count == 0 { return } - + // convert to json Dexcom Share format var dictionary = [Dictionary]() for reading in lastReadings { @@ -59,6 +70,58 @@ public class LoopManager:NSObject { representation["from"] = "xDrip" dictionary.append(representation) } + + // now, if needed, increase the timestamp for each reading + if UserDefaults.standard.loopDelay > 0 { + + // create new dictionary that will have the readings with timestamp increased + var newDictionary = [Dictionary]() + + // iterate through dictionary + for reading in dictionary { + + var readingTimeStamp: Date? + if let rawGlucoseStartDate = reading["DT"] as? String { + do { + + readingTimeStamp = try self.parseTimestamp(rawGlucoseStartDate) + + } catch { + + } + } + + if let readingTimeStamp = readingTimeStamp, let slopeOrdinal = reading["Trend"] as? Int, let value = reading["Value"] as? Double { + + // create new date : original date + loopDelay + let newReadingTimeStamp = readingTimeStamp.addingTimeInterval(TimeInterval(minutes: Double(UserDefaults.standard.loopDelay))) + + // ignore the reading if newReadingTimeStamp > now + if newReadingTimeStamp < Date() { + + // this is for the json representation + let dateAsString = "/Date(" + Int64(floor(newReadingTimeStamp.toMillisecondsAsDouble() / 1000) * 1000).description + ")/" + + // create new reading and append to new dictionary + let newReading: [String : Any] = [ + "Trend" : slopeOrdinal, + "ST" : dateAsString, + "DT" : dateAsString, + "Value" : value, + "direction" : slopeOrdinal + ] + + newDictionary.append(newReading) + + } + + } + + } + + dictionary = newDictionary + + } // get Dictionary stored in UserDefaults from previous session // append readings already stored in this storedDictionary so that we get dictionary filled with maxReadingsToShareWithLoop readings, if possible @@ -91,4 +154,13 @@ public class LoopManager:NSObject { UserDefaults.standard.readingsStoredInSharedUserDefaultsAsDictionary = dictionary } + private func parseTimestamp(_ timestamp: String) throws -> Date? { + let regex = try NSRegularExpression(pattern: "\\((.*)\\)") + if let match = regex.firstMatch(in: timestamp, range: NSMakeRange(0, timestamp.count)) { + let epoch = Double((timestamp as NSString).substring(with: match.range(at: 1)))! / 1000 + return Date(timeIntervalSince1970: epoch) + } + return nil + } + } diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDevelopmentSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDevelopmentSettingsViewModel.swift index a6e5db95..c311e6f4 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDevelopmentSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDevelopmentSettingsViewModel.swift @@ -11,6 +11,11 @@ fileprivate enum Setting:Int, CaseIterable { /// case smooth libre values case smoothLibreValues = 2 + /// to create artificial delay in readings stored in sharedUserDefaults for loop. Minutes - so that Loop receives more smoothed values. + /// + /// Default value 0, if used then recommende value between 4 and 9 + case loopDelay = 3 + } struct SettingsViewDevelopmentSettingsViewModel:SettingsViewModelProtocol { @@ -42,6 +47,9 @@ struct SettingsViewDevelopmentSettingsViewModel:SettingsViewModelProtocol { case .smoothLibreValues: return Texts_SettingsView.smoothLibreValues + case .loopDelay: + return "Loop Delay" + } } @@ -54,6 +62,9 @@ struct SettingsViewDevelopmentSettingsViewModel:SettingsViewModelProtocol { case .NSLogEnabled, .OSLogEnabled, .smoothLibreValues: return UITableViewCell.AccessoryType.none + case .loopDelay: + return UITableViewCell.AccessoryType.disclosureIndicator + } } @@ -72,6 +83,9 @@ struct SettingsViewDevelopmentSettingsViewModel:SettingsViewModelProtocol { case .smoothLibreValues: return nil + case .loopDelay: + return UserDefaults.standard.loopDelay.description + } } @@ -106,6 +120,9 @@ struct SettingsViewDevelopmentSettingsViewModel:SettingsViewModelProtocol { }) + case .loopDelay: + return nil + } } @@ -123,6 +140,10 @@ struct SettingsViewDevelopmentSettingsViewModel:SettingsViewModelProtocol { case .NSLogEnabled, .OSLogEnabled, .smoothLibreValues: return .nothing + case .loopDelay: + return SettingsSelectedRowAction.askText(title: "Loop Delay", message: "Artificial delay in readings when sending to Loop (minutes) - 0 means no delay. Use maximum 10 minutes.", keyboardType: .numberPad, text: UserDefaults.standard.loopDelay.description, placeHolder: "0", actionTitle: nil, cancelTitle: nil, actionHandler: {(interval:String) in if let interval = Int(interval) {UserDefaults.standard.loopDelay = Int(interval)}}, cancelHandler: nil, inputValidator: nil) + + } }