Ivan Valkou 5 éve
szülő
commit
bfde3f67f3

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -182,6 +182,7 @@
 		38C4D33A25E9A1ED00D30B77 /* NSObject+AssociatedValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C4D33925E9A1ED00D30B77 /* NSObject+AssociatedValues.swift */; };
 		38D0B3B625EBE24900CB6E88 /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D0B3B525EBE24900CB6E88 /* Battery.swift */; };
 		38D0B3D925EC07C400CB6E88 /* CarbsEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D0B3D825EC07C400CB6E88 /* CarbsEntry.swift */; };
+		38DAB280260CBB7F00F74C1A /* PumpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DAB27F260CBB7F00F74C1A /* PumpView.swift */; };
 		38E989DD25F5021400C0CED0 /* PumpStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E989DC25F5021400C0CED0 /* PumpStatus.swift */; };
 		38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E98A1B25F52C9300C0CED0 /* Signpost.swift */; };
 		38E98A2425F52C9300C0CED0 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E98A1C25F52C9300C0CED0 /* Logger.swift */; };
@@ -454,6 +455,7 @@
 		38C4D33925E9A1ED00D30B77 /* NSObject+AssociatedValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+AssociatedValues.swift"; sourceTree = "<group>"; };
 		38D0B3B525EBE24900CB6E88 /* Battery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = "<group>"; };
 		38D0B3D825EC07C400CB6E88 /* CarbsEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbsEntry.swift; sourceTree = "<group>"; };
+		38DAB27F260CBB7F00F74C1A /* PumpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpView.swift; sourceTree = "<group>"; };
 		38E989DC25F5021400C0CED0 /* PumpStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpStatus.swift; sourceTree = "<group>"; };
 		38E98A1B25F52C9300C0CED0 /* Signpost.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signpost.swift; sourceTree = "<group>"; };
 		38E98A1C25F52C9300C0CED0 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
@@ -952,6 +954,7 @@
 			children = (
 				383420D525FFE38C002D46C1 /* LoopView.swift */,
 				38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */,
+				38DAB27F260CBB7F00F74C1A /* PumpView.swift */,
 			);
 			path = Header;
 			sourceTree = "<group>";
@@ -1597,6 +1600,7 @@
 				384E803825C388640086DB71 /* Script.swift in Sources */,
 				3811DE0925C9D32F00A708ED /* BaseViewModel.swift in Sources */,
 				3883583425EEB38000E024B2 /* PumpSettings.swift in Sources */,
+				38DAB280260CBB7F00F74C1A /* PumpView.swift in Sources */,
 				3811DEB125C9D88300A708ED /* Keychain.swift in Sources */,
 				382C133725F13A1E00715CE1 /* InsulinSensitivities.swift in Sources */,
 				3811DE7B25C9D6D300A708ED /* LoginProvider.swift in Sources */,

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

