Explorar el Código

Release/0.1.12 (#22)

* Bolus progress

* Trying to prevent deadlock

* revert prev changes

* BolusProgressViewStyle

* Bolus progress on omnipod

* Override uniq entries in storage

* remove comment

* Bolus cancellation

* Fix "Unsuspend if no temp" option

* bump ver
Ivan hace 5 años
padre
commit
827a0e2750

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -199,6 +199,7 @@
 		38E98A3025F52FF700C0CED0 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E98A2F25F52FF700C0CED0 /* Config.swift */; };
 		38E98A3725F5509500C0CED0 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E98A3625F5509500C0CED0 /* String+Extensions.swift */; };
 		38EA05DA261F6E7C0064E39B /* SimpleLogReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38EA05D9261F6E7C0064E39B /* SimpleLogReporter.swift */; };
+		38EA0600262091870064E39B /* BolusProgressViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38EA05FF262091870064E39B /* BolusProgressViewStyle.swift */; };
 		38F37828261260DC009DB701 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38F37827261260DC009DB701 /* Color+Extensions.swift */; };
 		38F3B2EF25ED8E2A005C48AA /* TempTargetsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38F3B2EE25ED8E2A005C48AA /* TempTargetsStorage.swift */; };
 		38FCF3D625E8FDF40078B0D1 /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FCF3D525E8FDF40078B0D1 /* MD5.swift */; };
@@ -481,6 +482,7 @@
 		38E98A2F25F52FF700C0CED0 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = "<group>"; };
 		38E98A3625F5509500C0CED0 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
 		38EA05D9261F6E7C0064E39B /* SimpleLogReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleLogReporter.swift; sourceTree = "<group>"; };
+		38EA05FF262091870064E39B /* BolusProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusProgressViewStyle.swift; sourceTree = "<group>"; };
 		38F37827261260DC009DB701 /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = "<group>"; };
 		38F3783A2613555C009DB701 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
 		38F3B2EE25ED8E2A005C48AA /* TempTargetsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetsStorage.swift; sourceTree = "<group>"; };
@@ -1002,6 +1004,7 @@
 				3883581B25EE79BB00E024B2 /* DecimalTextField.swift */,
 				383420D825FFEB3F002D46C1 /* Popup.swift */,
 				389ECDFD2601061500D86C4F /* View+Snapshot.swift */,
+				38EA05FF262091870064E39B /* BolusProgressViewStyle.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -1766,6 +1769,7 @@
 				2BE9A6FA20875F6F4F9CD461 /* PumpSettingsEditorProvider.swift in Sources */,
 				6B9625766B697D1C98E455A2 /* PumpSettingsEditorViewModel.swift in Sources */,
 				A0B8EC8CC5CD1DD237D1BCD2 /* PumpSettingsEditorRootView.swift in Sources */,
+				38EA0600262091870064E39B /* BolusProgressViewStyle.swift in Sources */,
 				389ECDFE2601061500D86C4F /* View+Snapshot.swift in Sources */,
 				38E98A3725F5509500C0CED0 /* String+Extensions.swift in Sources */,
 				1D086541F369D339A74893AC /* BasalProfileEditorBuilder.swift in Sources */,

+ 1 - 1
FreeAPS/Resources/Config.xcconfig

@@ -1 +1 @@
-BUILD_VERSION = 0.1.11
+BUILD_VERSION = 0.1.12

+ 54 - 5
FreeAPS/Sources/APS/APSManager.swift

