Bladeren bron

Merge PR from scrappy and more. (#501)

* correction for Apple Watch

(cherry picked from commit 47f950334d9febbd8cb8d39ec7ac7ab5de61f680)

* project management correction

* Redo Statistics. Decrease storage of glucose to 10 days.

* Test Scrappy's PR. Slightly modified.

* Add success rate and average loop cycle interval to dailyStats

Co-authored-by: avouspierre <pn.lagarde@gmail.com>
Co-authored by: scrappy <scrappy@hub.org>
Jon B Mårtensson 3 jaren geleden
bovenliggende
commit
6983634d61
25 gewijzigde bestanden met toevoegingen van 489 en 44 verwijderingen
  1. 62 0
      Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockPumpManagerDelegate.swift
  2. 35 0
      Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockPumpOps.swift
  3. 75 0
      Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockRileyLinkDevice.swift
  4. 55 0
      Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockRileyLinkProvider.swift
  5. 12 0
      FreeAPS.xcodeproj/project.pbxproj
  6. 1 1
      FreeAPS.xcworkspace/xcshareddata/swiftpm/Package.resolved
  7. BIN
      FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon117x117@2x.png
  8. BIN
      FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon129x129@2x.png
  9. BIN
      FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon33x33@2x.png
  10. BIN
      FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon46x46@2x.png
  11. BIN
      FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon51x51@2x.png
  12. BIN
      FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon54x54@2x.png
  13. 1 0
      FreeAPS/Resources/json/defaults/monitor/dailyStats.json
  14. 1 0
      FreeAPS/Resources/json/defaults/monitor/loopStats.json
  15. 1 0
      FreeAPS/Resources/json/defaults/monitor/tenDaysStats.json
  16. 156 39
      FreeAPS/Sources/APS/APSManager.swift
  17. 2 0
      FreeAPS/Sources/APS/OpenAPS/Constants.swift
  18. 1 1
      FreeAPS/Sources/APS/Storage/GlucoseStorage.swift
  19. 22 0
      FreeAPS/Sources/Application/FreeAPSApp.swift
  20. 1 1
      FreeAPS/Sources/Config/Config.swift
  21. 5 1
      FreeAPS/Sources/Models/DailyStats.swift
  22. 21 0
      FreeAPS/Sources/Models/LoopStats.swift
  23. 21 0
      FreeAPS/Sources/Models/TenDaysStats.swift
  24. 3 1
      FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift
  25. 14 0
      FreeAPSWatch/Assets.xcassets/AppIcon.appiconset/Contents.json

+ 62 - 0
Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockPumpManagerDelegate.swift

@@ -0,0 +1,62 @@
+//
+//  MockPumpManagerDelegate.swift
+//  MinimedKitTests
+//
+//  Created by Pete Schwamb on 9/5/22.
+//  Copyright © 2022 Pete Schwamb. All rights reserved.
+//
+
+import Foundation
+import LoopKit
+
+class MockPumpManagerDelegate: PumpManagerDelegate {
+
+    func pumpManagerBLEHeartbeatDidFire(_ pumpManager: PumpManager) {}
+
+    func pumpManagerMustProvideBLEHeartbeat(_ pumpManager: PumpManager) -> Bool {
+        return false
+    }
+
+    func pumpManagerWillDeactivate(_ pumpManager: PumpManager) {}
+
+    func pumpManagerPumpWasReplaced(_ pumpManager: PumpManager) {}
+
+    func pumpManager(_ pumpManager: PumpManager, didUpdatePumpRecordsBasalProfileStartEvents pumpRecordsBasalProfileStartEvents: Bool) {}
+
+    func pumpManager(_ pumpManager: PumpManager, didError error: PumpManagerError) {}
+
+    var reportedPumpEvents: [(events: [NewPumpEvent], lastReconciliation: Date?)] = []
+    
+    func pumpManager(_ pumpManager: PumpManager, hasNewPumpEvents events: [NewPumpEvent], lastReconciliation: Date?, completion: @escaping (Error?) -> Void) {
+        reportedPumpEvents.append((events: events, lastReconciliation: lastReconciliation))
+    }
+
+    func pumpManager(_ pumpManager: PumpManager, didReadReservoirValue units: Double, at date: Date, completion: @escaping (Result<(newValue: ReservoirValue, lastValue: ReservoirValue?, areStoredValuesContinuous: Bool), Error>) -> Void) {}
+
+    func pumpManager(_ pumpManager: PumpManager, didAdjustPumpClockBy adjustment: TimeInterval) {}
+
+    func pumpManagerDidUpdateState(_ pumpManager: PumpManager) {}
+
+    func startDateToFilterNewPumpEvents(for manager: PumpManager) -> Date {
+        return Date()
+    }
+
+    var detectedSystemTimeOffset: TimeInterval = 0
+
+    func deviceManager(_ manager: DeviceManager, logEventForDeviceIdentifier deviceIdentifier: String?, type: DeviceLogEntryType, message: String, completion: ((Error?) -> Void)?) {}
+
+    func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus: PumpManagerStatus) {}
+
+    func issueAlert(_ alert: Alert) {}
+
+    func retractAlert(identifier: Alert.Identifier) {}
+
+    func doesIssuedAlertExist(identifier: Alert.Identifier, completion: @escaping (Result<Bool, Error>) -> Void) {}
+
+    func lookupAllUnretracted(managerIdentifier: String, completion: @escaping (Result<[PersistedAlert], Error>) -> Void) {}
+
+    func lookupAllUnacknowledgedUnretracted(managerIdentifier: String, completion: @escaping (Result<[PersistedAlert], Error>) -> Void) {}
+
+    func recordRetractedAlert(_ alert: Alert, at date: Date) {}
+
+}

