diff --git a/xDrip4iOS Widget/xDripClient/XDripClient.swift b/xDrip4iOS Widget/xDripClient/XDripClient.swift index d50a158a..e35dd12e 100644 --- a/xDrip4iOS Widget/xDripClient/XDripClient.swift +++ b/xDrip4iOS Widget/xDripClient/XDripClient.swift @@ -30,7 +30,7 @@ public class XDripClient { private func fetchLastWithRetries(_ n: Int, remaining: Int, callback: @escaping (ClientError?, [Glucose]?) -> Void) { do { - guard let sharedData = shared?.data(forKey: "latestReadings") else { + guard let sharedData = shared?.data(forKey: "latestReadings-widget") else { throw ClientError.fetchError } diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 198a3f6b..6055f8d4 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -378,6 +378,8 @@ F8B48AA422B2FA9B009BCC01 /* CalibrationRequest.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8B48AA222B2FA9A009BCC01 /* CalibrationRequest.strings */; }; F8B955EB2591355200C06016 /* CGMLibre2Transmitter+TestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B955EA2591355200C06016 /* CGMLibre2Transmitter+TestData.swift */; }; F8B9560A259294FA00C06016 /* GlucoseDataFilterFlatValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B95609259294FA00C06016 /* GlucoseDataFilterFlatValues.swift */; }; + F8B955B1258BEE9D00C06016 /* ConstantsSpeakReading.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B955B0258BEE9D00C06016 /* ConstantsSpeakReading.swift */; }; + F8B955B7258D5E2000C06016 /* ConstantsHealthKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B955B6258D5E2000C06016 /* ConstantsHealthKit.swift */; }; F8BDD4242218790E006EAB84 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8BDD4232218790E006EAB84 /* UserDefaults.swift */; }; F8BDD438221A0349006EAB84 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8BDD436221A0349006EAB84 /* Localizable.strings */; }; F8BDD43F221B5BAF006EAB84 /* TextsErrorMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8BDD43E221B5BAF006EAB84 /* TextsErrorMessages.swift */; }; @@ -987,6 +989,8 @@ F8B48B1222B37C84009BCC01 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/SettingsViews.strings; sourceTree = ""; }; F8B955EA2591355200C06016 /* CGMLibre2Transmitter+TestData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGMLibre2Transmitter+TestData.swift"; sourceTree = ""; }; F8B95609259294FA00C06016 /* GlucoseDataFilterFlatValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseDataFilterFlatValues.swift; sourceTree = ""; }; + F8B955B0258BEE9D00C06016 /* ConstantsSpeakReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsSpeakReading.swift; sourceTree = ""; }; + F8B955B6258D5E2000C06016 /* ConstantsHealthKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsHealthKit.swift; sourceTree = ""; }; F8BDD4232218790E006EAB84 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; F8BDD435221A0005006EAB84 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Main.strings; sourceTree = ""; }; F8BDD437221A0349006EAB84 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -2131,6 +2135,7 @@ F8A1585A22EDB7EA007F5B5D /* ConstantsDexcomG5.swift */, F8A1587222EDC893007F5B5D /* ConstantsDexcomShare.swift */, F80859262364355F00F3829D /* ConstantsGlucoseChart.swift */, + F8B955B6258D5E2000C06016 /* ConstantsHealthKit.swift */, F8A1586622EDB8BF007F5B5D /* ConstantsHomeView.swift */, F8E5404B2522624800052CE5 /* ConstantsHousekeeping.swift */, F8252866243E50FE0067AF77 /* ConstantsLibre.swift */, @@ -2140,12 +2145,13 @@ F8A389EC23342EB10010F405 /* ConstantsNightScout.swift */, F8A1586022EDB844007F5B5D /* ConstantsNotifications.swift */, F8E51D5E2448E2E8001C9E5A /* ConstantsShareWithLoop.swift */, + F85FB768255DE14600D1C39E /* ConstantsSmoothing.swift */, F8A1586222EDB86E007F5B5D /* ConstantsSounds.swift */, + F8B955B0258BEE9D00C06016 /* ConstantsSpeakReading.swift */, F8A1587022EDC865007F5B5D /* ConstantsSpeakReadingLanguages.swift */, F8A1586E22EDC7EE007F5B5D /* ConstantsSuspensionPrevention.swift */, F8AF36142455C6F700B5977B /* ConstantsTrace.swift */, F8E3A2AA23DA520B00E5E98A /* ConstantsWatch.swift */, - F85FB768255DE14600D1C39E /* ConstantsSmoothing.swift */, ); name = Constants; path = xdrip/Constants; @@ -2912,6 +2918,7 @@ F8C97856242AA86B00A09483 /* CGMMiaoMiaoTransmitterDelegate.swift in Sources */, F898EDF6234A8A5700BFB79B /* UInt32.swift in Sources */, F816E0F32433DAA9009EE65B /* BluetoothPeripheralManager+CGMBluconTransmitterDelegate.swift in Sources */, + F8B955B1258BEE9D00C06016 /* ConstantsSpeakReading.swift in Sources */, F8F9723223A5915900C3F17D /* M5StackReadBlePassWordTxMessage.swift in Sources */, F8A54AAF22D686CD00934E7A /* NightScoutBgReading.swift in Sources */, F8DF765323E34F4500063910 /* DexcomG5+CoreDataClass.swift in Sources */, @@ -2961,6 +2968,7 @@ F8F9723923A5928D00C3F17D /* M5StackBluetoothPeripheralViewModel.swift in Sources */, F8F9722B23A5915900C3F17D /* CGMTransmitterDelegate.swift in Sources */, F80859292364D61B00F3829D /* UserDefaults+charts.swift in Sources */, + F8B955B7258D5E2000C06016 /* ConstantsHealthKit.swift in Sources */, F8B3A7B2226A0878004BA588 /* TextsAlerts.swift in Sources */, F8297F46238DC4AC00D74D66 /* BluetoothPeripheralManaging.swift in Sources */, F80D917024F85C7A006840B5 /* Libre2+BluetoothPeripheral.swift in Sources */, diff --git a/xdrip/Constants/ConstantsDexcomShare.swift b/xdrip/Constants/ConstantsDexcomShare.swift index 7c043e77..2a9e3104 100644 --- a/xdrip/Constants/ConstantsDexcomShare.swift +++ b/xdrip/Constants/ConstantsDexcomShare.swift @@ -10,6 +10,9 @@ enum ConstantsDexcomShare { /// non us share base url static let nonUsBaseShareUrl = "https://shareous1.dexcom.com/ShareWebServices/Services" + /// if the time between the last and last but one reading is less than minimiumTimeBetweenTwoReadingsInMinutes, then the last reading will not be uploaded - except if there's been a disconnect in between these two readings + static let minimiumTimeBetweenTwoReadingsInMinutes = 4.75 + } diff --git a/xdrip/Constants/ConstantsHealthKit.swift b/xdrip/Constants/ConstantsHealthKit.swift new file mode 100644 index 00000000..776728af --- /dev/null +++ b/xdrip/Constants/ConstantsHealthKit.swift @@ -0,0 +1,8 @@ +import Foundation + +enum ConstantsHealthKit { + + /// if the time between the last and last but one reading is less than minimiumTimeBetweenTwoReadingsInMinutes, then no new event event will be stored in healthkit - except if there's been a disconnect in between these two readings + static let minimiumTimeBetweenTwoReadingsInMinutes = 4.75 + +} diff --git a/xdrip/Constants/ConstantsNightScout.swift b/xdrip/Constants/ConstantsNightScout.swift index b6bb79cf..356ca3f5 100644 --- a/xdrip/Constants/ConstantsNightScout.swift +++ b/xdrip/Constants/ConstantsNightScout.swift @@ -7,4 +7,8 @@ enum ConstantsNightScout { /// - used in settings, when setting first time nightscout url static let defaultNightScoutUrl = "https://yoursitename.herokuapp.com" + + /// if the time between the last and last but one reading is less than minimiumTimeBetweenTwoReadingsInMinutes, then the last reading will not be uploaded - except if there's been a disconnect in between these two readings + static let minimiumTimeBetweenTwoReadingsInMinutes = 4.75 + } diff --git a/xdrip/Constants/ConstantsNotifications.swift b/xdrip/Constants/ConstantsNotifications.swift index 44d4dab7..e01e7a12 100644 --- a/xdrip/Constants/ConstantsNotifications.swift +++ b/xdrip/Constants/ConstantsNotifications.swift @@ -54,4 +54,7 @@ enum ConstantsNotifications { /// notification identifier for xDripErrors received in RootViewController's cgmTransmitterDelegate static let notificationIdentifierForxCGMTransmitterDelegatexDripError = "notificationIdentifierForxCGMTransmitterDelegatexDripError" + /// if the time between the last and last but one reading is less than minimiumTimeBetweenTwoReadingsInMinutes, then there will bd no notification created - except if there's been a disconnect in between these two readings + static let minimiumTimeBetweenTwoReadingsInMinutes = 4.75 + } diff --git a/xdrip/Constants/ConstantsShareWithLoop.swift b/xdrip/Constants/ConstantsShareWithLoop.swift index f098fea8..1bd0bc52 100644 --- a/xdrip/Constants/ConstantsShareWithLoop.swift +++ b/xdrip/Constants/ConstantsShareWithLoop.swift @@ -3,6 +3,9 @@ import Foundation enum ConstantsShareWithLoop { /// maximum number of readings to share with Loop - static let maxReadingsToShareWithLoop = 10 + static let maxReadingsToShareWithLoop = 12 + /// if the time between the last and last but one reading is less than minimiumTimeBetweenTwoReadingsInMinutes, then the reading will not be shared with loop - except if there's been a disconnect in between these two readings + static let minimiumTimeBetweenTwoReadingsInMinutes = 4.75 + } diff --git a/xdrip/Constants/ConstantsSpeakReading.swift b/xdrip/Constants/ConstantsSpeakReading.swift new file mode 100644 index 00000000..44767f66 --- /dev/null +++ b/xdrip/Constants/ConstantsSpeakReading.swift @@ -0,0 +1,8 @@ +import Foundation + +enum ConstantsSpeakReading { + + /// if the time between the last and last but one reading is less than minimiumTimeBetweenTwoReadingsInMinutes, then the reading will not be spoken - except if there's been a disconnect in between these two readings + static let minimiumTimeBetweenTwoReadingsInMinutes = 4.75 + +} diff --git a/xdrip/Constants/ConstantsWatch.swift b/xdrip/Constants/ConstantsWatch.swift index ecfc6d04..b25e7811 100644 --- a/xdrip/Constants/ConstantsWatch.swift +++ b/xdrip/Constants/ConstantsWatch.swift @@ -5,4 +5,7 @@ enum ConstantsWatch { /// text to add as notes in glucose events static let textInCreatedEvent = "created by xdrip" + /// if the time between the last and last but one reading is less than minimiumTimeBetweenTwoReadingsInMinutes, then no new event will be created - except if there's been a disconnect in between these two readings + static let minimiumTimeBetweenTwoReadingsInMinutes = 4.75 + } diff --git a/xdrip/Extensions/Array.swift b/xdrip/Extensions/Array.swift index 99f2f2e0..08ff82f9 100644 --- a/xdrip/Extensions/Array.swift +++ b/xdrip/Extensions/Array.swift @@ -271,7 +271,54 @@ extension Array where Element: GlucoseData { } } + +} + +extension Array where Element: BgReading { + /// Filter out readings that are too close to each other + /// - parameters: + /// - minimumTimeBetweenTwoReadingsInMinutes : filter out readings that are to close to each other in time, minimum difference in time between two readings = minimumTimeBetweenTwoReadingsInMinutes + /// - lastConnectionStatusChangeTimeStamp : lastConnectionStatusChangeTimeStamp > timeStampLastProcessedBgReading then the first connection will be returned, even if it's less than minimumTimeBetweenTwoReadingsInMinutes away from timeStampLastProcessedBgReading + /// - timeStampLastProcessedBgReading : only readings younger than timeStampLastProcessedBgReading will be returned, if nil then this check is not done + /// - returns + /// filtered array, with readings at least minimumTimeBetweenTwoReadingsInMinutes away from each other + func filter(minimumTimeBetweenTwoReadingsInMinutes: Double, lastConnectionStatusChangeTimeStamp: Date?, timeStampLastProcessedBgReading: Date?) -> [BgReading] { + + var didCheckLastConnectionStatusChangeTimeStamp = false + + var timeStampLatestCheckedReading = timeStampLastProcessedBgReading + + return self.filter({ + + if let lastConnectionStatusChangeTimeStamp = lastConnectionStatusChangeTimeStamp, let timeStampLastProcessedBgReading = timeStampLastProcessedBgReading, !didCheckLastConnectionStatusChangeTimeStamp { + + didCheckLastConnectionStatusChangeTimeStamp = true + + // if there was a disconnect or reconnect after the latest processed reading, then add this reading - this will only apply to the first reading + if lastConnectionStatusChangeTimeStamp.timeIntervalSince(timeStampLastProcessedBgReading) > 0.0 { + + return true + + } + + } + + var returnValue = true + if let timeStampLatestCheckedReading = timeStampLatestCheckedReading { + + returnValue = $0.timeStamp.timeIntervalSince(timeStampLatestCheckedReading) > minimumTimeBetweenTwoReadingsInMinutes * 60.0 + + } + + timeStampLatestCheckedReading = $0.timeStamp + + return returnValue + + }) + + } + } /// source https://github.com/raywenderlich/swift-algorithm-club/tree/master/Linear%20Regression diff --git a/xdrip/Managers/DexcomShare/DexcomShareUploadManager.swift b/xdrip/Managers/DexcomShare/DexcomShareUploadManager.swift index c660483e..c7256ab5 100644 --- a/xdrip/Managers/DexcomShare/DexcomShareUploadManager.swift +++ b/xdrip/Managers/DexcomShare/DexcomShareUploadManager.swift @@ -66,7 +66,9 @@ class DexcomShareUploadManager:NSObject { // MARK: - public functions /// uploads latest BgReadings to Dexcom Share - public func upload() { + /// - parameters: + /// - lastConnectionStatusChangeTimeStamp : when was the last transmitter dis/reconnect - if nil then 1 1 1970 is used + public func upload(lastConnectionStatusChangeTimeStamp: Date?) { // check if dexcomShare is enabled guard UserDefaults.standard.uploadReadingstoDexcomShare else {return} @@ -87,7 +89,7 @@ class DexcomShareUploadManager:NSObject { } // upload - uploadBgReadingsToDexcomShare(firstAttempt: true) + uploadBgReadingsToDexcomShare(firstAttempt: true, lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp) } @@ -114,7 +116,7 @@ class DexcomShareUploadManager:NSObject { if success { trace("in observeValue, start upload", log: self.log, category: ConstantsLog.categoryDexcomShareUploadManager, type: .info) - self.upload() + self.upload(lastConnectionStatusChangeTimeStamp: nil) } else { trace("in observeValue, Dexcom Share credential check failed", log: self.log, category: ConstantsLog.categoryDexcomShareUploadManager, type: .error) @@ -143,7 +145,7 @@ class DexcomShareUploadManager:NSObject { if success { trace("in observeValue, start upload", log: self.log, category: ConstantsLog.categoryDexcomShareUploadManager, type: .info) - self.upload() + self.upload(lastConnectionStatusChangeTimeStamp: nil) } else { @@ -170,9 +172,11 @@ class DexcomShareUploadManager:NSObject { // MARK: - private helper functions /// will call StartRemoteMonitoringSession with serialNumber + /// - parameters: + /// - lastConnectionStatusChangeTimeStamp : when was the last transmitter dis/reconnect - if nil then 1 1 1970 is used /// /// dexcomShareSessionId and UserDefaults.standard.dexcomShareSerialNumber should be not nil - private func startRemoteMonitoringSessionAndStartUpload() { + private func startRemoteMonitoringSessionAndStartUpload(lastConnectionStatusChangeTimeStamp: Date?) { trace("in startRemoteMonitoringSessionAndStartUpload", log: log, category: ConstantsLog.categoryDexcomShareUploadManager, type: .info) @@ -274,7 +278,7 @@ class DexcomShareUploadManager:NSObject { //there's no error, call uploadBgReadingsToDexcomShare in main thread DispatchQueue.main.async { - self.uploadBgReadingsToDexcomShare(firstAttempt: true) + self.uploadBgReadingsToDexcomShare(firstAttempt: true, lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp) } } else { @@ -297,9 +301,10 @@ class DexcomShareUploadManager:NSObject { /// will try to upload the latest readings - if dexcomShareSessionId is nil then first a login attempt will be done and after that an upload. /// - parameters: /// - firstAttempt : if true, and if dexcomShareSessionId not nil, but upload attempt fails with because dexcomShareSessionId is not valid, then a new login attempt will be done, after which a new upload attempt - if false, then no new upload attempt will be done + /// - lastConnectionStatusChangeTimeStamp : when was the last transmitter dis/reconnect - if nil then 1 1 1970 is used /// /// firstAttempt is there to avoid that the app runs in an endless loop - private func uploadBgReadingsToDexcomShare(firstAttempt:Bool) { + private func uploadBgReadingsToDexcomShare(firstAttempt:Bool, lastConnectionStatusChangeTimeStamp: Date?) { trace("in uploadBgReadingsToDexcomShare", log: log, category: ConstantsLog.categoryDexcomShareUploadManager, type: .info) @@ -319,7 +324,7 @@ class DexcomShareUploadManager:NSObject { if success { trace("in uploadBgReadingsToDexcomShare, login successful, will restart uploadBgReadingsToDexcomShare", log: self.log, category: ConstantsLog.categoryDexcomShareUploadManager, type: .info) // retry the upload - self.uploadBgReadingsToDexcomShare(firstAttempt: firstAttempt) + self.uploadBgReadingsToDexcomShare(firstAttempt: firstAttempt, lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp) } else { trace("in uploadBgReadingsToDexcomShare, login failed, no further processing", log: self.log, category: ConstantsLog.categoryDexcomShareUploadManager, type: .error) } @@ -328,12 +333,15 @@ class DexcomShareUploadManager:NSObject { return } - // get readings to upload, limit to 8 hours + // get timestamp of first reading to upload, limit to 8 hours var timeStamp = Date(timeIntervalSinceNow: -8*60*60) if let timeStampLatestDexcomShareUploadedBgReading = UserDefaults.standard.timeStampLatestDexcomShareUploadedBgReading { timeStamp = timeStampLatestDexcomShareUploadedBgReading } - let bgReadingsToUpload = bgReadingsAccessor.getLatestBgReadings(limit: nil, fromDate: timeStamp, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false) + + // get readings to upload, applying minimumTimeBetweenTwoReadingsInMinutes filter + let bgReadingsToUpload = bgReadingsAccessor.getLatestBgReadings(limit: nil, fromDate: timeStamp, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false).filter(minimumTimeBetweenTwoReadingsInMinutes: ConstantsDexcomShare.minimiumTimeBetweenTwoReadingsInMinutes, lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp, timeStampLastProcessedBgReading: timeStamp) + trace(" number of readings to upload : %{public}@", log: log, category: ConstantsLog.categoryDexcomShareUploadManager, type: .info, bgReadingsToUpload.count.description) // if no no readings to upload, no further processing @@ -422,7 +430,7 @@ class DexcomShareUploadManager:NSObject { if success { trace("in uploadBgReadingsToDexcomShare, new login successful", log: self.log, category: ConstantsLog.categoryDexcomShareUploadManager, type: .info) // retry the upload - self.uploadBgReadingsToDexcomShare(firstAttempt: false) + self.uploadBgReadingsToDexcomShare(firstAttempt: false, lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp) } else { trace("in uploadBgReadingsToDexcomShare, new login failed", log: self.log, category: ConstantsLog.categoryDexcomShareUploadManager, type: .error) } @@ -434,7 +442,7 @@ class DexcomShareUploadManager:NSObject { } else if errorCode == "MonitoringSessionNotActive" { // call startRemoteMonitoringSessionAndStartUpload in main thread DispatchQueue.main.async { - self.startRemoteMonitoringSessionAndStartUpload() + self.startRemoteMonitoringSessionAndStartUpload(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp) } return diff --git a/xdrip/Managers/HealthKit/HealthKitManager.swift b/xdrip/Managers/HealthKit/HealthKitManager.swift index dd2661b1..e133054c 100644 --- a/xdrip/Managers/HealthKit/HealthKitManager.swift +++ b/xdrip/Managers/HealthKit/HealthKitManager.swift @@ -50,7 +50,7 @@ public class HealthKitManager:NSObject { healthKitInitialized = initializeHealthKit() // do first store - storeBgReadings() + storeBgReadings(lastConnectionStatusChangeTimeStamp: nil) } @@ -95,7 +95,9 @@ public class HealthKitManager:NSObject { } /// stores latest readings in healthkit, only if HK supported, authorized, enabled in settings - public func storeBgReadings() { + /// - parameters: + /// - lastConnectionStatusChangeTimeStamp : when was the last transmitter dis/reconnect - if nil then 1 1 1970 is used + public func storeBgReadings(lastConnectionStatusChangeTimeStamp: Date?) { // healthkit setting must be on, and healthkit must be initialized successfully if !UserDefaults.standard.storeReadingsInHealthkit || !healthKitInitialized { @@ -105,8 +107,8 @@ public class HealthKitManager:NSObject { // bloodGlucoseType should not be nil guard let bloodGlucoseType = bloodGlucoseType else {return} - // get readings to store, limit to 2016 = maximum 1 week - just to avoid a huge array is being returned here - let bgReadingsToStore = bgReadingsAccessor.getLatestBgReadings(limit: 2016, fromDate: UserDefaults.standard.timeStampLatestHealthKitStoreBgReading, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false) + // get readings to store, limit to 2016 = maximum 1 week - just to avoid a huge array is being returned here, applying minimumTimeBetweenTwoReadingsInMinutes filter + let bgReadingsToStore = bgReadingsAccessor.getLatestBgReadings(limit: 2016, fromDate: UserDefaults.standard.timeStampLatestHealthKitStoreBgReading, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false).filter(minimumTimeBetweenTwoReadingsInMinutes: ConstantsHealthKit.minimiumTimeBetweenTwoReadingsInMinutes, lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp, timeStampLastProcessedBgReading: UserDefaults.standard.timeStampLatestHealthKitStoreBgReading) let bloodGlucoseUnit = HKUnit.init(from: "mg/dL") @@ -149,7 +151,7 @@ public class HealthKitManager:NSObject { healthKitInitialized = initializeHealthKit() // doesn't matter which if the two settings got changed, it's ok to call initialize - storeBgReadings() + storeBgReadings(lastConnectionStatusChangeTimeStamp: nil) } diff --git a/xdrip/Managers/Loop/LoopManager.swift b/xdrip/Managers/Loop/LoopManager.swift index 85b9c46b..5ccc8de0 100644 --- a/xdrip/Managers/Loop/LoopManager.swift +++ b/xdrip/Managers/Loop/LoopManager.swift @@ -10,8 +10,6 @@ import Foundation public class LoopManager:NSObject { - // MARK: - public properties - // MARK: - private properties /// reference to coreDataManager @@ -23,6 +21,8 @@ public class LoopManager:NSObject { /// shared UserDefaults to publish data private let sharedUserDefaults = UserDefaults(suiteName: Bundle.main.appGroupSuiteName) + // MARK: - initializer + init(coreDataManager:CoreDataManager) { // initialize non optional private properties @@ -34,31 +34,46 @@ public class LoopManager:NSObject { } + // MARK: - public functions + /// share latest readings with Loop - public func share() { + /// - lastConnectionStatusChangeTimeStamp : when was the last transmitter dis/reconnect - if nil then 1 1 1970 is used + public func share(lastConnectionStatusChangeTimeStamp: Date?) { // unwrap sharedUserDefaults guard let sharedUserDefaults = sharedUserDefaults else {return} - // get last readings with calculated value - let lastReadings = bgReadingsAccessor.getLatestBgReadings(limit: ConstantsShareWithLoop.maxReadingsToShareWithLoop, fromDate: nil, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false) - - // if there's no readings, then no further processing - if lastReadings.count == 0 { - return - } - + // get last readings with calculated value, don't apply yet the filtering, we will first store for the widget unfiltered + var lastReadings = bgReadingsAccessor.getLatestBgReadings(limit: ConstantsShareWithLoop.maxReadingsToShareWithLoop, fromDate: nil, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false) + // convert to json Dexcom Share format var dictionary = [Dictionary]() for reading in lastReadings { dictionary.append(reading.dictionaryRepresentationForDexcomShareUpload) } + + // to json + if let data = try? JSONSerialization.data(withJSONObject: dictionary) { + + // share to userDefaults for widget + if lastReadings.count > 0 { + sharedUserDefaults.set(data, forKey: "latestReadings-widget") + } + + } + + // applying minimumTimeBetweenTwoReadingsInMinutes filter, for loop + lastReadings = lastReadings.filter(minimumTimeBetweenTwoReadingsInMinutes: ConstantsShareWithLoop.minimiumTimeBetweenTwoReadingsInMinutes, lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp, timeStampLastProcessedBgReading: nil) + + // if there's no readings, then no further processing + if lastReadings.count == 0 { + return + } guard let data = try? JSONSerialization.data(withJSONObject: dictionary) else { return } - sharedUserDefaults.set(data, forKey: "latestReadings") } diff --git a/xdrip/Managers/NightScout/NightScoutUploadManager.swift b/xdrip/Managers/NightScout/NightScoutUploadManager.swift index 419c2fb6..31f28ef9 100644 --- a/xdrip/Managers/NightScout/NightScoutUploadManager.swift +++ b/xdrip/Managers/NightScout/NightScoutUploadManager.swift @@ -51,6 +51,7 @@ public class NightScoutUploadManager:NSObject { /// - parameters: /// - coreDataManager : needed to get latest readings /// - messageHandler : in case errors occur like credential check error, then this closure will be called with title and message + /// - checkIfDisReConnectAfterTimeStampFunction : function to verify if there's been a disconnect or reconnect after the timestamp of the given reading init(coreDataManager: CoreDataManager, messageHandler:((_ title:String, _ message:String) -> Void)?) { // init properties @@ -74,7 +75,9 @@ public class NightScoutUploadManager:NSObject { // MARK: - public functions /// uploads latest BgReadings to NightScout, only if nightscout enabled, not master, url and key defined, if schedule enabled then check also schedule - public func upload() { + /// - parameters: + /// - lastConnectionStatusChangeTimeStamp : when was the last transmitter dis/reconnect - if nil then 1 1 1970 is used + public func upload(lastConnectionStatusChangeTimeStamp: Date?) { // check if NightScout is enabled guard UserDefaults.standard.nightScoutEnabled else {return} @@ -95,7 +98,7 @@ public class NightScoutUploadManager:NSObject { } // upload readings - uploadBgReadingsToNightScout(siteURL: siteURL, apiKey: apiKey) + uploadBgReadingsToNightScout(siteURL: siteURL, apiKey: apiKey, lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp) // upload calibrations uploadCalibrationsToNightScout(siteURL: siteURL, apiKey: apiKey) @@ -146,7 +149,7 @@ public class NightScoutUploadManager:NSObject { DispatchQueue.main.async { self.callMessageHandler(withCredentialVerificationResult: success, error: error) if success { - self.upload() + self.upload(lastConnectionStatusChangeTimeStamp: nil) } else { trace("in observeValue, NightScout credential check failed", log: self.oslog, category: ConstantsLog.categoryNightScoutUploadManager, type: .info) } @@ -168,7 +171,7 @@ public class NightScoutUploadManager:NSObject { testNightScoutCredentials(apiKey: apiKey, siteURL: siteUrl, { (success, error) in DispatchQueue.main.async { if success { - self.upload() + self.upload(lastConnectionStatusChangeTimeStamp: nil) } else { trace("in observeValue, NightScout credential check failed", log: self.oslog, category: ConstantsLog.categoryNightScoutUploadManager, type: .info) } @@ -253,7 +256,7 @@ public class NightScoutUploadManager:NSObject { /// - parameters: /// - siteURL : nightscout site url /// - apiKey : nightscout api key - private func uploadBgReadingsToNightScout(siteURL:String, apiKey:String) { + private func uploadBgReadingsToNightScout(siteURL:String, apiKey:String, lastConnectionStatusChangeTimeStamp: Date?) { trace("in uploadBgReadingsToNightScout", log: self.oslog, category: ConstantsLog.categoryNightScoutUploadManager, type: .info) @@ -266,7 +269,8 @@ public class NightScoutUploadManager:NSObject { } } - let bgReadingsToUpload = bgReadingsAccessor.getLatestBgReadings(limit: nil, fromDate: timeStamp, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false) + // get latest readings, filter : minimiumTimeBetweenTwoReadingsInMinutes beteen two readings, except for the first if a dis/reconnect occured since the latest reading + let bgReadingsToUpload = bgReadingsAccessor.getLatestBgReadings(limit: nil, fromDate: timeStamp, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false).filter(minimumTimeBetweenTwoReadingsInMinutes: ConstantsNightScout.minimiumTimeBetweenTwoReadingsInMinutes, lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp, timeStampLastProcessedBgReading: timeStamp) if bgReadingsToUpload.count > 0 { trace(" number of readings to upload : %{public}@", log: self.oslog, category: ConstantsLog.categoryNightScoutUploadManager, type: .info, bgReadingsToUpload.count.description) diff --git a/xdrip/Managers/Speak/BGReadingSpeaker.swift b/xdrip/Managers/Speak/BGReadingSpeaker.swift index 286ad5b1..7ce6a02e 100644 --- a/xdrip/Managers/Speak/BGReadingSpeaker.swift +++ b/xdrip/Managers/Speak/BGReadingSpeaker.swift @@ -60,7 +60,8 @@ class BGReadingSpeaker:NSObject { /// - no other sound is playing (via sharedAudioPlayer) /// - there' s a recent reading less than 4.5 minutes old /// - time since last spoken reading > interval defined by user (UserDefaults.standard.speakInterval) - public func speakNewReading() { + /// - lastConnectionStatusChangeTimeStamp : when was the last transmitter dis/reconnect - if nil then 1 1 1970 is used + public func speakNewReading(lastConnectionStatusChangeTimeStamp: Date?) { // if speak reading not enabled, then no further processing if !UserDefaults.standard.speakReadings { @@ -72,8 +73,8 @@ class BGReadingSpeaker:NSObject { return } - // get latest reading, ignore sensor, rawdata, timestamp - only 1 - let lastReadings = bgReadingsAccessor.get2LatestBgReadings(minimumTimeIntervalInMinutes: 4.0) + // get latest reading, ignore sensor, rawdata, timestamp - only 1 - applying minimumTimeBetweenTwoReadingsInMinutes filter + let lastReadings = bgReadingsAccessor.get2LatestBgReadings(minimumTimeIntervalInMinutes: 4.0).filter(minimumTimeBetweenTwoReadingsInMinutes: ConstantsSpeakReading.minimiumTimeBetweenTwoReadingsInMinutes, lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp, timeStampLastProcessedBgReading: nil) // if there's no readings, then no further processing if lastReadings.count == 0 { diff --git a/xdrip/Managers/Watch/WatchManager.swift b/xdrip/Managers/Watch/WatchManager.swift index 83b4fe75..0d2c43c9 100644 --- a/xdrip/Managers/Watch/WatchManager.swift +++ b/xdrip/Managers/Watch/WatchManager.swift @@ -30,18 +30,19 @@ class WatchManager: NSObject { // MARK: - public functions /// process new readings - public func processNewReading() { + /// - lastConnectionStatusChangeTimeStamp : when was the last transmitter dis/reconnect - if nil then 1 1 1970 is used + public func processNewReading(lastConnectionStatusChangeTimeStamp: Date?) { // check if createCalenderEvent is enabled in the settings and if so create calender event if UserDefaults.standard.createCalendarEvent { - createCalendarEvent() + createCalendarEvent(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp) } } // MARK: - private functions - private func createCalendarEvent() { + private func createCalendarEvent(lastConnectionStatusChangeTimeStamp: Date?) { // check that access to calendar is authorized by the user guard EKEventStore.authorizationStatus(for: .event) == .authorized else { @@ -56,7 +57,7 @@ class WatchManager: NSObject { } // get 2 last Readings, with a calculatedValue - let lastReading = bgReadingsAccessor.get2LatestBgReadings(minimumTimeIntervalInMinutes: 4.0)// + let lastReading = bgReadingsAccessor.get2LatestBgReadings(minimumTimeIntervalInMinutes: 4.0).filter(minimumTimeBetweenTwoReadingsInMinutes: ConstantsWatch.minimiumTimeBetweenTwoReadingsInMinutes, lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp, timeStampLastProcessedBgReading: nil) // there should be at least one reading guard lastReading.count > 0 else { diff --git a/xdrip/View Controllers/Root View Controller/RootViewController.swift b/xdrip/View Controllers/Root View Controller/RootViewController.swift index c630f8fc..604f2195 100644 --- a/xdrip/View Controllers/Root View Controller/RootViewController.swift +++ b/xdrip/View Controllers/Root View Controller/RootViewController.swift @@ -774,19 +774,19 @@ final class RootViewController: UIViewController { updateLabelsAndChart(overrideApplicationState: false) } - nightScoutUploadManager?.upload() + nightScoutUploadManager?.upload(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp()) - healthKitManager?.storeBgReadings() + healthKitManager?.storeBgReadings(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp()) - bgReadingSpeaker?.speakNewReading() + bgReadingSpeaker?.speakNewReading(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp()) - dexcomShareUploadManager?.upload() + dexcomShareUploadManager?.upload(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp()) bluetoothPeripheralManager?.sendLatestReading() - watchManager?.processNewReading() + watchManager?.processNewReading(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp()) - loopManager?.share() + loopManager?.share(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp()) } } @@ -1074,12 +1074,12 @@ final class RootViewController: UIViewController { // initiate upload to NightScout, if needed if let nightScoutUploadManager = self.nightScoutUploadManager { - nightScoutUploadManager.upload() + nightScoutUploadManager.upload(lastConnectionStatusChangeTimeStamp: self.lastConnectionStatusChangeTimeStamp()) } // initiate upload to Dexcom Share, if needed if let dexcomShareUploadManager = self.dexcomShareUploadManager { - dexcomShareUploadManager.upload() + dexcomShareUploadManager.upload(lastConnectionStatusChangeTimeStamp: self.lastConnectionStatusChangeTimeStamp()) } // update labels @@ -1089,10 +1089,10 @@ final class RootViewController: UIViewController { self.bluetoothPeripheralManager?.sendLatestReading() // watchManager should process new reading - self.watchManager?.processNewReading() + self.watchManager?.processNewReading(lastConnectionStatusChangeTimeStamp: self.lastConnectionStatusChangeTimeStamp()) // send also to loopmanager, not interesting for loop probably, but the data is also used for today widget - self.loopManager?.share() + self.loopManager?.share(lastConnectionStatusChangeTimeStamp: self.lastConnectionStatusChangeTimeStamp()) } @@ -1205,7 +1205,7 @@ final class RootViewController: UIViewController { } // get lastReading, with a calculatedValue - no check on activeSensor because in follower mode there is no active sensor - let lastReading = bgReadingsAccessor.get2LatestBgReadings(minimumTimeIntervalInMinutes: 4.0)// + let lastReading = bgReadingsAccessor.get2LatestBgReadings(minimumTimeIntervalInMinutes: 4.0).filter(minimumTimeBetweenTwoReadingsInMinutes: ConstantsNotifications.minimiumTimeBetweenTwoReadingsInMinutes, lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp(), timeStampLastProcessedBgReading: nil) // if there's no reading for active sensor with calculated value , then no reason to continue if lastReading.count == 0 { @@ -1591,6 +1591,16 @@ final class RootViewController: UIViewController { } + // a long function just to get the timestamp of the last disconnect or reconnect. If not known then returns 1 1 1970 + private func lastConnectionStatusChangeTimeStamp() -> Date { + + // this is actually unwrapping of optionals, goal is to get date of last disconnect/reconnect - all optionals should exist so it doesn't matter what is returned true or false + guard let cgmTransmitter = self.bluetoothPeripheralManager?.getCGMTransmitter(), let bluetoothTransmitter = cgmTransmitter as? BluetoothTransmitter, let bluetoothPeripheral = self.bluetoothPeripheralManager?.getBluetoothPeripheral(for: bluetoothTransmitter), let lastConnectionStatusChangeTimeStamp = bluetoothPeripheral.blePeripheral.lastConnectionStatusChangeTimeStamp else {return Date(timeIntervalSince1970: 0)} + + return lastConnectionStatusChangeTimeStamp + + } + } // MARK: - conform to CGMTransmitter protocol @@ -1797,17 +1807,17 @@ extension RootViewController:NightScoutFollowerDelegate { checkAlertsCreateNotificationAndSetAppBadge() if let healthKitManager = healthKitManager { - healthKitManager.storeBgReadings() + healthKitManager.storeBgReadings(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp()) } if let bgReadingSpeaker = bgReadingSpeaker { - bgReadingSpeaker.speakNewReading() + bgReadingSpeaker.speakNewReading(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp()) } bluetoothPeripheralManager?.sendLatestReading() // send also to loopmanager, not interesting for loop probably, but the data is also used for today widget - self.loopManager?.share() + self.loopManager?.share(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp()) } }