AlertSlot.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. //
  2. // Alert.swift
  3. // OmniKit
  4. //
  5. // Created by Pete Schwamb on 10/24/18.
  6. // Copyright © 2018 Pete Schwamb. All rights reserved.
  7. //
  8. import Foundation
  9. public enum AlertTrigger {
  10. case unitsRemaining(Double)
  11. case timeUntilAlert(TimeInterval)
  12. }
  13. public enum BeepRepeat: UInt8 {
  14. case once = 0
  15. case every1MinuteFor3MinutesAndRepeatEvery60Minutes = 1
  16. case every1MinuteFor15Minutes = 2
  17. case every1MinuteFor3MinutesAndRepeatEvery15Minutes = 3
  18. case every3MinutesFor60minutesStartingAt2Minutes = 4
  19. case every60Minutes = 5
  20. case every15Minutes = 6
  21. case every15MinutesFor60minutesStartingAt14Minutes = 7
  22. case every5Minutes = 8
  23. }
  24. public struct AlertConfiguration {
  25. let slot: AlertSlot
  26. let trigger: AlertTrigger
  27. let active: Bool
  28. let duration: TimeInterval
  29. let beepRepeat: BeepRepeat
  30. let beepType: BeepType
  31. let autoOffModifier: Bool
  32. static let length = 6
  33. public init(alertType: AlertSlot, active: Bool = true, autoOffModifier: Bool = false, duration: TimeInterval, trigger: AlertTrigger, beepRepeat: BeepRepeat, beepType: BeepType) {
  34. self.slot = alertType
  35. self.active = active
  36. self.autoOffModifier = autoOffModifier
  37. self.duration = duration
  38. self.trigger = trigger
  39. self.beepRepeat = beepRepeat
  40. self.beepType = beepType
  41. }
  42. }
  43. extension AlertConfiguration: CustomDebugStringConvertible {
  44. public var debugDescription: String {
  45. return "AlertConfiguration(slot:\(slot), active:\(active), autoOffModifier:\(autoOffModifier), duration:\(duration), trigger:\(trigger), beepRepeat:\(beepRepeat), beepType:\(beepType))"
  46. }
  47. }
  48. public enum PodAlert: CustomStringConvertible, RawRepresentable, Equatable {
  49. public typealias RawValue = [String: Any]
  50. // 2 hours long, time for user to start pairing process
  51. case waitingForPairingReminder
  52. // 1 hour long, time for user to finish priming, cannula insertion
  53. case finishSetupReminder
  54. // User configurable with PDM (1-24 hours before 72 hour expiration) "Change Pod Soon"
  55. case expirationAlert(TimeInterval)
  56. // 72 hour alarm
  57. case expirationAdvisoryAlarm(alarmTime: TimeInterval, duration: TimeInterval)
  58. // 79 hour alarm (1 hour before shutdown)
  59. case shutdownImminentAlarm(TimeInterval)
  60. // reservoir below configured value alarm
  61. case lowReservoirAlarm(Double)
  62. // auto-off timer; requires user input every x minutes
  63. case autoOffAlarm(active: Bool, countdownDuration: TimeInterval)
  64. // pod suspended reminder, before suspendTime; short beep every 15 minutes if >= 30 min, else every 5 minutes
  65. case podSuspendedReminder(active: Bool, suspendTime: TimeInterval)
  66. // pod suspend time expired alarm, after suspendTime; 2 sets of beeps every min for 3 minutes repeated every 15 minutes
  67. case suspendTimeExpired(suspendTime: TimeInterval)
  68. public var description: String {
  69. var alertName: String
  70. switch self {
  71. case .waitingForPairingReminder:
  72. return LocalizedString("Waiting for pairing reminder", comment: "Description waiting for pairing reminder")
  73. case .finishSetupReminder:
  74. return LocalizedString("Finish setup reminder", comment: "Description for finish setup reminder")
  75. case .expirationAlert:
  76. alertName = LocalizedString("Expiration alert", comment: "Description for expiration alert")
  77. case .expirationAdvisoryAlarm:
  78. alertName = LocalizedString("Expiration advisory", comment: "Description for expiration advisory")
  79. case .shutdownImminentAlarm:
  80. alertName = LocalizedString("Shutdown imminent", comment: "Description for shutdown imminent")
  81. case .lowReservoirAlarm:
  82. alertName = LocalizedString("Low reservoir advisory", comment: "Description for low reservoir advisory")
  83. case .autoOffAlarm:
  84. alertName = LocalizedString("Auto-off", comment: "Description for auto-off")
  85. case .podSuspendedReminder:
  86. alertName = LocalizedString("Pod suspended reminder", comment: "Description for pod suspended reminder")
  87. case .suspendTimeExpired:
  88. alertName = LocalizedString("Suspend time expired", comment: "Description for suspend time expired")
  89. }
  90. if self.configuration.active == false {
  91. alertName += LocalizedString(" (inactive)", comment: "Description for an inactive alert modifier")
  92. }
  93. return alertName
  94. }
  95. public var configuration: AlertConfiguration {
  96. switch self {
  97. case .waitingForPairingReminder:
  98. return AlertConfiguration(alertType: .slot7, duration: .minutes(110), trigger: .timeUntilAlert(.minutes(10)), beepRepeat: .every5Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
  99. case .finishSetupReminder:
  100. return AlertConfiguration(alertType: .slot7, duration: .minutes(55), trigger: .timeUntilAlert(.minutes(5)), beepRepeat: .every5Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
  101. case .expirationAlert(let alertTime):
  102. let active = alertTime != 0 // disable if alertTime is 0
  103. return AlertConfiguration(alertType: .slot3, active: active, duration: 0, trigger: .timeUntilAlert(alertTime), beepRepeat: .every1MinuteFor3MinutesAndRepeatEvery15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
  104. case .expirationAdvisoryAlarm(let alarmTime, let duration):
  105. let active = alarmTime != 0 // disable if alarmTime is 0
  106. return AlertConfiguration(alertType: .slot7, active: active, duration: duration, trigger: .timeUntilAlert(alarmTime), beepRepeat: .every60Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
  107. case .shutdownImminentAlarm(let alarmTime):
  108. let active = alarmTime != 0 // disable if alarmTime is 0
  109. return AlertConfiguration(alertType: .slot2, active: active, duration: 0, trigger: .timeUntilAlert(alarmTime), beepRepeat: .every15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
  110. case .lowReservoirAlarm(let units):
  111. let active = units != 0 // disable if units is 0
  112. return AlertConfiguration(alertType: .slot4, active: active, duration: 0, trigger: .unitsRemaining(units), beepRepeat: .every1MinuteFor3MinutesAndRepeatEvery60Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
  113. case .autoOffAlarm(let active, let countdownDuration):
  114. return AlertConfiguration(alertType: .slot0, active: active, autoOffModifier: true, duration: .minutes(15), trigger: .timeUntilAlert(countdownDuration), beepRepeat: .every1MinuteFor15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep)
  115. case .podSuspendedReminder(let active, let suspendTime):
  116. // A suspendTime of 0 is an untimed suspend
  117. let reminderInterval, duration: TimeInterval
  118. let trigger: AlertTrigger
  119. let beepRepeat: BeepRepeat
  120. let beepType: BeepType
  121. if active {
  122. if suspendTime >= TimeInterval(minutes :30) {
  123. // Use 15-minute pod suspended reminder beeps for longer scheduled suspend times as per PDM.
  124. reminderInterval = TimeInterval(minutes: 15)
  125. beepRepeat = .every15Minutes
  126. } else {
  127. // Use 5-minute pod suspended reminder beeps for untimed & shorter scheduled suspend times.
  128. reminderInterval = TimeInterval(minutes: 5)
  129. beepRepeat = .every5Minutes
  130. }
  131. if suspendTime == 0 {
  132. duration = 0 // Untimed suspend, no duration
  133. } else if suspendTime > reminderInterval {
  134. duration = suspendTime - reminderInterval // End after suspendTime total time
  135. } else {
  136. duration = .minutes(1) // Degenerate case, end ASAP
  137. }
  138. trigger = .timeUntilAlert(reminderInterval) // Start after reminderInterval has passed
  139. beepType = .beep
  140. } else {
  141. duration = 0
  142. trigger = .timeUntilAlert(.minutes(0))
  143. beepRepeat = .once
  144. beepType = .noBeep
  145. }
  146. return AlertConfiguration(alertType: .slot5, active: active, duration: duration, trigger: trigger, beepRepeat: beepRepeat, beepType: beepType)
  147. case .suspendTimeExpired(let suspendTime):
  148. let active = suspendTime != 0 // disable if suspendTime is 0
  149. let trigger: AlertTrigger
  150. let beepRepeat: BeepRepeat
  151. let beepType: BeepType
  152. if active {
  153. trigger = .timeUntilAlert(suspendTime)
  154. beepRepeat = .every1MinuteFor3MinutesAndRepeatEvery15Minutes
  155. beepType = .bipBeepBipBeepBipBeepBipBeep
  156. } else {
  157. trigger = .timeUntilAlert(.minutes(0))
  158. beepRepeat = .once
  159. beepType = .noBeep
  160. }
  161. return AlertConfiguration(alertType: .slot6, active: active, duration: 0, trigger: trigger, beepRepeat: beepRepeat, beepType: beepType)
  162. }
  163. }
  164. // MARK: - RawRepresentable
  165. public init?(rawValue: RawValue) {
  166. guard let name = rawValue["name"] as? String else {
  167. return nil
  168. }
  169. switch name {
  170. case "waitingForPairingReminder":
  171. self = .waitingForPairingReminder
  172. case "finishSetupReminder":
  173. self = .finishSetupReminder
  174. case "expirationAlert":
  175. guard let alertTime = rawValue["alertTime"] as? Double else {
  176. return nil
  177. }
  178. self = .expirationAlert(TimeInterval(alertTime))
  179. case "expirationAdvisoryAlarm":
  180. guard let alarmTime = rawValue["alarmTime"] as? Double,
  181. let duration = rawValue["duration"] as? Double else
  182. {
  183. return nil
  184. }
  185. self = .expirationAdvisoryAlarm(alarmTime: TimeInterval(alarmTime), duration: TimeInterval(duration))
  186. case "shutdownImminentAlarm":
  187. guard let alarmTime = rawValue["alarmTime"] as? Double else {
  188. return nil
  189. }
  190. self = .shutdownImminentAlarm(alarmTime)
  191. case "lowReservoirAlarm":
  192. guard let units = rawValue["units"] as? Double else {
  193. return nil
  194. }
  195. self = .lowReservoirAlarm(units)
  196. case "autoOffAlarm":
  197. guard let active = rawValue["active"] as? Bool,
  198. let countdownDuration = rawValue["countdownDuration"] as? Double else
  199. {
  200. return nil
  201. }
  202. self = .autoOffAlarm(active: active, countdownDuration: TimeInterval(countdownDuration))
  203. case "podSuspendedReminder":
  204. guard let active = rawValue["active"] as? Bool,
  205. let suspendTime = rawValue["suspendTime"] as? Double else
  206. {
  207. return nil
  208. }
  209. self = .podSuspendedReminder(active: active, suspendTime: suspendTime)
  210. case "suspendTimeExpired":
  211. guard let suspendTime = rawValue["suspendTime"] as? Double else {
  212. return nil
  213. }
  214. self = .suspendTimeExpired(suspendTime: suspendTime)
  215. default:
  216. return nil
  217. }
  218. }
  219. public var rawValue: RawValue {
  220. let name: String = {
  221. switch self {
  222. case .waitingForPairingReminder:
  223. return "waitingForPairingReminder"
  224. case .finishSetupReminder:
  225. return "finishSetupReminder"
  226. case .expirationAlert:
  227. return "expirationAlert"
  228. case .expirationAdvisoryAlarm:
  229. return "expirationAdvisoryAlarm"
  230. case .shutdownImminentAlarm:
  231. return "shutdownImminentAlarm"
  232. case .lowReservoirAlarm:
  233. return "lowReservoirAlarm"
  234. case .autoOffAlarm:
  235. return "autoOffAlarm"
  236. case .podSuspendedReminder:
  237. return "podSuspendedReminder"
  238. case .suspendTimeExpired:
  239. return "suspendTimeExpired"
  240. }
  241. }()
  242. var rawValue: RawValue = [
  243. "name": name,
  244. ]
  245. switch self {
  246. case .expirationAlert(let alertTime):
  247. rawValue["alertTime"] = alertTime
  248. case .expirationAdvisoryAlarm(let alarmTime, let duration):
  249. rawValue["alarmTime"] = alarmTime
  250. rawValue["duration"] = duration
  251. case .shutdownImminentAlarm(let alarmTime):
  252. rawValue["alarmTime"] = alarmTime
  253. case .lowReservoirAlarm(let units):
  254. rawValue["units"] = units
  255. case .autoOffAlarm(let active, let countdownDuration):
  256. rawValue["active"] = active
  257. rawValue["countdownDuration"] = countdownDuration
  258. case .podSuspendedReminder(let active, let suspendTime):
  259. rawValue["active"] = active
  260. rawValue["suspendTime"] = suspendTime
  261. case .suspendTimeExpired(let suspendTime):
  262. rawValue["suspendTime"] = suspendTime
  263. default:
  264. break
  265. }
  266. return rawValue
  267. }
  268. }
  269. public enum AlertSlot: UInt8 {
  270. case slot0 = 0x00
  271. case slot1 = 0x01
  272. case slot2 = 0x02
  273. case slot3 = 0x03
  274. case slot4 = 0x04
  275. case slot5 = 0x05
  276. case slot6 = 0x06
  277. case slot7 = 0x07
  278. public var bitMaskValue: UInt8 {
  279. return 1<<rawValue
  280. }
  281. public typealias AllCases = [AlertSlot]
  282. static var allCases: AllCases {
  283. return (0..<8).map { AlertSlot(rawValue: $0)! }
  284. }
  285. }
  286. public struct AlertSet: RawRepresentable, Collection, CustomStringConvertible, Equatable {
  287. public typealias RawValue = UInt8
  288. public typealias Index = Int
  289. public let startIndex: Int
  290. public let endIndex: Int
  291. private let elements: [AlertSlot]
  292. public static let none = AlertSet(rawValue: 0)
  293. public var rawValue: UInt8 {
  294. return elements.reduce(0) { $0 | $1.bitMaskValue }
  295. }
  296. public init(slots: [AlertSlot]) {
  297. self.elements = slots
  298. self.startIndex = 0
  299. self.endIndex = self.elements.count
  300. }
  301. public init(rawValue: UInt8) {
  302. self.init(slots: AlertSlot.allCases.filter { rawValue & $0.bitMaskValue != 0 })
  303. }
  304. public subscript(index: Index) -> AlertSlot {
  305. return elements[index]
  306. }
  307. public func index(after i: Int) -> Int {
  308. return i+1
  309. }
  310. public var description: String {
  311. if elements.count == 0 {
  312. return LocalizedString("No alerts", comment: "Pod alert state when no alerts are active")
  313. } else {
  314. let alarmDescriptions = elements.map { String(describing: $0) }
  315. return alarmDescriptions.joined(separator: ", ")
  316. }
  317. }
  318. }