@@ -22,6 +22,7 @@ protocol APSManager {
     func determineBasal() -> AnyPublisher<Bool, Never>
     func roundBolus(amount: Decimal) -> Decimal
     var lastError: CurrentValueSubject<Error?, Never> { get }
+    func cancelBolus()
 }
 
 enum APSError: LocalizedError {
@@ -29,17 +30,20 @@ enum APSError: LocalizedError {
     case invalidPumpState(message: String)
     case glucoseError(message: String)
     case apsError(message: String)
+    case deviceSyncError(message: String)
 
     var errorDescription: String? {
         switch self {
         case let .pumpError(error):
             return "Pump error: \(error.localizedDescription)"
         case let .invalidPumpState(message):
-            return "Invalid Pump State: \(message)"
+            return "Error: Invalid Pump State: \(message)"
         case let .glucoseError(message):
-            return "Invalid glucose: \(message)"
+            return "Error: Invalid glucose: \(message)"
         case let .apsError(message):
             return "APS error: \(message)"
+        case let .deviceSyncError(message):
+            return "Sync error: \(message)"
         }
     }
 }
@@ -117,6 +121,13 @@ final class BaseAPSManager: APSManager, Injectable {
                 self.processError($0)
             }
             .store(in: &lifetime)
+
+        deviceDataManager.bolusTrigger
+            .receive(on: processQueue)
+            .sink {
+                self.createBolusReporter()
+            }
+            .store(in: &lifetime)
     }
 
     func heartbeat(date: Date, force: Bool) {
@@ -237,7 +248,8 @@ final class BaseAPSManager: APSManager, Injectable {
         if temp.duration == 0,
            settings.closedLoop,
            settingsManager.preferences.unsuspendIfNoTemp,
-           let pump = pumpManager
+           let pump = pumpManager,
+           pump.status.pumpStatus.suspended
         {
             return pump.resumeDelivery()
                 .flatMap { _ in mainPublisher }
@@ -276,6 +288,8 @@ final class BaseAPSManager: APSManager, Injectable {
 
         let roundedAmout = pump.roundToSupportedBolusVolume(units: amount)
 
+        debug(.apsManager, "Enact bolus \(roundedAmout), manual \(!isSMB)")
+
         pump.enactBolus(units: roundedAmout, automatic: isSMB).sink { completion in
             if case let .failure(error) = completion {
                 debug(.apsManager, "Bolus failed with error: \(error.localizedDescription)")
@@ -286,15 +300,31 @@ final class BaseAPSManager: APSManager, Injectable {
                     self.determineBasal().sink { _ in }.store(in: &self.lifetime)
                 }
             }
+        } receiveValue: { _ in }
+            .store(in: &lifetime)
+    }
+
+    func cancelBolus() {
+        guard let pump = pumpManager else { return }
+        debug(.apsManager, "Cancel bolus")
+        pump.cancelBolus().sink { completion in
+            if case let .failure(error) = completion {
+                debug(.apsManager, "Bolus cancellation failed with error: \(error.localizedDescription)")
+                self.processError(APSError.pumpError(error))
+            } else {
+                debug(.apsManager, "Bolus cancelled")
+            }
 
-            self.bolusReporter = pump.createBolusProgressReporter(reportingOn: self.processQueue)
-            self.bolusReporter?.addObserver(self)
+            self.bolusReporter?.removeObserver(self)
+            self.bolusReporter = nil
+            self.bolusProgress.send(nil)
         } receiveValue: { _ in }
             .store(in: &lifetime)
     }
 
     func enactTempBasal(rate: Double, duration: TimeInterval) {
         guard let pump = pumpManager, verifyStatus() else { return }
+        debug(.apsManager, "Enact temp basal \(rate) - \(duration)")
 
         let roundedAmout = pump.roundToSupportedBasalRate(unitsPerHour: rate)
         pump.enactTempBasal(unitsPerHour: roundedAmout, for: duration) { result in
@@ -497,6 +527,11 @@ final class BaseAPSManager: APSManager, Injectable {
         debug(.apsManager, "\(error.localizedDescription)")
         lastError.send(error)
     }
+
+    private func createBolusReporter() {
+        bolusReporter = pumpManager?.createBolusProgressReporter(reportingOn: processQueue)
+        bolusReporter?.addObserver(self)
+    }
 }
 
 private extension PumpManager {
@@ -526,6 +561,20 @@ private extension PumpManager {
         }.eraseToAnyPublisher()
     }
 
+    func cancelBolus() -> AnyPublisher<DoseEntry?, Error> {
+        Future { promise in
+            self.cancelBolus { result in
+                switch result {
+                case let .success(dose):
+                    promise(.success(dose))
+                case let .failure(error):
+                    promise(.failure(error))
+                }
+            }
+        }
+        .eraseToAnyPublisher()
+    }
+
     func suspendDelivery() -> AnyPublisher<Void, Error> {
         Future { promise in
             self.suspendDelivery { error in

+ 7 - 0
FreeAPS/Sources/APS/DeviceDataManager.swift

@@ -13,6 +13,7 @@ protocol DeviceDataManager {
     var pumpManager: PumpManagerUI? { get set }
     var pumpDisplayState: CurrentValueSubject<PumpDisplayState?, Never> { get }
     var recommendsLoop: PassthroughSubject<Void, Never> { get }
+    var bolusTrigger: PassthroughSubject<Void, Never> { get }
     var errorSubject: PassthroughSubject<Error, Never> { get }
     var pumpName: CurrentValueSubject<String, Never> { get }
     var pumpExpiresAtDate: CurrentValueSubject<Date?, Never> { get }
@@ -44,6 +45,7 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
         .distantPast
 
     let recommendsLoop = PassthroughSubject<Void, Never>()
+    let bolusTrigger = PassthroughSubject<Void, Never>()
     let errorSubject = PassthroughSubject<Error, Never>()
 
     var pumpManager: PumpManagerUI? {
@@ -182,6 +184,10 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
         debug(.deviceManager, "New pump status Bolus: \(status.bolusState)")
         debug(.deviceManager, "New pump status Basal: \(String(describing: status.basalDeliveryState))")
 
+        if case .inProgress = status.bolusState {
+            bolusTrigger.send()
+        }
+
         let batteryPercent = Int((status.pumpBatteryChargeRemaining ?? 1) * 100)
         let battery = Battery(
             percent: batteryPercent,
@@ -229,6 +235,7 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
         completion: @escaping (_ error: Error?) -> Void
     ) {
         dispatchPrecondition(condition: .onQueue(processQueue))
+        debug(.deviceManager, "New pump events:\n\(events.map(\.title).joined(separator: "\n"))")
         pumpHistoryStorage.storePumpEvents(events)
         lastEventDate = events.last?.date
         completion(nil)

+ 4 - 0
FreeAPS/Sources/Modules/Home/HomeViewModel.swift

@@ -125,6 +125,10 @@ extension Home {
             provider.heartbeatNow()
         }
 
+        func cancelBolus() {
+            apsManager.cancelBolus()
+        }
+
         private func setupGlucose() {
             DispatchQueue.main.async {
                 self.glucose = self.provider.filteredGlucose(hours: self.filteredHours)

+ 7 - 2
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -85,7 +85,7 @@ extension Home {
         }
 
         var infoPanal: some View {
-            HStack(alignment: .firstTextBaseline) {
+            HStack(alignment: .center) {
                 if let tempRate = viewModel.tempRate {
                     Text((numberFormatter.string(from: tempRate as NSNumber) ?? "0") + " U/hr")
                         .font(.system(size: 12, weight: .bold)).foregroundColor(.insulin)
@@ -137,9 +137,14 @@ extension Home {
                 }
                 Spacer()
                 if let progress = viewModel.bolusProgress {
-                    Text("Bolus " + (numberFormatter.string(from: progress * 100 as NSNumber)!) + "%")
+                    Text("Bolusing")
                         .font(.system(size: 12, weight: .bold)).foregroundColor(.insulin)
+                    ProgressView(value: Double(progress))
+                        .progressViewStyle(BolusProgressViewStyle())
                         .padding(.trailing, 8)
+                        .onTapGesture {
+                            viewModel.cancelBolus()
+                        }
                 }
             }
             .frame(maxWidth: .infinity, maxHeight: 30)

+ 9 - 6
FreeAPS/Sources/Services/Storage/FileStorage.swift

@@ -73,16 +73,19 @@ final class BaseFileStorage: FileStorage {
 
     func append<Value: JSON, T: Equatable>(_ newValues: [Value], to name: String, uniqBy keyPath: KeyPath<Value, T>) {
         if let value = retrieve(name, as: Value.self) {
-            guard newValues.first(where: { $0[keyPath: keyPath] == value[keyPath: keyPath] }) == nil else {
+            if newValues.firstIndex(where: { $0[keyPath: keyPath] == value[keyPath: keyPath] }) != nil {
+                save(newValues, as: name)
                 return
             }
             append(newValues, to: name)
-        } else if let values = retrieve(name, as: [Value].self) {
-            newValues.forEach { newValue in
-                guard values.first(where: { $0[keyPath: keyPath] == newValue[keyPath: keyPath] }) == nil else {
-                    return
+        } else if var values = retrieve(name, as: [Value].self) {
+            for newValue in newValues {
+                if let index = values.firstIndex(where: { $0[keyPath: keyPath] == newValue[keyPath: keyPath] }) {
+                    values[index] = newValue
+                } else {
+                    values.append(newValue)
                 }
-                append(newValue, to: name)
+                save(values, as: name)
             }
         } else {
             save(newValues, as: name)

+ 23 - 0
FreeAPS/Sources/Views/BolusProgressViewStyle.swift

@@ -0,0 +1,23 @@
+import SwiftUI
+
+public struct BolusProgressViewStyle: ProgressViewStyle {
+    public func makeBody(configuration: LinearProgressViewStyle.Configuration) -> some View {
+        ZStack {
+            Circle()
+                .stroke(lineWidth: 4.0)
+                .opacity(0.3)
+                .foregroundColor(.secondary)
+                .frame(width: 22, height: 22)
+
+            Rectangle().fill(Color.insulin)
+                .frame(width: 8, height: 8)
+
+            Circle()
+                .trim(from: 0.0, to: CGFloat(configuration.fractionCompleted ?? 0))
+                .stroke(style: StrokeStyle(lineWidth: 4.0, lineCap: .butt, lineJoin: .round))
+                .foregroundColor(.insulin)
+                .rotationEffect(Angle(degrees: -90))
+                .frame(width: 22, height: 22)
+        }.frame(width: 30, height: 30)
+    }
+}