MainViewController.swift 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365
  1. //
  2. // FirstViewController.swift
  3. // LoopFollow
  4. //
  5. // Created by Jon Fawcett on 6/1/20.
  6. // Copyright © 2020 Jon Fawcett. All rights reserved.
  7. //
  8. import UIKit
  9. import Charts
  10. import EventKit
  11. class MainViewController: UIViewController, UITableViewDataSource, ChartViewDelegate {
  12. @IBOutlet weak var BGText: UILabel!
  13. @IBOutlet weak var DeltaText: UILabel!
  14. @IBOutlet weak var DirectionText: UILabel!
  15. @IBOutlet weak var BGChart: LineChartView!
  16. @IBOutlet weak var BGChartFull: LineChartView!
  17. @IBOutlet weak var MinAgoText: UILabel!
  18. @IBOutlet weak var infoTable: UITableView!
  19. @IBOutlet weak var Console: UITableViewCell!
  20. @IBOutlet weak var DragBar: UIImageView!
  21. @IBOutlet weak var PredictionLabel: UILabel!
  22. @IBOutlet weak var LoopStatusLabel: UILabel!
  23. //NS BG Struct
  24. struct sgvData: Codable {
  25. var sgv: Int
  26. var date: TimeInterval
  27. var direction: String?
  28. }
  29. //NS Cage Struct
  30. struct cageData: Codable {
  31. var created_at: String
  32. }
  33. //NS Basal Profile Struct
  34. struct basalProfileStruct: Codable {
  35. var value: Double
  36. var time: String
  37. var timeAsSeconds: Double
  38. }
  39. //NS Basal Data Struct
  40. struct basalDataStruct: Codable {
  41. var value: Double
  42. var date: TimeInterval
  43. }
  44. // Data Table Struct
  45. struct infoData {
  46. var name: String
  47. var value: String
  48. }
  49. // Variables for BG Charts
  50. public var numPoints: Int = 13
  51. public var linePlotData: [Double] = []
  52. public var linePlotDataTime: [Double] = []
  53. var firstStart: Bool = true
  54. // Vars for NS Pull
  55. var graphHours:Int=24
  56. var mmol = false as Bool
  57. var urlUser = UserDefaultsRepository.url.value as String
  58. var token = "" as String
  59. var defaults : UserDefaults?
  60. let consoleLogging = true
  61. var timeofLastBGUpdate = 0 as TimeInterval
  62. var backgroundTask = BackgroundTask()
  63. // Refresh NS Data
  64. var timer = Timer()
  65. // check every 30 Seconds whether new bgvalues should be retrieved
  66. let timeInterval: TimeInterval = 30.0
  67. // Check Alarms Timer
  68. // Don't check within 1 minute of alarm triggering to give the snoozer time to save data
  69. var checkAlarmTimer = Timer()
  70. var checkAlarmInterval: TimeInterval = 60.0
  71. // Info Table Setup
  72. var tableData = [
  73. infoData(name: "IOB", value: ""), //0
  74. infoData(name: "COB", value: ""), //1
  75. infoData(name: "Basal", value: ""), //2
  76. infoData(name: "Override", value: ""), //3
  77. infoData(name: "Battery", value: ""), //4
  78. infoData(name: "Pump", value: ""), //5
  79. infoData(name: "SAGE", value: ""), //6
  80. infoData(name: "CAGE", value: "") //7
  81. ]
  82. var bgData: [sgvData] = []
  83. var basalProfile: [basalProfileStruct] = []
  84. var basalData: [basalDataStruct] = []
  85. var predictionData: [Double] = []
  86. // calendar setup
  87. let store = EKEventStore()
  88. var snoozeTabItem: UITabBarItem = UITabBarItem()
  89. override func viewDidLoad() {
  90. super.viewDidLoad()
  91. if UserDefaultsRepository.forceDarkMode.value {
  92. overrideUserInterfaceStyle = .dark
  93. self.tabBarController?.overrideUserInterfaceStyle = .dark
  94. }
  95. // Disable the snoozer tab unless an alarm is active
  96. let tabBarControllerItems = self.tabBarController?.tabBar.items
  97. if let arrayOfTabBarItems = tabBarControllerItems as AnyObject as? NSArray{
  98. snoozeTabItem = arrayOfTabBarItems[2] as! UITabBarItem
  99. }
  100. snoozeTabItem.isEnabled = false;
  101. // Grant Badge Access
  102. UNUserNotificationCenter.current().requestAuthorization(options: .badge) { (granted, error) in
  103. if error != nil {
  104. }
  105. }
  106. // Trigger foreground and background functions
  107. let notificationCenter = NotificationCenter.default
  108. notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
  109. notificationCenter.addObserver(self, selector: #selector(appCameToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
  110. //Bind info data
  111. infoTable.rowHeight = 25
  112. infoTable.dataSource = self
  113. // Load Data
  114. appCameToForeground()
  115. }
  116. override func viewWillAppear(_ animated: Bool) {
  117. // set screen lock
  118. UIApplication.shared.isIdleTimerDisabled = UserDefaultsRepository.screenlockSwitchState.value;
  119. // Pull fresh data when view appears
  120. // moved this to didload, timer end, and foreground
  121. //nightscoutLoader()
  122. }
  123. // Info Table Functions
  124. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  125. return tableData.count
  126. }
  127. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  128. let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell", for: indexPath)
  129. let values = tableData[indexPath.row]
  130. cell.textLabel?.text = values.name
  131. cell.detailTextLabel?.text = values.value
  132. return cell
  133. }
  134. // NS Loader Timer
  135. fileprivate func startTimer(time: TimeInterval) {
  136. timer = Timer.scheduledTimer(timeInterval: time,
  137. target: self,
  138. selector: #selector(MainViewController.timerDidEnd(_:)),
  139. userInfo: nil,
  140. repeats: true)
  141. }
  142. // Check Alarm Timer
  143. func startCheckAlarmTimer(time: TimeInterval) {
  144. checkAlarmTimer = Timer.scheduledTimer(timeInterval: time,
  145. target: self,
  146. selector: #selector(MainViewController.checkAlarmTimerDidEnd(_:)),
  147. userInfo: nil,
  148. repeats: false)
  149. }
  150. // Nothing should be done when this timer ends because it just blocks the alarms from firing when it's active
  151. @objc func checkAlarmTimerDidEnd(_ timer:Timer) {
  152. print("check alarm timer ended")
  153. }
  154. @objc func appMovedToBackground() {
  155. // Allow screen to turn off
  156. UIApplication.shared.isIdleTimerDisabled = false;
  157. // We want to always come back to the home screen
  158. tabBarController?.selectedIndex = 0
  159. // Cancel the current timer and start a fresh background timer using the settings value only if background task is enabled
  160. timer.invalidate()
  161. if UserDefaultsRepository.backgroundRefresh.value {
  162. backgroundTask.startBackgroundTask()
  163. startTimer(time: TimeInterval(UserDefaultsRepository.backgroundRefreshFrequency.value*60))
  164. }
  165. }
  166. @objc func appCameToForeground() {
  167. // reset screenlock state if needed
  168. UIApplication.shared.isIdleTimerDisabled = UserDefaultsRepository.screenlockSwitchState.value;
  169. // Cancel the background tasks, start a fresh timer, and immediately check for new data
  170. if UserDefaultsRepository.backgroundRefresh.value {
  171. backgroundTask.stopBackgroundTask()
  172. timer.invalidate()
  173. }
  174. startTimer(time: timeInterval)
  175. nightscoutLoader()
  176. }
  177. // Check for new data when timer ends
  178. @objc func timerDidEnd(_ timer:Timer) {
  179. print("timer ended")
  180. nightscoutLoader()
  181. }
  182. // Main loader for all data
  183. func nightscoutLoader(forceLoad: Bool = false) {
  184. var needsLoaded: Bool = false
  185. var onlyPullLastRecord = false
  186. // If we have existing data and it's within 5 minutes, we aren't going to do a network call
  187. if bgData.count > 0 {
  188. let now = NSDate().timeIntervalSince1970
  189. let lastReadingTime = bgData[bgData.count - 1].date
  190. let secondsAgo = now - lastReadingTime
  191. if secondsAgo >= 5*60 {
  192. needsLoaded = true
  193. if secondsAgo < 10*60 {
  194. onlyPullLastRecord = true
  195. }
  196. }
  197. } else {
  198. needsLoaded = true
  199. }
  200. if forceLoad { needsLoaded = true}
  201. // Only do the network calls if we don't have a current reading
  202. if needsLoaded {
  203. loadDeviceStatus(urlUser: urlUser)
  204. loadBGData(urlUser: urlUser, onlyPullLastRecord: onlyPullLastRecord)
  205. clearLastInfoData()
  206. loadCage(urlUser: urlUser)
  207. loadSage(urlUser: urlUser)
  208. loadProfile(urlUser: urlUser)
  209. // loadBoluses(urlUser: urlUser)
  210. // loadTempBasals(urlUser: urlUser)
  211. } else {
  212. loadDeviceStatus(urlUser: urlUser)
  213. updateMinAgo()
  214. clearOldSnoozes()
  215. checkAlarms(bgs: bgData)
  216. loadProfile(urlUser: urlUser)
  217. }
  218. }
  219. // Main NS BG Data Pull
  220. func loadBGData(urlUser: String, onlyPullLastRecord: Bool = false) {
  221. // Set the count= in the url either to pull 24 hours or only the last record
  222. var points = "1"
  223. if !onlyPullLastRecord {
  224. points = String(self.graphHours * 12 + 1)
  225. }
  226. // URL processor
  227. var urlBGDataPath: String = urlUser + "/api/v1/entries/sgv.json?"
  228. if token == "" {
  229. urlBGDataPath = urlBGDataPath + "count=" + points
  230. } else {
  231. urlBGDataPath = urlBGDataPath + "token=" + token + "&count=" + points
  232. }
  233. guard let urlBGData = URL(string: urlBGDataPath) else { return }
  234. var request = URLRequest(url: urlBGData)
  235. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  236. // Downloader
  237. let getBGTask = URLSession.shared.dataTask(with: request) { data, response, error in
  238. if self.consoleLogging == true {print("start bg url")}
  239. guard error == nil else {
  240. return
  241. }
  242. guard let data = data else {
  243. return
  244. }
  245. let decoder = JSONDecoder()
  246. let entriesResponse = try? decoder.decode([sgvData].self, from: data)
  247. if let entriesResponse = entriesResponse {
  248. DispatchQueue.main.async {
  249. // trigger the processor for the data after downloading.
  250. self.ProcessNSBGData(data: entriesResponse, onlyPullLastRecord: onlyPullLastRecord)
  251. }
  252. } else {
  253. return
  254. }
  255. }
  256. getBGTask.resume()
  257. }
  258. // Primary processor for what to do with the downloaded NS BG data
  259. func ProcessNSBGData(data: [sgvData], onlyPullLastRecord: Bool){
  260. var pullDate = data[data.count - 1].date / 1000
  261. pullDate.round(FloatingPointRoundingRule.toNearestOrEven)
  262. // If we already have data, we're going to pop it to the end and remove the first. If we have old or no data, we'll destroy the whole array and start over. This is simpler than determining how far back we need to get new data from in case Dex back-filled readings
  263. if !onlyPullLastRecord {
  264. bgData.removeAll()
  265. } else if bgData[bgData.count - 1].date != pullDate {
  266. bgData.removeFirst()
  267. } else {
  268. // Update the badge, bg, graph settings even if we don't have a new reading.
  269. self.updateBadge(entries: bgData)
  270. self.updateBG(entries: bgData)
  271. self.createGraph(entries: bgData)
  272. return
  273. }
  274. // loop through the data so we can reverse the order to oldest first for the graph and convert the NS timestamp to seconds instead of milliseconds. Makes date comparisons easier for everything else.
  275. for i in 0..<data.count{
  276. var dateString = data[data.count - 1 - i].date / 1000
  277. dateString.round(FloatingPointRoundingRule.toNearestOrEven)
  278. let reading = sgvData(sgv: data[data.count - 1 - i].sgv, date: dateString, direction: data[data.count - 1 - i].direction)
  279. bgData.append(reading)
  280. }
  281. self.updateBadge(entries: bgData)
  282. self.updateBG(entries: bgData)
  283. self.createGraph(entries: bgData)
  284. if UserDefaultsRepository.writeCalendarEvent.value {
  285. self.writeCalendar()
  286. }
  287. }
  288. //update Min Ago Text. We need to call this separately because it updates between readings
  289. func updateMinAgo(){
  290. let deltaTime = (TimeInterval(Date().timeIntervalSince1970)-bgData[bgData.count - 1].date) / 60
  291. MinAgoText.text = String(Int(deltaTime)) + " min ago"
  292. }
  293. //Clear the info data before next pull. This ensures we aren't displaying old data if something fails.
  294. func clearLastInfoData(){
  295. for i in 0..<tableData.count{
  296. tableData[i].value = ""
  297. }
  298. }
  299. // NS Device Status Pull from NS
  300. func loadDeviceStatus(urlUser: String) {
  301. var urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?count=1"
  302. if token != "" {
  303. urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?token=" + token + "&count=1"
  304. }
  305. let escapedAddress = urlStringDeviceStatus.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
  306. guard let urlDeviceStatus = URL(string: escapedAddress!) else {
  307. return
  308. }
  309. if consoleLogging == true {print("entered device status task.")}
  310. var requestDeviceStatus = URLRequest(url: urlDeviceStatus)
  311. requestDeviceStatus.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  312. let deviceStatusTask = URLSession.shared.dataTask(with: requestDeviceStatus) { data, response, error in
  313. if self.consoleLogging == true {print("in device status loop.")}
  314. guard error == nil else {
  315. return
  316. }
  317. guard let data = data else {
  318. return
  319. }
  320. let json = try? (JSONSerialization.jsonObject(with: data) as! [[String:AnyObject]])
  321. if let json = json {
  322. DispatchQueue.main.async {
  323. self.updateDeviceStatusDisplay(jsonDeviceStatus: json)
  324. }
  325. } else {
  326. return
  327. }
  328. if self.consoleLogging == true {print("finish pump update")}}
  329. deviceStatusTask.resume()
  330. }
  331. // Parse Device Status Data
  332. func updateDeviceStatusDisplay(jsonDeviceStatus: [[String:AnyObject]]) {
  333. if consoleLogging == true {print("in updatePump")}
  334. if jsonDeviceStatus.count == 0 {
  335. return
  336. }
  337. //only grabbing one record since ns sorts by {created_at : -1}
  338. let lastDeviceStatus = jsonDeviceStatus[0] as [String : AnyObject]?
  339. //pump and uploader
  340. let formatter = ISO8601DateFormatter()
  341. formatter.formatOptions = [.withFullDate,
  342. .withTime,
  343. .withDashSeparatorInDate,
  344. .withColonSeparatorInTime]
  345. if let lastPumpRecord = lastDeviceStatus?["pump"] as! [String : AnyObject]? {
  346. if let lastPumpTime = formatter.date(from: (lastPumpRecord["clock"] as! String))?.timeIntervalSince1970 {
  347. if let reservoirData = lastPumpRecord["reservoir"] as? Double
  348. {
  349. tableData[5].value = String(format:"%.0f", reservoirData) + "U"
  350. } else {
  351. tableData[5].value = "50+U"
  352. }
  353. if let uploader = lastDeviceStatus?["uploader"] as? [String:AnyObject] {
  354. let upbat = uploader["battery"] as! Double
  355. tableData[4].value = String(format:"%.0f", upbat) + "%"
  356. }
  357. }
  358. }
  359. // Loop
  360. if let lastLoopRecord = lastDeviceStatus?["loop"] as! [String : AnyObject]? {
  361. if let lastLoopTime = formatter.date(from: (lastLoopRecord["timestamp"] as! String))?.timeIntervalSince1970 {
  362. if let failure = lastLoopRecord["failureReason"] {
  363. LoopStatusLabel.text = "⚠"
  364. }
  365. else
  366. {
  367. if let enacted = lastLoopRecord["enacted"] as? [String:AnyObject] {
  368. if let lastTempBasal = enacted["rate"] as? Double {
  369. tableData[2].value = String(format:"%.1f", lastTempBasal)
  370. }
  371. }
  372. if let iobdata = lastLoopRecord["iob"] as? [String:AnyObject] {
  373. tableData[0].value = String(format:"%.1f", (iobdata["iob"] as! Double))
  374. }
  375. if let cobdata = lastLoopRecord["cob"] as? [String:AnyObject] {
  376. tableData[1].value = String(format:"%.0f", cobdata["cob"] as! Double)
  377. }
  378. if let predictdata = lastLoopRecord["predicted"] as? [String:AnyObject] {
  379. let prediction = predictdata["values"] as! [Double]
  380. PredictionLabel.text = String(Int(prediction.last!))
  381. PredictionLabel.textColor = UIColor.systemPurple
  382. predictionData.removeAll()
  383. var i = 1
  384. while i <= 12 {
  385. predictionData.append(prediction[i])
  386. i += 1
  387. }
  388. }
  389. if let loopStatus = lastLoopRecord["recommendedTempBasal"] as? [String:AnyObject] {
  390. if let tempBasalTime = formatter.date(from: (loopStatus["timestamp"] as! String))?.timeIntervalSince1970 {
  391. if tempBasalTime > lastLoopTime {
  392. LoopStatusLabel.text = "⏀"
  393. } else {
  394. LoopStatusLabel.text = "↻"
  395. }
  396. }
  397. } else {
  398. LoopStatusLabel.text = "↻"
  399. }
  400. }
  401. }
  402. }
  403. var oText = "" as String
  404. if let lastOverride = lastDeviceStatus?["override"] as! [String : AnyObject]? {
  405. if let lastOverrideTime = formatter.date(from: (lastOverride["timestamp"] as! String))?.timeIntervalSince1970 {
  406. }
  407. if lastOverride["active"] as! Bool {
  408. let lastCorrection = lastOverride["currentCorrectionRange"] as! [String: AnyObject]
  409. if let multiplier = lastOverride["multiplier"] as? Double {
  410. oText += String(format:"%.1f", multiplier*100)
  411. }
  412. else
  413. {
  414. oText += String(format:"%.1f", 100)
  415. }
  416. oText += "% ("
  417. let minValue = lastCorrection["minValue"] as! Double
  418. let maxValue = lastCorrection["maxValue"] as! Double
  419. oText += bgOutputFormat(bg: minValue, mmol: mmol) + "-" + bgOutputFormat(bg: maxValue, mmol: mmol) + ")"
  420. tableData[3].value = oText
  421. }
  422. }
  423. infoTable.reloadData()
  424. }
  425. // Get last CAGE entry
  426. func loadCage(urlUser: String) {
  427. var urlString = urlUser + "/api/v1/treatments.json?find[eventType]=Site%20Change&count=1"
  428. if token != "" {
  429. urlString = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType]=Site%20Change&count=1"
  430. }
  431. guard let urlData = URL(string: urlString) else {
  432. return
  433. }
  434. var request = URLRequest(url: urlData)
  435. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  436. let task = URLSession.shared.dataTask(with: request) { data, response, error in
  437. if self.consoleLogging == true {print("start cage url")}
  438. guard error == nil else {
  439. return
  440. }
  441. guard let data = data else {
  442. return
  443. }
  444. let decoder = JSONDecoder()
  445. let entriesResponse = try? decoder.decode([cageData].self, from: data)
  446. if let entriesResponse = entriesResponse {
  447. DispatchQueue.main.async {
  448. self.updateCage(data: entriesResponse)
  449. }
  450. } else {
  451. return
  452. }
  453. }
  454. task.resume()
  455. }
  456. // Parse Cage Data
  457. func updateCage(data: [cageData]) {
  458. if consoleLogging == true {print("in updateCage")}
  459. if data.count == 0 {
  460. return
  461. }
  462. let lastCageString = data[0].created_at
  463. let formatter = ISO8601DateFormatter()
  464. formatter.formatOptions = [.withFullDate,
  465. .withTime,
  466. .withDashSeparatorInDate,
  467. .withColonSeparatorInTime]
  468. UserDefaultsRepository.alertCageInsertTime.value = formatter.date(from: (lastCageString))?.timeIntervalSince1970 as! TimeInterval
  469. if let cageTime = formatter.date(from: (lastCageString))?.timeIntervalSince1970 {
  470. let now = NSDate().timeIntervalSince1970
  471. let secondsAgo = now - cageTime
  472. //let days = 24 * 60 * 60
  473. let formatter = DateComponentsFormatter()
  474. formatter.unitsStyle = .positional // Use the appropriate positioning for the current locale
  475. formatter.allowedUnits = [ .hour, .minute ] // Units to display in the formatted string
  476. formatter.zeroFormattingBehavior = [ .pad ] // Pad with zeroes where appropriate for the locale
  477. let formattedDuration = formatter.string(from: secondsAgo)
  478. tableData[7].value = formattedDuration ?? ""
  479. }
  480. infoTable.reloadData()
  481. }
  482. // Get last SAGE entry
  483. func loadSage(urlUser: String) {
  484. var dayComponent = DateComponents()
  485. dayComponent.day = -10 // For removing 10 days
  486. let theCalendar = Calendar.current
  487. let startDate = theCalendar.date(byAdding: dayComponent, to: Date())!
  488. let dateFormatter = DateFormatter()
  489. dateFormatter.dateFormat = "yyyy-MM-dd"
  490. var startDateString = dateFormatter.string(from: startDate)
  491. var urlString = urlUser + "/api/v1/treatments.json?find[eventType]=Sensor%20Start&find[created_at][$gte]=2020-05-31&count=1"
  492. if token != "" {
  493. urlString = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType]=Sensor%20Start&find[created_at][$gte]=2020-05-31&count=1"
  494. }
  495. guard let urlData = URL(string: urlString) else {
  496. return
  497. }
  498. var request = URLRequest(url: urlData)
  499. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  500. let task = URLSession.shared.dataTask(with: request) { data, response, error in
  501. if self.consoleLogging == true {print("start cage url")}
  502. guard error == nil else {
  503. return
  504. }
  505. guard let data = data else {
  506. return
  507. }
  508. let decoder = JSONDecoder()
  509. let entriesResponse = try? decoder.decode([cageData].self, from: data)
  510. if let entriesResponse = entriesResponse {
  511. DispatchQueue.main.async {
  512. self.updateSage(data: entriesResponse)
  513. }
  514. } else {
  515. return
  516. }
  517. }
  518. task.resume()
  519. }
  520. // Parse Sage Data
  521. func updateSage(data: [cageData]) {
  522. if consoleLogging == true {print("in updateSage")}
  523. if data.count == 0 {
  524. return
  525. }
  526. var lastSageString = data[0].created_at
  527. let formatter = ISO8601DateFormatter()
  528. formatter.formatOptions = [.withFullDate,
  529. .withTime,
  530. .withDashSeparatorInDate,
  531. .withColonSeparatorInTime]
  532. UserDefaultsRepository.alertSageInsertTime.value = formatter.date(from: (lastSageString))?.timeIntervalSince1970 as! TimeInterval
  533. if let sageTime = formatter.date(from: (lastSageString as! String))?.timeIntervalSince1970 {
  534. let now = NSDate().timeIntervalSince1970
  535. let secondsAgo = now - sageTime
  536. let days = 24 * 60 * 60
  537. let formatter = DateComponentsFormatter()
  538. formatter.unitsStyle = .positional // Use the appropriate positioning for the current locale
  539. formatter.allowedUnits = [ .day, .hour] // Units to display in the formatted string
  540. formatter.zeroFormattingBehavior = [ .pad ] // Pad with zeroes where appropriate for the locale
  541. let formattedDuration = formatter.string(from: secondsAgo)
  542. tableData[6].value = formattedDuration ?? ""
  543. }
  544. infoTable.reloadData()
  545. }
  546. // Load Current Profile
  547. func loadProfile(urlUser: String) {
  548. let urlString = urlUser + "/api/v1/profile/current.json"
  549. let escapedAddress = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
  550. guard let url = URL(string: escapedAddress!) else {
  551. return
  552. }
  553. var request = URLRequest(url: url)
  554. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  555. let task = URLSession.shared.dataTask(with: request) { data, response, error in
  556. guard error == nil else {
  557. return
  558. }
  559. guard let data = data else {
  560. return
  561. }
  562. let json = try? JSONSerialization.jsonObject(with: data) as! Dictionary<String, Any>
  563. if let json = json {
  564. DispatchQueue.main.async {
  565. self.updateProfile(jsonDeviceStatus: json)
  566. }
  567. } else {
  568. return
  569. }
  570. }
  571. task.resume()
  572. }
  573. // Parse Basal schedule from the profile
  574. func updateProfile(jsonDeviceStatus: Dictionary<String, Any>) {
  575. if jsonDeviceStatus.count == 0 {
  576. return
  577. }
  578. let basal = jsonDeviceStatus[keyPath: "store.Default.basal"] as! NSArray
  579. for i in 0..<basal.count {
  580. let dict = basal[i] as! Dictionary<String, Any>
  581. let thisValue = dict[keyPath: "value"] as! Double
  582. let thisTime = dict[keyPath: "time"] as! String
  583. let thisTimeAsSeconds = dict[keyPath: "timeAsSeconds"] as! Double
  584. let entry = basalProfileStruct(value: thisValue, time: thisTime, timeAsSeconds: thisTimeAsSeconds)
  585. basalProfile.append(entry)
  586. }
  587. }
  588. func createBasalIncrements() {
  589. // remove old entries
  590. // Get the starting time for first BG entry
  591. // cycle through temp basals
  592. // if a temp basal doesn't exist, check for the scheduled basal based on time
  593. }
  594. // Need to figure out the date to pull only last 24 hours
  595. // NOT IMPLEMENTED YET
  596. func loadTempBasals(urlUser: String) {
  597. var dayComponent = DateComponents()
  598. dayComponent.day = -1 // For removing one day (yesterday): -1
  599. let theCalendar = Calendar.current
  600. let yesterday = theCalendar.date(byAdding: dayComponent, to: Date())
  601. let dateFormatter = DateFormatter()
  602. dateFormatter.dateFormat = "yyyy-mm-ddTHH:mm:ss"
  603. //dateFormatter.timeZone = NSTimeZone(name: "UTC")
  604. //let date: NSDate? = dateFormatter.dateFromString("2016-02-29 12:24:26")
  605. //print(date)
  606. var urlStringBasal = urlUser + "/api/v1/treatments.json?find[eventType][$eq]=Temp%20Basal&find[created_at][$gte]=2020-06-02T22:46:26"
  607. if token != "" {
  608. urlStringBasal = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType][$eq]=Temp%20Basal&find[created_at][$gte]=2020-06-02T22:46:26"
  609. }
  610. let escapedAddress = urlStringBasal.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
  611. guard let urlBasal = URL(string: escapedAddress!) else {
  612. return
  613. }
  614. if consoleLogging == true {print("entered 2nd task.")}
  615. var requestBasal = URLRequest(url: urlBasal)
  616. requestBasal.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  617. let basalTask = URLSession.shared.dataTask(with: requestBasal) { data, response, error in
  618. if self.consoleLogging == true {print("in update loop.")}
  619. guard error == nil else {
  620. return
  621. }
  622. guard let data = data else {
  623. return
  624. }
  625. let json = try? JSONSerialization.jsonObject(with: data) as! [[String:AnyObject]]
  626. if let json = json {
  627. DispatchQueue.main.async {
  628. self.updateDeviceStatusDisplay(jsonDeviceStatus: json)
  629. }
  630. }
  631. else
  632. {
  633. return
  634. }
  635. if self.consoleLogging == true {print("finish pump update")}
  636. }
  637. basalTask.resume()
  638. }
  639. // NOT IMPLEMENTED YET
  640. func loadBoluses(urlUser: String){
  641. var calendar = Calendar.current
  642. let today = Date()
  643. let midnight = calendar.startOfDay(for: today)
  644. let yesterday = calendar.date(byAdding: .day, value: -1, to: today)!
  645. let dateFormatter = DateFormatter()
  646. dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
  647. dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  648. let formattedDate = dateFormatter.string(from: yesterday)
  649. var searchString = "find[eventType]=Meal%20Bolus&find[created_at][$gte]=" + formattedDate
  650. var urlBGDataPath: String = urlUser + "/api/v1/treatments.json?"
  651. if token == "" {
  652. urlBGDataPath = urlBGDataPath + searchString
  653. }
  654. else
  655. {
  656. urlBGDataPath = urlBGDataPath + "token=" + token + searchString
  657. }
  658. guard let urlBGData = URL(string: urlBGDataPath) else {
  659. return
  660. }
  661. var request = URLRequest(url: urlBGData)
  662. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  663. let getTask = URLSession.shared.dataTask(with: request) { data, response, error in
  664. if self.consoleLogging == true {print("start meal bolus url")}
  665. guard error == nil else {
  666. return
  667. }
  668. guard let data = data else {
  669. return
  670. }
  671. let decoder = JSONDecoder()
  672. let entriesResponse = try? decoder.decode([sgvData].self, from: data)
  673. if let entriesResponse = entriesResponse {
  674. DispatchQueue.main.async {
  675. // self.ProcessNSData(data: entriesResponse, onlyPullLastRecord: onlyPullLastRecord)
  676. }
  677. }
  678. else
  679. {
  680. return
  681. }
  682. }
  683. getTask.resume()
  684. }
  685. func stringFromTimeInterval(interval: TimeInterval) -> String {
  686. let interval = Int(interval)
  687. let minutes = (interval / 60) % 60
  688. let hours = (interval / 3600)
  689. return String(format: "%02d:%02d", hours, minutes)
  690. }
  691. func createGraph(entries: [sgvData]){
  692. var bgChartEntry = [ChartDataEntry]()
  693. var colors = [NSUIColor]()
  694. var maxBG: Int = 250
  695. for i in 0..<entries.count{
  696. var dateString = String(entries[i].date).prefix(10)
  697. let dateSecondsOnly = Double(String(dateString))!
  698. if entries[i].sgv > maxBG {
  699. maxBG = entries[i].sgv
  700. }
  701. let value = ChartDataEntry(x: Double(entries[i].date), y: Double(entries[i].sgv))
  702. bgChartEntry.append(value)
  703. if Double(entries[i].sgv) >= Double(UserDefaultsRepository.highLine.value) {
  704. colors.append(NSUIColor.systemYellow)
  705. } else if Double(entries[i].sgv) <= Double(UserDefaultsRepository.lowLine.value) {
  706. colors.append(NSUIColor.systemRed)
  707. } else {
  708. colors.append(NSUIColor.systemGreen)
  709. }
  710. }
  711. // Add Prediction Data
  712. if predictionData.count > 0 {
  713. var startingTime = bgChartEntry[bgChartEntry.count - 1].x + 300
  714. var i = 0
  715. // Add 1 hour of predictions
  716. while i < 12 {
  717. var predictionVal = Double(predictionData[i])
  718. // Below can be turned on to prevent out of range on the graph if desired.
  719. // It currently just drops them out of view
  720. if predictionVal > 400 {
  721. // predictionVal = 400
  722. } else if predictionVal < 0 {
  723. // predictionVal = 0
  724. }
  725. let value = ChartDataEntry(x: startingTime + 5, y: predictionVal)
  726. bgChartEntry.append(value)
  727. colors.append(NSUIColor.systemPurple)
  728. startingTime += 300
  729. i += 1
  730. }
  731. }
  732. let line1 = LineChartDataSet(entries:bgChartEntry, label: "")
  733. line1.circleRadius = 3
  734. line1.circleColors = [NSUIColor.systemGreen]
  735. line1.drawCircleHoleEnabled = false
  736. if UserDefaultsRepository.showLines.value {
  737. line1.lineWidth = 2
  738. } else {
  739. line1.lineWidth = 0
  740. }
  741. if UserDefaultsRepository.showDots.value {
  742. line1.drawCirclesEnabled = true
  743. } else {
  744. line1.drawCirclesEnabled = false
  745. }
  746. line1.setDrawHighlightIndicators(false)
  747. line1.valueFont.withSize(50)
  748. for i in 1..<colors.count{
  749. line1.addColor(colors[i])
  750. line1.circleColors.append(colors[i])
  751. }
  752. let data = LineChartData()
  753. data.addDataSet(line1)
  754. data.setValueFont(UIFont(name: UIFont.systemFont(ofSize: 10).fontName, size: 10)!)
  755. data.setDrawValues(false)
  756. //Add lower red line based on low alert value
  757. let ll = ChartLimitLine()
  758. ll.limit = Double(UserDefaultsRepository.lowLine.value)
  759. ll.lineColor = NSUIColor.systemRed
  760. BGChart.rightAxis.addLimitLine(ll)
  761. //Add upper yellow line based on low alert value
  762. let ul = ChartLimitLine()
  763. ul.limit = Double(UserDefaultsRepository.highLine.value)
  764. ul.lineColor = NSUIColor.systemYellow
  765. BGChart.rightAxis.addLimitLine(ul)
  766. BGChart.xAxis.valueFormatter = ChartXValueFormatter()
  767. BGChart.xAxis.granularity = 1800
  768. BGChart.xAxis.labelTextColor = NSUIColor.label
  769. BGChart.xAxis.labelPosition = XAxis.LabelPosition.bottom
  770. BGChart.rightAxis.labelTextColor = NSUIColor.label
  771. BGChart.rightAxis.axisMinimum = 40
  772. BGChart.leftAxis.axisMinimum = 40
  773. BGChart.rightAxis.axisMaximum = Double(maxBG)
  774. BGChart.leftAxis.axisMaximum = Double(maxBG)
  775. BGChart.leftAxis.enabled = false
  776. BGChart.legend.enabled = false
  777. BGChart.scaleYEnabled = false
  778. BGChart.data = data
  779. BGChart.setExtraOffsets(left: 10, top: 10, right: 10, bottom: 10)
  780. BGChart.setVisibleXRangeMinimum(10)
  781. BGChart.drawGridBackgroundEnabled = true
  782. BGChart.gridBackgroundColor = NSUIColor.secondarySystemBackground
  783. if firstStart {
  784. BGChart.zoom(scaleX: 18, scaleY: 1, x: 1, y: 1)
  785. firstStart = false
  786. }
  787. // 7000 only shows 30 minutes of the hour predictions, leaving the rest on the right of the screen requiring a scroll
  788. BGChart.moveViewToX(BGChart.chartXMax - 7000)
  789. //24 Hour Small Graph
  790. let line2 = LineChartDataSet(entries:bgChartEntry, label: "Number")
  791. line2.drawCirclesEnabled = false
  792. line2.setDrawHighlightIndicators(false)
  793. line2.lineWidth = 1
  794. for i in 1..<colors.count{
  795. line2.addColor(colors[i])
  796. line2.circleColors.append(colors[i])
  797. }
  798. let data2 = LineChartData()
  799. data2.addDataSet(line2)
  800. BGChartFull.leftAxis.enabled = false
  801. BGChartFull.rightAxis.enabled = false
  802. BGChartFull.xAxis.enabled = false
  803. BGChartFull.legend.enabled = false
  804. BGChartFull.scaleYEnabled = false
  805. BGChartFull.scaleXEnabled = false
  806. BGChartFull.drawGridBackgroundEnabled = false
  807. BGChartFull.data = data2
  808. }
  809. func updateBadge(entries: [sgvData]) {
  810. if entries.count > 0 && UserDefaultsRepository.appBadge.value {
  811. let latestBG = entries[entries.count - 1].sgv
  812. UIApplication.shared.applicationIconBadgeNumber = latestBG
  813. } else {
  814. UIApplication.shared.applicationIconBadgeNumber = 0
  815. }
  816. print("updated badge")
  817. }
  818. func updateBG (entries: [sgvData]) {
  819. if consoleLogging == true {print("in update BG")}
  820. if entries.count > 0 {
  821. let latestEntryi = entries.count - 1
  822. let latestBG = entries[latestEntryi].sgv
  823. let priorBG = entries[latestEntryi - 1].sgv
  824. let deltaBG = latestBG - priorBG as Int
  825. let lastBGTime = entries[latestEntryi].date //NS has different units
  826. let deltaTime = (TimeInterval(Date().timeIntervalSince1970)-lastBGTime) / 60
  827. var userUnit = " mg/dL"
  828. if mmol {
  829. userUnit = " mmol/L"
  830. }
  831. if UserDefaultsRepository.appBadge.value {
  832. UIApplication.shared.applicationIconBadgeNumber = latestBG
  833. } else {
  834. UIApplication.shared.applicationIconBadgeNumber = 0
  835. }
  836. BGText.text = bgOutputFormat(bg: Double(latestBG), mmol: mmol)
  837. setBGTextColor()
  838. MinAgoText.text = String(Int(deltaTime)) + " min ago"
  839. print(String(Int(deltaTime)) + " min ago")
  840. if let directionBG = entries[latestEntryi].direction {
  841. DirectionText.text = bgDirectionGraphic(directionBG)
  842. }
  843. else
  844. {
  845. DirectionText.text = ""
  846. }
  847. if deltaBG < 0 {
  848. self.DeltaText.text = String(deltaBG)
  849. }
  850. else
  851. {
  852. self.DeltaText.text = "+" + String(deltaBG)
  853. }
  854. }
  855. else
  856. {
  857. return
  858. }
  859. checkAlarms(bgs: entries)
  860. }
  861. func setBGTextColor() {
  862. let latestBG = bgData[bgData.count - 1].sgv
  863. if UserDefaultsRepository.colorBGText.value {
  864. if latestBG >= UserDefaultsRepository.highLine.value {
  865. BGText.textColor = NSUIColor.systemYellow
  866. } else if latestBG <= UserDefaultsRepository.lowLine.value {
  867. BGText.textColor = NSUIColor.systemRed
  868. } else {
  869. BGText.textColor = NSUIColor.systemGreen
  870. }
  871. } else {
  872. BGText.textColor = NSUIColor.label
  873. }
  874. }
  875. func bgOutputFormat(bg: Double, mmol: Bool) -> String {
  876. if !mmol {
  877. return String(format:"%.0f", bg)
  878. }
  879. else
  880. {
  881. return String(format:"%.1f", bg / 18.0)
  882. }
  883. }
  884. func bgDirectionGraphic(_ value:String)->String
  885. {
  886. let //graphics:[String:String]=["Flat":"\u{2192}","DoubleUp":"\u{21C8}","SingleUp":"\u{2191}","FortyFiveUp":"\u{2197}\u{FE0E}","FortyFiveDown":"\u{2198}\u{FE0E}","SingleDown":"\u{2193}","DoubleDown":"\u{21CA}","None":"-","NOT COMPUTABLE":"-","RATE OUT OF RANGE":"-"]
  887. graphics:[String:String]=["Flat":"→","DoubleUp":"↑↑","SingleUp":"↑","FortyFiveUp":"↗","FortyFiveDown":"↘︎","SingleDown":"↓","DoubleDown":"↓↓","None":"-","NOT COMPUTABLE":"-","RATE OUT OF RANGE":"-"]
  888. return graphics[value]!
  889. }
  890. // Write calendar
  891. func writeCalendar() {
  892. store.requestAccess(to: .event) {(granted, error) in
  893. if !granted { return }
  894. // Create Event info
  895. // eventTitle = BGText.text + " " + DirectionText.text + " " + DeltaText.text + "\nC:" + tableData[1].value + "g I:" + tableData[0].value + "u"
  896. let deltaBG = self.bgData[self.bgData.count - 1].sgv - self.bgData[self.bgData.count - 2].sgv as Int
  897. var deltaString = ""
  898. if deltaBG < 0 {
  899. deltaString = String(deltaBG)
  900. }
  901. else
  902. {
  903. deltaString = "+" + String(deltaBG)
  904. }
  905. let direction = self.bgDirectionGraphic(self.bgData[self.bgData.count - 1].direction ?? "")
  906. var eventTitle = String(self.bgData[self.bgData.count - 1].sgv) + " "
  907. eventTitle += direction + " "
  908. eventTitle += deltaString + "\n"
  909. eventTitle += "C:" + self.tableData[1].value + "g "
  910. eventTitle += "I: " + self.tableData[0].value + "u"
  911. var eventStartDate = Date(timeIntervalSince1970: self.bgData[self.bgData.count - 1].date)
  912. // Delete Last Event
  913. let eventToRemove = self.store.event(withIdentifier: UserDefaultsRepository.savedEventID.value)
  914. if eventToRemove != nil {
  915. do {
  916. try self.store.remove(eventToRemove!, span: .thisEvent, commit: true)
  917. } catch {
  918. // Display error to user
  919. }
  920. }
  921. // Write New Event
  922. var event = EKEvent(eventStore: self.store)
  923. event.title = eventTitle
  924. event.startDate = eventStartDate
  925. event.endDate = event.startDate.addingTimeInterval(60*10) //Add 10 minutes
  926. event.calendar = self.store.calendar(withIdentifier: UserDefaultsRepository.calendarIdentifier.value)
  927. do {
  928. try self.store.save(event, span: .thisEvent, commit: true)
  929. UserDefaultsRepository.savedEventID.value = event.eventIdentifier //save event id to access this particular event later
  930. } catch {
  931. // Display error to user
  932. }
  933. }
  934. }
  935. func checkAlarms(bgs: [sgvData]) {
  936. // Don't check or fire alarms within 1 minute of prior alarm
  937. if checkAlarmTimer.isValid { return }
  938. let date = Date()
  939. let now = date.timeIntervalSince1970
  940. let currentBG = bgs[bgs.count - 1].sgv
  941. let lastBG = bgs[bgs.count - 2].sgv
  942. guard let deltas: [Int] = [
  943. bgs[bgs.count - 1].sgv - bgs[bgs.count - 2].sgv,
  944. bgs[bgs.count - 2].sgv - bgs[bgs.count - 3].sgv,
  945. bgs[bgs.count - 3].sgv - bgs[bgs.count - 4].sgv
  946. ] else {}
  947. let currentBGTime = bgs[bgs.count - 1].date
  948. var alarmTriggered = false
  949. clearOldSnoozes()
  950. // Exit if all is snoozed
  951. if UserDefaultsRepository.alertSnoozeAllIsSnoozed.value {
  952. return
  953. }
  954. // BG Based Alarms
  955. // Check to make sure it is a current reading and has not already triggered alarm from this reading
  956. if now - currentBGTime <= (5*60) && currentBGTime > UserDefaultsRepository.snoozedBGReadingTime.value as! TimeInterval {
  957. // trigger temporary alert first
  958. if UserDefaultsRepository.alertTemporaryActive.value {
  959. if UserDefaultsRepository.alertTemporaryBelow.value {
  960. if currentBG < UserDefaultsRepository.alertTemporaryBG.value {
  961. UserDefaultsRepository.alertTemporaryActive.value = false
  962. AlarmSound.whichAlarm = "Temporary Alert"
  963. triggerAlarm(sound: UserDefaultsRepository.alertTemporarySound.value, snooozedBGReadingTime: currentBGTime)
  964. return
  965. }
  966. } else{
  967. if currentBG > UserDefaultsRepository.alertTemporaryBG.value {
  968. tabBarController?.selectedIndex = 2
  969. AlarmSound.whichAlarm = "Temporary Alert"
  970. triggerAlarm(sound: UserDefaultsRepository.alertTemporarySound.value, snooozedBGReadingTime: currentBGTime)
  971. return
  972. }
  973. }
  974. }
  975. // Check Urgent Low
  976. if UserDefaultsRepository.alertUrgentLowActive.value && !UserDefaultsRepository.alertUrgentLowIsSnoozed.value &&
  977. currentBG <= UserDefaultsRepository.alertUrgentLowBG.value {
  978. // Separating this makes it so the low or drop alerts won't trigger if they already snoozed the urgent low
  979. if !UserDefaultsRepository.alertUrgentLowIsSnoozed.value {
  980. AlarmSound.whichAlarm = "Urgent Low Alert"
  981. triggerAlarm(sound: UserDefaultsRepository.alertUrgentLowSound.value, snooozedBGReadingTime: currentBGTime)
  982. return
  983. } else {
  984. return
  985. }
  986. }
  987. // Check Low
  988. if UserDefaultsRepository.alertLowActive.value && !UserDefaultsRepository.alertUrgentLowIsSnoozed.value &&
  989. currentBG <= UserDefaultsRepository.alertLowBG.value && !UserDefaultsRepository.alertLowIsSnoozed.value {
  990. AlarmSound.whichAlarm = "Low Alert"
  991. triggerAlarm(sound: UserDefaultsRepository.alertLowSound.value, snooozedBGReadingTime: currentBGTime)
  992. return
  993. }
  994. // Check Urgent High
  995. if UserDefaultsRepository.alertUrgentHighActive.value && !UserDefaultsRepository.alertUrgentHighIsSnoozed.value &&
  996. currentBG >= UserDefaultsRepository.alertUrgentHighBG.value {
  997. // Separating this makes it so the high or rise alerts won't trigger if they already snoozed the urgent low
  998. if !UserDefaultsRepository.alertUrgentHighIsSnoozed.value {
  999. AlarmSound.whichAlarm = "Urgent High Alert"
  1000. triggerAlarm(sound: UserDefaultsRepository.alertUrgentHighSound.value, snooozedBGReadingTime: currentBGTime)
  1001. return
  1002. } else {
  1003. return
  1004. }
  1005. }
  1006. // Check High
  1007. if UserDefaultsRepository.alertHighActive.value && !UserDefaultsRepository.alertHighIsSnoozed.value &&
  1008. currentBG >= UserDefaultsRepository.alertHighBG.value && !UserDefaultsRepository.alertHighIsSnoozed.value {
  1009. AlarmSound.whichAlarm = "High Alert"
  1010. triggerAlarm(sound: UserDefaultsRepository.alertHighSound.value, snooozedBGReadingTime: currentBGTime)
  1011. return
  1012. }
  1013. // Check Fast Drop
  1014. if UserDefaultsRepository.alertFastDropActive.value && !UserDefaultsRepository.alertFastDropIsSnoozed.value {
  1015. // make sure limit is off or BG is below value
  1016. if (!UserDefaultsRepository.alertFastDropUseLimit.value) || (UserDefaultsRepository.alertFastDropUseLimit.value && currentBG < UserDefaultsRepository.alertFastDropBelowBG.value) {
  1017. let compare = 0 - UserDefaultsRepository.alertFastDropDelta.value
  1018. //check last 2/3/4 readings
  1019. if (UserDefaultsRepository.alertFastDropReadings.value == 2 && deltas[0] <= compare)
  1020. || (UserDefaultsRepository.alertFastDropReadings.value == 3 && deltas[0] <= compare && deltas[1] <= compare)
  1021. || (UserDefaultsRepository.alertFastDropReadings.value == 4 && deltas[0] <= compare && deltas[1] <= compare && deltas[2] <= compare) {
  1022. AlarmSound.whichAlarm = "Fast Drop Alert"
  1023. triggerAlarm(sound: UserDefaultsRepository.alertFastDropSound.value, snooozedBGReadingTime: currentBGTime)
  1024. return
  1025. }
  1026. }
  1027. }
  1028. // Check Fast Rise
  1029. if UserDefaultsRepository.alertFastRiseActive.value && !UserDefaultsRepository.alertFastRiseIsSnoozed.value {
  1030. // make sure limit is off or BG is above value
  1031. if (!UserDefaultsRepository.alertFastRiseUseLimit.value) || (UserDefaultsRepository.alertFastRiseUseLimit.value && currentBG > UserDefaultsRepository.alertFastRiseAboveBG.value) {
  1032. let compare = UserDefaultsRepository.alertFastDropDelta.value
  1033. //check last 2/3/4 readings
  1034. if (UserDefaultsRepository.alertFastRiseReadings.value == 2 && deltas[0] >= compare)
  1035. || (UserDefaultsRepository.alertFastRiseReadings.value == 3 && deltas[0] >= compare && deltas[1] >= compare)
  1036. || (UserDefaultsRepository.alertFastRiseReadings.value == 4 && deltas[0] >= compare && deltas[1] >= compare && deltas[2] >= compare) {
  1037. AlarmSound.whichAlarm = "Fast Rise Alert"
  1038. triggerAlarm(sound: UserDefaultsRepository.alertFastRiseSound.value, snooozedBGReadingTime: currentBGTime)
  1039. return
  1040. }
  1041. }
  1042. }
  1043. }
  1044. // These only get checked and fire if a BG reading doesn't fire
  1045. //check for missed reading alert
  1046. if UserDefaultsRepository.alertMissedReadingActive.value && !UserDefaultsRepository.alertMissedReadingIsSnoozed.value && (Double(now - currentBGTime) >= Double(UserDefaultsRepository.alertMissedReading.value * 60)) {
  1047. AlarmSound.whichAlarm = "Missed Reading Alert"
  1048. triggerAlarm(sound: UserDefaultsRepository.alertMissedReadingSound.value, snooozedBGReadingTime: nil)
  1049. return
  1050. }
  1051. // Check Sage
  1052. if UserDefaultsRepository.alertSAGEActive.value {
  1053. let insertTime = Double(UserDefaultsRepository.alertSageInsertTime.value)
  1054. let alertDistance = Double(UserDefaultsRepository.alertSAGE.value * 60 * 60)
  1055. let delta = now - insertTime
  1056. let tenDays = 10 * 24 * 60 * 60
  1057. if Double(tenDays) - Double(delta) <= alertDistance {
  1058. AlarmSound.whichAlarm = "Sensor Change Alert"
  1059. triggerAlarm(sound: UserDefaultsRepository.alertSAGESound.value, snooozedBGReadingTime: nil)
  1060. return
  1061. }
  1062. }
  1063. // Check Cage
  1064. if UserDefaultsRepository.alertCAGEActive.value {
  1065. let insertTime = Double(UserDefaultsRepository.alertCageInsertTime.value)
  1066. let alertDistance = Double(UserDefaultsRepository.alertCAGE.value * 60 * 60)
  1067. let delta = now - insertTime
  1068. let tenDays = 3 * 24 * 60 * 60
  1069. if Double(tenDays) - Double(delta) <= alertDistance {
  1070. AlarmSound.whichAlarm = "Pump Change Alert"
  1071. triggerAlarm(sound: UserDefaultsRepository.alertCAGESound.value, snooozedBGReadingTime: nil)
  1072. return
  1073. }
  1074. }
  1075. }
  1076. func triggerAlarm(sound: String, snooozedBGReadingTime: TimeInterval?)
  1077. {
  1078. guard let snoozer = self.tabBarController!.viewControllers?[2] as? SnoozeViewController else { return }
  1079. snoozer.updateDisplayWhenTriggered(bgVal: BGText.text ?? "", directionVal: DirectionText.text ?? "", deltaVal: DeltaText.text ?? "", minAgoVal: MinAgoText.text ?? "", alertLabelVal: AlarmSound.whichAlarm)
  1080. snoozeTabItem.isEnabled = true;
  1081. tabBarController?.selectedIndex = 2
  1082. if snooozedBGReadingTime != nil {
  1083. UserDefaultsRepository.snoozedBGReadingTime.value = snooozedBGReadingTime
  1084. }
  1085. AlarmSound.setSoundFile(str: sound)
  1086. AlarmSound.play()
  1087. }
  1088. func clearOldSnoozes(){
  1089. let date = Date()
  1090. let now = date.timeIntervalSince1970
  1091. var needsReload: Bool = false
  1092. guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
  1093. if date > UserDefaultsRepository.alertSnoozeAllTime.value ?? date {
  1094. UserDefaultsRepository.alertSnoozeAllTime.setNil(key: "alertSnoozeAllTime")
  1095. UserDefaultsRepository.alertSnoozeAllIsSnoozed.value = false
  1096. alarms.reloadSnoozeTime(key: "alertSnoozeAllTime", setNil: true)
  1097. alarms.reloadIsSnoozed(key: "alertSnoozeAllIsSnoozed", value: false)
  1098. }
  1099. if date > UserDefaultsRepository.alertUrgentLowSnoozedTime.value ?? date {
  1100. UserDefaultsRepository.alertUrgentLowSnoozedTime.setNil(key: "alertUrgentLowSnoozedTime")
  1101. UserDefaultsRepository.alertUrgentLowIsSnoozed.value = false
  1102. alarms.reloadSnoozeTime(key: "alertUrgentLowSnoozedTime", setNil: true)
  1103. alarms.reloadIsSnoozed(key: "alertUrgentLowIsSnoozed", value: false)
  1104. }
  1105. if date > UserDefaultsRepository.alertLowSnoozedTime.value ?? date {
  1106. UserDefaultsRepository.alertLowSnoozedTime.setNil(key: "alertLowSnoozedTime")
  1107. UserDefaultsRepository.alertLowIsSnoozed.value = false
  1108. alarms.reloadSnoozeTime(key: "alertLowSnoozedTime", setNil: true)
  1109. alarms.reloadIsSnoozed(key: "alertLowIsSnoozed", value: false)
  1110. }
  1111. if date > UserDefaultsRepository.alertHighSnoozedTime.value ?? date {
  1112. UserDefaultsRepository.alertHighSnoozedTime.setNil(key: "alertHighSnoozedTime")
  1113. UserDefaultsRepository.alertHighIsSnoozed.value = false
  1114. alarms.reloadSnoozeTime(key: "alertHighSnoozedTime", setNil: true)
  1115. alarms.reloadIsSnoozed(key: "alertHighIsSnoozed", value: false)
  1116. }
  1117. if date > UserDefaultsRepository.alertUrgentHighSnoozedTime.value ?? date {
  1118. UserDefaultsRepository.alertUrgentHighSnoozedTime.setNil(key: "alertUrgentHighSnoozedTime")
  1119. UserDefaultsRepository.alertUrgentHighIsSnoozed.value = false
  1120. alarms.reloadSnoozeTime(key: "alertUrgentHighSnoozedTime", setNil: true)
  1121. alarms.reloadIsSnoozed(key: "alertUrgentHighIsSnoozed", value: false)
  1122. }
  1123. if date > UserDefaultsRepository.alertFastDropSnoozedTime.value ?? date {
  1124. UserDefaultsRepository.alertFastDropSnoozedTime.setNil(key: "alertFastDropSnoozedTime")
  1125. UserDefaultsRepository.alertFastDropIsSnoozed.value = false
  1126. alarms.reloadSnoozeTime(key: "alertFastDropSnoozedTime", setNil: true)
  1127. alarms.reloadIsSnoozed(key: "alertFastDropIsSnoozed", value: false)
  1128. }
  1129. if date > UserDefaultsRepository.alertFastRiseSnoozedTime.value ?? date {
  1130. UserDefaultsRepository.alertFastRiseSnoozedTime.setNil(key: "alertFastRiseSnoozedTime")
  1131. UserDefaultsRepository.alertFastRiseIsSnoozed.value = false
  1132. alarms.reloadSnoozeTime(key: "alertFastRiseSnoozedTime", setNil: true)
  1133. alarms.reloadIsSnoozed(key: "alertFastRiseIsSnoozed", value: false)
  1134. }
  1135. if date > UserDefaultsRepository.alertMissedReadingSnoozedTime.value ?? date {
  1136. UserDefaultsRepository.alertMissedReadingSnoozedTime.setNil(key: "alertMissedReadingSnoozedTime")
  1137. UserDefaultsRepository.alertMissedReadingIsSnoozed.value = false
  1138. alarms.reloadSnoozeTime(key: "alertMissedReadingSnoozedTime", setNil: true)
  1139. alarms.reloadIsSnoozed(key: "alertMissedReadingIsSnoozed", value: false)
  1140. }
  1141. if date > UserDefaultsRepository.alertNotLoopingSnoozedTime.value ?? date {
  1142. UserDefaultsRepository.alertNotLoopingSnoozedTime.setNil(key: "alertNotLoopingSnoozedTime")
  1143. UserDefaultsRepository.alertNotLoopingIsSnoozed.value = false
  1144. alarms.reloadSnoozeTime(key: "alertNotLoopingSnoozedTime", setNil: true)
  1145. alarms.reloadIsSnoozed(key: "alertNotLoopingIsSnoozed", value: false)
  1146. }
  1147. if date > UserDefaultsRepository.alertMissedBolusSnoozedTime.value ?? date {
  1148. UserDefaultsRepository.alertMissedBolusSnoozedTime.setNil(key: "alertMissedBolusSnoozedTime")
  1149. UserDefaultsRepository.alertMissedBolusIsSnoozed.value = false
  1150. alarms.reloadSnoozeTime(key: "alertMissedBolusSnoozedTime", setNil: true)
  1151. alarms.reloadIsSnoozed(key: "alertMissedBolusIsSnoozed", value: false)
  1152. }
  1153. if date > UserDefaultsRepository.alertSAGESnoozedTime.value ?? date {
  1154. UserDefaultsRepository.alertSAGESnoozedTime.setNil(key: "alertSAGESnoozedTime")
  1155. UserDefaultsRepository.alertSAGEIsSnoozed.value = false
  1156. alarms.reloadSnoozeTime(key: "alertSAGESnoozedTime", setNil: true)
  1157. alarms.reloadIsSnoozed(key: "alertSAGEIsSnoozed", value: false)
  1158. }
  1159. if date > UserDefaultsRepository.alertCAGESnoozedTime.value ?? date {
  1160. UserDefaultsRepository.alertCAGESnoozedTime.setNil(key: "alertCAGESnoozedTime")
  1161. UserDefaultsRepository.alertCAGEIsSnoozed.value = false
  1162. alarms.reloadSnoozeTime(key: "alertCAGESnoozedTime", setNil: true)
  1163. alarms.reloadIsSnoozed(key: "alertCAGEIsSnoozed", value: false)
  1164. }
  1165. }
  1166. }