Explorar o código

Add alarm for fast battery drop

David Novosel hai 1 ano
pai
achega
c2a72e4a96

+ 28 - 0
LoopFollow/Controllers/Alarms.swift

@@ -507,6 +507,27 @@ extension MainViewController {
             }
         }
 
+        // Check for rapid battery drop
+        if UserDefaultsRepository.alertBatteryDropActive.value && !UserDefaultsRepository.alertBatteryDropIsSnoozed.value {
+            let targetDate = Calendar.current.date(byAdding: .minute, value: Int(-UserDefaultsRepository.alertBatteryDropPeriod.value), to: Date())!
+            let currentBatteryLevel = UserDefaultsRepository.deviceBatteryLevel.value as Double
+            let dropPercentage = Double(UserDefaultsRepository.alertBatteryDropPercentage.value)
+
+            // find the closest matching entry to the user defined timeframe
+            // this allows flexibility for ingress data matching as it can come at different intervals
+            if let previousBatteryLevel = deviceBatteryData.min(by: {
+                abs($0.timestamp.timeIntervalSince(targetDate)) < abs($1.timestamp.timeIntervalSince(targetDate))
+            }) {
+                if (previousBatteryLevel.batteryLevel - currentBatteryLevel) >= dropPercentage {
+                    AlarmSound.whichAlarm = "Battery Drop"
+
+                    if UserDefaultsRepository.alertBatteryDropRepeat.value { numLoops = -1 }
+                    triggerAlarm(sound: UserDefaultsRepository.alertBatteryDropSound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops, snoozeTime: UserDefaultsRepository.alertBatteryDropSnoozeHours.value, snoozeIncrement: 1, audio: true)
+                    return
+                }
+            }
+        }
+
         if UserDefaultsRepository.alertRecBolusActive.value,
            !UserDefaultsRepository.alertRecBolusIsSnoozed.value
         {
@@ -855,6 +876,13 @@ extension MainViewController {
             alarms.reloadIsSnoozed(key: "alertBatteryIsSnoozed", value: false)
         }
 
+        if date > UserDefaultsRepository.alertBatteryDropSnoozedTime.value ?? date {
+            UserDefaultsRepository.alertBatteryDropSnoozedTime.setNil(key: "alertBatteryDropSnoozedTime")
+            UserDefaultsRepository.alertBatteryDropIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertBatteryDropSnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertBatteryDropIsSnoozed", value: false)
+        }
+
         if date > UserDefaultsRepository.alertRecBolusSnoozedTime.value ?? date {
             UserDefaultsRepository.alertRecBolusSnoozedTime.setNil(key: "alertRecBolusSnoozedTime")
             UserDefaultsRepository.alertRecBolusIsSnoozed.value = false

+ 10 - 1
LoopFollow/Controllers/Nightscout/DeviceStatus.swift

@@ -112,10 +112,19 @@ extension MainViewController {
                    let upbat = uploader["battery"] as? Double {
                     infoManager.updateInfoData(type: .battery, value: String(format: "%.0f", upbat) + "%")
                     UserDefaultsRepository.deviceBatteryLevel.value = upbat
+                    let timestamp = uploader["timestamp"] as? Date ?? Date()
+
+                    let currentBattery = DataStructs.batteryStruct(batteryLevel: upbat, timestamp: timestamp)
+                    deviceBatteryData.append(currentBattery)
+
+                    // store only the last 30 battery readings
+                    if deviceBatteryData.count > 30 {
+                        deviceBatteryData.removeFirst()
+                    }
                 }
             }
         }
-        
+
         // Loop - handle new data
         if let lastLoopRecord = lastDeviceStatus?["loop"] as! [String : AnyObject]? {
             DeviceStatusLoop(formatter: formatter, lastLoopRecord: lastLoopRecord)

+ 6 - 0
LoopFollow/Helpers/DataStructs.swift

@@ -36,6 +36,12 @@ class DataStructs {
         var note: String
     }
 
+    //NS Battery Data  Struct
+    struct batteryStruct: Codable {
+        var batteryLevel: Double
+        var timestamp: Date
+    }
+
     //NS Override Data  Struct
     struct overrideStruct: Codable {
         var insulNeedsScaleFactor: Double

+ 9 - 0
LoopFollow/Storage/UserDefaults.swift

@@ -514,6 +514,15 @@ class UserDefaultsRepository {
     static let alertBatterySnoozeHours = UserDefaultsValue<Int>(key: "alertBatterySnoozeHours", default: 1)
     static var deviceBatteryLevel: UserDefaultsValue<Double> = UserDefaultsValue(key: "deviceBatteryLevel", default: 100.0)
 
+    static let alertBatteryDropActive = UserDefaultsValue<Bool>(key: "alertBatteryDropActive", default: false)
+    static let alertBatteryDropPercentage = UserDefaultsValue<Int>(key: "alertBatteryDropPercentage", default: 5)
+    static let alertBatteryDropPeriod = UserDefaultsValue<Int>(key: "alertBatteryDropPeriod", default: 15)
+    static let alertBatteryDropSound = UserDefaultsValue<String>(key: "alertBatteryDropSound", default: "Machine_Charge")
+    static let alertBatteryDropRepeat = UserDefaultsValue<Bool>(key: "alertBatteryDropRepeat", default: true)
+    static let alertBatteryDropIsSnoozed = UserDefaultsValue<Bool>(key: "alertBatteryDropIsSnoozed", default: false)
+    static let alertBatteryDropSnoozedTime = UserDefaultsValue<Date?>(key: "alertBatteryDropSnoozedTime", default: nil)
+    static let alertBatteryDropSnoozeHours = UserDefaultsValue<Int>(key: "alertBatteryDropSnoozeHours", default: 1)
+
     static let alertRecBolusActive = UserDefaultsValue<Bool>(key: "alertRecBolusActive", default: false)
     static let alertRecBolusLevel = UserDefaultsValue<Double>(key: "alertRecBolusLevel", default: 1)  //Unit[s]
     static let alertRecBolusSound = UserDefaultsValue<String>(key: "alertRecBolusSound", default: "Dhol_Shuffleloop")

+ 80 - 1
LoopFollow/ViewControllers/AlarmViewController.swift

@@ -351,7 +351,7 @@ class AlarmViewController: FormViewController {
 
         <<< SegmentedRow<String>("otherAlerts3"){ row in
             row.title = ""
-            row.options = ["IOB", "COB", "Battery"]
+            row.options = ["IOB", "COB", "Battery", "Battery Drop"]
             if !IsNightscoutEnabled() {
                 row.hidden = true
             }
@@ -428,6 +428,7 @@ class AlarmViewController: FormViewController {
         buildIOB()
         buildCOB()
         buildBatteryAlarm()
+        buildBatteryDropAlarm()
         buildRecBolus()
         buildTempTargetStart()
         buildTempTargetEnd()
@@ -3283,6 +3284,84 @@ class AlarmViewController: FormViewController {
         }
     }
 
+    func buildBatteryDropAlarm(){
+        form
+        +++ Section(header: "Battery Drop Alarm", footer: "Activates a notification alert whenever the battery level drops quickly based on a user-defined percentage and time interval, allowing for proactive device charging and power management.") { row in
+            row.hidden = "$otherAlerts3 != 'Battery Drop'"
+        }
+        <<< SwitchRow("alertBatteryDropActive"){ row in
+            row.title = "Active"
+            row.value = UserDefaultsRepository.alertBatteryDropActive.value
+        }.onChange { [weak self] row in
+            guard let value = row.value else { return }
+            UserDefaultsRepository.alertBatteryDropActive.value = value
+        }
+        <<< StepperRow("alertBatteryDropPercentage") { row in
+            row.title = "Battery Drop"
+            row.cell.stepper.stepValue = 5
+            row.cell.stepper.minimumValue = 5
+            row.cell.stepper.maximumValue = 100
+            row.value = Double(UserDefaultsRepository.alertBatteryDropPercentage.value)
+            row.displayValueFor = { value in
+                guard let value = value else { return nil }
+                return "\(Int(value))%"
+            }
+        }.onChange { [weak self] row in
+            guard let value = row.value else { return }
+            UserDefaultsRepository.alertBatteryDropPercentage.value = Int(value)
+        }
+        <<< StepperRow("alertBatteryDropPeriod") { row in
+            row.title = "Period (minutes)"
+            row.cell.stepper.stepValue = 5
+            row.cell.stepper.minimumValue = 5
+            row.cell.stepper.maximumValue = 30
+            row.value = Double(UserDefaultsRepository.alertBatteryDropPeriod.value)
+            row.displayValueFor = { value in
+                guard let value = value else { return nil }
+                return "\(Int(value))"
+            }
+        }.onChange { [weak self] row in
+            guard let value = row.value else { return }
+            UserDefaultsRepository.alertBatteryDropPeriod.value = Int(value)
+        }
+        <<< StepperRow("alertBatteryDropSnoozeHours") { row in
+            row.title = "Snooze Hours"
+            row.cell.stepper.stepValue = 1
+            row.cell.stepper.minimumValue = 1
+            row.cell.stepper.maximumValue = 24
+            row.value = Double(UserDefaultsRepository.alertBatterySnoozeHours.value)
+            row.displayValueFor = { value in
+                guard let value = value else { return nil }
+                return "\(Int(value))"
+            }
+        }.onChange { [weak self] row in
+            guard let value = row.value else { return }
+            UserDefaultsRepository.alertBatteryDropSnoozeHours.value = Int(value)
+        }
+        <<< PickerInputRow<String>("alertBatteryDropSound") { row in
+            row.title = "Sound"
+            row.options = soundFiles
+            row.value = UserDefaultsRepository.alertBatteryDropSound.value
+            row.displayValueFor = { value in
+                guard let value = value else { return nil }
+                return "\(String(value.replacingOccurrences(of: "_", with: " ")))"
+            }
+        }.onChange { [weak self] row in
+            guard let value = row.value else { return }
+            UserDefaultsRepository.alertBatteryDropSound.value = value
+            AlarmSound.setSoundFile(str: value)
+            AlarmSound.stop()
+            AlarmSound.playTest()
+        }
+        <<< SwitchRow("alertBatteryDropRepeat"){ row in
+            row.title = "Repeat Sound"
+            row.value = UserDefaultsRepository.alertBatteryDropRepeat.value
+        }.onChange { [weak self] row in
+            guard let value = row.value else { return }
+            UserDefaultsRepository.alertBatteryDropRepeat.value = value
+        }
+    }
+
     func buildRecBolus(){
         form
         +++ Section(header: "Rec. Bolus Alert", footer: "Activates a notification alert whenever recommended bolus is above a user-defined threshold, allowing for proactive manual bolusing.") { row in

+ 1 - 0
LoopFollow/ViewControllers/MainViewController.swift

@@ -102,6 +102,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     var sensorStartGraphData: [DataStructs.timestampOnlyStruct] = []
     var noteGraphData: [DataStructs.noteStruct] = []
     var chartData = LineChartData()
+    var deviceBatteryData: [DataStructs.batteryStruct] = []
     var newBGPulled = false
     var lastCalDate: Double = 0
     var latestDirectionString = ""

+ 6 - 0
LoopFollow/ViewControllers/SnoozeViewController.swift

@@ -227,6 +227,12 @@ class SnoozeViewController: UIViewController, UNUserNotificationCenterDelegate {
             alarms.reloadIsSnoozed(key: "alertBatteryIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertBatterySnoozedTime", setNil: false, value: currentDate.addingTimeInterval(longSnoozeDuration))
 
+        case "Battery Drop":
+            UserDefaultsRepository.alertBatteryDropIsSnoozed.value = true
+            UserDefaultsRepository.alertBatteryDropSnoozedTime.value = currentDate.addingTimeInterval(longSnoozeDuration)
+            alarms.reloadIsSnoozed(key: "alertBatteryDropIsSnoozed", value: true)
+            alarms.reloadSnoozeTime(key: "alertBatteryDropSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(longSnoozeDuration))
+
         case "Rec. Bolus":
             UserDefaultsRepository.alertRecBolusIsSnoozed.value = true
             UserDefaultsRepository.alertRecBolusSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)