|
@@ -9,11 +9,29 @@
|
|
|
|
|
|
|
|
import Foundation
|
|
import Foundation
|
|
|
|
|
|
|
|
|
|
+fileprivate let defaultShutdownImminentTime = Pod.serviceDuration - Pod.endOfServiceImminentWindow
|
|
|
|
|
+fileprivate let defaultExpirationReminderTime = Pod.nominalPodLife - Pod.defaultExpirationReminderOffset
|
|
|
|
|
+fileprivate let defaultExpiredTime = Pod.nominalPodLife
|
|
|
|
|
+
|
|
|
|
|
+// PDM and pre-SwiftUI use every1MinuteFor3MinutesAndRepeatEvery15Minutes, but with SwiftUI use every15Minutes
|
|
|
|
|
+fileprivate let suspendTimeExpiredBeepRepeat = BeepRepeat.every15Minutes
|
|
|
|
|
+
|
|
|
public enum AlertTrigger {
|
|
public enum AlertTrigger {
|
|
|
case unitsRemaining(Double)
|
|
case unitsRemaining(Double)
|
|
|
case timeUntilAlert(TimeInterval)
|
|
case timeUntilAlert(TimeInterval)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+extension AlertTrigger: CustomDebugStringConvertible {
|
|
|
|
|
+ public var debugDescription: String {
|
|
|
|
|
+ switch self {
|
|
|
|
|
+ case .unitsRemaining(let units):
|
|
|
|
|
+ return "\(Int(units))U"
|
|
|
|
|
+ case .timeUntilAlert(let triggerTime):
|
|
|
|
|
+ return "triggerTime=\(triggerTime.timeIntervalStr)"
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
public enum BeepRepeat: UInt8 {
|
|
public enum BeepRepeat: UInt8 {
|
|
|
case once = 0
|
|
case once = 0
|
|
|
case every1MinuteFor3MinutesAndRepeatEvery60Minutes = 1
|
|
case every1MinuteFor3MinutesAndRepeatEvery60Minutes = 1
|
|
@@ -30,29 +48,48 @@ public enum BeepRepeat: UInt8 {
|
|
|
public struct AlertConfiguration {
|
|
public struct AlertConfiguration {
|
|
|
|
|
|
|
|
let slot: AlertSlot
|
|
let slot: AlertSlot
|
|
|
- let trigger: AlertTrigger
|
|
|
|
|
let active: Bool
|
|
let active: Bool
|
|
|
let duration: TimeInterval
|
|
let duration: TimeInterval
|
|
|
|
|
+ let trigger: AlertTrigger
|
|
|
let beepRepeat: BeepRepeat
|
|
let beepRepeat: BeepRepeat
|
|
|
let beepType: BeepType
|
|
let beepType: BeepType
|
|
|
|
|
+ let silent: Bool
|
|
|
let autoOffModifier: Bool
|
|
let autoOffModifier: Bool
|
|
|
|
|
|
|
|
static let length = 6
|
|
static let length = 6
|
|
|
|
|
|
|
|
- public init(alertType: AlertSlot, active: Bool = true, autoOffModifier: Bool = false, duration: TimeInterval, trigger: AlertTrigger, beepRepeat: BeepRepeat, beepType: BeepType) {
|
|
|
|
|
|
|
+ public init(alertType: AlertSlot, active: Bool = true, duration: TimeInterval = 0, trigger: AlertTrigger, beepRepeat: BeepRepeat, beepType: BeepType, silent: Bool = false, autoOffModifier: Bool = false)
|
|
|
|
|
+ {
|
|
|
self.slot = alertType
|
|
self.slot = alertType
|
|
|
self.active = active
|
|
self.active = active
|
|
|
- self.autoOffModifier = autoOffModifier
|
|
|
|
|
self.duration = duration
|
|
self.duration = duration
|
|
|
self.trigger = trigger
|
|
self.trigger = trigger
|
|
|
self.beepRepeat = beepRepeat
|
|
self.beepRepeat = beepRepeat
|
|
|
self.beepType = beepType
|
|
self.beepType = beepType
|
|
|
|
|
+ self.silent = silent
|
|
|
|
|
+ self.autoOffModifier = autoOffModifier
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
extension AlertConfiguration: CustomDebugStringConvertible {
|
|
extension AlertConfiguration: CustomDebugStringConvertible {
|
|
|
public var debugDescription: String {
|
|
public var debugDescription: String {
|
|
|
- return "AlertConfiguration(slot:\(slot), active:\(active), autoOffModifier:\(autoOffModifier), duration:\(duration), trigger:\(trigger), beepRepeat:\(beepRepeat), beepType:\(beepType))"
|
|
|
|
|
|
|
+ var str = "slot:\(slot)"
|
|
|
|
|
+ if !active {
|
|
|
|
|
+ str += ", active:\(active)"
|
|
|
|
|
+ }
|
|
|
|
|
+ if duration != 0 {
|
|
|
|
|
+ str += ", duration:\(duration.timeIntervalStr)"
|
|
|
|
|
+ }
|
|
|
|
|
+ str += ", trigger:\(trigger), beepRepeat:\(beepRepeat)"
|
|
|
|
|
+ if beepType != .noBeepNonCancel {
|
|
|
|
|
+ str += ", beepType:\(beepType)"
|
|
|
|
|
+ } else {
|
|
|
|
|
+ str += ", silent:\(silent)"
|
|
|
|
|
+ }
|
|
|
|
|
+ if autoOffModifier {
|
|
|
|
|
+ str += ", autoOffModifier:\(autoOffModifier)"
|
|
|
|
|
+ }
|
|
|
|
|
+ return "\nAlertConfiguration(\(str))"
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -61,54 +98,73 @@ extension AlertConfiguration: CustomDebugStringConvertible {
|
|
|
public enum PodAlert: CustomStringConvertible, RawRepresentable, Equatable {
|
|
public enum PodAlert: CustomStringConvertible, RawRepresentable, Equatable {
|
|
|
public typealias RawValue = [String: Any]
|
|
public typealias RawValue = [String: Any]
|
|
|
|
|
|
|
|
- // 2 hours long, time for user to start pairing process
|
|
|
|
|
- case waitingForPairingReminder
|
|
|
|
|
|
|
+ // slot0AutoOff: auto-off timer; requires user input every x minutes -- NOT IMPLEMENTED
|
|
|
|
|
+ case autoOff(active: Bool, offset: TimeInterval, countdownDuration: TimeInterval, silent: Bool = false)
|
|
|
|
|
|
|
|
- // 1 hour long, time for user to finish priming, cannula insertion
|
|
|
|
|
- case finishSetupReminder
|
|
|
|
|
|
|
+ // slot1NotUsed
|
|
|
|
|
+ case notUsed
|
|
|
|
|
|
|
|
- // User configurable with PDM (1-24 hours before 72 hour expiration) "Change Pod Soon"
|
|
|
|
|
- case expirationReminder(TimeInterval)
|
|
|
|
|
|
|
+ // slot2ShutdownImminent: 79 hour alarm (1 hour before shutdown)
|
|
|
|
|
+ // 2 sets of beeps every 15 minutes for 1 hour
|
|
|
|
|
+ case shutdownImminent(offset: TimeInterval, absAlertTime: TimeInterval, silent: Bool = false)
|
|
|
|
|
|
|
|
- // 72 hour alarm
|
|
|
|
|
- case expired(alertTime: TimeInterval, duration: TimeInterval)
|
|
|
|
|
|
|
+ // slot3ExpirationReminder: User configurable with PDM (1-24 hours before 72 hour expiration)
|
|
|
|
|
+ // 2 sets of beeps every minute for 3 minutes and repeat every 15 minutes
|
|
|
|
|
+ // The PDM doesn't use a duration for this alert (presumably because it is limited to 2^9-1 minutes or 8h31m)
|
|
|
|
|
+ case expirationReminder(offset: TimeInterval, absAlertTime: TimeInterval, duration: TimeInterval = 0, silent: Bool = false)
|
|
|
|
|
|
|
|
- // 79 hour alarm (1 hour before shutdown)
|
|
|
|
|
- case shutdownImminent(TimeInterval)
|
|
|
|
|
|
|
+ // slot4LowReservoir: reservoir below configured value alert
|
|
|
|
|
+ case lowReservoir(units: Double, silent: Bool = false)
|
|
|
|
|
|
|
|
- // reservoir below configured value alert
|
|
|
|
|
- case lowReservoir(Double)
|
|
|
|
|
|
|
+ // slot5SuspendedReminder: pod suspended reminder, before suspendTime;
|
|
|
|
|
+ // short beep every 15 minutes if > 30 min, else short beep every 5 minutes
|
|
|
|
|
+ case podSuspendedReminder(active: Bool, offset: TimeInterval, suspendTime: TimeInterval, timePassed: TimeInterval = 0, silent: Bool = false)
|
|
|
|
|
|
|
|
- // auto-off timer; requires user input every x minutes
|
|
|
|
|
- case autoOff(active: Bool, countdownDuration: TimeInterval)
|
|
|
|
|
|
|
+ // slot6SuspendTimeExpired: pod suspend time expired alarm, after suspendTime;
|
|
|
|
|
+ // 2 sets of beeps every minute for 3 minutes repeated every 15 minutes (PDM & pre-SwiftUI implementations)
|
|
|
|
|
+ // 2 sets of beeps every 15 minutes (for SwiftUI PumpManagerAlerts implementations)
|
|
|
|
|
+ case suspendTimeExpired(offset: TimeInterval, suspendTime: TimeInterval, silent: Bool = false)
|
|
|
|
|
|
|
|
- // pod suspended reminder, before suspendTime; short beep every 15 minutes if > 30 min, else every 5 minutes
|
|
|
|
|
- case podSuspendedReminder(active: Bool, suspendTime: TimeInterval)
|
|
|
|
|
|
|
+ // slot7Expired: 2 hours long, time for user to start pairing process
|
|
|
|
|
+ case waitingForPairingReminder
|
|
|
|
|
|
|
|
- // pod suspend time expired alarm, after suspendTime; 2 sets of beeps every min for 3 minutes repeated every 15 minutes
|
|
|
|
|
- case suspendTimeExpired(suspendTime: TimeInterval)
|
|
|
|
|
|
|
+ // slot7Expired: 1 hour long, time for user to finish priming, cannula insertion
|
|
|
|
|
+ case finishSetupReminder
|
|
|
|
|
+
|
|
|
|
|
+ // slot7Expired: 72 hour alarm
|
|
|
|
|
+ case expired(offset: TimeInterval, absAlertTime: TimeInterval, duration: TimeInterval, silent: Bool = false)
|
|
|
|
|
|
|
|
public var description: String {
|
|
public var description: String {
|
|
|
var alertName: String
|
|
var alertName: String
|
|
|
switch self {
|
|
switch self {
|
|
|
- case .waitingForPairingReminder:
|
|
|
|
|
- return LocalizedString("Waiting for pairing reminder", comment: "Description waiting for pairing reminder")
|
|
|
|
|
- case .finishSetupReminder:
|
|
|
|
|
- return LocalizedString("Finish setup reminder", comment: "Description for finish setup reminder")
|
|
|
|
|
- case .expirationReminder:
|
|
|
|
|
- alertName = LocalizedString("Expiration alert", comment: "Description for expiration alert")
|
|
|
|
|
- case .expired:
|
|
|
|
|
- alertName = LocalizedString("Expiration advisory", comment: "Description for expiration advisory")
|
|
|
|
|
- case .shutdownImminent:
|
|
|
|
|
- alertName = LocalizedString("Shutdown imminent", comment: "Description for shutdown imminent")
|
|
|
|
|
- case .lowReservoir(let units):
|
|
|
|
|
- alertName = String(format: LocalizedString("Low reservoir advisory (%1$gU)", comment: "Format string for description for low reservoir advisory (1: reminder units)"), units)
|
|
|
|
|
|
|
+ // slot0AutoOff
|
|
|
case .autoOff:
|
|
case .autoOff:
|
|
|
- alertName = LocalizedString("Auto-off", comment: "Description for auto-off")
|
|
|
|
|
|
|
+ alertName = LocalizedString("Auto-off", comment: "Description for auto-off alert")
|
|
|
|
|
+ // slot1NotUsed
|
|
|
|
|
+ case .notUsed:
|
|
|
|
|
+ alertName = LocalizedString("Not used", comment: "Description for not used slot alert")
|
|
|
|
|
+ // slot2ShutdownImminent
|
|
|
|
|
+ case .shutdownImminent:
|
|
|
|
|
+ alertName = LocalizedString("Shutdown imminent", comment: "Description for shutdown imminent alert")
|
|
|
|
|
+ // slot3ExpirationReminder
|
|
|
|
|
+ case .expirationReminder:
|
|
|
|
|
+ alertName = LocalizedString("Expiration reminder", comment: "Description for expiration reminder alert")
|
|
|
|
|
+ // slot4LowReservoir
|
|
|
|
|
+ case .lowReservoir:
|
|
|
|
|
+ alertName = LocalizedString("Low reservoir", comment: "Format string for description for low reservoir alert")
|
|
|
|
|
+ // slot5SuspendedReminder
|
|
|
case .podSuspendedReminder:
|
|
case .podSuspendedReminder:
|
|
|
- alertName = LocalizedString("Pod suspended reminder", comment: "Description for pod suspended reminder")
|
|
|
|
|
|
|
+ alertName = LocalizedString("Pod suspended reminder", comment: "Description for pod suspended reminder alert")
|
|
|
|
|
+ // slot6SuspendTimeExpired
|
|
|
case .suspendTimeExpired:
|
|
case .suspendTimeExpired:
|
|
|
- alertName = LocalizedString("Suspend time expired", comment: "Description for suspend time expired")
|
|
|
|
|
|
|
+ alertName = LocalizedString("Suspend time expired", comment: "Description for suspend time expired alert")
|
|
|
|
|
+ // slot7Expired
|
|
|
|
|
+ case .waitingForPairingReminder:
|
|
|
|
|
+ alertName = LocalizedString("Waiting for pairing reminder", comment: "Description waiting for pairing reminder alert")
|
|
|
|
|
+ case .finishSetupReminder:
|
|
|
|
|
+ alertName = LocalizedString("Finish setup reminder", comment: "Description for finish setup reminder alert")
|
|
|
|
|
+ case .expired:
|
|
|
|
|
+ alertName = LocalizedString("Pod expired", comment: "Description for pod expired alert")
|
|
|
}
|
|
}
|
|
|
if self.configuration.active == false {
|
|
if self.configuration.active == false {
|
|
|
alertName += LocalizedString(" (inactive)", comment: "Description for an inactive alert modifier")
|
|
alertName += LocalizedString(" (inactive)", comment: "Description for an inactive alert modifier")
|
|
@@ -118,71 +174,126 @@ public enum PodAlert: CustomStringConvertible, RawRepresentable, Equatable {
|
|
|
|
|
|
|
|
public var configuration: AlertConfiguration {
|
|
public var configuration: AlertConfiguration {
|
|
|
switch self {
|
|
switch self {
|
|
|
- case .waitingForPairingReminder:
|
|
|
|
|
- return AlertConfiguration(alertType: .slot7, duration: .minutes(110), trigger: .timeUntilAlert(.minutes(10)), beepRepeat: .every5Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
|
|
|
|
|
- case .finishSetupReminder:
|
|
|
|
|
- return AlertConfiguration(alertType: .slot7, duration: .minutes(55), trigger: .timeUntilAlert(.minutes(5)), beepRepeat: .every5Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
|
|
|
|
|
- case .expirationReminder(let alertTime):
|
|
|
|
|
- let active = alertTime != 0 // disable if alertTime is 0
|
|
|
|
|
- return AlertConfiguration(alertType: .slot3, active: active, duration: 0, trigger: .timeUntilAlert(alertTime), beepRepeat: .every1MinuteFor3MinutesAndRepeatEvery15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
|
|
|
|
|
- case .expired(let alarmTime, let duration):
|
|
|
|
|
- let active = alarmTime != 0 // disable if alarmTime is 0
|
|
|
|
|
- return AlertConfiguration(alertType: .slot7, active: active, duration: duration, trigger: .timeUntilAlert(alarmTime), beepRepeat: .every60Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
|
|
|
|
|
- case .shutdownImminent(let alarmTime):
|
|
|
|
|
- let active = alarmTime != 0 // disable if alarmTime is 0
|
|
|
|
|
- return AlertConfiguration(alertType: .slot2, active: active, duration: 0, trigger: .timeUntilAlert(alarmTime), beepRepeat: .every15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
|
|
|
|
|
- case .lowReservoir(let units):
|
|
|
|
|
|
|
+ // slot0AutoOff
|
|
|
|
|
+ case .autoOff(let active, _, let countdownDuration, let silent):
|
|
|
|
|
+ return AlertConfiguration(alertType: .slot0AutoOff, active: active, duration: .minutes(15), trigger: .timeUntilAlert(countdownDuration), beepRepeat: .every1MinuteFor15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep, silent: silent, autoOffModifier: true)
|
|
|
|
|
+
|
|
|
|
|
+ // slot1NotUsed
|
|
|
|
|
+ case .notUsed:
|
|
|
|
|
+ return AlertConfiguration(alertType: .slot1NotUsed, duration: .minutes(55), trigger: .timeUntilAlert(.minutes(5)), beepRepeat: .every5Minutes, beepType: .noBeepNonCancel)
|
|
|
|
|
+
|
|
|
|
|
+ // slot2ShutdownImminent
|
|
|
|
|
+ case .shutdownImminent(let offset, let absAlertTime, let silent):
|
|
|
|
|
+ let active = absAlertTime != 0 // disable if absAlertTime is 0
|
|
|
|
|
+ let triggerTime: TimeInterval
|
|
|
|
|
+ if active {
|
|
|
|
|
+ triggerTime = absAlertTime - offset
|
|
|
|
|
+ } else {
|
|
|
|
|
+ triggerTime = 0
|
|
|
|
|
+ }
|
|
|
|
|
+ return AlertConfiguration(alertType: .slot2ShutdownImminent, active: active, trigger: .timeUntilAlert(triggerTime), beepRepeat: .every15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep, silent: silent)
|
|
|
|
|
+
|
|
|
|
|
+ // slot3ExpirationReminder
|
|
|
|
|
+ case .expirationReminder(let offset, let absAlertTime, let duration, let silent):
|
|
|
|
|
+ let active = absAlertTime != 0 // disable if absAlertTime is 0
|
|
|
|
|
+ let triggerTime: TimeInterval
|
|
|
|
|
+ if active {
|
|
|
|
|
+ triggerTime = absAlertTime - offset
|
|
|
|
|
+ } else {
|
|
|
|
|
+ triggerTime = 0
|
|
|
|
|
+ }
|
|
|
|
|
+ return AlertConfiguration(alertType: .slot3ExpirationReminder, active: active, duration: duration, trigger: .timeUntilAlert(triggerTime), beepRepeat: .every1MinuteFor3MinutesAndRepeatEvery15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep, silent: silent)
|
|
|
|
|
+
|
|
|
|
|
+ // slot4LowReservoir
|
|
|
|
|
+ case .lowReservoir(let units, let silent):
|
|
|
let active = units != 0 // disable if units is 0
|
|
let active = units != 0 // disable if units is 0
|
|
|
- return AlertConfiguration(alertType: .slot4, active: active, duration: 0, trigger: .unitsRemaining(units), beepRepeat: .every1MinuteFor3MinutesAndRepeatEvery60Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
|
|
|
|
|
- case .autoOff(let active, let countdownDuration):
|
|
|
|
|
- return AlertConfiguration(alertType: .slot0, active: active, autoOffModifier: true, duration: .minutes(15), trigger: .timeUntilAlert(countdownDuration), beepRepeat: .every1MinuteFor15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
|
|
|
|
|
- case .podSuspendedReminder(let active, let suspendTime):
|
|
|
|
|
- // A suspendTime of 0 is an untimed suspend
|
|
|
|
|
|
|
+ return AlertConfiguration(alertType: .slot4LowReservoir, active: active, trigger: .unitsRemaining(units), beepRepeat: .every1MinuteFor3MinutesAndRepeatEvery60Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep, silent: silent)
|
|
|
|
|
+
|
|
|
|
|
+ // slot5SuspendedReminder
|
|
|
|
|
+ // A suspendTime of 0 is an untimed suspend
|
|
|
|
|
+ // timePassed will be > 0 for an existing pod suspended reminder changing its silent state
|
|
|
|
|
+ case .podSuspendedReminder(let active, _, let suspendTime, let timePassed, let silent):
|
|
|
let reminderInterval, duration: TimeInterval
|
|
let reminderInterval, duration: TimeInterval
|
|
|
- let trigger: AlertTrigger
|
|
|
|
|
- let beepRepeat: BeepRepeat
|
|
|
|
|
|
|
+ var beepRepeat: BeepRepeat
|
|
|
let beepType: BeepType
|
|
let beepType: BeepType
|
|
|
- if active {
|
|
|
|
|
- if suspendTime >= TimeInterval(minutes :30) {
|
|
|
|
|
- // Use 15-minute pod suspended reminder beeps for longer scheduled suspend times as per PDM.
|
|
|
|
|
- reminderInterval = TimeInterval(minutes: 15)
|
|
|
|
|
- beepRepeat = .every15Minutes
|
|
|
|
|
- } else {
|
|
|
|
|
- // Use 5-minute pod suspended reminder beeps for shorter scheduled suspend times.
|
|
|
|
|
- reminderInterval = TimeInterval(minutes: 5)
|
|
|
|
|
- beepRepeat = .every5Minutes
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ let trigger: AlertTrigger
|
|
|
|
|
+ var isActive: Bool = active
|
|
|
|
|
+
|
|
|
|
|
+ if suspendTime == 0 || suspendTime >= TimeInterval(minutes: 30) {
|
|
|
|
|
+ // Use 15-minute pod suspended reminder beeps for untimed or longer scheduled suspend times.
|
|
|
|
|
+ reminderInterval = TimeInterval(minutes: 15)
|
|
|
|
|
+ beepRepeat = .every15Minutes
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Use 5-minute pod suspended reminder beeps for shorter scheduled suspend times.
|
|
|
|
|
+ reminderInterval = TimeInterval(minutes: 5)
|
|
|
|
|
+ beepRepeat = .every5Minutes
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Make alert inactive if there isn't enough remaining in suspend time for a reminder beep.
|
|
|
|
|
+ let suspendTimeRemaining = suspendTime - timePassed
|
|
|
|
|
+ if suspendTime != 0 && suspendTimeRemaining <= reminderInterval {
|
|
|
|
|
+ isActive = false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if isActive {
|
|
|
|
|
+ // Compute the alert trigger time as the interval until the next upcoming reminder interval
|
|
|
|
|
+ let triggerTime: TimeInterval = .seconds(reminderInterval - Double((Int(timePassed) % Int(reminderInterval))))
|
|
|
|
|
+
|
|
|
if suspendTime == 0 {
|
|
if suspendTime == 0 {
|
|
|
duration = 0 // Untimed suspend, no duration
|
|
duration = 0 // Untimed suspend, no duration
|
|
|
- } else if suspendTime > reminderInterval {
|
|
|
|
|
- duration = suspendTime - reminderInterval // End after suspendTime total time
|
|
|
|
|
} else {
|
|
} else {
|
|
|
- duration = .minutes(1) // Degenerate case, end ASAP
|
|
|
|
|
|
|
+ // duration is from triggerTime to suspend time remaining
|
|
|
|
|
+ duration = suspendTimeRemaining - triggerTime
|
|
|
}
|
|
}
|
|
|
- trigger = .timeUntilAlert(reminderInterval) // Start after reminderInterval has passed
|
|
|
|
|
|
|
+ trigger = .timeUntilAlert(triggerTime) // time to next reminder interval with the suspend time
|
|
|
beepType = .beep
|
|
beepType = .beep
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ beepRepeat = .once
|
|
|
duration = 0
|
|
duration = 0
|
|
|
trigger = .timeUntilAlert(.minutes(0))
|
|
trigger = .timeUntilAlert(.minutes(0))
|
|
|
- beepRepeat = .once
|
|
|
|
|
beepType = .noBeepCancel
|
|
beepType = .noBeepCancel
|
|
|
}
|
|
}
|
|
|
- return AlertConfiguration(alertType: .slot5, active: active, duration: duration, trigger: trigger, beepRepeat: beepRepeat, beepType: beepType)
|
|
|
|
|
- case .suspendTimeExpired(let suspendTime):
|
|
|
|
|
|
|
+ return AlertConfiguration(alertType: .slot5SuspendedReminder, active: isActive, duration: duration, trigger: trigger, beepRepeat: beepRepeat, beepType: beepType, silent: silent)
|
|
|
|
|
+
|
|
|
|
|
+ // slot6SuspendTimeExpired
|
|
|
|
|
+ case .suspendTimeExpired(_, let suspendTime, let silent):
|
|
|
let active = suspendTime != 0 // disable if suspendTime is 0
|
|
let active = suspendTime != 0 // disable if suspendTime is 0
|
|
|
let trigger: AlertTrigger
|
|
let trigger: AlertTrigger
|
|
|
let beepRepeat: BeepRepeat
|
|
let beepRepeat: BeepRepeat
|
|
|
let beepType: BeepType
|
|
let beepType: BeepType
|
|
|
if active {
|
|
if active {
|
|
|
trigger = .timeUntilAlert(suspendTime)
|
|
trigger = .timeUntilAlert(suspendTime)
|
|
|
- beepRepeat = .every1MinuteFor3MinutesAndRepeatEvery15Minutes
|
|
|
|
|
|
|
+ beepRepeat = suspendTimeExpiredBeepRepeat
|
|
|
beepType = .bipBeepBipBeepBipBeepBipBeep
|
|
beepType = .bipBeepBipBeepBipBeepBipBeep
|
|
|
} else {
|
|
} else {
|
|
|
trigger = .timeUntilAlert(.minutes(0))
|
|
trigger = .timeUntilAlert(.minutes(0))
|
|
|
beepRepeat = .once
|
|
beepRepeat = .once
|
|
|
beepType = .noBeepCancel
|
|
beepType = .noBeepCancel
|
|
|
}
|
|
}
|
|
|
- return AlertConfiguration(alertType: .slot6, active: active, duration: 0, trigger: trigger, beepRepeat: beepRepeat, beepType: beepType)
|
|
|
|
|
|
|
+ return AlertConfiguration(alertType: .slot6SuspendTimeExpired, active: active, trigger: trigger, beepRepeat: beepRepeat, beepType: beepType, silent: silent)
|
|
|
|
|
+
|
|
|
|
|
+ // slot7Expired
|
|
|
|
|
+ case .waitingForPairingReminder:
|
|
|
|
|
+ // After pod is powered up, beep every 10 minutes for up to 2 hours before pairing before failing
|
|
|
|
|
+ let totalDuration: TimeInterval = .hours(2)
|
|
|
|
|
+ let startOffset: TimeInterval = .minutes(10)
|
|
|
|
|
+ return AlertConfiguration(alertType: .slot7Expired, duration: totalDuration - startOffset, trigger: .timeUntilAlert(startOffset), beepRepeat: .every5Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
|
|
|
|
|
+ case .finishSetupReminder:
|
|
|
|
|
+ // After pod is paired, beep every 5 minutes for up to 1 hour for pod setup to complete before failing
|
|
|
|
|
+ let totalDuration: TimeInterval = .hours(1)
|
|
|
|
|
+ let startOffset: TimeInterval = .minutes(5)
|
|
|
|
|
+ return AlertConfiguration(alertType: .slot7Expired, duration: totalDuration - startOffset, trigger: .timeUntilAlert(startOffset), beepRepeat: .every5Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
|
|
|
|
|
+ case .expired(let offset, let absAlertTime, let duration, let silent):
|
|
|
|
|
+ // Normally used to alert at Pod.nominalPodLife (72 hours) for Pod.expirationAdvisoryWindow (7 hours)
|
|
|
|
|
+ // 2 sets of beeps repeating every 60 minutes
|
|
|
|
|
+ let active = absAlertTime != 0 // disable if absAlertTime is 0
|
|
|
|
|
+ let triggerTime: TimeInterval
|
|
|
|
|
+ if active {
|
|
|
|
|
+ triggerTime = absAlertTime - offset
|
|
|
|
|
+ } else {
|
|
|
|
|
+ triggerTime = .minutes(0)
|
|
|
|
|
+ }
|
|
|
|
|
+ return AlertConfiguration(alertType: .slot7Expired, active: active, duration: duration, trigger: .timeUntilAlert(triggerTime), beepRepeat: .every60Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep, silent: silent)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -195,51 +306,92 @@ public enum PodAlert: CustomStringConvertible, RawRepresentable, Equatable {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
switch name {
|
|
switch name {
|
|
|
- case "waitingForPairingReminder":
|
|
|
|
|
- self = .waitingForPairingReminder
|
|
|
|
|
- case "finishSetupReminder":
|
|
|
|
|
- self = .finishSetupReminder
|
|
|
|
|
- case "expirationReminder":
|
|
|
|
|
- guard let alertTime = rawValue["alertTime"] as? Double else {
|
|
|
|
|
- return nil
|
|
|
|
|
- }
|
|
|
|
|
- self = .expirationReminder(TimeInterval(alertTime))
|
|
|
|
|
- case "expired":
|
|
|
|
|
- guard let alarmTime = rawValue["alarmTime"] as? Double,
|
|
|
|
|
- let duration = rawValue["duration"] as? Double else
|
|
|
|
|
|
|
+ case "autoOff":
|
|
|
|
|
+ guard let active = rawValue["active"] as? Bool,
|
|
|
|
|
+ let countdownDuration = rawValue["countdownDuration"] as? TimeInterval else
|
|
|
{
|
|
{
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
- self = .expired(alertTime: TimeInterval(alarmTime), duration: TimeInterval(duration))
|
|
|
|
|
|
|
+ let offset = rawValue["offset"] as? TimeInterval ?? 0
|
|
|
|
|
+ let silent = rawValue["silent"] as? Bool ?? false
|
|
|
|
|
+ self = .autoOff(active: active, offset: offset, countdownDuration: countdownDuration, silent: silent)
|
|
|
case "shutdownImminent":
|
|
case "shutdownImminent":
|
|
|
- guard let alarmTime = rawValue["alarmTime"] as? Double else {
|
|
|
|
|
|
|
+ guard let alarmTime = rawValue["alarmTime"] as? TimeInterval else {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
- self = .shutdownImminent(alarmTime)
|
|
|
|
|
- case "lowReservoir":
|
|
|
|
|
- guard let units = rawValue["units"] as? Double else {
|
|
|
|
|
|
|
+ let offset = rawValue["offset"] as? TimeInterval ?? 0
|
|
|
|
|
+ let offsetToUse, absAlertTime: TimeInterval
|
|
|
|
|
+ if offset == 0 {
|
|
|
|
|
+ // use default values as no offset value was found
|
|
|
|
|
+ absAlertTime = defaultShutdownImminentTime
|
|
|
|
|
+ offsetToUse = absAlertTime - alarmTime
|
|
|
|
|
+ } else {
|
|
|
|
|
+ absAlertTime = offset + alarmTime
|
|
|
|
|
+ offsetToUse = offset
|
|
|
|
|
+ }
|
|
|
|
|
+ let silent = rawValue["silent"] as? Bool ?? false
|
|
|
|
|
+ self = .shutdownImminent(offset: offsetToUse, absAlertTime: absAlertTime, silent: silent)
|
|
|
|
|
+ case "expirationReminder":
|
|
|
|
|
+ guard let alertTime = rawValue["alertTime"] as? TimeInterval else {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
- self = .lowReservoir(units)
|
|
|
|
|
- case "autoOff":
|
|
|
|
|
- guard let active = rawValue["active"] as? Bool,
|
|
|
|
|
- let countdownDuration = rawValue["countdownDuration"] as? Double else
|
|
|
|
|
- {
|
|
|
|
|
|
|
+ let offset = rawValue["offset"] as? TimeInterval ?? 0
|
|
|
|
|
+ let offsetToUse, absAlertTime: TimeInterval
|
|
|
|
|
+ if offset == 0 {
|
|
|
|
|
+ // use default values as no offset value was found
|
|
|
|
|
+ absAlertTime = defaultExpirationReminderTime
|
|
|
|
|
+ offsetToUse = absAlertTime - alertTime
|
|
|
|
|
+ } else {
|
|
|
|
|
+ absAlertTime = offset + alertTime
|
|
|
|
|
+ offsetToUse = offset
|
|
|
|
|
+ }
|
|
|
|
|
+ let duration = rawValue["duration"] as? TimeInterval ?? 0
|
|
|
|
|
+ let silent = rawValue["silent"] as? Bool ?? false
|
|
|
|
|
+ self = .expirationReminder(offset: offsetToUse, absAlertTime: absAlertTime, duration: duration, silent: silent)
|
|
|
|
|
+ case "lowReservoir":
|
|
|
|
|
+ guard let units = rawValue["units"] as? Double else {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
- self = .autoOff(active: active, countdownDuration: TimeInterval(countdownDuration))
|
|
|
|
|
|
|
+ let silent = rawValue["silent"] as? Bool ?? false
|
|
|
|
|
+ self = .lowReservoir(units: units, silent: silent)
|
|
|
case "podSuspendedReminder":
|
|
case "podSuspendedReminder":
|
|
|
guard let active = rawValue["active"] as? Bool,
|
|
guard let active = rawValue["active"] as? Bool,
|
|
|
- let suspendTime = rawValue["suspendTime"] as? Double else
|
|
|
|
|
|
|
+ let suspendTime = rawValue["suspendTime"] as? TimeInterval else
|
|
|
{
|
|
{
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
- self = .podSuspendedReminder(active: active, suspendTime: suspendTime)
|
|
|
|
|
|
|
+ let offset = rawValue["offset"] as? TimeInterval ?? 0
|
|
|
|
|
+ let silent = rawValue["silent"] as? Bool ?? false
|
|
|
|
|
+ self = .podSuspendedReminder(active: active, offset: offset, suspendTime: suspendTime, silent: silent)
|
|
|
case "suspendTimeExpired":
|
|
case "suspendTimeExpired":
|
|
|
guard let suspendTime = rawValue["suspendTime"] as? Double else {
|
|
guard let suspendTime = rawValue["suspendTime"] as? Double else {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
- self = .suspendTimeExpired(suspendTime: suspendTime)
|
|
|
|
|
|
|
+ let offset = rawValue["offset"] as? TimeInterval ?? 0
|
|
|
|
|
+ let silent = rawValue["silent"] as? Bool ?? false
|
|
|
|
|
+ self = .suspendTimeExpired(offset: offset, suspendTime: suspendTime, silent: silent)
|
|
|
|
|
+ case "waitingForPairingReminder":
|
|
|
|
|
+ self = .waitingForPairingReminder
|
|
|
|
|
+ case "finishSetupReminder":
|
|
|
|
|
+ self = .finishSetupReminder
|
|
|
|
|
+ case "expired":
|
|
|
|
|
+ guard let alarmTime = rawValue["alarmTime"] as? TimeInterval,
|
|
|
|
|
+ let duration = rawValue["duration"] as? TimeInterval else
|
|
|
|
|
+ {
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+ let offset = rawValue["offset"] as? TimeInterval ?? 0
|
|
|
|
|
+ let offsetToUse, absAlertTime: TimeInterval
|
|
|
|
|
+ if offset == 0 {
|
|
|
|
|
+ // use default values as no offset value was found
|
|
|
|
|
+ absAlertTime = defaultExpiredTime
|
|
|
|
|
+ offsetToUse = absAlertTime - alarmTime
|
|
|
|
|
+ } else {
|
|
|
|
|
+ absAlertTime = offset + alarmTime
|
|
|
|
|
+ offsetToUse = offset
|
|
|
|
|
+ }
|
|
|
|
|
+ let silent = rawValue["silent"] as? Bool ?? false
|
|
|
|
|
+ self = .expired(offset: offsetToUse, absAlertTime: absAlertTime, duration: duration, silent: silent)
|
|
|
default:
|
|
default:
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
@@ -249,50 +401,65 @@ public enum PodAlert: CustomStringConvertible, RawRepresentable, Equatable {
|
|
|
|
|
|
|
|
let name: String = {
|
|
let name: String = {
|
|
|
switch self {
|
|
switch self {
|
|
|
- case .waitingForPairingReminder:
|
|
|
|
|
- return "waitingForPairingReminder"
|
|
|
|
|
- case .finishSetupReminder:
|
|
|
|
|
- return "finishSetupReminder"
|
|
|
|
|
- case .expirationReminder:
|
|
|
|
|
- return "expirationReminder"
|
|
|
|
|
- case .expired:
|
|
|
|
|
- return "expired"
|
|
|
|
|
|
|
+ case .autoOff:
|
|
|
|
|
+ return "autoOff"
|
|
|
|
|
+ case .notUsed:
|
|
|
|
|
+ return "notUsed"
|
|
|
case .shutdownImminent:
|
|
case .shutdownImminent:
|
|
|
return "shutdownImminent"
|
|
return "shutdownImminent"
|
|
|
|
|
+ case .expirationReminder:
|
|
|
|
|
+ return "expirationReminder"
|
|
|
case .lowReservoir:
|
|
case .lowReservoir:
|
|
|
return "lowReservoir"
|
|
return "lowReservoir"
|
|
|
- case .autoOff:
|
|
|
|
|
- return "autoOff"
|
|
|
|
|
case .podSuspendedReminder:
|
|
case .podSuspendedReminder:
|
|
|
return "podSuspendedReminder"
|
|
return "podSuspendedReminder"
|
|
|
case .suspendTimeExpired:
|
|
case .suspendTimeExpired:
|
|
|
return "suspendTimeExpired"
|
|
return "suspendTimeExpired"
|
|
|
|
|
+ case .waitingForPairingReminder:
|
|
|
|
|
+ return "waitingForPairingReminder"
|
|
|
|
|
+ case .finishSetupReminder:
|
|
|
|
|
+ return "finishSetupReminder"
|
|
|
|
|
+ case .expired:
|
|
|
|
|
+ return "expired"
|
|
|
}
|
|
}
|
|
|
}()
|
|
}()
|
|
|
|
|
|
|
|
-
|
|
|
|
|
var rawValue: RawValue = [
|
|
var rawValue: RawValue = [
|
|
|
"name": name,
|
|
"name": name,
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
switch self {
|
|
switch self {
|
|
|
- case .expirationReminder(let alertTime):
|
|
|
|
|
- rawValue["alertTime"] = alertTime
|
|
|
|
|
- case .expired(let alarmTime, let duration):
|
|
|
|
|
- rawValue["alarmTime"] = alarmTime
|
|
|
|
|
- rawValue["duration"] = duration
|
|
|
|
|
- case .shutdownImminent(let alarmTime):
|
|
|
|
|
- rawValue["alarmTime"] = alarmTime
|
|
|
|
|
- case .lowReservoir(let units):
|
|
|
|
|
- rawValue["units"] = units
|
|
|
|
|
- case .autoOff(let active, let countdownDuration):
|
|
|
|
|
|
|
+ case .autoOff(let active, let offset, let countdownDuration, let silent):
|
|
|
rawValue["active"] = active
|
|
rawValue["active"] = active
|
|
|
|
|
+ rawValue["offset"] = offset
|
|
|
rawValue["countdownDuration"] = countdownDuration
|
|
rawValue["countdownDuration"] = countdownDuration
|
|
|
- case .podSuspendedReminder(let active, let suspendTime):
|
|
|
|
|
|
|
+ rawValue["silent"] = silent
|
|
|
|
|
+ case .shutdownImminent(let offset, let absAlertTime, let silent):
|
|
|
|
|
+ rawValue["offset"] = offset
|
|
|
|
|
+ rawValue["alarmTime"] = absAlertTime - offset
|
|
|
|
|
+ rawValue["silent"] = silent
|
|
|
|
|
+ case .expirationReminder(let offset, let absAlertTime, let duration, let silent):
|
|
|
|
|
+ rawValue["offset"] = offset
|
|
|
|
|
+ rawValue["alertTime"] = absAlertTime - offset
|
|
|
|
|
+ rawValue["duration"] = duration
|
|
|
|
|
+ rawValue["silent"] = silent
|
|
|
|
|
+ case .lowReservoir(let units, let silent):
|
|
|
|
|
+ rawValue["units"] = units
|
|
|
|
|
+ rawValue["silent"] = silent
|
|
|
|
|
+ case .podSuspendedReminder(let active, let offset, let suspendTime, _, let silent):
|
|
|
rawValue["active"] = active
|
|
rawValue["active"] = active
|
|
|
|
|
+ rawValue["offset"] = offset
|
|
|
rawValue["suspendTime"] = suspendTime
|
|
rawValue["suspendTime"] = suspendTime
|
|
|
- case .suspendTimeExpired(let suspendTime):
|
|
|
|
|
|
|
+ rawValue["silent"] = silent
|
|
|
|
|
+ case .suspendTimeExpired(let offset, let suspendTime, let silent):
|
|
|
|
|
+ rawValue["offset"] = offset
|
|
|
rawValue["suspendTime"] = suspendTime
|
|
rawValue["suspendTime"] = suspendTime
|
|
|
|
|
+ rawValue["silent"] = silent
|
|
|
|
|
+ case .expired(let offset, let absAlertTime, let duration, let silent):
|
|
|
|
|
+ rawValue["offset"] = offset
|
|
|
|
|
+ rawValue["alarmTime"] = absAlertTime - offset
|
|
|
|
|
+ rawValue["duration"] = duration
|
|
|
|
|
+ rawValue["silent"] = silent
|
|
|
default:
|
|
default:
|
|
|
break
|
|
break
|
|
|
}
|
|
}
|
|
@@ -302,42 +469,42 @@ public enum PodAlert: CustomStringConvertible, RawRepresentable, Equatable {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public enum AlertSlot: UInt8 {
|
|
public enum AlertSlot: UInt8 {
|
|
|
- case slot0 = 0x00
|
|
|
|
|
- case slot1 = 0x01
|
|
|
|
|
- case slot2 = 0x02
|
|
|
|
|
- case slot3 = 0x03
|
|
|
|
|
- case slot4 = 0x04
|
|
|
|
|
- case slot5 = 0x05
|
|
|
|
|
- case slot6 = 0x06
|
|
|
|
|
- case slot7 = 0x07
|
|
|
|
|
|
|
+ case slot0AutoOff = 0x00
|
|
|
|
|
+ case slot1NotUsed = 0x01
|
|
|
|
|
+ case slot2ShutdownImminent = 0x02
|
|
|
|
|
+ case slot3ExpirationReminder = 0x03
|
|
|
|
|
+ case slot4LowReservoir = 0x04
|
|
|
|
|
+ case slot5SuspendedReminder = 0x05
|
|
|
|
|
+ case slot6SuspendTimeExpired = 0x06
|
|
|
|
|
+ case slot7Expired = 0x07
|
|
|
|
|
|
|
|
public var bitMaskValue: UInt8 {
|
|
public var bitMaskValue: UInt8 {
|
|
|
return 1<<rawValue
|
|
return 1<<rawValue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public typealias AllCases = [AlertSlot]
|
|
public typealias AllCases = [AlertSlot]
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
static var allCases: AllCases {
|
|
static var allCases: AllCases {
|
|
|
return (0..<8).map { AlertSlot(rawValue: $0)! }
|
|
return (0..<8).map { AlertSlot(rawValue: $0)! }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public struct AlertSet: RawRepresentable, Collection, CustomStringConvertible, Equatable {
|
|
public struct AlertSet: RawRepresentable, Collection, CustomStringConvertible, Equatable {
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
public typealias RawValue = UInt8
|
|
public typealias RawValue = UInt8
|
|
|
public typealias Index = Int
|
|
public typealias Index = Int
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
public let startIndex: Int
|
|
public let startIndex: Int
|
|
|
public let endIndex: Int
|
|
public let endIndex: Int
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
private let elements: [AlertSlot]
|
|
private let elements: [AlertSlot]
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
public static let none = AlertSet(rawValue: 0)
|
|
public static let none = AlertSet(rawValue: 0)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
public var rawValue: UInt8 {
|
|
public var rawValue: UInt8 {
|
|
|
return elements.reduce(0) { $0 | $1.bitMaskValue }
|
|
return elements.reduce(0) { $0 | $1.bitMaskValue }
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
public init(slots: [AlertSlot]) {
|
|
public init(slots: [AlertSlot]) {
|
|
|
self.elements = slots
|
|
self.elements = slots
|
|
|
self.startIndex = 0
|
|
self.startIndex = 0
|
|
@@ -351,11 +518,11 @@ public struct AlertSet: RawRepresentable, Collection, CustomStringConvertible, E
|
|
|
public subscript(index: Index) -> AlertSlot {
|
|
public subscript(index: Index) -> AlertSlot {
|
|
|
return elements[index]
|
|
return elements[index]
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
public func index(after i: Int) -> Int {
|
|
public func index(after i: Int) -> Int {
|
|
|
return i+1
|
|
return i+1
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
public var description: String {
|
|
public var description: String {
|
|
|
if elements.count == 0 {
|
|
if elements.count == 0 {
|
|
|
return LocalizedString("No alerts", comment: "Pod alert state when no alerts are active")
|
|
return LocalizedString("No alerts", comment: "Pod alert state when no alerts are active")
|
|
@@ -374,9 +541,179 @@ public struct AlertSet: RawRepresentable, Collection, CustomStringConvertible, E
|
|
|
|
|
|
|
|
// Returns true if there are any active suspend related alerts
|
|
// Returns true if there are any active suspend related alerts
|
|
|
public func hasActiveSuspendAlert(configuredAlerts: [AlertSlot : PodAlert]) -> Bool {
|
|
public func hasActiveSuspendAlert(configuredAlerts: [AlertSlot : PodAlert]) -> Bool {
|
|
|
- // slot5 is for podSuspendedReminder and slot6 is for suspendTimeExpired
|
|
|
|
|
- if configuredAlerts.contains(where: { ($0.key == .slot5 || $0.key == .slot6) && $0.value.configuration.active }) {
|
|
|
|
|
|
|
+ if configuredAlerts.contains(where: { ($0.key == .slot5SuspendedReminder || $0.key == .slot6SuspendTimeExpired) && $0.value.configuration.active })
|
|
|
|
|
+ {
|
|
|
return true
|
|
return true
|
|
|
}
|
|
}
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// Returns a descriptive string for all the alerts in alertSet
|
|
|
|
|
+public func alertSetString(alertSet: AlertSet) -> String {
|
|
|
|
|
+
|
|
|
|
|
+ if alertSet.isEmpty {
|
|
|
|
|
+ // Don't bother displaying any additional info for an inactive alert
|
|
|
|
|
+ return String(describing: alertSet)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let alertDescription = alertSet.map { (slot) -> String in
|
|
|
|
|
+ switch slot {
|
|
|
|
|
+ case .slot0AutoOff:
|
|
|
|
|
+ return PodAlert.autoOff(active: true, offset: 0, countdownDuration: 0).description
|
|
|
|
|
+ case .slot1NotUsed:
|
|
|
|
|
+ return PodAlert.notUsed.description
|
|
|
|
|
+ case .slot2ShutdownImminent:
|
|
|
|
|
+ return PodAlert.shutdownImminent(offset: 0, absAlertTime: defaultShutdownImminentTime).description
|
|
|
|
|
+ case .slot3ExpirationReminder:
|
|
|
|
|
+ return PodAlert.expirationReminder(offset: 0, absAlertTime: defaultExpirationReminderTime).description
|
|
|
|
|
+ case .slot4LowReservoir:
|
|
|
|
|
+ return PodAlert.lowReservoir(units: Pod.maximumReservoirReading).description
|
|
|
|
|
+ case .slot5SuspendedReminder:
|
|
|
|
|
+ return PodAlert.podSuspendedReminder(active: true, offset: 0, suspendTime: .minutes(30)).description
|
|
|
|
|
+ case .slot6SuspendTimeExpired:
|
|
|
|
|
+ return PodAlert.suspendTimeExpired(offset: 0, suspendTime: .minutes(30)).description
|
|
|
|
|
+ case .slot7Expired:
|
|
|
|
|
+ return PodAlert.expired(offset: 0, absAlertTime: defaultExpiredTime, duration: Pod.expirationAdvisoryWindow).description
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return alertDescription.joined(separator: ", ")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func configuredAlertsString(configuredAlerts: [AlertSlot : PodAlert]) -> String {
|
|
|
|
|
+
|
|
|
|
|
+ if configuredAlerts.isEmpty {
|
|
|
|
|
+ return String(describing: configuredAlerts)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let configuredAlertString = configuredAlerts.map { (configuredAlert) -> String in
|
|
|
|
|
+
|
|
|
|
|
+ let podAlert = configuredAlert.value
|
|
|
|
|
+ let description = podAlert.description
|
|
|
|
|
+ guard podAlert.configuration.active else {
|
|
|
|
|
+ return description
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ switch podAlert {
|
|
|
|
|
+ case .shutdownImminent(_, let absAlertTime, _):
|
|
|
|
|
+ return String(format: "%@ @ %@", description, absAlertTime.timeIntervalStr)
|
|
|
|
|
+ case .expirationReminder(_, let absAlertTime, _, _):
|
|
|
|
|
+ return String(format: "%@ @ %@", description, absAlertTime.timeIntervalStr)
|
|
|
|
|
+ case .lowReservoir(let unitTrigger, _):
|
|
|
|
|
+ return String(format: "%@ @ %dU", description, Int(unitTrigger))
|
|
|
|
|
+ case .podSuspendedReminder(_, let offset, let suspendTime, _, _):
|
|
|
|
|
+ return String(format: "%@ ending @ %@ after %@", description, (offset + suspendTime).timeIntervalStr, suspendTime.timeIntervalStr)
|
|
|
|
|
+ case .suspendTimeExpired(let offset, let suspendTime, _):
|
|
|
|
|
+ return String(format: "%@ @ %@ after %@", description, (offset + suspendTime).timeIntervalStr, suspendTime.timeIntervalStr)
|
|
|
|
|
+ case .expired(_, let absAlertTime, _, _):
|
|
|
|
|
+ return String(format: "%@ @ %@", description, absAlertTime.timeIntervalStr)
|
|
|
|
|
+ default:
|
|
|
|
|
+ return ""
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return configuredAlertString.joined(separator: ", ")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Returns an array of appropriate PodAlerts with the specified silent value
|
|
|
|
|
+// for all the configuredAlerts given all the current pod conditions.
|
|
|
|
|
+func regeneratePodAlerts(silent: Bool, configuredAlerts: [AlertSlot: PodAlert], activeAlertSlots: AlertSet, currentPodTime: TimeInterval, currentReservoirLevel: Double) -> [PodAlert] {
|
|
|
|
|
+ var podAlerts: [PodAlert] = []
|
|
|
|
|
+
|
|
|
|
|
+ for alert in configuredAlerts {
|
|
|
|
|
+ // Just skip this alert if not previously active
|
|
|
|
|
+ guard alert.value.configuration.active else {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Map alerts to corresponding appropriate new ones at the current pod time using the specified silent value.
|
|
|
|
|
+ switch alert.value {
|
|
|
|
|
+
|
|
|
|
|
+ case .shutdownImminent(let offset, let alertTime, _):
|
|
|
|
|
+ // alertTime is absolute when offset is non-zero, otherwise use default value
|
|
|
|
|
+ var absAlertTime = offset != 0 ? alertTime : defaultShutdownImminentTime
|
|
|
|
|
+ if currentPodTime >= absAlertTime {
|
|
|
|
|
+ // alert trigger is not in the future, make inactive using a 0 value
|
|
|
|
|
+ absAlertTime = 0
|
|
|
|
|
+ }
|
|
|
|
|
+ // create new shutdownImminent podAlert using the current timeActive and the original absolute alert time
|
|
|
|
|
+ podAlerts.append(PodAlert.shutdownImminent(offset: currentPodTime, absAlertTime: absAlertTime, silent: silent))
|
|
|
|
|
+
|
|
|
|
|
+ case .expirationReminder(let offset, let alertTime, let alertDuration, _):
|
|
|
|
|
+ let duration: TimeInterval
|
|
|
|
|
+
|
|
|
|
|
+ // alertTime is absolute when offset is non-zero, otherwise use default value
|
|
|
|
|
+ var absAlertTime = offset != 0 ? alertTime : defaultExpirationReminderTime
|
|
|
|
|
+ if currentPodTime >= absAlertTime {
|
|
|
|
|
+ // alert trigger is not in the future, make inactive using a 0 value
|
|
|
|
|
+ absAlertTime = 0
|
|
|
|
|
+ duration = 0
|
|
|
|
|
+ } else {
|
|
|
|
|
+ duration = alertDuration
|
|
|
|
|
+ }
|
|
|
|
|
+ // create new expirationReminder podAlert using the current active time and the original absolute alert time and duration
|
|
|
|
|
+ podAlerts.append(PodAlert.expirationReminder(offset: currentPodTime, absAlertTime: absAlertTime, duration: duration, silent: silent))
|
|
|
|
|
+
|
|
|
|
|
+ case .lowReservoir(let unitTrigger, _):
|
|
|
|
|
+ let units: Double
|
|
|
|
|
+ if currentReservoirLevel > unitTrigger {
|
|
|
|
|
+ units = unitTrigger
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // reservoir is no longer more than the unitTrigger, make inactive using a 0 value
|
|
|
|
|
+ units = 0
|
|
|
|
|
+ }
|
|
|
|
|
+ podAlerts.append(PodAlert.lowReservoir(units: units, silent: silent))
|
|
|
|
|
+
|
|
|
|
|
+ case .podSuspendedReminder(let active, let offset, let suspendTime, _, _):
|
|
|
|
|
+ let timePassed: TimeInterval = min(currentPodTime - offset, .hours(2))
|
|
|
|
|
+ // Pass along the computed time passed since alert was originally set so creation routine can
|
|
|
|
|
+ // do all the grunt work dealing with varying reminder intervals and time passing scenarios.
|
|
|
|
|
+ podAlerts.append(PodAlert.podSuspendedReminder(active: active, offset: offset, suspendTime: suspendTime, timePassed: timePassed, silent: silent))
|
|
|
|
|
+
|
|
|
|
|
+ case .suspendTimeExpired(let lastOffset, let lastSuspendTime, _):
|
|
|
|
|
+ let absAlertTime = lastOffset + lastSuspendTime
|
|
|
|
|
+ let suspendTime: TimeInterval
|
|
|
|
|
+ if currentPodTime >= absAlertTime {
|
|
|
|
|
+ // alert trigger is no longer in the future
|
|
|
|
|
+ if activeAlertSlots.contains(where: { $0 == .slot6SuspendTimeExpired } ) {
|
|
|
|
|
+ // The suspendTimeExpired alert has yet been acknowledged,
|
|
|
|
|
+ // set up a suspendTimeExpired alert for the next 15m interval.
|
|
|
|
|
+ // Compute a new suspendTime that is a multiple of 15 minutes
|
|
|
|
|
+ // from lastOffset which is at least one minute in the future.
|
|
|
|
|
+ let newOffsetSuspendTime = ceil((currentPodTime - lastOffset) / .minutes(15)) * .minutes(15)
|
|
|
|
|
+ let newAbsAlertTime = lastOffset + newOffsetSuspendTime
|
|
|
|
|
+ suspendTime = max(newAbsAlertTime - currentPodTime, .minutes(1))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // The suspendTimeExpired alert was already been acknowledged,
|
|
|
|
|
+ // so now make this alert inactive by using a 0 suspendTime.
|
|
|
|
|
+ suspendTime = 0
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // recompute a new suspendTime based on the current pod time
|
|
|
|
|
+ suspendTime = absAlertTime - currentPodTime
|
|
|
|
|
+ print("setting new suspendTimeExpired suspendTime of \(suspendTime) with currentPodTime\(currentPodTime) and absAlertTime=\(absAlertTime)")
|
|
|
|
|
+ }
|
|
|
|
|
+ // create a new suspendTimeExpired PodAlert using the current active time and the computed suspendTime (if any)
|
|
|
|
|
+ podAlerts.append(PodAlert.suspendTimeExpired(offset: currentPodTime, suspendTime: suspendTime, silent: silent))
|
|
|
|
|
+
|
|
|
|
|
+ case .expired(let offset, let alertTime, let alertDuration, _):
|
|
|
|
|
+ let duration: TimeInterval
|
|
|
|
|
+
|
|
|
|
|
+ // alertTime is absolute when offset is non-zero, otherwise use default value
|
|
|
|
|
+ var absAlertTime = offset != 0 ? alertTime : defaultExpiredTime
|
|
|
|
|
+ if currentPodTime >= absAlertTime {
|
|
|
|
|
+ // alert trigger is not in the future, make inactive using a 0 value
|
|
|
|
|
+ absAlertTime = 0
|
|
|
|
|
+ duration = 0
|
|
|
|
|
+ } else {
|
|
|
|
|
+ duration = alertDuration
|
|
|
|
|
+ }
|
|
|
|
|
+ // create new expired podAlert using the current active time and the original absolute alert time and duration
|
|
|
|
|
+ podAlerts.append(PodAlert.expired(offset: currentPodTime, absAlertTime: absAlertTime, duration: duration, silent: silent))
|
|
|
|
|
+
|
|
|
|
|
+ default:
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return podAlerts
|
|
|
|
|
+}
|