Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion LoopFollow/Alarm/Alarm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ struct Alarm: Identifiable, Codable, Equatable {
/// Number of minutes that must satisfy the alarm criteria
var persistentMinutes: Int?

/// When set, the low alarm stays silent while BG is rising (positive delta),
/// only firing when the latest reading is flat or still falling.
var suppressIfRising: Bool = false

/// When set, the high alarm stays silent while BG is falling (negative delta),
/// only firing when the latest reading is flat or still rising.
var suppressIfFalling: Bool = false

/// When set, the phone-battery alarm stays silent while the phone is charging.
var suppressIfCharging: Bool = false

/// Size of window to observe values, for example battery drop of x within this number of minutes,
var monitoringWindow: Int?

Expand All @@ -101,7 +112,8 @@ struct Alarm: Identifiable, Codable, Equatable {
enum CodingKeys: String, CodingKey {
case id, type, name, isEnabled, snoozedUntil
case aboveBG, belowBG, threshold, predictiveMinutes, delta
case persistentMinutes, monitoringWindow, soundFile
case persistentMinutes, suppressIfRising, suppressIfFalling, suppressIfCharging
case monitoringWindow, soundFile
case snoozeDuration, playSoundOption, repeatSoundOption
case soundDelay, activeOption
case missedBolusPrebolusWindow, missedBolusIgnoreSmallBolusUnits
Expand All @@ -124,6 +136,9 @@ struct Alarm: Identifiable, Codable, Equatable {
predictiveMinutes = try container.decodeIfPresent(Int.self, forKey: .predictiveMinutes)
delta = try container.decodeIfPresent(Double.self, forKey: .delta)
persistentMinutes = try container.decodeIfPresent(Int.self, forKey: .persistentMinutes)
suppressIfRising = try container.decodeIfPresent(Bool.self, forKey: .suppressIfRising) ?? false
suppressIfFalling = try container.decodeIfPresent(Bool.self, forKey: .suppressIfFalling) ?? false
suppressIfCharging = try container.decodeIfPresent(Bool.self, forKey: .suppressIfCharging) ?? false
monitoringWindow = try container.decodeIfPresent(Int.self, forKey: .monitoringWindow)
soundFile = try container.decode(SoundFile.self, forKey: .soundFile)
snoozeDuration = try container.decodeIfPresent(Int.self, forKey: .snoozeDuration) ?? 5
Expand Down Expand Up @@ -154,6 +169,9 @@ struct Alarm: Identifiable, Codable, Equatable {
try container.encodeIfPresent(predictiveMinutes, forKey: .predictiveMinutes)
try container.encodeIfPresent(delta, forKey: .delta)
try container.encodeIfPresent(persistentMinutes, forKey: .persistentMinutes)
try container.encode(suppressIfRising, forKey: .suppressIfRising)
try container.encode(suppressIfFalling, forKey: .suppressIfFalling)
try container.encode(suppressIfCharging, forKey: .suppressIfCharging)
try container.encodeIfPresent(monitoringWindow, forKey: .monitoringWindow)
try container.encode(soundFile, forKey: .soundFile)
try container.encode(snoozeDuration, forKey: .snoozeDuration)
Expand Down
5 changes: 5 additions & 0 deletions LoopFollow/Alarm/AlarmCondition/BatteryCondition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ struct BatteryCondition: AlarmCondition {
guard let limit = alarm.threshold, limit > 0 else { return false }
guard let level = data.latestBattery else { return false }

// Optional: stay silent while the phone is charging back up.
if alarm.suppressIfCharging, data.latestBatteryIsCharging == true {
return false
}

return level <= limit
}
}
10 changes: 10 additions & 0 deletions LoopFollow/Alarm/AlarmCondition/HighBGCondition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ struct HighBGCondition: AlarmCondition {
}
}

// Optional: stay silent while the latest reading is below the previous one;
// only alarm when BG is flat or still rising.
if alarm.suppressIfFalling, data.bgReadings.count >= 2 {
let last = data.bgReadings[data.bgReadings.count - 1]
let previous = data.bgReadings[data.bgReadings.count - 2]
if last.sgv > 0, previous.sgv > 0, last.sgv < previous.sgv {
return false
}
}

return persistentOK
}
}
15 changes: 14 additions & 1 deletion LoopFollow/Alarm/AlarmCondition/LowBGCondition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,20 @@ struct LowBGCondition: AlarmCondition {
}

// ────────────────────────────────
// 3. final decision
// 3. rising BG suppression (optional)
// Stay silent while the latest reading is above the previous one;
// only alarm when BG is flat or still falling.
// ────────────────────────────────
if alarm.suppressIfRising, data.bgReadings.count >= 2 {
let last = data.bgReadings[data.bgReadings.count - 1]
let previous = data.bgReadings[data.bgReadings.count - 2]
if last.sgv > 0, previous.sgv > 0, last.sgv > previous.sgv {
return false
}
}

// ────────────────────────────────
// 4. final decision
// ────────────────────────────────
return persistentOK || predictiveTrigger
}
Expand Down
1 change: 1 addition & 0 deletions LoopFollow/Alarm/AlarmData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ struct AlarmData: Codable {
let IOB: Double?
let recentBoluses: [BolusEntry]
let latestBattery: Double?
let latestBatteryIsCharging: Bool?
let latestPumpBattery: Double?
let batteryHistory: [DataStructs.batteryStruct]
let recentCarbs: [CarbSample]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ struct HighBgAlarmEditor: View {
value: $alarm.persistentMinutes
)

Section(
header: Text("FALLING BG"),
footer: Text("Stay silent while BG is falling. The alert only sounds "
+ "when the latest reading is flat or still rising.")
) {
Toggle("Skip if BG is falling", isOn: $alarm.suppressIfFalling)
}

AlarmActiveSection(alarm: $alarm)
AlarmAudioSection(alarm: $alarm)
AlarmSnoozeSection(alarm: $alarm)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ struct LowBgAlarmEditor: View {
value: $alarm.predictiveMinutes
)

Section(
header: Text("RISING BG"),
footer: Text("Stay silent while BG is rising. The alert only sounds "
+ "when the latest reading is flat or still falling.")
) {
Toggle("Skip if BG is rising", isOn: $alarm.suppressIfRising)
}

AlarmActiveSection(alarm: $alarm)
AlarmAudioSection(alarm: $alarm)
AlarmSnoozeSection(alarm: $alarm)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ struct PhoneBatteryAlarmEditor: View {
value: $alarm.threshold
)

Section(
header: Text("CHARGING"),
footer: Text("Stay silent while the phone is charging. Requires the "
+ "uploader to report charging status; if it doesn't, the alert still sounds.")
) {
Toggle("Skip while charging", isOn: $alarm.suppressIfCharging)
}

AlarmActiveSection(alarm: $alarm)
AlarmAudioSection(alarm: $alarm)
AlarmSnoozeSection(alarm: $alarm)
Expand Down
4 changes: 3 additions & 1 deletion LoopFollow/Controllers/Nightscout/DeviceStatus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,16 @@ extension MainViewController {
if let uploader = lastDeviceStatus?["uploader"] as? [String: AnyObject],
let upbat = uploader["battery"] as? Double
{
let isCharging = uploader["isCharging"] as? Bool
let batteryText: String
if let isCharging = uploader["isCharging"] as? Bool, isCharging {
if isCharging == true {
batteryText = "⚡️ " + String(format: "%.0f", upbat) + "%"
} else {
batteryText = String(format: "%.0f", upbat) + "%"
}
infoManager.updateInfoData(type: .battery, value: batteryText)
Observable.shared.deviceBatteryLevel.value = upbat
Observable.shared.deviceBatteryIsCharging.value = isCharging

let timestamp = uploader["timestamp"] as? Date ?? Date()
let currentBattery = DataStructs.batteryStruct(batteryLevel: upbat, timestamp: timestamp)
Expand Down
1 change: 1 addition & 0 deletions LoopFollow/Storage/Observable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class Observable {
var previousAlertLastLoopTime = ObservableValue<TimeInterval?>(default: nil)
var deviceRecBolus = ObservableValue<Double?>(default: nil)
var deviceBatteryLevel = ObservableValue<Double?>(default: nil)
var deviceBatteryIsCharging = ObservableValue<Bool?>(default: nil)
var pumpBatteryLevel = ObservableValue<Double?>(default: nil)
var enactedOrSuggested = ObservableValue<TimeInterval?>(default: nil)

Expand Down
1 change: 1 addition & 0 deletions LoopFollow/Task/AlarmTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ extension MainViewController {
IOB: self.latestIOB?.value,
recentBoluses: bolusEntries,
latestBattery: latestBattery,
latestBatteryIsCharging: Observable.shared.deviceBatteryIsCharging.value,
latestPumpBattery: latestPumpBattery,
batteryHistory: self.deviceBatteryData,
recentCarbs: recentCarbs
Expand Down
3 changes: 3 additions & 0 deletions Tests/AlarmConditions/Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ extension AlarmData {
IOB: nil,
recentBoluses: [],
latestBattery: level,
latestBatteryIsCharging: nil,
latestPumpBattery: nil,
batteryHistory: [],
recentCarbs: []
Expand All @@ -75,6 +76,7 @@ extension AlarmData {
IOB: nil,
recentBoluses: [],
latestBattery: nil,
latestBatteryIsCharging: nil,
latestPumpBattery: nil,
batteryHistory: [],
recentCarbs: []
Expand All @@ -99,6 +101,7 @@ extension AlarmData {
IOB: nil,
recentBoluses: [],
latestBattery: nil,
latestBatteryIsCharging: nil,
latestPumpBattery: nil,
batteryHistory: [],
recentCarbs: carbs
Expand Down
Loading