+ 35 - 0
Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockPumpOps.swift

@@ -0,0 +1,35 @@
+//
+//  MockPumpOps.swift
+//  MinimedKitTests
+//
+//  Created by Pete Schwamb on 9/5/22.
+//  Copyright © 2022 Pete Schwamb. All rights reserved.
+//
+
+import Foundation
+import MinimedKit
+import RileyLinkBLEKit
+
+class MockPumpOps: PumpOps, PumpOpsSessionDelegate {
+
+    var pumpState: PumpState
+
+    var pumpSettings: PumpSettings
+
+    func pumpOpsSession(_ session: MinimedKit.PumpOpsSession, didChange state: MinimedKit.PumpState) {
+        pumpState = state
+    }
+
+    func pumpOpsSessionDidChangeRadioConfig(_ session: MinimedKit.PumpOpsSession) { }
+
+    public func runSession(withName name: String, using device: RileyLinkDevice, _ block: @escaping (_ session: PumpOpsSession) -> Void) {
+        let minimedPumpMessageSender = MockPumpMessageSender()
+        let session = PumpOpsSession(settings: self.pumpSettings, pumpState: self.pumpState, messageSender: minimedPumpMessageSender, delegate: self)
+        block(session)
+    }
+
+    init(pumpState: PumpState, pumpSettings: PumpSettings) {
+        self.pumpState = pumpState
+        self.pumpSettings = pumpSettings
+    }
+}

+ 75 - 0
Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockRileyLinkDevice.swift

@@ -0,0 +1,75 @@
+//
+//  MockRileyLinkDevice.swift
+//  MinimedKitTests
+//
+//  Created by Pete Schwamb on 9/5/22.
+//  Copyright © 2022 Pete Schwamb. All rights reserved.
+//
+
+import Foundation
+import RileyLinkBLEKit
+import CoreBluetooth
+
+class MockRileyLinkDevice: RileyLinkDevice {
+    var isConnected: Bool = true
+
+    var rlFirmwareDescription: String = "Mock"
+
+    var hasOrangeLinkService: Bool = false
+
+    var hardwareType: RileyLinkHardwareType? = .riley
+
+    var rssi: Int? = nil
+
+    var name: String? = "Mock"
+
+    var deviceURI: String =  "rileylink://Mock"
+
+    var peripheralIdentifier: UUID = UUID()
+
+    var peripheralState: CBPeripheralState = .connected
+
+    func readRSSI() {}
+
+    func setCustomName(_ name: String) {}
+
+    func updateBatteryLevel() {}
+
+    func orangeAction(_ command: OrangeLinkCommand) {}
+
+    func setOrangeConfig(_ config: OrangeLinkConfigurationSetting, isOn: Bool) {}
+
+    func orangeWritePwd() {}
+
+    func orangeClose() {}
+
+    func orangeReadSet() {}
+
+    func orangeReadVDC() {}
+
+    func findDevice() {}
+
+    func setDiagnosticeLEDModeForBLEChip(_ mode: RileyLinkLEDMode) {}
+
+    func readDiagnosticLEDModeForBLEChip(completion: @escaping (RileyLinkLEDMode?) -> Void) {}
+
+    func assertOnSessionQueue() {}
+
+    func sessionQueueAsyncAfter(deadline: DispatchTime, execute: @escaping () -> Void) {}
+
+    func runSession(withName name: String, _ block: @escaping (CommandSession) -> Void) {
+        assertionFailure("MockRileyLinkDevice.runSession should not be called during testing.  Use MockPumpOps for communication stubs.")
+    }
+
+    func getStatus(_ completion: @escaping (RileyLinkDeviceStatus) -> Void) {
+        completion(RileyLinkDeviceStatus(
+            lastIdle: Date(),
+            name: name,
+            version: rlFirmwareDescription,
+            ledOn: false,
+            vibrationOn: false,
+            voltage: 3.0,
+            battery: nil,
+            hasPiezo: false))
+    }
+}

+ 55 - 0
Dependencies/rileylink_ios/MinimedKitTests/Mocks/MockRileyLinkProvider.swift