@@ -11,8 +11,10 @@ protocol APSManager {
     func enactBolus(amount: Double)
     var pumpManager: PumpManagerUI? { get set }
     var pumpDisplayState: CurrentValueSubject<PumpDisplayState?, Never> { get }
+    var pumpName: CurrentValueSubject<String, Never> { get }
     var isLooping: CurrentValueSubject<Bool, Never> { get }
     var lastLoopDate: PassthroughSubject<Date, Never> { get }
+    var pumpExpiresAtDate: CurrentValueSubject<Date?, Never> { get }
     func enactTempBasal(rate: Double, duration: TimeInterval)
     func makeProfiles() -> AnyPublisher<Bool, Never>
     func determineBasal() -> AnyPublisher<Bool, Never>
@@ -48,6 +50,14 @@ final class BaseAPSManager: APSManager, Injectable {
         deviceDataManager.pumpDisplayState
     }
 
+    var pumpName: CurrentValueSubject<String, Never> {
+        deviceDataManager.pumpName
+    }
+
+    var pumpExpiresAtDate: CurrentValueSubject<Date?, Never> {
+        deviceDataManager.pumpExpiresAtDate
+    }
+
     var settings: FreeAPSSettings {
         get { settingsManager.settings }
         set { settingsManager.settings = newValue }
@@ -459,13 +469,14 @@ private extension PumpManager {
 extension BaseAPSManager: PumpManagerStatusObserver {
     func pumpManager(_: PumpManager, didUpdate status: PumpManagerStatus, oldStatus _: PumpManagerStatus) {
         let percent = Int((status.pumpBatteryChargeRemaining ?? 1) * 100)
-        let battery = Battery(percent: percent, voltage: nil, string: percent > 10 ? .normal : .low)
+        let battery = Battery(
+            percent: percent,
+            voltage: nil,
+            string: percent > 10 ? .normal : .low,
+            display: status.pumpBatteryChargeRemaining != nil
+        )
         storage.save(battery, as: OpenAPS.Monitor.battery)
         storage.save(status.pumpStatus, as: OpenAPS.Monitor.status)
-//        if oldStatus.pumpStatus.status != status.pumpStatus.status {
-//            debug(.apsManager, "Pump status did change: \(status.pumpStatus)")
-//            nightscout.uploadStatus()
-//        }
     }
 }
 

+ 52 - 5
FreeAPS/Sources/APS/DeviceDataManager.swift

@@ -13,6 +13,8 @@ protocol DeviceDataManager {
     var pumpManager: PumpManagerUI? { get set }
     var pumpDisplayState: CurrentValueSubject<PumpDisplayState?, Never> { get }
     var recommendsLoop: PassthroughSubject<Void, Never> { get }
+    var pumpName: CurrentValueSubject<String, Never> { get }
+    var pumpExpiresAtDate: CurrentValueSubject<Date?, Never> { get }
 }
 
 private let staticPumpManagers: [PumpManagerUI.Type] = [
@@ -29,6 +31,7 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
     private let processQueue = DispatchQueue(label: "BaseDeviceDataManager.processQueue")
     @Injected() private var pumpHistoryStorage: PumpHistoryStorage!
     @Injected() private var storage: FileStorage!
+    @Injected() private var broadcaster: Broadcaster!
 
     @Persisted(key: "BaseDeviceDataManager.lastEventDate") var lastEventDate: Date? = nil
     @Persisted(key: "BaseDeviceDataManager.lastHeartBeatTime") var lastHeartBeatTime: Date = .distantPast
@@ -42,6 +45,16 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
             UserDefaults.standard.pumpManagerRawValue = pumpManager?.rawValue
             if let pumpManager = pumpManager {
                 pumpDisplayState.value = PumpDisplayState(name: pumpManager.localizedTitle, image: pumpManager.smallImage)
+                pumpName.send(pumpManager.localizedTitle)
+
+                if let omnipod = pumpManager as? OmnipodPumpManager {
+                    guard let endTime = omnipod.state.podState?.expiresAt else {
+                        pumpExpiresAtDate.send(nil)
+                        return
+                    }
+                    pumpExpiresAtDate.send(endTime)
+                }
+
             } else {
                 pumpDisplayState.value = nil
             }
@@ -49,6 +62,8 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
     }
 
     let pumpDisplayState = CurrentValueSubject<PumpDisplayState?, Never>(nil)
+    let pumpExpiresAtDate = CurrentValueSubject<Date?, Never>(nil)
+    let pumpName = CurrentValueSubject<String, Never>("Pump")
 
     var heartBeat: Timer!
 
@@ -102,12 +117,13 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
 }
 
 extension BaseDeviceDataManager: PumpManagerDelegate {
-    func pumpManager(_: PumpManager, didAdjustPumpClockBy _: TimeInterval) {
-//        log.debug("didAdjustPumpClockBy %@", adjustment)
+    func pumpManager(_: PumpManager, didAdjustPumpClockBy adjustment: TimeInterval) {
+        debug(.deviceManager, "didAdjustPumpClockBy \(adjustment)")
     }
 
     func pumpManagerDidUpdateState(_ pumpManager: PumpManager) {
         UserDefaults.standard.pumpManagerRawValue = pumpManager.rawValue
+        pumpName.send(pumpManager.localizedTitle)
     }
 
     func pumpManagerBLEHeartbeatDidFire(_: PumpManager) {
@@ -122,6 +138,26 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
     func pumpManager(_: PumpManager, didUpdate status: PumpManagerStatus, oldStatus _: PumpManagerStatus) {
         debug(.deviceManager, "New pump status Bolus: \(status.bolusState)")
         debug(.deviceManager, "New pump status Basal: \(String(describing: status.basalDeliveryState))")
+
+        let batteryPercent = Int((status.pumpBatteryChargeRemaining ?? 1) * 100)
+        let battery = Battery(
+            percent: batteryPercent,
+            voltage: nil,
+            string: batteryPercent >= 10 ? .normal : .low,
+            display: pumpManager?.status.pumpBatteryChargeRemaining != nil
+        )
+        storage.save(battery, as: OpenAPS.Monitor.battery)
+        broadcaster.notify(PumpBatteryObserver.self, on: processQueue) {
+            $0.pumpBatteryDidChange(battery)
+        }
+
+        if let omnipod = pumpManager as? OmnipodPumpManager {
+            guard let endTime = omnipod.state.podState?.expiresAt else {
+                pumpExpiresAtDate.send(nil)
+                return
+            }
+            pumpExpiresAtDate.send(endTime)
+        }
     }
 
     func pumpManagerWillDeactivate(_: PumpManager) {
@@ -158,9 +194,10 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
         dispatchPrecondition(condition: .onQueue(processQueue))
         debug(.deviceManager, "Reservoir Value \(units), at: \(date)")
         storage.save(Decimal(units), as: OpenAPS.Monitor.reservoir)
-        let batteryPercent = Int((pumpManager?.status.pumpBatteryChargeRemaining ?? 1) * 100)
-        let battery = Battery(percent: batteryPercent, voltage: nil, string: batteryPercent >= 10 ? .normal : .low)
-        storage.save(battery, as: OpenAPS.Monitor.battery)
+        broadcaster.notify(PumpReservoirObserver.self, on: processQueue) {
+            $0.pumpReservoirDidChange(Decimal(units))
+        }
+
         completion(.success((
             newValue: Reservoir(startDate: Date(), unitVolume: units),
             lastValue: nil,
@@ -227,3 +264,13 @@ extension BaseDeviceDataManager: AlertPresenter {
 
     func retractAlert(identifier _: Alert.Identifier) {}
 }
+
+// MARK: Others
+
+protocol PumpReservoirObserver {
+    func pumpReservoirDidChange(_ reservoir: Decimal)
+}
+
+protocol PumpBatteryObserver {
+    func pumpBatteryDidChange(_ battery: Battery)
+}

+ 1 - 0
FreeAPS/Sources/Models/Battery.swift

@@ -4,6 +4,7 @@ struct Battery: JSON {
     let percent: Int?
     let voltage: Decimal?
     let string: BatteryState
+    let display: Bool?
 }
 
 enum BatteryState: String, JSON {

+ 2 - 0
FreeAPS/Sources/Modules/Home/HomeDataFlow.swift

@@ -14,4 +14,6 @@ protocol HomeProvider: Provider {
     func basalProfile() -> [BasalProfileEntry]
     func tempTargets(hours: Int) -> [TempTarget]
     func carbs(hours: Int) -> [CarbsEntry]
+    func pumpBattery() -> Battery?
+    func pumpReservoir() -> Decimal?
 }

+ 8 - 0
FreeAPS/Sources/Modules/Home/HomeProvider.swift

@@ -51,6 +51,14 @@ extension Home {
                 ?? PumpSettings(insulinActionCurve: 5, maxBolus: 10, maxBasal: 2)
         }
 
+        func pumpBattery() -> Battery? {
+            storage.retrieve(OpenAPS.Monitor.battery, as: Battery.self)
+        }
+
+        func pumpReservoir() -> Decimal? {
+            storage.retrieve(OpenAPS.Monitor.reservoir, as: Decimal.self)
+        }
+
         func basalProfile() -> [BasalProfileEntry] {
             storage.retrieve(OpenAPS.Settings.profile, as: Autotune.self)?.basalProfile
                 ?? storage.retrieve(OpenAPS.Settings.pumpProfile, as: Autotune.self)?.basalProfile

+ 42 - 1
FreeAPS/Sources/Modules/Home/HomeViewModel.swift

@@ -26,6 +26,10 @@ extension Home {
         @Published var statusTitle = ""
         @Published var lastLoopDate: Date = .distantPast
         @Published var tempRate: Decimal?
+        @Published var battery: Battery?
+        @Published var reservoir: Decimal?
+        @Published var pumpName = "Pump"
+        @Published var pumpExpiresAtDate: Date?
 
         @Published var allowManualTemp = false
         private(set) var units: GlucoseUnits = .mmolL
@@ -38,6 +42,8 @@ extension Home {
             setupBasalProfile()
             setupTempTargets()
             setupCarbs()
+            setupBattery()
+            setupReservoir()
 
             suggestion = provider.suggestion
             enactedSuggestion = provider.enactedSuggestion
@@ -63,6 +69,8 @@ extension Home {
             broadcaster.register(TempTargetsObserver.self, observer: self)
             broadcaster.register(CarbsObserver.self, observer: self)
             broadcaster.register(EnactedSuggestionObserver.self, observer: self)
+            broadcaster.register(PumpBatteryObserver.self, observer: self)
+            broadcaster.register(PumpReservoirObserver.self, observer: self)
 
             timer.assign(to: \.timerDate, on: self)
                 .store(in: &lifetime)
@@ -76,6 +84,17 @@ extension Home {
                 .receive(on: DispatchQueue.main)
                 .assign(to: \.lastLoopDate, on: self)
                 .store(in: &lifetime)
+
+            apsManager.pumpName
+                .receive(on: DispatchQueue.main)
+                .assign(to: \.pumpName, on: self)
+                .store(in: &lifetime)
+
+//            pumpExpiresAtDate = Date().addingTimeInterval(2.days.timeInterval + 3.hours.timeInterval)
+            apsManager.pumpExpiresAtDate
+                .receive(on: DispatchQueue.main)
+                .assign(to: \.pumpExpiresAtDate, on: self)
+                .store(in: &lifetime)
         }
 
         func addCarbs() {
@@ -188,6 +207,18 @@ extension Home {
                 statusTitle = "Suggested"
             }
         }
+
+        private func setupReservoir() {
+            DispatchQueue.main.async {
+                self.reservoir = self.provider.pumpReservoir()
+            }
+        }
+
+        private func setupBattery() {
+            DispatchQueue.main.async {
+                self.battery = self.provider.pumpBattery()
+            }
+        }
     }
 }
 
@@ -200,7 +231,9 @@ extension Home.ViewModel:
     BasalProfileObserver,
     TempTargetsObserver,
     CarbsObserver,
-    EnactedSuggestionObserver
+    EnactedSuggestionObserver,
+    PumpBatteryObserver,
+    PumpReservoirObserver
 {
     func glucoseDidUpdate(_: [BloodGlucose]) {
         setupGlucose()
@@ -241,4 +274,12 @@ extension Home.ViewModel:
         enactedSuggestion = suggestion
         setStatusTitle()
     }
+
+    func pumpBatteryDidChange(_: Battery) {
+        setupBattery()
+    }
+
+    func pumpReservoirDidChange(_: Decimal) {
+        setupReservoir()
+    }
 }

+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -26,7 +26,7 @@ struct MainChartView: View {
         static let minGlucose = 70
         static let yLinesCount = 5
         static let bolusSize: CGFloat = 8
-        static let bolusScale: CGFloat = 8
+        static let bolusScale: CGFloat = 3
         static let carbsSize: CGFloat = 10
         static let carbsScale: CGFloat = 0.5
     }

+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift

@@ -43,7 +43,7 @@ struct CurrentGlucoseView: View {
                 Text(
                     recentGlucose.map { dateFormatter.string(from: $0.dateString) } ?? "--"
                 ).font(.caption)
-            }
+            }.padding(.leading, 4)
             VStack {
                 Spacer()
                 image.padding(.bottom, 2)

+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift

@@ -41,7 +41,7 @@ struct LoopView: View {
             } else {
                 Text("--").font(.caption)
             }
-        }
+        }.padding(.trailing)
     }
 
     private var color: Color {

+ 128 - 0
FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift

@@ -0,0 +1,128 @@
+import SwiftUI
+
+struct PumpView: View {
+    @Binding var reservoir: Decimal?
+    @Binding var battery: Battery?
+    @Binding var name: String
+    @Binding var expiresAtDate: Date?
+    @Binding var timerDate: Date
+
+    private var reservoirFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        return formatter
+    }
+
+    private var batteryFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .percent
+        return formatter
+    }
+
+    var body: some View {
+        VStack(alignment: .leading) {
+            Text(name).font(.caption)
+                .minimumScaleFactor(0.01)
+            if let reservoir = reservoir {
+                HStack {
+                    Image(systemName: "drop.fill")
+                        .resizable()
+                        .aspectRatio(contentMode: .fit)
+                        .frame(height: 8)
+                        .foregroundColor(reservoirColor)
+                    Text(reservoirFormatter.string(from: reservoir as NSNumber)! + " U").font(.caption2)
+                }
+            }
+
+            if let battery = battery, battery.display ?? false, expiresAtDate == nil {
+                HStack {
+                    Image(systemName: "battery.100")
+                        .resizable()
+                        .aspectRatio(contentMode: .fit)
+                        .frame(height: 8)
+                        .foregroundColor(batteryColor)
+                    Text("\(Int(battery.percent ?? 100)) %").font(.caption2)
+                }
+            }
+
+            if let date = expiresAtDate {
+                HStack {
+                    Image(systemName: "stopwatch.fill")
+                        .resizable()
+                        .aspectRatio(contentMode: .fit)
+                        .frame(height: 8)
+                        .foregroundColor(timerColor)
+                    Text(remainingTimeString(time: date.timeIntervalSince(timerDate))).font(.caption2)
+                }
+            }
+
+        }.padding(.leading)
+    }
+
+    private func remainingTimeString(time: TimeInterval) -> String {
+        var time = time
+        let days = Int(time / 1.days.timeInterval)
+        time -= days.days.timeInterval
+        let hours = Int(time / 1.hours.timeInterval)
+        time -= hours.hours.timeInterval
+        let minutes = Int(time / 1.minutes.timeInterval)
+
+        if days > 1 {
+            return "\(days)d \(hours)h"
+        }
+
+        if hours > 1 {
+            return "\(hours)h"
+        }
+
+        return "\(minutes)m"
+    }
+
+    private var batteryColor: Color {
+        guard let battery = battery, let percent = battery.percent else {
+            return .gray
+        }
+
+        switch percent {
+        case ...10:
+            return .red
+        case ...20:
+            return .orange
+        default:
+            return .green
+        }
+    }
+
+    private var reservoirColor: Color {
+        guard let reservoir = reservoir else {
+            return .gray
+        }
+
+        switch reservoir {
+        case ...10:
+            return .red
+        case ...30:
+            return .orange
+        default:
+            return .blue
+        }
+    }
+
+    private var timerColor: Color {
+        guard let expisesAt = expiresAtDate else {
+            return .gray
+        }
+
+        let time = expisesAt.timeIntervalSince(timerDate)
+
+        switch time {
+        case ...8.hours.timeInterval:
+            return .red
+        case ...1.days.timeInterval:
+            return .orange
+        default:
+            return .green
+        }
+    }
+}

+ 8 - 4
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -14,9 +14,13 @@ extension Home {
 
         var header: some View {
             HStack {
-                VStack(alignment: .leading) {
-                    Text("PUMP").font(.caption)
-                }.frame(minWidth: 0, maxWidth: .infinity)
+                PumpView(
+                    reservoir: $viewModel.reservoir,
+                    battery: $viewModel.battery,
+                    name: $viewModel.pumpName,
+                    expiresAtDate: $viewModel.pumpExpiresAtDate,
+                    timerDate: $viewModel.timerDate
+                )
                 Spacer()
                 CurrentGlucoseView(
                     recentGlucose: $viewModel.recentGlucose,
@@ -35,7 +39,7 @@ extension Home {
                     isStatusPopupPresented = true
                 }.onLongPressGesture {
                     viewModel.runLoop()
-                }.frame(minWidth: 0, maxWidth: .infinity)
+                }
             }.frame(maxWidth: .infinity)
         }