Skip to content

Commit 6815cf7

Browse files
authored
Merge pull request #370 from dnovosel/battery-drop-alarm
Add battery drop alarm
2 parents bdf5e77 + c4c3556 commit 6815cf7

File tree

7 files changed

+143
-2
lines changed

7 files changed

+143
-2
lines changed

LoopFollow/Controllers/Alarms.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,30 @@ extension MainViewController {
507507
}
508508
}
509509

510+
// Check for rapid battery drop
511+
if UserDefaultsRepository.alertBatteryDropActive.value && !UserDefaultsRepository.alertBatteryDropIsSnoozed.value {
512+
let targetDate = Calendar.current.date(byAdding: .minute, value: Int(-UserDefaultsRepository.alertBatteryDropPeriod.value), to: Date())!
513+
let currentBatteryLevel = UserDefaultsRepository.deviceBatteryLevel.value as Double
514+
let dropPercentage = Double(UserDefaultsRepository.alertBatteryDropPercentage.value)
515+
516+
// find the closest matching entry to the user defined timeframe
517+
// this allows flexibility for ingress data matching as it can come at different intervals
518+
if let previousBatteryLevel = deviceBatteryData.min(by: {
519+
abs($0.timestamp.timeIntervalSince(targetDate)) < abs($1.timestamp.timeIntervalSince(targetDate))
520+
}) {
521+
// ignore a drop with a previous level of 100 as it will trigger a false alarm
522+
if (previousBatteryLevel.batteryLevel < 100) {
523+
if (previousBatteryLevel.batteryLevel - currentBatteryLevel) >= dropPercentage {
524+
AlarmSound.whichAlarm = "Battery Drop"
525+
526+
if UserDefaultsRepository.alertBatteryDropRepeat.value { numLoops = -1 }
527+
triggerAlarm(sound: UserDefaultsRepository.alertBatteryDropSound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops, snoozeTime: UserDefaultsRepository.alertBatteryDropSnoozeHours.value, snoozeIncrement: 1, audio: true)
528+
return
529+
}
530+
}
531+
}
532+
}
533+
510534
if UserDefaultsRepository.alertRecBolusActive.value,
511535
!UserDefaultsRepository.alertRecBolusIsSnoozed.value
512536
{
@@ -855,6 +879,13 @@ extension MainViewController {
855879
alarms.reloadIsSnoozed(key: "alertBatteryIsSnoozed", value: false)
856880
}
857881

882+
if date > UserDefaultsRepository.alertBatteryDropSnoozedTime.value ?? date {
883+
UserDefaultsRepository.alertBatteryDropSnoozedTime.setNil(key: "alertBatteryDropSnoozedTime")
884+
UserDefaultsRepository.alertBatteryDropIsSnoozed.value = false
885+
alarms.reloadSnoozeTime(key: "alertBatteryDropSnoozedTime", setNil: true)
886+
alarms.reloadIsSnoozed(key: "alertBatteryDropIsSnoozed", value: false)
887+
}
888+
858889
if date > UserDefaultsRepository.alertRecBolusSnoozedTime.value ?? date {
859890
UserDefaultsRepository.alertRecBolusSnoozedTime.setNil(key: "alertRecBolusSnoozedTime")
860891
UserDefaultsRepository.alertRecBolusIsSnoozed.value = false

LoopFollow/Controllers/Nightscout/DeviceStatus.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,19 @@ extension MainViewController {
112112
let upbat = uploader["battery"] as? Double {
113113
infoManager.updateInfoData(type: .battery, value: String(format: "%.0f", upbat) + "%")
114114
UserDefaultsRepository.deviceBatteryLevel.value = upbat
115+
let timestamp = uploader["timestamp"] as? Date ?? Date()
116+
117+
let currentBattery = DataStructs.batteryStruct(batteryLevel: upbat, timestamp: timestamp)
118+
deviceBatteryData.append(currentBattery)
119+
120+
// store only the last 30 battery readings
121+
if deviceBatteryData.count > 30 {
122+
deviceBatteryData.removeFirst()
123+
}
115124
}
116125
}
117126
}
118-
127+
119128
// Loop - handle new data
120129
if let lastLoopRecord = lastDeviceStatus?["loop"] as! [String : AnyObject]? {
121130
DeviceStatusLoop(formatter: formatter, lastLoopRecord: lastLoopRecord)

LoopFollow/Helpers/DataStructs.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ class DataStructs {
3636
var note: String
3737
}
3838

39+
//NS Battery Data Struct
40+
struct batteryStruct: Codable {
41+
var batteryLevel: Double
42+
var timestamp: Date
43+
}
44+
3945
//NS Override Data Struct
4046
struct overrideStruct: Codable {
4147
var insulNeedsScaleFactor: Double

LoopFollow/Storage/UserDefaults.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,15 @@ class UserDefaultsRepository {
514514
static let alertBatterySnoozeHours = UserDefaultsValue<Int>(key: "alertBatterySnoozeHours", default: 1)
515515
static var deviceBatteryLevel: UserDefaultsValue<Double> = UserDefaultsValue(key: "deviceBatteryLevel", default: 100.0)
516516

517+
static let alertBatteryDropActive = UserDefaultsValue<Bool>(key: "alertBatteryDropActive", default: false)
518+
static let alertBatteryDropPercentage = UserDefaultsValue<Int>(key: "alertBatteryDropPercentage", default: 5)
519+
static let alertBatteryDropPeriod = UserDefaultsValue<Int>(key: "alertBatteryDropPeriod", default: 15)
520+
static let alertBatteryDropSound = UserDefaultsValue<String>(key: "alertBatteryDropSound", default: "Machine_Charge")
521+
static let alertBatteryDropRepeat = UserDefaultsValue<Bool>(key: "alertBatteryDropRepeat", default: true)
522+
static let alertBatteryDropIsSnoozed = UserDefaultsValue<Bool>(key: "alertBatteryDropIsSnoozed", default: false)
523+
static let alertBatteryDropSnoozedTime = UserDefaultsValue<Date?>(key: "alertBatteryDropSnoozedTime", default: nil)
524+
static let alertBatteryDropSnoozeHours = UserDefaultsValue<Int>(key: "alertBatteryDropSnoozeHours", default: 1)
525+
517526
static let alertRecBolusActive = UserDefaultsValue<Bool>(key: "alertRecBolusActive", default: false)
518527
static let alertRecBolusLevel = UserDefaultsValue<Double>(key: "alertRecBolusLevel", default: 1) //Unit[s]
519528
static let alertRecBolusSound = UserDefaultsValue<String>(key: "alertRecBolusSound", default: "Dhol_Shuffleloop")

LoopFollow/ViewControllers/AlarmViewController.swift

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ class AlarmViewController: FormViewController {
351351

352352
<<< SegmentedRow<String>("otherAlerts3"){ row in
353353
row.title = ""
354-
row.options = ["IOB", "COB", "Battery"]
354+
row.options = ["IOB", "COB", "Battery", "Battery Drop"]
355355
if !IsNightscoutEnabled() {
356356
row.hidden = true
357357
}
@@ -428,6 +428,7 @@ class AlarmViewController: FormViewController {
428428
buildIOB()
429429
buildCOB()
430430
buildBatteryAlarm()
431+
buildBatteryDropAlarm()
431432
buildRecBolus()
432433
buildTempTargetStart()
433434
buildTempTargetEnd()
@@ -3283,6 +3284,84 @@ class AlarmViewController: FormViewController {
32833284
}
32843285
}
32853286

3287+
func buildBatteryDropAlarm(){
3288+
form
3289+
+++ 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
3290+
row.hidden = "$otherAlerts3 != 'Battery Drop'"
3291+
}
3292+
<<< SwitchRow("alertBatteryDropActive"){ row in
3293+
row.title = "Active"
3294+
row.value = UserDefaultsRepository.alertBatteryDropActive.value
3295+
}.onChange { [weak self] row in
3296+
guard let value = row.value else { return }
3297+
UserDefaultsRepository.alertBatteryDropActive.value = value
3298+
}
3299+
<<< StepperRow("alertBatteryDropPercentage") { row in
3300+
row.title = "Battery Drop"
3301+
row.cell.stepper.stepValue = 5
3302+
row.cell.stepper.minimumValue = 5
3303+
row.cell.stepper.maximumValue = 100
3304+
row.value = Double(UserDefaultsRepository.alertBatteryDropPercentage.value)
3305+
row.displayValueFor = { value in
3306+
guard let value = value else { return nil }
3307+
return "\(Int(value))%"
3308+
}
3309+
}.onChange { [weak self] row in
3310+
guard let value = row.value else { return }
3311+
UserDefaultsRepository.alertBatteryDropPercentage.value = Int(value)
3312+
}
3313+
<<< StepperRow("alertBatteryDropPeriod") { row in
3314+
row.title = "Period (minutes)"
3315+
row.cell.stepper.stepValue = 5
3316+
row.cell.stepper.minimumValue = 5
3317+
row.cell.stepper.maximumValue = 30
3318+
row.value = Double(UserDefaultsRepository.alertBatteryDropPeriod.value)
3319+
row.displayValueFor = { value in
3320+
guard let value = value else { return nil }
3321+
return "\(Int(value))"
3322+
}
3323+
}.onChange { [weak self] row in
3324+
guard let value = row.value else { return }
3325+
UserDefaultsRepository.alertBatteryDropPeriod.value = Int(value)
3326+
}
3327+
<<< StepperRow("alertBatteryDropSnoozeHours") { row in
3328+
row.title = "Snooze Hours"
3329+
row.cell.stepper.stepValue = 1
3330+
row.cell.stepper.minimumValue = 1
3331+
row.cell.stepper.maximumValue = 24
3332+
row.value = Double(UserDefaultsRepository.alertBatterySnoozeHours.value)
3333+
row.displayValueFor = { value in
3334+
guard let value = value else { return nil }
3335+
return "\(Int(value))"
3336+
}
3337+
}.onChange { [weak self] row in
3338+
guard let value = row.value else { return }
3339+
UserDefaultsRepository.alertBatteryDropSnoozeHours.value = Int(value)
3340+
}
3341+
<<< PickerInputRow<String>("alertBatteryDropSound") { row in
3342+
row.title = "Sound"
3343+
row.options = soundFiles
3344+
row.value = UserDefaultsRepository.alertBatteryDropSound.value
3345+
row.displayValueFor = { value in
3346+
guard let value = value else { return nil }
3347+
return "\(String(value.replacingOccurrences(of: "_", with: " ")))"
3348+
}
3349+
}.onChange { [weak self] row in
3350+
guard let value = row.value else { return }
3351+
UserDefaultsRepository.alertBatteryDropSound.value = value
3352+
AlarmSound.setSoundFile(str: value)
3353+
AlarmSound.stop()
3354+
AlarmSound.playTest()
3355+
}
3356+
<<< SwitchRow("alertBatteryDropRepeat"){ row in
3357+
row.title = "Repeat Sound"
3358+
row.value = UserDefaultsRepository.alertBatteryDropRepeat.value
3359+
}.onChange { [weak self] row in
3360+
guard let value = row.value else { return }
3361+
UserDefaultsRepository.alertBatteryDropRepeat.value = value
3362+
}
3363+
}
3364+
32863365
func buildRecBolus(){
32873366
form
32883367
+++ 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

LoopFollow/ViewControllers/MainViewController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
102102
var sensorStartGraphData: [DataStructs.timestampOnlyStruct] = []
103103
var noteGraphData: [DataStructs.noteStruct] = []
104104
var chartData = LineChartData()
105+
var deviceBatteryData: [DataStructs.batteryStruct] = []
105106
var newBGPulled = false
106107
var lastCalDate: Double = 0
107108
var latestDirectionString = ""

LoopFollow/ViewControllers/SnoozeViewController.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@ class SnoozeViewController: UIViewController, UNUserNotificationCenterDelegate {
227227
alarms.reloadIsSnoozed(key: "alertBatteryIsSnoozed", value: true)
228228
alarms.reloadSnoozeTime(key: "alertBatterySnoozedTime", setNil: false, value: currentDate.addingTimeInterval(longSnoozeDuration))
229229

230+
case "Battery Drop":
231+
UserDefaultsRepository.alertBatteryDropIsSnoozed.value = true
232+
UserDefaultsRepository.alertBatteryDropSnoozedTime.value = currentDate.addingTimeInterval(longSnoozeDuration)
233+
alarms.reloadIsSnoozed(key: "alertBatteryDropIsSnoozed", value: true)
234+
alarms.reloadSnoozeTime(key: "alertBatteryDropSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(longSnoozeDuration))
235+
230236
case "Rec. Bolus":
231237
UserDefaultsRepository.alertRecBolusIsSnoozed.value = true
232238
UserDefaultsRepository.alertRecBolusSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)

0 commit comments

Comments
 (0)