@@ -0,0 +1,55 @@
+//
+//  MockRileyLinkProvider.swift
+//  MinimedKitTests
+//
+//  Created by Pete Schwamb on 9/5/22.
+//  Copyright © 2022 Pete Schwamb. All rights reserved.
+//
+
+import Foundation
+import RileyLinkBLEKit
+
+class MockRileyLinkProvider: RileyLinkDeviceProvider {
+
+    init(devices: [RileyLinkDevice]) {
+        self.devices = devices
+    }
+
+    var devices: [RileyLinkDevice]
+
+    var delegate: RileyLinkDeviceProviderDelegate?
+
+    var idleListeningState: RileyLinkBluetoothDevice.IdleListeningState = .disabled
+
+    var idleListeningEnabled: Bool = false
+
+    var timerTickEnabled: Bool = false
+
+    var connectingCount: Int = 0
+
+    func deprioritize(_ device: RileyLinkDevice, completion: (() -> Void)?) {
+    }
+
+    func assertIdleListening(forcingRestart: Bool) {
+    }
+
+    func getDevices(_ completion: @escaping ([RileyLinkDevice]) -> Void) {
+        completion(devices)
+    }
+
+    func connect(_ device: RileyLinkDevice) {
+    }
+
+    func disconnect(_ device: RileyLinkDevice) {
+    }
+
+    func setScanningEnabled(_ enabled: Bool) {
+    }
+
+    func shouldConnect(to deviceID: String) -> Bool {
+        return false
+    }
+
+    var debugDescription: String = "MockRileyLinkProvider"
+
+}

+ 12 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -13,8 +13,10 @@
 		0D9A5E34A899219C5C4CDFAF /* DataTableStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9455FA2D92E77A6C4AFED8A3 /* DataTableStateModel.swift */; };
 		0F7A65FBD2CD8D6477ED4539 /* NotificationsConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E625985B47742D498CB1681A /* NotificationsConfigProvider.swift */; };
 		17A9D0899046B45E87834820 /* CREditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C8D5F457B5AFF763F8CF3DF /* CREditorProvider.swift */; };
+		19012CDC291D2CB900FB8210 /* LoopStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19012CDB291D2CB900FB8210 /* LoopStats.swift */; };
 		1927C8E62744606D00347C69 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1927C8E82744606D00347C69 /* InfoPlist.strings */; };
 		1935364028496F7D001E0B16 /* TDD_averages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1935363F28496F7D001E0B16 /* TDD_averages.swift */; };
+		193B6FAA291AB9AC0087410F /* TenDaysStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193B6FA9291AB9AC0087410F /* TenDaysStats.swift */; };
 		19795118275953E50044850D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 198377D4266BFFF6004DE65E /* Localizable.strings */; };
 		198377D2266BFFF6004DE65E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 198377D4266BFFF6004DE65E /* Localizable.strings */; };
 		199561C1275E61A50077B976 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 199561C0275E61A50077B976 /* HealthKit.framework */; };
@@ -288,6 +290,7 @@
 		CD78BB94E43B249D60CC1A1B /* NotificationsConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22963BD06A9C83959D4914E4 /* NotificationsConfigRootView.swift */; };
 		CE48C86428CA69D5007C0598 /* OmniBLEPumpManagerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE48C86328CA69D5007C0598 /* OmniBLEPumpManagerExtensions.swift */; };
 		CE48C86628CA6B48007C0598 /* OmniPodManagerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE48C86528CA6B48007C0598 /* OmniPodManagerExtensions.swift */; };
+		CE6B025728F350FF000C5502 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE6B025628F350FF000C5502 /* HealthKit.framework */; };
 		CE82E02528E867BA00473A9C /* AlertStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE82E02428E867BA00473A9C /* AlertStorage.swift */; };
 		CE82E02728E869DF00473A9C /* AlertEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE82E02628E869DF00473A9C /* AlertEntry.swift */; };
 		CEB434DC28B8F5B900B70274 /* MKRingProgressView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEB434DB28B8F5B900B70274 /* MKRingProgressView.framework */; };
@@ -415,6 +418,7 @@
 		0CA3E609094E064C99A4752C /* PreferencesEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorStateModel.swift; sourceTree = "<group>"; };
 		10A0C32B0DAB52726EF9B6D9 /* BolusRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusRootView.swift; sourceTree = "<group>"; };
 		12204445D7632AF09264A979 /* PreferencesEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorDataFlow.swift; sourceTree = "<group>"; };
+		19012CDB291D2CB900FB8210 /* LoopStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStats.swift; sourceTree = "<group>"; };
 		1918333A26ADA46800F45722 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
 		1927C8E92744611700347C69 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		1927C8EA2744611800347C69 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -437,6 +441,7 @@
 		1927C8FB2744612600347C69 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		1927C8FE274489BA00347C69 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		1935363F28496F7D001E0B16 /* TDD_averages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDD_averages.swift; sourceTree = "<group>"; };
+		193B6FA9291AB9AC0087410F /* TenDaysStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TenDaysStats.swift; sourceTree = "<group>"; };
 		198377D3266BFFF6004DE65E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
 		198377D5266C0A05004DE65E /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
 		198377D6266C0A0A004DE65E /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -720,6 +725,7 @@
 		C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusDataFlow.swift; sourceTree = "<group>"; };
 		CE48C86328CA69D5007C0598 /* OmniBLEPumpManagerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniBLEPumpManagerExtensions.swift; sourceTree = "<group>"; };
 		CE48C86528CA6B48007C0598 /* OmniPodManagerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniPodManagerExtensions.swift; sourceTree = "<group>"; };
+		CE6B025628F350FF000C5502 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS9.1.sdk/System/Library/Frameworks/HealthKit.framework; sourceTree = DEVELOPER_DIR; };
 		CE82E02428E867BA00473A9C /* AlertStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertStorage.swift; sourceTree = "<group>"; };
 		CE82E02628E869DF00473A9C /* AlertEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertEntry.swift; sourceTree = "<group>"; };
 		CEB434DB28B8F5B900B70274 /* MKRingProgressView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MKRingProgressView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -795,6 +801,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				CE6B025728F350FF000C5502 /* HealthKit.framework in Frameworks */,
 				38E8755827567AE400975559 /* SwiftDate in Frameworks */,
 				199561C1275E61A50077B976 /* HealthKit.framework in Frameworks */,
 			);
