From 3687d03ebade8e80d89393a75a87d1f3916f268a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20All=C3=A8ne?= Date: Sat, 16 Mar 2024 09:08:11 +0100 Subject: [PATCH 1/8] =?UTF-8?q?Add=20OpenGl=C3=BCck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xdrip.xcodeproj/project.pbxproj | 46 +++- xdrip/Constants/ConstantsLog.swift | 3 + xdrip/Extensions/UserDefaults.swift | 59 +++++ xdrip/Managers/Alerts/AlertManager.swift | 38 ++- .../OpenGl\303\274ckManager.swift" | 218 ++++++++++++++++++ xdrip/Texts/TextsSettingsView.swift | 30 +++ .../RootViewController.swift | 16 +- .../SettingsViewController.swift | 5 + ...ewOpenGl\303\274ckSettingsViewModel.swift" | 166 +++++++++++++ 9 files changed, 570 insertions(+), 11 deletions(-) create mode 100644 "xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" create mode 100644 "xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewOpenGl\303\274ckSettingsViewModel.swift" diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 61741df19..2cc33a123 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -221,6 +221,12 @@ 47F7B1C62C68BBE100609DA7 /* ConstantsCalibrationAlgorithms.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1585422EDB706007F5B5D /* ConstantsCalibrationAlgorithms.swift */; }; 47F7B1CD2C68CC3000609DA7 /* ConstantsCalibrationAlgorithms.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1585422EDB706007F5B5D /* ConstantsCalibrationAlgorithms.swift */; }; 47FB28082636B04200042FFB /* StatisticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FB28072636B04200042FFB /* StatisticsManager.swift */; }; + 9D06FF5B2A76EB1600ECEA9B /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + 9D06FF602A76EC0000ECEA9B /* DiablyLib in Frameworks */ = {isa = PBXBuildFile; productRef = 9D06FF5F2A76EC0000ECEA9B /* DiablyLib */; }; + 9D06FF622A76EC0000ECEA9B /* OG in Frameworks */ = {isa = PBXBuildFile; productRef = 9D06FF612A76EC0000ECEA9B /* OG */; }; + 9D67DF752A6DBEDC009A15DD /* SettingsViewOpenGlückSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D67DF742A6DBEDC009A15DD /* SettingsViewOpenGlückSettingsViewModel.swift */; }; + 9D67DF7A2A6DC365009A15DD /* OpenGlückManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D67DF792A6DC365009A15DD /* OpenGlückManager.swift */; }; + 9D67DF7D2A6DC603009A15DD /* (null) in Frameworks */ = {isa = PBXBuildFile; }; CE1B2FE025D0264B00F642F5 /* LaunchScreen.strings in Resources */ = {isa = PBXBuildFile; fileRef = CE1B2FD125D0264900F642F5 /* LaunchScreen.strings */; }; CE1B2FE125D0264B00F642F5 /* Main.strings in Resources */ = {isa = PBXBuildFile; fileRef = CE1B2FD425D0264900F642F5 /* Main.strings */; }; D400F8032778BD8000B57648 /* TextsTreatmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D400F8022778BD8000B57648 /* TextsTreatmentsView.swift */; }; @@ -1060,6 +1066,8 @@ 47FB28072636B04200042FFB /* StatisticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsManager.swift; sourceTree = ""; }; 666E283826F7E54C00ACE4DF /* xDrip.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = xDrip.xcconfig; path = xdrip/xDrip.xcconfig; sourceTree = ""; }; 666E283926F7E54C00ACE4DF /* Version.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Version.xcconfig; path = xdrip/Version.xcconfig; sourceTree = ""; }; + 9D67DF742A6DBEDC009A15DD /* SettingsViewOpenGlückSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsViewOpenGlückSettingsViewModel.swift"; sourceTree = ""; }; + 9D67DF792A6DC365009A15DD /* OpenGlückManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OpenGlückManager.swift"; sourceTree = ""; }; CE1B2FC825D0261500F642F5 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Alerts.strings; sourceTree = ""; }; CE1B2FCD25D0264900F642F5 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/AlertTypesSettingsView.strings; sourceTree = ""; }; CE1B2FCE25D0264900F642F5 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/WatlaaView.strings; sourceTree = ""; }; @@ -1937,11 +1945,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9D06FF622A76EC0000ECEA9B /* OG in Frameworks */, F81F3C4225D1D91300520946 /* CoreNFC.framework in Frameworks */, 470824D2297484B500C52317 /* SwiftCharts in Frameworks */, + 9D67DF7D2A6DC603009A15DD /* (null) in Frameworks */, 4779BCEE2974306300515714 /* ActionClosurable in Frameworks */, F821CF9722AE589E005C1E43 /* HealthKit.framework in Frameworks */, - 47DE41AA2B860DF00041DA19 /* WatchConnectivity.framework in Frameworks */, + 9D06FF602A76EC0000ECEA9B /* DiablyLib in Frameworks */, + 9D06FF5B2A76EB1600ECEA9B /* (null) in Frameworks */, 4779BCF42974308F00515714 /* PieCharts in Frameworks */, 4779BCF12974307700515714 /* CryptoSwift in Frameworks */, ); @@ -2231,6 +2242,14 @@ name = Frameworks; sourceTree = ""; }; + 9D67DF782A6DC339009A15DD /* OpenGlückManager */ = { + isa = PBXGroup; + children = ( + 9D67DF792A6DC365009A15DD /* OpenGlückManager.swift */, + ); + path = "OpenGlückManager"; + sourceTree = ""; + }; D4028CBE2774A4B900341476 /* Treatments */ = { isa = PBXGroup; children = ( @@ -2680,6 +2699,7 @@ 4716A5122B41CA9C00419052 /* LiveActivity */, F8E51D5B2448D8A3001C9E5A /* Loop */, F821CF4F229BF43A005C1E43 /* Nightscout */, + 9D67DF782A6DC339009A15DD /* OpenGlückManager */, F64039AE281C3F8D0051EFFE /* QuickActions */, F821CF9922AEF2DF005C1E43 /* Speak */, 47FB28052636AFE700042FFB /* Statistics */, @@ -3140,6 +3160,7 @@ F8B3A83D227F090D004BA588 /* SettingsViewSpeakSettingsViewModel.swift */, 4752B4052635878E0081D551 /* SettingsViewStatisticsSettingsViewModel.swift */, F8E51D6824549E2C001C9E5A /* SettingsViewTraceSettingsViewModel.swift */, + 9D67DF742A6DBEDC009A15DD /* SettingsViewOpenGlückSettingsViewModel.swift */, ); path = SettingsViewModels; sourceTree = ""; @@ -3943,6 +3964,8 @@ 4779BCF02974307700515714 /* CryptoSwift */, 4779BCF32974308F00515714 /* PieCharts */, 470824D1297484B500C52317 /* SwiftCharts */, + 9D06FF5F2A76EC0000ECEA9B /* DiablyLib */, + 9D06FF612A76EC0000ECEA9B /* OG */, ); productName = xdrip; productReference = F8AC425A21ADEBD60078C348 /* xdrip.app */; @@ -4017,6 +4040,7 @@ 4779BCEC2974306300515714 /* XCRemoteSwiftPackageReference "ActionClosurable" */, 4779BCEF2974307700515714 /* XCRemoteSwiftPackageReference "CryptoSwift" */, 4779BCF22974308F00515714 /* XCRemoteSwiftPackageReference "PieCharts" */, + 9D06FF5E2A76EC0000ECEA9B /* XCRemoteSwiftPackageReference "OG" */, ); productRefGroup = F8AC425B21ADEBD60078C348 /* Products */; projectDirPath = ""; @@ -4466,6 +4490,7 @@ F85DC2ED21CFE2F500B9F74A /* BgReading+CoreDataProperties.swift in Sources */, F85544912B83E6C3002569F8 /* DexcomG7GlucoseDataRxMessage.swift in Sources */, F8A2BC0D25DB0B12001D1E78 /* Atom+BluetoothPeripheral.swift in Sources */, + 9D67DF752A6DBEDC009A15DD /* SettingsViewOpenGlückSettingsViewModel.swift in Sources */, F8F9723123A5915900C3F17D /* M5StackAuthenticateTXMessage.swift in Sources */, F8EE3EAE2B6834FD00B27B96 /* DexcomG7HeartBeat+CoreDataClass.swift in Sources */, F8F9721223A5915900C3F17D /* FirmwareVersionTxMessage.swift in Sources */, @@ -4615,6 +4640,7 @@ F8CB59C22738206D00BA199E /* DexcomGlucoseDataTxMessage.swift in Sources */, F8C97854242AA70D00A09483 /* MiaoMiao+CoreDataProperties.swift in Sources */, F8F9720A23A5915900C3F17D /* DexcomTransmitterOpCode.swift in Sources */, + 9D67DF7A2A6DC365009A15DD /* OpenGlückManager.swift in Sources */, F8AC425E21ADEBD60078C348 /* AppDelegate.swift in Sources */, F8A2BC0825DB09BE001D1E78 /* Atom+CoreDataProperties.swift in Sources */, F8F1671927288FC6001AA3D8 /* DexcomSessionStartRxMessage.swift in Sources */, @@ -6042,6 +6068,14 @@ kind = branch; }; }; + 9D06FF5E2A76EC0000ECEA9B /* XCRemoteSwiftPackageReference "OG" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/callms/OG"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.65; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -6065,6 +6099,16 @@ package = 4779BCF22974308F00515714 /* XCRemoteSwiftPackageReference "PieCharts" */; productName = PieCharts; }; + 9D06FF5F2A76EC0000ECEA9B /* DiablyLib */ = { + isa = XCSwiftPackageProductDependency; + package = 9D06FF5E2A76EC0000ECEA9B /* XCRemoteSwiftPackageReference "OG" */; + productName = DiablyLib; + }; + 9D06FF612A76EC0000ECEA9B /* OG */ = { + isa = XCSwiftPackageProductDependency; + package = 9D06FF5E2A76EC0000ECEA9B /* XCRemoteSwiftPackageReference "OG" */; + productName = OG; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/xdrip/Constants/ConstantsLog.swift b/xdrip/Constants/ConstantsLog.swift index 6b5f11a45..4e3edb9ba 100644 --- a/xdrip/Constants/ConstantsLog.swift +++ b/xdrip/Constants/ConstantsLog.swift @@ -106,6 +106,9 @@ enum ConstantsLog { /// healthkit manager static let categoryHealthKitManager = "HealthKitManager " + /// openglück + static let categoryOpenGlückManager = "OpenGlückManager " + /// SettingsViewHealthKitSettingsViewModel static let categorySettingsViewHealthKitSettingsViewModel = "SettingsViewHealthKitSettingsViewModel" diff --git a/xdrip/Extensions/UserDefaults.swift b/xdrip/Extensions/UserDefaults.swift index 9f67d6bfc..aa90a2b48 100644 --- a/xdrip/Extensions/UserDefaults.swift +++ b/xdrip/Extensions/UserDefaults.swift @@ -238,6 +238,13 @@ extension UserDefaults { /// should readings be stored in healthkit, true or false case storeReadingsInHealthkit = "storeReadingsInHealthkit" + // OpenGlück + case openGlückEnabled = "openGlückEnabled" + case openGlückUploadEnabled = "openGlückUploadEnabled" + case openGlückHostname = "openGlückHostname" + case openGlückToken = "openGlückToken" + case timeStampLatestOpenGlückBgReading = "openGlückLastUpload" + // Speak readings /// speak readings @@ -1625,6 +1632,58 @@ extension UserDefaults { } } + // MARK: OpenGlück Settings + + /// is OpenGlück enabled? + @objc dynamic var openGlückEnabled: Bool { + get { + return bool(forKey: Key.openGlückEnabled.rawValue) + } + set { + set(newValue, forKey: Key.openGlückEnabled.rawValue) + } + } + + /// is OpenGlück upload enabled? + @objc dynamic var openGlückUploadEnabled: Bool { + get { + return bool(forKey: Key.openGlückUploadEnabled.rawValue) + } + set { + set(newValue, forKey: Key.openGlückUploadEnabled.rawValue) + } + } + + /// OpenGlück hostname + @objc dynamic var openGlückHostname: String? { + get { + return string(forKey: Key.openGlückHostname.rawValue) + } + set { + set(newValue, forKey: Key.openGlückHostname.rawValue) + } + } + + /// OpenGlück token + @objc dynamic var openGlückToken: String? { + get { + return string(forKey: Key.openGlückToken.rawValue) + } + set { + set(newValue, forKey: Key.openGlückToken.rawValue) + } + } + + /// OpenGlück last upload + @objc dynamic var timeStampLatestOpenGlückBgReading: Date? { + get { + return object(forKey: Key.timeStampLatestOpenGlückBgReading.rawValue) as? Date + } + set { + set(newValue, forKey: Key.timeStampLatestOpenGlückBgReading.rawValue) + } + } + // MARK: Speak Settings /// should readings be spoken or not diff --git a/xdrip/Managers/Alerts/AlertManager.swift b/xdrip/Managers/Alerts/AlertManager.swift index bfa78b568..afda07df9 100644 --- a/xdrip/Managers/Alerts/AlertManager.swift +++ b/xdrip/Managers/Alerts/AlertManager.swift @@ -38,6 +38,9 @@ public class AlertManager:NSObject { /// playSound instance private var soundPlayer:SoundPlayer? + /// openGlück manager instance + private var openGlückManager:OpenGlückManager? + /// snooze parameters private var snoozeParameters = [SnoozeParameters]() @@ -63,7 +66,7 @@ public class AlertManager:NSObject { // MARK: - initializer - init(coreDataManager:CoreDataManager, soundPlayer:SoundPlayer?) { + init(coreDataManager:CoreDataManager, soundPlayer:SoundPlayer?, openGlückManager:OpenGlückManager?) { // initialize properties self.bgReadingsAccessor = BgReadingsAccessor(coreDataManager: coreDataManager) self.alertTypesAccessor = AlertTypesAccessor(coreDataManager: coreDataManager) @@ -71,6 +74,7 @@ public class AlertManager:NSObject { self.calibrationsAccessor = CalibrationsAccessor(coreDataManager: coreDataManager) self.sensorsAccessor = SensorsAccessor(coreDataManager: coreDataManager) self.soundPlayer = soundPlayer + self.openGlückManager = openGlückManager self.uNUserNotificationCenter = UNUserNotificationCenter.current() self.coreDataManager = coreDataManager @@ -773,18 +777,36 @@ public class AlertManager:NSObject { let notificationRequest = UNNotificationRequest(identifier: alertKind.notificationIdentifier(), content: content, trigger: trigger) // Add Request to User Notification Center - uNUserNotificationCenter.add(notificationRequest) { (error) in - if let error = error { - trace("Unable to Add Notification Request %{public}@", log: self.log, category: ConstantsLog.categoryAlertManager, type: .error, error.localizedDescription) + let lastBgReadingTimestamp = lastBgReading?.timeStamp + Task { + // check if current alert can be dismissed by third-party manager + switch alertKind { + case .low: + // ask openglück + if let lastBgReadingTimestamp, let openGlückManager, await openGlückManager.shouldDismissLow(at: lastBgReadingTimestamp) { + trace("OpenGlück asked to dismiss a low notification", log: self.log, category: ConstantsLog.categoryAlertManager, type: .info) + return + } + default: + // no possible dismissal + break + } + uNUserNotificationCenter.add(notificationRequest) { (error) in + if let error = error { + trace("Unable to Add Notification Request %{public}@", log: self.log, category: ConstantsLog.categoryAlertManager, type: .error, error.localizedDescription) + } } } // snooze default period, to avoid that alert goes off every minute for Libre 2, except if it's a delayed alert (for delayed alerts it looks a bit risky to me) if delayInSecondsToUse == 0 { - - trace("in checkAlert, snoozing alert %{public}@ for %{public}@ minutes", log: self.log, category: ConstantsLog.categoryAlertManager, type: .info, alertKind.descriptionForLogging(), ConstantsAlerts.defaultDelayBetweenAlertsOfSameKindInMinutes.description) - - getSnoozeParameters(alertKind: alertKind).snooze(snoozePeriodInMinutes: ConstantsAlerts.defaultDelayBetweenAlertsOfSameKindInMinutes) + if alertKind == .low || alertKind == .verylow { + trace("in checkAlert, deliberately NOT snoozing alert %{public}@", log: self.log, category: ConstantsLog.categoryAlertManager, type: .info, alertKind.descriptionForLogging()) + } else { + trace("in checkAlert, snoozing alert %{public}@ for %{public}@ minutes", log: self.log, category: ConstantsLog.categoryAlertManager, type: .info, alertKind.descriptionForLogging(), ConstantsAlerts.defaultDelayBetweenAlertsOfSameKindInMinutes.description) + + getSnoozeParameters(alertKind: alertKind).snooze(snoozePeriodInMinutes: ConstantsAlerts.defaultDelayBetweenAlertsOfSameKindInMinutes) + } } diff --git "a/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" "b/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" new file mode 100644 index 000000000..ef30af55b --- /dev/null +++ "b/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" @@ -0,0 +1,218 @@ +import Foundation +import OG +import os + +public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { + // MARK: - public properties + + // MARK: - private properties + + /// to solve problem that sometemes UserDefaults key value changes is triggered twice for just one change + private let keyValueObserverTimeKeeper: KeyValueObserverTimeKeeper = .init() + + /// for logging + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryOpenGlückManager) + + /// reference to coredatamanager + private var coreDataManager: CoreDataManager + + /// reference to BgReadingsAccessor + private var bgReadingsAccessor: BgReadingsAccessor + + /// is OpenGlück fully initiazed or not, that includes checking if healthkit is available, created successfully bloodGlucoseType, user authorized - value will get changed + private var openGlückInitialized = false + + /// reference to the OpenGlück client, should be used only if we're sure OpenGlück is supported on the device + private var openGlückClient: OpenGlückClient? + private var openGlückSyncClient: OpenGlückSyncClient? + + /// dismisses low notifications 30m after low record + private let dismissLowAfter: TimeInterval = 30 * 60 // 30m + private var lastLowRecordAt: Date? = nil + + // MARK: - intialization + + init(coreDataManager: CoreDataManager) { + // initialize non optional private properties + self.coreDataManager = coreDataManager + bgReadingsAccessor = BgReadingsAccessor(coreDataManager: coreDataManager) + + // call super.init + super.init() + + // listen for changes to userdefaults + UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.openGlückEnabled.rawValue, options: .new, context: nil) + UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.openGlückUploadEnabled.rawValue, options: .new, context: nil) + UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.openGlückHostname.rawValue, options: .new, context: nil) + UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.openGlückToken.rawValue, options: .new, context: nil) + + // call initializeOpenGlück, set openGlückInitialized according to result of initialization + openGlückInitialized = initializeOpenGlück() + + // do first store + storeBgReadings() + } + + // MARK: - private functions + + /// checks if OpenGlück enabled, creates client + /// - returns: + /// - result which indicates if initialize was successful or not + /// + /// the return value of the function does not depend on UserDefaults.standard.openGlückEnabled - this setting needs to be verified each time there's an new reading to store + private func initializeOpenGlück() -> Bool { + openGlückClient = nil + openGlückSyncClient = nil + guard UserDefaults.standard.openGlückEnabled, let openGlückHostname = UserDefaults.standard.openGlückHostname, let openGlückToken = UserDefaults.standard.openGlückToken else { return false } + + openGlückClient = OpenGlückClient(hostname: openGlückHostname, token: openGlückToken, target: "xdripswift") + openGlückSyncClient = OpenGlückSyncClient() + openGlückSyncClient!.delegate = self + + // all checks ok , return true + return true + } + + let intervalBetweenHistoricRecords: TimeInterval = 5 * 60 // 5m + let historicScanTipoffInterval: TimeInterval = 20 * 60 // 20m + private func splitRecordsByHistoricScan(_ readings: [BgReading]) -> ([BgReading], [BgReading]) { + guard !readings.isEmpty else { return ([], []); } + let historicScanTipoffDate = Date().addingTimeInterval(-historicScanTipoffInterval) + print("historicScanTipoffDate=\(historicScanTipoffDate)") + let readings = readings.sorted(by: { $0.timeStamp < $1.timeStamp }) + let earliest = readings.first!.timeStamp + let latest = readings.last!.timeStamp + let base = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: earliest)! + var usedTimeStamps: Set = Set() + var d = base + var historics = [BgReading]() + while d < latest { + if let match = readings + .filter({ $0.timeStamp <= historicScanTipoffDate }) + .filter({ $0.timeStamp > d }).first, match.timeStamp.timeIntervalSince(d) < intervalBetweenHistoricRecords, !usedTimeStamps.contains(match.timeStamp) { + usedTimeStamps.insert(match.timeStamp) + historics.append(match) + } + d = d.addingTimeInterval(intervalBetweenHistoricRecords) + } + let lastHistoricAt = historics.map { $0.timeStamp }.max() ?? historicScanTipoffDate + let scans = readings.filter { $0.timeStamp > lastHistoricAt } + return (historics, scans) + } + + /// stores latest readings in healthkit, only if HK supported, authorized, enabled in settings + public func storeBgReadings() { + // TODO: + // healthkit setting must be on, and healthkit must be initialized successfully + if !UserDefaults.standard.openGlückEnabled || !openGlückInitialized { return } + + guard let openGlückClient = openGlückClient else { return } + + // get readings to store, limit to 15 = 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.timeStampLatestOpenGlückBgReading, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false).filter(minimumTimeBetweenTwoReadingsInMinutes: 1, lastConnectionStatusChangeTimeStamp: nil, timeStampLastProcessedBgReading: UserDefaults.standard.timeStampLatestOpenGlückBgReading) + + let loadLastRecordsSince = Date().addingTimeInterval(-86400) + let bgRecentRecords = bgReadingsAccessor.getLatestBgReadings(limit: nil, fromDate: loadLastRecordsSince, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false).filter(minimumTimeBetweenTwoReadingsInMinutes: 1, lastConnectionStatusChangeTimeStamp: nil, timeStampLastProcessedBgReading: loadLastRecordsSince) + let (historics, scans) = splitRecordsByHistoricScan(bgRecentRecords) + print("Historic", historics.map { "\($0.timeStamp) \($0.calculatedValue)"}.joined(separator: "\n")) + print("Scans", scans.map { "\($0.timeStamp) \($0.calculatedValue)"}.joined(separator: "\n")) + // NOTE: when Libre smoothing is enabled, we kind of lose scan records and only have one available; if we don't want this + // we should store scan readings and re-upload them later -- though not sure this is a feature we'd want + + // reupload at least 4 historic records + scans + let uploadHistoricAfter = (UserDefaults.standard.timeStampLatestOpenGlückBgReading ?? Date().addingTimeInterval(-86400)).addingTimeInterval(-historicScanTipoffInterval) + let glucoseRecordsToUpload: [OpenGlückGlucoseRecord] = ( + historics.map ({ + OpenGlückGlucoseRecord(timestamp: $0.timeStamp, mgDl: Int(round($0.calculatedValue)), recordType: "historic") + }) + scans.map ({ + OpenGlückGlucoseRecord(timestamp: $0.timeStamp, mgDl: Int(round($0.calculatedValue)), recordType: "scan") + }) + ).filter { + $0.timestamp >= uploadHistoricAfter + } + let modelName = "xdripswift" + let deviceId = UIDevice.current.identifierForVendor?.uuidString ?? "(unknown-ifv)" + let instantGlucoseRecords = bgReadingsToStore.map { bgReading in + OpenGlückInstantGlucoseRecord(timestamp: bgReading.timeStamp, mgDl: Int(round(bgReading.calculatedValue)), modelName: modelName, deviceId: deviceId) + } + Task { + if UserDefaults.standard.openGlückUploadEnabled { + if !glucoseRecordsToUpload.isEmpty { + do { + let timeStampLastReadingToUpload = glucoseRecordsToUpload.map { $0.timestamp }.max()! + let device = OpenGlückDevice(modelName: modelName, deviceId: deviceId) + _ = try await openGlückClient.upload(currentCgmProperties: CgmCurrentDeviceProperties(hasRealTime: true, realTimeInterval: 60), device: device, glucoseRecords: glucoseRecordsToUpload) + UserDefaults.standard.timeStampLatestOpenGlückBgReading = timeStampLastReadingToUpload + } catch { + trace("Could not upload to OpenGlück", log: self.log, category: ConstantsLog.categoryOpenGlückManager, type: .error, error.localizedDescription) + } + } + } else { + if !bgReadingsToStore.isEmpty { + let timeStampLastReadingToUpload = instantGlucoseRecords.map { $0.timestamp }.max()! + do { + let result = try await openGlückClient.upload(instantGlucoseRecords: instantGlucoseRecords) + if result.success { + UserDefaults.standard.timeStampLatestOpenGlückBgReading = timeStampLastReadingToUpload + } + } catch { + return + } + } + } + } + } + + public func getClient() -> OpenGlückClient { + return openGlückClient! + } + + /// whether we should dismiss a low notification — typically this happens when a low has been recorded in the last 30 minutes + public func shouldDismissLow(at: Date) async -> Bool { + guard let openGlückSyncClient else { return false } + + if let lastLowRecordAt, at >= lastLowRecordAt && at <= lastLowRecordAt.addingTimeInterval(dismissLowAfter) { + trace("shouldDismissLow => true (cache)", log: self.log, category: ConstantsLog.categoryOpenGlückManager, type: .info, "Low bg reading at \(at) should be dismissed because of a low at \(lastLowRecordAt)") + return true + } + + // get the latest low + do { + let latest = try await openGlückSyncClient.getLastData() + if let lows = latest.lowRecords { + lastLowRecordAt = lows + .filter { !$0.deleted } + .filter {at >= $0.timestamp && at <= $0.timestamp.addingTimeInterval(dismissLowAfter) } + .sorted(by: { $0.timestamp > $1.timestamp }) + .first?.timestamp + } + if let lastLowRecordAt, at >= lastLowRecordAt && at <= lastLowRecordAt.addingTimeInterval(dismissLowAfter) { + trace("shouldDismissLow => true (sync)", log: self.log, category: ConstantsLog.categoryOpenGlückManager, type: .info, "Low bg reading at \(at) should be dismissed because of a low at \(lastLowRecordAt)") + return true + } + } catch { + // ignore errors + trace("Caught error while syncing OpenGlück, ignoring", log: self.log, category: ConstantsLog.categoryOpenGlückManager, type: .error, error.localizedDescription) + } + + return false + } + + // MARK: - observe function + + /// when UserDefaults storeReadingsInHealthkitAuthorized or storeReadingsInHealthkit changes, then reinitialize the property openGlückInitialized + override public func observeValue(forKeyPath keyPath: String?, of _: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) { + if let keyPath = keyPath { + if let keyPathEnum = UserDefaults.Key(rawValue: keyPath) { + switch keyPathEnum { + case UserDefaults.Key.openGlückEnabled, UserDefaults.Key.openGlückUploadEnabled, UserDefaults.Key.openGlückHostname, UserDefaults.Key.openGlückToken: + openGlückInitialized = initializeOpenGlück() + storeBgReadings() + + default: + break + } + } + } + } +} diff --git a/xdrip/Texts/TextsSettingsView.swift b/xdrip/Texts/TextsSettingsView.swift index b3bd34d04..3f74d6518 100644 --- a/xdrip/Texts/TextsSettingsView.swift +++ b/xdrip/Texts/TextsSettingsView.swift @@ -425,6 +425,36 @@ class Texts_SettingsView { return NSLocalizedString("settingsviews_healthkit", tableName: filename, bundle: Bundle.main, value: "Write Data to Apple Health", comment: "healthkit settings, literally 'healthkit'") }() + // MARK: - Section OpenGlück + + static let sectionTitleOpenGlück: String = { + return NSLocalizedString("settingsviews_sectiontitleopenglück", tableName: filename, bundle: Bundle.main, value: "OpenGlück", comment: "openglück settings, section title") + }() + + static let labelOpenGlückEnabled = { + return NSLocalizedString("settingsviews_openGlückEnabled", tableName: filename, bundle: Bundle.main, value: "Upload To OpenGlück?", comment: "openglück settings, is it enabled") + }() + + static let labelOpenGlückUploadEnabled = { + return NSLocalizedString("settingsviews_openGlückUploadEnabled", tableName: filename, bundle: Bundle.main, value: "Upload Historic/Scan?", comment: "openglück settings, is it enabled to upload historic and scan records") + }() + + static let labelOpenGlückHostname = { + return NSLocalizedString("settingsviews_openGlückHostname", tableName: filename, bundle: Bundle.main, value: "Hostname:", comment: "openglück settings, hostname") + }() + + static let labelOpenGlückToken = { + return NSLocalizedString("settingsviews_openGlückToken", tableName: filename, bundle: Bundle.main, value: "Token:", comment: "openglück settings, token") + }() + + static let giveOpenGlückHostname = { + return NSLocalizedString("settingsviews_openGlückHostname", tableName: filename, bundle: Bundle.main, value: "Enter OpenGlück Hostname", comment: "openglück settings, pop up that asks user to enter openglück hostname") + }() + + static let giveOpenGlückToken = { + return NSLocalizedString("settingsviews_openGlückToken", tableName: filename, bundle: Bundle.main, value: "Enter OpenGlück Token", comment: "openglück settings, pop up that asks user to enter openglück token") + }() + // MARK: - Section Dexcom Share static let sectionTitleDexcomShare: String = { diff --git a/xdrip/View Controllers/Root View Controller/RootViewController.swift b/xdrip/View Controllers/Root View Controller/RootViewController.swift index 0b39adf58..858180b68 100644 --- a/xdrip/View Controllers/Root View Controller/RootViewController.swift +++ b/xdrip/View Controllers/Root View Controller/RootViewController.swift @@ -524,6 +524,9 @@ final class RootViewController: UIViewController, ObservableObject { /// HealthKit manager instance private var healthKitManager:HealthKitManager? + /// OpenGlück manager instance + private var openGlückManager:OpenGlückManager? + /// reference to activeSensor private var activeSensor:Sensor? @@ -1114,7 +1117,7 @@ final class RootViewController: UIViewController, ObservableObject { } } - // creates activeSensor, bgreadingsAccessor, calibrationsAccessor, NightscoutSyncManager, soundPlayer, dexcomShareUploadManager, nightscoutFollowManager, alertManager, healthKitManager, bgReadingSpeaker, bluetoothPeripheralManager, calendarManager, housekeeper, contactImageManager + // creates activeSensor, bgreadingsAccessor, calibrationsAccessor, NightscoutSyncManager, soundPlayer, dexcomShareUploadManager, nightscoutFollowManager, alertManager, healthKitManager, openGlückManager, bgReadingSpeaker, bluetoothPeripheralManager, calendarManager, housekeeper, contactImageManager private func setupApplicationData() { // setup Trace @@ -1170,6 +1173,9 @@ final class RootViewController: UIViewController, ObservableObject { // setup healthkitmanager healthKitManager = HealthKitManager(coreDataManager: coreDataManager) + // setup openGlückManager + openGlückManager = OpenGlückManager(coreDataManager: coreDataManager) + // setup bgReadingSpeaker bgReadingSpeaker = BGReadingSpeaker(sharedSoundPlayer: soundPlayer, coreDataManager: coreDataManager) @@ -1261,7 +1267,7 @@ final class RootViewController: UIViewController, ObservableObject { cgmTransmitterInfoChanged() // setup alertmanager - alertManager = AlertManager(coreDataManager: coreDataManager, soundPlayer: soundPlayer) + alertManager = AlertManager(coreDataManager: coreDataManager, soundPlayer: soundPlayer, openGlückManager: openGlückManager) // setup calendarManager calendarManager = CalendarManager(coreDataManager: coreDataManager) @@ -1547,6 +1553,8 @@ final class RootViewController: UIViewController, ObservableObject { nightscoutSyncManager?.uploadLatestBgReadings(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp()) healthKitManager?.storeBgReadings() + + openGlückManager?.storeBgReadings() bgReadingSpeaker?.speakNewReading(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp()) @@ -4169,6 +4177,10 @@ extension RootViewController: FollowerDelegate { healthKitManager.storeBgReadings() } + if let openGlückManager = openGlückManager { + openGlückManager.storeBgReadings() + } + if let bgReadingSpeaker = bgReadingSpeaker { bgReadingSpeaker.speakNewReading(lastConnectionStatusChangeTimeStamp: lastConnectionStatusChangeTimeStamp()) } diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewController.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewController.swift index 8a292aeee..e98abed5e 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewController.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewController.swift @@ -53,6 +53,9 @@ final class SettingsViewController: UIViewController { /// healthkit case healthkit + /// openglück + case openGlück + /// store bg values in healthkit case speak @@ -101,6 +104,8 @@ final class SettingsViewController: UIViewController { return SettingsViewDexcomShareSettingsViewModel() case .healthkit: return SettingsViewHealthKitSettingsViewModel() + case .openGlück: + return SettingsViewOpenGlückSettingsViewModel() case .speak: return SettingsViewSpeakSettingsViewModel() case .M5stack: diff --git "a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewOpenGl\303\274ckSettingsViewModel.swift" "b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewOpenGl\303\274ckSettingsViewModel.swift" new file mode 100644 index 000000000..cf909b7a7 --- /dev/null +++ "b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewOpenGl\303\274ckSettingsViewModel.swift" @@ -0,0 +1,166 @@ +import UIKit + +fileprivate enum Setting:Int, CaseIterable { + case openGlückEnabled = 0 + case openGlückUploadEnabled = 3 + case openGlückHostname = 1 + case openGlückToken = 2 + +} + +/// conforms to SettingsViewModelProtocol for all OpenGlück settings in the first sections screen +class SettingsViewOpenGlückSettingsViewModel:SettingsViewModelProtocol { + + func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {} + + func storeUIViewController(uIViewController: UIViewController) {} + + func storeMessageHandler(messageHandler: ((String, String) -> Void)) { + // this ViewModel does need to send back messages to the viewcontroller asynchronously + } + + func completeSettingsViewRefreshNeeded(index: Int) -> Bool { + return true + } + + func isEnabled(index: Int) -> Bool { + + return true + + } + + func onRowSelect(index: Int) -> SettingsSelectedRowAction { + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + + case .openGlückEnabled, .openGlückUploadEnabled: + return SettingsSelectedRowAction.nothing + + case .openGlückHostname: + return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelOpenGlückHostname, message: Texts_SettingsView.giveOpenGlückHostname, keyboardType: UIKeyboardType.alphabet, text: UserDefaults.standard.openGlückHostname, placeHolder: nil, actionTitle: nil, cancelTitle: nil, actionHandler: {(hostname:String) in UserDefaults.standard.openGlückHostname = hostname.toNilIfLength0()}, cancelHandler: nil, inputValidator: nil) + + case .openGlückToken: + return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelOpenGlückToken, message: Texts_SettingsView.giveOpenGlückToken, keyboardType: UIKeyboardType.alphabet, text: UserDefaults.standard.openGlückToken, placeHolder: nil, actionTitle: nil, cancelTitle: nil, actionHandler: {(token:String) in UserDefaults.standard.openGlückToken = token.toNilIfLength0()}, cancelHandler: nil, inputValidator: nil) + + } + } + + func sectionTitle() -> String? { + return Texts_SettingsView.sectionTitleOpenGlück + } + + func numberOfRows() -> Int { + + if !UserDefaults.standard.openGlückEnabled { + return 1 + } + else { + return Setting.allCases.count + } + } + + func settingsRowText(index: Int) -> String { + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + case .openGlückEnabled: + return Texts_SettingsView.labelOpenGlückEnabled + case .openGlückUploadEnabled: + return Texts_SettingsView.labelOpenGlückUploadEnabled + case .openGlückHostname: + return Texts_SettingsView.labelOpenGlückHostname + case .openGlückToken: + return Texts_SettingsView.labelOpenGlückToken + } + } + + func accessoryType(index: Int) -> UITableViewCell.AccessoryType { + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + case .openGlückEnabled: + return UITableViewCell.AccessoryType.none + case .openGlückUploadEnabled: + return UITableViewCell.AccessoryType.none + case .openGlückHostname: + return UITableViewCell.AccessoryType.disclosureIndicator + case .openGlückToken: + return UITableViewCell.AccessoryType.disclosureIndicator + } + } + + func detailedText(index: Int) -> String? { + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + case .openGlückEnabled, .openGlückUploadEnabled: + return nil + case .openGlückHostname: + return UserDefaults.standard.openGlückHostname + case .openGlückToken: + return UserDefaults.standard.openGlückToken != nil ? "***********" : nil + } + } + + func uiView(index:Int) -> UIView? { + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + case .openGlückEnabled: + return UISwitch(isOn: UserDefaults.standard.openGlückEnabled, action: {(isOn:Bool) in UserDefaults.standard.openGlückEnabled = isOn}) + case .openGlückUploadEnabled: + return UISwitch(isOn: UserDefaults.standard.openGlückUploadEnabled, action: {(isOn:Bool) in UserDefaults.standard.openGlückUploadEnabled = isOn}) + case .openGlückHostname: + return nil + case .openGlückToken: + return nil + } + } +} +/* +extension SettingsViewDexcomSettingsViewModel: TimeSchedule { + + func serviceName() -> String { + return "OpenGlück" + } + + func getSchedule() -> [Int] { + + var schedule = [Int]() + + if let scheduleInSettings = UserDefaults.standard.dexcomShareSchedule { + + schedule = scheduleInSettings.split(separator: "-").map({Int($0) ?? 0}) + + } + + return schedule + + } + + func storeSchedule(schedule: [Int]) { + + var scheduleToStore: String? + + for entry in schedule { + + if scheduleToStore == nil { + + scheduleToStore = entry.description + + } else { + + scheduleToStore = scheduleToStore! + "-" + entry.description + + } + + } + + UserDefaults.standard.dexcomShareSchedule = scheduleToStore + + } + + +}*/ + From 5656d5c90ad67898d157751b6c9199a33469832b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20All=C3=A8ne?= Date: Sat, 16 Mar 2024 09:15:12 +0100 Subject: [PATCH 2/8] rename hk to og --- .../OpenGl\303\274ckManager.swift" | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git "a/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" "b/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" index ef30af55b..9ecf6de5b 100644 --- "a/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" +++ "b/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" @@ -19,7 +19,7 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { /// reference to BgReadingsAccessor private var bgReadingsAccessor: BgReadingsAccessor - /// is OpenGlück fully initiazed or not, that includes checking if healthkit is available, created successfully bloodGlucoseType, user authorized - value will get changed + /// is OpenGlück fully initiazed or not, that includes checking if OpenGlück is available, created successfully bloodGlucoseType, user authorized - value will get changed private var openGlückInitialized = false /// reference to the OpenGlück client, should be used only if we're sure OpenGlück is supported on the device @@ -100,10 +100,9 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { return (historics, scans) } - /// stores latest readings in healthkit, only if HK supported, authorized, enabled in settings + /// stores latest readings in OpenGlück, only if OG supported, authorized, enabled in settings public func storeBgReadings() { - // TODO: - // healthkit setting must be on, and healthkit must be initialized successfully + // OpenGlück setting must be on, and OG must be initialized successfully if !UserDefaults.standard.openGlückEnabled || !openGlückInitialized { return } guard let openGlückClient = openGlückClient else { return } @@ -200,7 +199,7 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { // MARK: - observe function - /// when UserDefaults storeReadingsInHealthkitAuthorized or storeReadingsInHealthkit changes, then reinitialize the property openGlückInitialized + /// when UserDefaults openGlückEnabled, openGlückUploadEnabled or openGlückToken changes, then reinitialize the property openGlückInitialized override public func observeValue(forKeyPath keyPath: String?, of _: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) { if let keyPath = keyPath { if let keyPathEnum = UserDefaults.Key(rawValue: keyPath) { From 13beeb82eb060d3bc39558b366b0a9d36b8ee834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20All=C3=A8ne?= Date: Sat, 16 Mar 2024 10:33:14 +0100 Subject: [PATCH 3/8] Update OG repository URL --- xdrip.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 2cc33a123..96d9a9ccc 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -6070,7 +6070,7 @@ }; 9D06FF5E2A76EC0000ECEA9B /* XCRemoteSwiftPackageReference "OG" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/callms/OG"; + repositoryURL = "https://github.com/open-gluck/OG"; requirement = { kind = upToNextMajorVersion; minimumVersion = 1.0.65; From 8ebeead40fe103428932e1c7f58e20d5816bcebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20All=C3=A8ne?= Date: Sat, 16 Mar 2024 10:41:52 +0100 Subject: [PATCH 4/8] update code for latest OG library --- xdrip.xcodeproj/project.pbxproj | 14 +++++++------- .../OpenGl\303\274ckManager.swift" | 7 +++++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 96d9a9ccc..0214eb365 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -222,11 +222,11 @@ 47F7B1CD2C68CC3000609DA7 /* ConstantsCalibrationAlgorithms.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1585422EDB706007F5B5D /* ConstantsCalibrationAlgorithms.swift */; }; 47FB28082636B04200042FFB /* StatisticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FB28072636B04200042FFB /* StatisticsManager.swift */; }; 9D06FF5B2A76EB1600ECEA9B /* (null) in Frameworks */ = {isa = PBXBuildFile; }; - 9D06FF602A76EC0000ECEA9B /* DiablyLib in Frameworks */ = {isa = PBXBuildFile; productRef = 9D06FF5F2A76EC0000ECEA9B /* DiablyLib */; }; 9D06FF622A76EC0000ECEA9B /* OG in Frameworks */ = {isa = PBXBuildFile; productRef = 9D06FF612A76EC0000ECEA9B /* OG */; }; 9D67DF752A6DBEDC009A15DD /* SettingsViewOpenGlückSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D67DF742A6DBEDC009A15DD /* SettingsViewOpenGlückSettingsViewModel.swift */; }; 9D67DF7A2A6DC365009A15DD /* OpenGlückManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D67DF792A6DC365009A15DD /* OpenGlückManager.swift */; }; 9D67DF7D2A6DC603009A15DD /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + 9DBB1A4A2BA5A02D004B255A /* OGUI in Frameworks */ = {isa = PBXBuildFile; productRef = 9DBB1A492BA5A02D004B255A /* OGUI */; }; CE1B2FE025D0264B00F642F5 /* LaunchScreen.strings in Resources */ = {isa = PBXBuildFile; fileRef = CE1B2FD125D0264900F642F5 /* LaunchScreen.strings */; }; CE1B2FE125D0264B00F642F5 /* Main.strings in Resources */ = {isa = PBXBuildFile; fileRef = CE1B2FD425D0264900F642F5 /* Main.strings */; }; D400F8032778BD8000B57648 /* TextsTreatmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D400F8022778BD8000B57648 /* TextsTreatmentsView.swift */; }; @@ -1951,7 +1951,7 @@ 9D67DF7D2A6DC603009A15DD /* (null) in Frameworks */, 4779BCEE2974306300515714 /* ActionClosurable in Frameworks */, F821CF9722AE589E005C1E43 /* HealthKit.framework in Frameworks */, - 9D06FF602A76EC0000ECEA9B /* DiablyLib in Frameworks */, + 9DBB1A4A2BA5A02D004B255A /* OGUI in Frameworks */, 9D06FF5B2A76EB1600ECEA9B /* (null) in Frameworks */, 4779BCF42974308F00515714 /* PieCharts in Frameworks */, 4779BCF12974307700515714 /* CryptoSwift in Frameworks */, @@ -3964,8 +3964,8 @@ 4779BCF02974307700515714 /* CryptoSwift */, 4779BCF32974308F00515714 /* PieCharts */, 470824D1297484B500C52317 /* SwiftCharts */, - 9D06FF5F2A76EC0000ECEA9B /* DiablyLib */, 9D06FF612A76EC0000ECEA9B /* OG */, + 9DBB1A492BA5A02D004B255A /* OGUI */, ); productName = xdrip; productReference = F8AC425A21ADEBD60078C348 /* xdrip.app */; @@ -6099,15 +6099,15 @@ package = 4779BCF22974308F00515714 /* XCRemoteSwiftPackageReference "PieCharts" */; productName = PieCharts; }; - 9D06FF5F2A76EC0000ECEA9B /* DiablyLib */ = { + 9D06FF612A76EC0000ECEA9B /* OG */ = { isa = XCSwiftPackageProductDependency; package = 9D06FF5E2A76EC0000ECEA9B /* XCRemoteSwiftPackageReference "OG" */; - productName = DiablyLib; + productName = OG; }; - 9D06FF612A76EC0000ECEA9B /* OG */ = { + 9DBB1A492BA5A02D004B255A /* OGUI */ = { isa = XCSwiftPackageProductDependency; package = 9D06FF5E2A76EC0000ECEA9B /* XCRemoteSwiftPackageReference "OG" */; - productName = OG; + productName = OGUI; }; /* End XCSwiftPackageProductDependency section */ diff --git "a/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" "b/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" index 9ecf6de5b..bb9bcab79 100644 --- "a/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" +++ "b/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" @@ -73,6 +73,10 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { return true } + public func getClient() -> OpenGlückClient? { + openGlückClient + } + let intervalBetweenHistoricRecords: TimeInterval = 5 * 60 // 5m let historicScanTipoffInterval: TimeInterval = 20 * 60 // 20m private func splitRecordsByHistoricScan(_ readings: [BgReading]) -> ([BgReading], [BgReading]) { @@ -177,8 +181,7 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { // get the latest low do { - let latest = try await openGlückSyncClient.getLastData() - if let lows = latest.lowRecords { + if let latest = try await openGlückSyncClient.getLastData(), let lows = latest.lowRecords { lastLowRecordAt = lows .filter { !$0.deleted } .filter {at >= $0.timestamp && at <= $0.timestamp.addingTimeInterval(dismissLowAfter) } From 7c91a4996ce1447002633bae282a0dcd0848e15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20All=C3=A8ne?= Date: Sat, 8 Jun 2024 08:44:48 +0200 Subject: [PATCH 5/8] improve comment regarding how we upload instant glucose records --- .../OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" | 2 ++ 1 file changed, 2 insertions(+) diff --git "a/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" "b/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" index bb9bcab79..704142de8 100644 --- "a/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" +++ "b/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" @@ -141,6 +141,8 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { Task { if UserDefaults.standard.openGlückUploadEnabled { if !glucoseRecordsToUpload.isEmpty { + // we don't need to specifically upload instant glucose records as this will update the latest scan record, + // and all records uploaded here also end up in the instant glucose records do { let timeStampLastReadingToUpload = glucoseRecordsToUpload.map { $0.timestamp }.max()! let device = OpenGlückDevice(modelName: modelName, deviceId: deviceId) From 583f33239f214dbd81ceafc222c6601a76921e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20All=C3=A8ne?= Date: Sat, 8 Jun 2024 10:21:40 +0200 Subject: [PATCH 6/8] feat: add icon to OG section in settings --- xdrip/Constants/ConstantsSettingsIcons.swift | 1 + .../SettingsViewOpenGl\303\274ckSettingsViewModel.swift" | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/xdrip/Constants/ConstantsSettingsIcons.swift b/xdrip/Constants/ConstantsSettingsIcons.swift index 67dfc33e8..8fd2bc15c 100644 --- a/xdrip/Constants/ConstantsSettingsIcons.swift +++ b/xdrip/Constants/ConstantsSettingsIcons.swift @@ -18,6 +18,7 @@ enum ConstantsSettingsIcons { static let dataSourceSettingsIcon: String = "➡️ " static let developerSettingsIcon: String = "👨🏻‍💻 " static let dexcomSettingsIcon: String = "⬆️ " + static let openGlückSettingsIcon: String = "⬆️ " static let healthKitSettingsIcon: String = "❤️ " static let helpSettingsIcon: String = "📖 " static let homeScreenSettingsIcon: String = "📈 " diff --git "a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewOpenGl\303\274ckSettingsViewModel.swift" "b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewOpenGl\303\274ckSettingsViewModel.swift" index cf909b7a7..29ee11e76 100644 --- "a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewOpenGl\303\274ckSettingsViewModel.swift" +++ "b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewOpenGl\303\274ckSettingsViewModel.swift" @@ -47,7 +47,7 @@ class SettingsViewOpenGlückSettingsViewModel:SettingsViewModelProtocol { } func sectionTitle() -> String? { - return Texts_SettingsView.sectionTitleOpenGlück + return ConstantsSettingsIcons.openGlückSettingsIcon + " " + Texts_SettingsView.sectionTitleOpenGlück } func numberOfRows() -> Int { From 068aa45787895d49613c2911e4b19f0df51609c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20All=C3=A8ne?= Date: Fri, 21 Jun 2024 18:23:05 +0200 Subject: [PATCH 7/8] =?UTF-8?q?improve=20store=20only=20historic=20records?= =?UTF-8?q?=20in=20timeStampLatestOpenGl=C3=BCckBgReading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OpenGl\303\274ckManager.swift" | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git "a/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" "b/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" index 704142de8..c4b7b419f 100644 --- "a/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" +++ "b/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" @@ -144,10 +144,15 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { // we don't need to specifically upload instant glucose records as this will update the latest scan record, // and all records uploaded here also end up in the instant glucose records do { - let timeStampLastReadingToUpload = glucoseRecordsToUpload.map { $0.timestamp }.max()! + let timeStampLastReadingToUpload = glucoseRecordsToUpload.filter { $0.recordType == "historic" }.map { $0.timestamp }.max()! let device = OpenGlückDevice(modelName: modelName, deviceId: deviceId) _ = try await openGlückClient.upload(currentCgmProperties: CgmCurrentDeviceProperties(hasRealTime: true, realTimeInterval: 60), device: device, glucoseRecords: glucoseRecordsToUpload) - UserDefaults.standard.timeStampLatestOpenGlückBgReading = timeStampLastReadingToUpload + await MainActor.run { + let currentLatest = UserDefaults.standard.timeStampLatestOpenGlückBgReading + if currentLatest == nil || timeStampLastReadingToUpload > currentLatest! { + UserDefaults.standard.timeStampLatestOpenGlückBgReading = timeStampLastReadingToUpload + } + } } catch { trace("Could not upload to OpenGlück", log: self.log, category: ConstantsLog.categoryOpenGlückManager, type: .error, error.localizedDescription) } From 42ceb72d1a7808a03eac07aa924b2db1d181b05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20All=C3=A8ne?= Date: Sat, 29 Jun 2024 09:26:30 +0200 Subject: [PATCH 8/8] update OG/OGUI code to latest version --- xdrip.xcodeproj/project.pbxproj | 1 - .../OpenGl\303\274ckManager.swift" | 29 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 0214eb365..53dfd38d3 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -198,7 +198,6 @@ 47DB06E32A7137B000267BE3 /* LibreLinkUpFollowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47DB06E22A7137B000267BE3 /* LibreLinkUpFollowManager.swift */; }; 47DB06E72A715EC500267BE3 /* ConstantsLibreLinkUp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47DB06E62A715EC500267BE3 /* ConstantsLibreLinkUp.swift */; }; 47DB06E92A715FD900267BE3 /* LibreLinkUpModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47DB06E82A715FD900267BE3 /* LibreLinkUpModels.swift */; }; - 47DE41AA2B860DF00041DA19 /* WatchConnectivity.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47DE41A92B860DF00041DA19 /* WatchConnectivity.framework */; }; 47DE41AD2B863D370041DA19 /* WatchState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47DE41AC2B863D370041DA19 /* WatchState.swift */; }; 47DE41AE2B863D370041DA19 /* WatchState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47DE41AC2B863D370041DA19 /* WatchState.swift */; }; 47DE41AF2B864EE50041DA19 /* xDrip Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 47A6ABDF2B790CC60047A4BA /* xDrip Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; diff --git "a/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" "b/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" index c4b7b419f..8a9a56d5c 100644 --- "a/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" +++ "b/xdrip/Managers/OpenGl\303\274ckManager/OpenGl\303\274ckManager.swift" @@ -2,7 +2,7 @@ import Foundation import OG import os -public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { +public class OpenGlückManager: NSObject, OpenGluckSyncClientDelegate { // MARK: - public properties // MARK: - private properties @@ -23,8 +23,8 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { private var openGlückInitialized = false /// reference to the OpenGlück client, should be used only if we're sure OpenGlück is supported on the device - private var openGlückClient: OpenGlückClient? - private var openGlückSyncClient: OpenGlückSyncClient? + private var openGlückClient: OpenGluckClient? + private var openGlückSyncClient: OpenGluckSyncClient? /// dismisses low notifications 30m after low record private let dismissLowAfter: TimeInterval = 30 * 60 // 30m @@ -65,15 +65,18 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { openGlückSyncClient = nil guard UserDefaults.standard.openGlückEnabled, let openGlückHostname = UserDefaults.standard.openGlückHostname, let openGlückToken = UserDefaults.standard.openGlückToken else { return false } - openGlückClient = OpenGlückClient(hostname: openGlückHostname, token: openGlückToken, target: "xdripswift") - openGlückSyncClient = OpenGlückSyncClient() - openGlückSyncClient!.delegate = self + openGlückClient = OpenGluckClient(hostname: openGlückHostname, token: openGlückToken, target: "xdripswift") + Task { + let openGlückSyncClient = OpenGluckSyncClient() + await openGlückSyncClient.setDelegate(self) + self.openGlückSyncClient = openGlückSyncClient + } // all checks ok , return true return true } - public func getClient() -> OpenGlückClient? { + public func getClient() -> OpenGluckClient? { openGlückClient } @@ -124,11 +127,11 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { // reupload at least 4 historic records + scans let uploadHistoricAfter = (UserDefaults.standard.timeStampLatestOpenGlückBgReading ?? Date().addingTimeInterval(-86400)).addingTimeInterval(-historicScanTipoffInterval) - let glucoseRecordsToUpload: [OpenGlückGlucoseRecord] = ( + let glucoseRecordsToUpload: [OpenGluckGlucoseRecord] = ( historics.map ({ - OpenGlückGlucoseRecord(timestamp: $0.timeStamp, mgDl: Int(round($0.calculatedValue)), recordType: "historic") + OpenGluckGlucoseRecord(timestamp: $0.timeStamp, mgDl: Int(round($0.calculatedValue)), recordType: "historic") }) + scans.map ({ - OpenGlückGlucoseRecord(timestamp: $0.timeStamp, mgDl: Int(round($0.calculatedValue)), recordType: "scan") + OpenGluckGlucoseRecord(timestamp: $0.timeStamp, mgDl: Int(round($0.calculatedValue)), recordType: "scan") }) ).filter { $0.timestamp >= uploadHistoricAfter @@ -136,7 +139,7 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { let modelName = "xdripswift" let deviceId = UIDevice.current.identifierForVendor?.uuidString ?? "(unknown-ifv)" let instantGlucoseRecords = bgReadingsToStore.map { bgReading in - OpenGlückInstantGlucoseRecord(timestamp: bgReading.timeStamp, mgDl: Int(round(bgReading.calculatedValue)), modelName: modelName, deviceId: deviceId) + OpenGluckInstantGlucoseRecord(timestamp: bgReading.timeStamp, mgDl: Int(round(bgReading.calculatedValue)), modelName: modelName, deviceId: deviceId) } Task { if UserDefaults.standard.openGlückUploadEnabled { @@ -145,7 +148,7 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { // and all records uploaded here also end up in the instant glucose records do { let timeStampLastReadingToUpload = glucoseRecordsToUpload.filter { $0.recordType == "historic" }.map { $0.timestamp }.max()! - let device = OpenGlückDevice(modelName: modelName, deviceId: deviceId) + let device = OpenGluckDevice(modelName: modelName, deviceId: deviceId) _ = try await openGlückClient.upload(currentCgmProperties: CgmCurrentDeviceProperties(hasRealTime: true, realTimeInterval: 60), device: device, glucoseRecords: glucoseRecordsToUpload) await MainActor.run { let currentLatest = UserDefaults.standard.timeStampLatestOpenGlückBgReading @@ -173,7 +176,7 @@ public class OpenGlückManager: NSObject, OpenGlückSyncClientDelegate { } } - public func getClient() -> OpenGlückClient { + public func getClient() -> OpenGluckClient { return openGlückClient! }