Artificial delay in readings when sending to Loop

Libre transmitteris tend to show erratic values. Smoothing improves those values a lot but only for historic values, not for the most recent values.
To avoid that Loop takes actions based on those wrong values, this new option allows to introduce a delay. Most recent values will not be shown. Older values will shift in timestamp. To Loop it will look as if this is the most recent value. Advantage is that values used by Loop will be more reliable, but running a number of minutes behind the real values.

New setting in development section. Only to be used for developers.
This commit is contained in:
Johan Degraeve 2022-05-10 23:07:03 +02:00
parent d2a292ee35
commit 08f827d1e3
3 changed files with 112 additions and 2 deletions

View File

@ -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 {

View File

@ -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<String, Any>]()
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<String, Any>]()
// 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
}
}

View File

@ -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)
}
}