@@ -1164,6 +1171,7 @@
 		3818AA48274C267000843DB3 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				CE6B025628F350FF000C5502 /* HealthKit.framework */,
 				CEB434DE28B8F5C400B70274 /* OmniBLE.framework */,
 				CEB434DB28B8F5B900B70274 /* MKRingProgressView.framework */,
 				E0CC2C5B275B9DAE00A7BC71 /* HealthKit.framework */,
@@ -1330,6 +1338,8 @@
 				1935363F28496F7D001E0B16 /* TDD_averages.swift */,
 				CE82E02628E869DF00473A9C /* AlertEntry.swift */,
 				19B0EF2028F6D66200069496 /* DailyStats.swift */,
+				193B6FA9291AB9AC0087410F /* TenDaysStats.swift */,
+				19012CDB291D2CB900FB8210 /* LoopStats.swift */,
 			);
 			path = Models;
 			sourceTree = "<group>";
@@ -2292,6 +2302,7 @@
 				389ECDFE2601061500D86C4F /* View+Snapshot.swift in Sources */,
 				38FEF3FE2738083E00574A46 /* CGMProvider.swift in Sources */,
 				38E98A3725F5509500C0CED0 /* String+Extensions.swift in Sources */,
+				193B6FAA291AB9AC0087410F /* TenDaysStats.swift in Sources */,
 				F90692D1274B99B60037068D /* HealthKitProvider.swift in Sources */,
 				385CEAC125F2EA52002D6D5B /* Announcement.swift in Sources */,
 				8B759CFCF47B392BB365C251 /* BasalProfileEditorDataFlow.swift in Sources */,
@@ -2306,6 +2317,7 @@
 				CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */,
 				E00EEC0327368630002FF094 /* ServiceAssembly.swift in Sources */,
 				38192E07261BA9960094D973 /* FetchTreatmentsManager.swift in Sources */,
+				19012CDC291D2CB900FB8210 /* LoopStats.swift in Sources */,
 				6632A0DC746872439A858B44 /* ISFEditorDataFlow.swift in Sources */,
 				DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */,
 				1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */,

+ 1 - 1
FreeAPS.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -30,7 +30,7 @@
       },
       {
         "package": "SwiftCharts",
-        "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts.git",
+        "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts",
         "state": {
           "branch": "master",
           "revision": "c354c1945bb35a1f01b665b22474f6db28cba4a2",

BIN
FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon117x117@2x.png


BIN
FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon129x129@2x.png


BIN
FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon33x33@2x.png


BIN
FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon46x46@2x.png


BIN
FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon51x51@2x.png


BIN
FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon54x54@2x.png


+ 1 - 0
FreeAPS/Resources/json/defaults/monitor/dailyStats.json

@@ -0,0 +1 @@
+[]

+ 1 - 0
FreeAPS/Resources/json/defaults/monitor/loopStats.json

@@ -0,0 +1 @@
+[]

+ 1 - 0
FreeAPS/Resources/json/defaults/monitor/tenDaysStats.json

@@ -0,0 +1 @@
+[]

+ 156 - 39
FreeAPS/Sources/APS/APSManager.swift

@@ -215,9 +215,11 @@ final class BaseAPSManager: APSManager, Injectable {
         isLooping.send(false)
 
         if let error = error {
+            loopStats(error: error)
             warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
             processError(error)
         } else {
+            loopStats()
             debug(.apsManager, "Loop succeeded")
             lastLoopDate = Date()
             lastError.send(nil)
@@ -735,7 +737,7 @@ final class BaseAPSManager: APSManager, Injectable {
             }
         }
 
-        var algo_ = "oref0" //Default
+        var algo_ = "oref0" // Default
         if preferences.enableChris, preferences.useNewFormula {
             algo_ = "Dynamic ISF, Logarithmic Formula"
         } else if !preferences.useNewFormula, preferences.enableChris {
@@ -743,22 +745,13 @@ final class BaseAPSManager: APSManager, Injectable {
         }
         let af = preferences.adjustmentFactor
         let insulin_type = preferences.curve
-        var buildDate: Date {
-            if let infoPath = Bundle.main.path(forResource: "Info", ofType: "plist"),
-               let infoAttr = try? FileManager.default.attributesOfItem(atPath: infoPath),
-               let infoDate = infoAttr[.modificationDate] as? Date
-            {
-                return infoDate
-            }
-            return Date()
-        }
-        let nsObject: AnyObject? = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as AnyObject
-        let version = nsObject as! String
-        let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
+        let buildDate = Bundle.main.buildDate
+        let version = Bundle.main.releaseVersionNumber
+        let build = Bundle.main.buildVersionNumber
         let branch = Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String
         let pump_ = pumpManager?.localizedTitle ?? ""
         let cgm = settingsManager.settings.cgm
-        var file = OpenAPS.Monitor.dailyStats
+        let file = OpenAPS.Monitor.dailyStats
         var iPa: Decimal = 75
         if preferences.useCustomPeakTime {
             iPa = preferences.insulinPeakTime
@@ -768,6 +761,54 @@ final class BaseAPSManager: APSManager, Injectable {
             iPa = 50
         }
 
+        // Retrieve the loopStats data
+        let lsData = storage.retrieve(OpenAPS.Monitor.loopStats, as: [LoopStats].self)?
+            .sorted { $0.createdAt > $1.createdAt } ?? []
+        var successRate: Double?
+        var roundedMinutesBetweenLoops: Double?
+
+        if !lsData.isEmpty {
+            var i = 0.0
+            var successNR = 0.0
+            var errorNR = 0.0
+            for each in lsData {
+                i += 1
+                if each.loopStatus.contains("Success") {
+                    successNR += 1
+                } else {
+                    errorNR += 1
+                }
+            }
+            successRate = (successNR / Double(i)) * 100
+
+            let loopDataTime = lsData[0].createdAt - lsData[Int(i) - 1].createdAt
+            let minutesBetweenLoops = (loopDataTime.timeInterval / Double(i)) / 60
+            roundedMinutesBetweenLoops = round(minutesBetweenLoops * 10) / 10
+        }
+
+        // Retrieve the 10 days data array
+        var uniqEvents_1 = storage.retrieve(OpenAPS.Monitor.tenDaysStats, as: [TenDaysStats].self)?
+            .filter { $0.createdAt.addingTimeInterval(365.days.timeInterval) > Date() }
+            .sorted { $0.createdAt > $1.createdAt } ?? []
+
+        var index = 0
+        var total: Decimal = 0
+        var thirtyDays: Decimal = 0
+        var ninetyDays: Decimal = 0
+
+        for uniqEvent in uniqEvents_1 {
+            if uniqEvent.past10daysAverage != 0 {
+                total += uniqEvent.past10daysAverage
+                index += 1
+            }
+            if index == 3 {
+                thirtyDays = total / 3
+            }
+            if index == 9 {
+                ninetyDays = total / 9
+            }
+        }
+
         // HbA1c estimation (%, mmol/mol)
         let NGSPa1CStatisticValue = (46.7 + tir().averageGlucose_1) / 28.7 // NGSP (%)
         let IFCCa1CStatisticValue = 10.929 *
@@ -775,30 +816,31 @@ final class BaseAPSManager: APSManager, Injectable {
         // 7 days
         let NGSPa1CStatisticValue_7 = (46.7 + tir().averageGlucose_7) / 28.7
         let IFCCa1CStatisticValue_7 = 10.929 * (NGSPa1CStatisticValue_7 - 2.152)
-        // 14 days
-        let NGSPa1CStatisticValue_30 = (46.7 + tir().averageGlucose_30) / 28.7
+        // 30 days
+        let NGSPa1CStatisticValue_30 = (46.7 + thirtyDays) / 28.7
         let IFCCa1CStatisticValue_30 = 10.929 * (NGSPa1CStatisticValue_30 - 2.152)
-        // All days
+        // Total days (up t0 10 days)
         let NGSPa1CStatisticValue_total = (46.7 + tir().averageGlucose) / 28.7
         let IFCCa1CStatisticValue_total = 10.929 * (NGSPa1CStatisticValue_total - 2.152)
+        // 90 Days
+        let NGSPa1CStatisticValue_90 = (46.7 + ninetyDays) / 28.7
+        let IFCCa1CStatisticValue_90 = 10.929 * (NGSPa1CStatisticValue_90 - 2.152)
 
         // HbA1c string and BG string:
-
         var HbA1c_string_1 = ""
         var string7Days = ""
         var string30Days = ""
+        var string90Days = ""
         var stringTotal = ""
-
         var bgString1day = ""
         var bgString7Days = ""
         var bgString30Days = ""
+        var bgString90Days = ""
         var bgAverageTotalString = ""
-
+        
         let daysBG = tir().daysWithBG
-
         let avg1 = tir().averageGlucose_1
         let avg7 = tir().averageGlucose_7
-        let avg30 = tir().averageGlucose_30
         let avgTot = tir().averageGlucose
 
         if avg1 != 0 {
@@ -812,12 +854,19 @@ final class BaseAPSManager: APSManager, Injectable {
                 " HbA1c 7 days (mmol/mol): \(roundDecimal(IFCCa1CStatisticValue_7, 1)). HbA1c 7 days (%): \(roundDecimal(NGSPa1CStatisticValue_7, 1))."
             bgString7Days = " Average BG (mmol/l) 7 days: \(roundDecimal(avg7 * 0.0555, 1)). Average BG (mg/dl) 7 days: \(avg7)."
         }
-        if avg30 != 0 {
+        if thirtyDays != 0 {
             string30Days =
                 " HbA1c 30 days (mmol/mol): \(roundDecimal(IFCCa1CStatisticValue_30, 1)).  HbA1c 30 days (%): \(roundDecimal(NGSPa1CStatisticValue_30, 1))."
             bgString30Days =
-                " Average BG 30 days (mmol/l): \(roundDecimal(avg30 * 0.0555, 1)). Average BG 30 days (mg/dl): \(avg30). "
+                " Average BG 30 days (mmol/l): \(roundDecimal(thirtyDays * 0.0555, 1)). Average BG 30 days (mg/dl): \(roundDecimal(thirtyDays, 0)). "
+        }
+        if ninetyDays != 0 {
+            string90Days =
+                " HbA1c 90 days (mmol/mol): \(roundDecimal(IFCCa1CStatisticValue_90, 1)).  HbA1c 90 days (%): \(roundDecimal(NGSPa1CStatisticValue_90, 1))."
+            bgString90Days =
+                " Average BG 90 days (mmol/l): \(roundDecimal(ninetyDays * 0.0555, 1)). Average BG 90 days (mg/dl): \(roundDecimal(ninetyDays, 0)). "
         }
+
         if avgTot != 0, daysBG >= 2 {
             stringTotal =
                 " HbA1c Total (\(daysBG)) Days (mmol/mol): \(roundDecimal(IFCCa1CStatisticValue_total, 1)). HbA1c Total (\(daysBG)) Days (mg/dl): \(roundDecimal(NGSPa1CStatisticValue_total, 1)) %."
@@ -825,7 +874,7 @@ final class BaseAPSManager: APSManager, Injectable {
                 "BG Average Total (\(daysBG)) Days (mmol/l): \(roundDecimal(avgTot * 0.0555, 1)). BG Average Total (\(daysBG)) Days (mmg/dl): \(avgTot)."
         }
 
-        let HbA1c_string = HbA1c_string_1 + string7Days + string30Days + stringTotal
+        let HbA1c_string = HbA1c_string_1 + string7Days + string30Days + string90Days + stringTotal
 
         var tirString =
             "TIR (24 hours): \(tir().TIR_1) %. Time with Hypoglucemia: \(tir().hypos_1) % (< 4 / 72). Time with Hyperglucemia: \(tir().hypers_1) % (> 10 / 180)."
@@ -835,11 +884,11 @@ final class BaseAPSManager: APSManager, Injectable {
                 " Total days (\(daysBG)) TIR: \(tir().TIR) %. Time with Hypoglucemia: \(tir().hypos) % (< 4 / 72). Time with Hyperglucemia: \(tir().hypers) % (> 10 / 180)."
         }
 
-        let bgAverageString = bgString1day + bgString7Days + bgString30Days + bgAverageTotalString
+        let bgAverageString = bgString1day + bgString7Days + bgString30Days + bgString90Days + bgAverageTotalString
 
         let dailystat = DailyStats(
             createdAt: Date(),
-            FAX_Build_Version: version,
+            FAX_Build_Version: version ?? "",
             FAX_Build_Number: build ?? "1",
             FAX_Branch: branch ?? "N/A",
             FAX_Build_Date: buildDate,
@@ -853,11 +902,51 @@ final class BaseAPSManager: APSManager, Injectable {
             Carbs_24h: carbTotal,
             TIR: tirString,
             BG_Average: bgAverageString,
-            HbA1c: HbA1c_string
+            HbA1c: HbA1c_string,
+            Loop_Cycles: "Success Rate : \(round(successRate ?? 0)) %. Average Time Between Loop Cycles: \(roundedMinutesBetweenLoops ?? 0) min."
         )
 
-        file = OpenAPS.Monitor.dailyStats
-        storage.save(dailystat, as: file)
+        let savedDailyStas = storage.retrieve(OpenAPS.Monitor.dailyStats, as: [DailyStats].self)?
+            .sorted { $0.createdAt > $1.createdAt } ?? []
+        let lastDailyStatsEntry = savedDailyStas.count - 1
+
+        var uniqeEvents: [DailyStats] = []
+
+        if lastDailyStatsEntry <= 0 {
+            storage.save(dailystat, as: file)
+        } else if Date() > savedDailyStas[0].createdAt.addingTimeInterval(1.days.timeInterval) {
+            storage.transaction { storage in
+                storage.append(dailystat, to: file, uniqBy: \.createdAt)
+                uniqeEvents = storage.retrieve(file, as: [DailyStats].self)?
+                    .filter { $0.createdAt.addingTimeInterval(365.days.timeInterval) > Date() }
+                    .sorted { $0.createdAt > $1.createdAt } ?? []
+                storage.save(Array(uniqeEvents), as: file)
+            }
+        }
+    }
+
+    func loopStats(error: Error? = nil) {
+        let file = OpenAPS.Monitor.loopStats
+        var errString = "Success"
+
+        if let error = error {
+            errString = error.localizedDescription
+        }
+        let loopstat = LoopStats(
+            createdAt: Date(),
+            loopStatus: errString
+        )
+
+        var uniqEvents: [LoopStats] = []
+
+        storage.transaction { storage in
+            storage.append(loopstat, to: file, uniqBy: \.createdAt)
+            uniqEvents = storage.retrieve(file, as: [LoopStats].self)?
+                .filter { $0.createdAt.addingTimeInterval(24.hours.timeInterval) > Date() }
+                .sorted { $0.createdAt > $1.createdAt } ?? []
+
+            storage.save(Array(uniqEvents), as: file)
+        }
     }
 
     // Time In Range (%) and Average Glucose (24 hours). This function looks dumb. I will refactor it later.
@@ -866,7 +955,7 @@ final class BaseAPSManager: APSManager, Injectable {
             averageGlucose: Decimal,
             averageGlucose_1: Decimal,
             averageGlucose_7: Decimal,
-            averageGlucose_30: Decimal,
+            averageGlucose_10: Decimal,
             hypos: Decimal,
             hypers: Decimal,
             TIR: Decimal,
@@ -891,10 +980,11 @@ final class BaseAPSManager: APSManager, Injectable {
         let startDate = glucose![0].date
         var end1 = false
         var end7 = false
-        var end30 = false
+        var end10 = false
         var bg_1: Decimal = 0
         var bg_7: Decimal = 0
-        var bg_30: Decimal = 0
+        var bg_10: Decimal = 0
+        var bg_total: Decimal = 0
         var j = -1
 
         for entry in glucose! {
@@ -913,14 +1003,16 @@ final class BaseAPSManager: APSManager, Injectable {
                     end7 = true
                     bg_7 = bg / nr_bgs
                 }
-                if startDate - entry.date > 2.59E9, !end30 {
-                    end30 = true
-                    bg_30 = bg / nr_bgs
+
+                if startDate - entry.date >= 8.64E8, !end10 {
+                    end10 = true
+                    bg_10 = bg / nr_bgs
                 }
             }
         }
 
-        let bg_90 = bg / nr_bgs
+        bg_total = bg / nr_bgs
+
         let fullTime = glucose![0].date - glucose![endIndex].date
         var fullTime_1 = glucose![0].date - glucose![oneDayGlucoseIndex].date
 
@@ -1004,11 +1096,36 @@ final class BaseAPSManager: APSManager, Injectable {
 
         let TIR_1 = 100 - (hypos_1 + hypers_1)
 
+        // Add 10 day average to weeklyStats.json
+        let file_10 = OpenAPS.Monitor.tenDaysStats
+        let tensDaysStats = storage.retrieve(file_10, as: [TenDaysStats].self)
+
+        let lastTenDaysStatEntry = tensDaysStats?[0].past10daysAverage ?? 0
+
+        print("Count entries on TenDaysData: \(lastTenDaysStatEntry)")
+
+        let tenStats = TenDaysStats(
+            createdAt: Date(), past10daysAverage: roundDecimal(bg_10, 1)
+        )
+        var uniqEvents: [TenDaysStats] = []
+
+        if lastTenDaysStatEntry == 0, daysBG >= 10 {
+            storage.save(tenStats, as: file_10)
+        } else if daysBG >= 10, Date() > tensDaysStats?[0].createdAt.addingTimeInterval(10.days.timeInterval) ?? Date() {
+            storage.transaction { storage in
+                storage.append(tenStats, to: file_10, uniqBy: \.createdAt)
+                uniqEvents = storage.retrieve(file_10, as: [TenDaysStats].self)?
+                    .filter { $0.createdAt.addingTimeInterval(365.days.timeInterval) > Date() }
+                    .sorted { $0.createdAt > $1.createdAt } ?? []
+                storage.save(Array(uniqEvents), as: file_10)
+            }
+        }
+
         return (
-            roundDecimal(bg_90, 0),
+            roundDecimal(bg_total, 0),
             roundDecimal(bg_1, 0),
             roundDecimal(bg_7, 0),
-            roundDecimal(bg_30, 0),
+            roundDecimal(bg_10, 0),
             roundDecimal(hypos, 1),
             roundDecimal(hypers, 1),
             roundDecimal(TIR, 1),

+ 2 - 0
FreeAPS/Sources/APS/OpenAPS/Constants.swift

@@ -58,6 +58,8 @@ extension OpenAPS {
         static let tdd_averages = "monitor/tdd_averages.json"
         static let alertHistory = "monitor/alerthistory.json"
         static let dailyStats = "monitor/dailyStats.json"
+        static let tenDaysStats = "monitor/tenDaysStats.json"
+        static let loopStats = "monitor/loopStats.json"
     }
 
     enum Enact {

+ 1 - 1
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -38,7 +38,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
             self.storage.transaction { storage in
                 storage.append(glucose, to: file, uniqBy: \.dateString)
                 let uniqEvents = storage.retrieve(file, as: [BloodGlucose].self)?
-                    .filter { $0.dateString.addingTimeInterval(90.days.timeInterval) > Date() }
+                    .filter { $0.dateString.addingTimeInterval(10.days.timeInterval) > Date() }
                     .sorted { $0.dateString > $1.dateString } ?? []
                 let glucose = Array(uniqEvents)
                 storage.save(glucose, as: file)

+ 22 - 0
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -43,6 +43,10 @@ import Swinject
     }
 
     init() {
+        debug(
+            .default,
+            "FreeAPS X Started: v\(Bundle.main.releaseVersionNumber ?? "")(\(Bundle.main.buildVersionNumber ?? "")) [buildDate: \(Bundle.main.buildDate)]"
+        )
         loadServices()
     }
 
@@ -55,3 +59,21 @@ import Swinject
         }
     }
 }
+
+extension Bundle {
+    var releaseVersionNumber: String? {
+        infoDictionary?["CFBundleShortVersionString"] as? String
+    }
+    var buildVersionNumber: String? {
+        infoDictionary?["CFBundleVersion"] as? String
+    }
+    var buildDate: Date {
+        if let infoPath = Bundle.main.path(forResource: "Info", ofType: "plist"),
+           let infoAttr = try? FileManager.default.attributesOfItem(atPath: infoPath),
+           let infoDate = infoAttr[.modificationDate] as? Date
+        {
+            return infoDate
+        }
+        return Date()
+    }
+}

+ 1 - 1
FreeAPS/Sources/Config/Config.swift

@@ -4,6 +4,6 @@ import SwiftDate
 enum Config {
     static let treatWarningsAsErrors = true
     static let withSignPosts = false
-    static let loopInterval = 5.minutes.timeInterval
+    static let loopInterval = 3.minutes.timeInterval
     static let eхpirationInterval = 10.minutes.timeInterval
 }

+ 5 - 1
FreeAPS/Sources/Models/DailyStats.swift

@@ -17,6 +17,7 @@ struct DailyStats: JSON, Equatable {
     var TIR: String
     var BG_Average: String
     var HbA1c: String
+    var Loop_Cycles: String
 
     init(
         createdAt: Date,
@@ -34,7 +35,8 @@ struct DailyStats: JSON, Equatable {
         Carbs_24h: Decimal,
         TIR: String,
         BG_Average: String,
-        HbA1c: String
+        HbA1c: String,
+        Loop_Cycles: String
     ) {
         self.createdAt = createdAt
         self.FAX_Build_Version = FAX_Build_Version
@@ -52,6 +54,7 @@ struct DailyStats: JSON, Equatable {
         self.TIR = TIR
         self.BG_Average = BG_Average
         self.HbA1c = HbA1c
+        self.Loop_Cycles = Loop_Cycles
     }
 
     static func == (lhs: DailyStats, rhs: DailyStats) -> Bool {
@@ -81,5 +84,6 @@ extension DailyStats {
         case TIR
         case BG_Average
         case HbA1c
+        case Loop_Cycles
     }
 }

+ 21 - 0
FreeAPS/Sources/Models/LoopStats.swift

@@ -0,0 +1,21 @@
+import Foundation
+
+struct LoopStats: JSON, Equatable {
+    var createdAt: Date
+    var loopStatus: String
+
+    init(
+        createdAt: Date,
+        loopStatus: String
+    ) {
+        self.createdAt = createdAt
+        self.loopStatus = loopStatus
+    }
+}
+
+extension LoopStats {
+    private enum CodingKeys: String, CodingKey {
+        case createdAt
+        case loopStatus
+    }
+}

+ 21 - 0
FreeAPS/Sources/Models/TenDaysStats.swift

@@ -0,0 +1,21 @@
+import Foundation
+
+struct TenDaysStats: JSON, Equatable {
+    var createdAt: Date
+    var past10daysAverage: Decimal
+
+    init(
+        createdAt: Date,
+        past10daysAverage: Decimal
+    ) {
+        self.createdAt = createdAt
+        self.past10daysAverage = past10daysAverage
+    }
+}
+
+extension TenDaysStats {
+    private enum CodingKeys: String, CodingKey {
+        case createdAt
+        case past10daysAverage
+    }
+}

+ 3 - 1
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -114,8 +114,10 @@ extension Settings {
                                 .navigationLink(to: .configEditor(file: OpenAPS.Monitor.tdd), from: self)
                             Text("TDD Averages")
                                 .navigationLink(to: .configEditor(file: OpenAPS.Monitor.tdd_averages), from: self)
-                            Text("Daily Statistics")
+                            Text("Statistics")
                                 .navigationLink(to: .configEditor(file: OpenAPS.Monitor.dailyStats), from: self)
+                            Text("Ten Days Data")
+                                .navigationLink(to: .configEditor(file: OpenAPS.Monitor.tenDaysStats), from: self)
                             Text("Edit settings json")
                                 .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.settings), from: self)
                         }

+ 14 - 0
FreeAPSWatch/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -70,6 +70,13 @@
     },
     {
       "idiom" : "watch",
+      "role" : "appLauncher",
+      "scale" : "2x",
+      "size" : "54x54",
+      "subtype" : "49mm"
+    },
+    {
+      "idiom" : "watch",
       "role" : "quickLook",
       "scale" : "2x",
       "size" : "86x86",
@@ -97,6 +104,13 @@
       "subtype" : "45mm"
     },
     {
+      "idiom" : "watch",
+      "role" : "quickLook",
+      "scale" : "2x",
+      "size" : "129x129",
+      "subtype" : "49mm"
+    },
+    {
       "idiom" : "watch-marketing",
       "scale" : "1x",
       "size" : "1024x1024"