MainViewController.swift 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330
  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. nightscoutLoader()
  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() {
  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. // Only do the network calls if we don't have a current reading
  201. if needsLoaded {
  202. loadDeviceStatus(urlUser: urlUser)
  203. loadBGData(urlUser: urlUser, onlyPullLastRecord: onlyPullLastRecord)
  204. clearLastInfoData()
  205. loadCage(urlUser: urlUser)
  206. loadSage(urlUser: urlUser)
  207. loadProfile(urlUser: urlUser)
  208. // loadBoluses(urlUser: urlUser)
  209. // loadTempBasals(urlUser: urlUser)
  210. } else {
  211. loadDeviceStatus(urlUser: urlUser)
  212. updateMinAgo()
  213. clearOldSnoozes()
  214. checkAlarms(bgs: bgData)
  215. loadProfile(urlUser: urlUser)
  216. }
  217. }
  218. // Main NS BG Data Pull
  219. func loadBGData(urlUser: String, onlyPullLastRecord: Bool = false) {
  220. // Set the count= in the url either to pull 24 hours or only the last record
  221. var points = "1"
  222. if !onlyPullLastRecord {
  223. points = String(self.graphHours * 12 + 1)
  224. }
  225. // URL processor
  226. var urlBGDataPath: String = urlUser + "/api/v1/entries/sgv.json?"
  227. if token == "" {
  228. urlBGDataPath = urlBGDataPath + "count=" + points
  229. } else {
  230. urlBGDataPath = urlBGDataPath + "token=" + token + "&count=" + points
  231. }
  232. guard let urlBGData = URL(string: urlBGDataPath) else { return }
  233. var request = URLRequest(url: urlBGData)
  234. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  235. // Downloader
  236. let getBGTask = URLSession.shared.dataTask(with: request) { data, response, error in
  237. if self.consoleLogging == true {print("start bg url")}
  238. guard error == nil else {
  239. return
  240. }
  241. guard let data = data else {
  242. return
  243. }
  244. let decoder = JSONDecoder()
  245. let entriesResponse = try? decoder.decode([sgvData].self, from: data)
  246. if let entriesResponse = entriesResponse {
  247. DispatchQueue.main.async {
  248. // trigger the processor for the data after downloading.
  249. self.ProcessNSBGData(data: entriesResponse, onlyPullLastRecord: onlyPullLastRecord)
  250. }
  251. } else {
  252. return
  253. }
  254. }
  255. getBGTask.resume()
  256. }
  257. // Primary processor for what to do with the downloaded NS BG data
  258. func ProcessNSBGData(data: [sgvData], onlyPullLastRecord: Bool){
  259. var pullDate = data[data.count - 1].date / 1000
  260. pullDate.round(FloatingPointRoundingRule.toNearestOrEven)
  261. // 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
  262. if !onlyPullLastRecord {
  263. bgData.removeAll()
  264. } else if bgData[bgData.count - 1].date != pullDate {
  265. bgData.removeFirst()
  266. } else {
  267. // Update the badge, bg, graph settings even if we don't have a new reading.
  268. self.updateBadge(entries: bgData)
  269. self.updateBG(entries: bgData)
  270. self.createGraph(entries: bgData)
  271. return
  272. }
  273. // 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.
  274. for i in 0..<data.count{
  275. var dateString = data[data.count - 1 - i].date / 1000
  276. dateString.round(FloatingPointRoundingRule.toNearestOrEven)
  277. let reading = sgvData(sgv: data[data.count - 1 - i].sgv, date: dateString, direction: data[data.count - 1 - i].direction)
  278. bgData.append(reading)
  279. }
  280. self.updateBadge(entries: bgData)
  281. self.updateBG(entries: bgData)
  282. self.createGraph(entries: bgData)
  283. if UserDefaultsRepository.writeCalendarEvent.value {
  284. self.writeCalendar()
  285. }
  286. }
  287. //update Min Ago Text. We need to call this separately because it updates between readings
  288. func updateMinAgo(){
  289. let deltaTime = (TimeInterval(Date().timeIntervalSince1970)-bgData[bgData.count - 1].date) / 60
  290. MinAgoText.text = String(Int(deltaTime)) + " min ago"
  291. }
  292. //Clear the info data before next pull. This ensures we aren't displaying old data if something fails.
  293. func clearLastInfoData(){
  294. for i in 0..<tableData.count{
  295. tableData[i].value = ""
  296. }
  297. }
  298. // NS Device Status Pull from NS
  299. func loadDeviceStatus(urlUser: String) {
  300. var urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?count=1"
  301. if token != "" {
  302. urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?token=" + token + "&count=1"
  303. }
  304. let escapedAddress = urlStringDeviceStatus.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
  305. guard let urlDeviceStatus = URL(string: escapedAddress!) else {
  306. return
  307. }
  308. if consoleLogging == true {print("entered device status task.")}
  309. var requestDeviceStatus = URLRequest(url: urlDeviceStatus)
  310. requestDeviceStatus.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  311. let deviceStatusTask = URLSession.shared.dataTask(with: requestDeviceStatus) { data, response, error in
  312. if self.consoleLogging == true {print("in device status loop.")}
  313. guard error == nil else {
  314. return
  315. }
  316. guard let data = data else {
  317. return
  318. }
  319. let json = try? (JSONSerialization.jsonObject(with: data) as! [[String:AnyObject]])
  320. if let json = json {
  321. DispatchQueue.main.async {
  322. self.updateDeviceStatusDisplay(jsonDeviceStatus: json)
  323. }
  324. } else {
  325. return
  326. }
  327. if self.consoleLogging == true {print("finish pump update")}}
  328. deviceStatusTask.resume()
  329. }
  330. // Parse Device Status Data
  331. func updateDeviceStatusDisplay(jsonDeviceStatus: [[String:AnyObject]]) {
  332. if consoleLogging == true {print("in updatePump")}
  333. if jsonDeviceStatus.count == 0 {
  334. return
  335. }
  336. //only grabbing one record since ns sorts by {created_at : -1}
  337. let lastDeviceStatus = jsonDeviceStatus[0] as [String : AnyObject]?
  338. //pump and uploader
  339. let formatter = ISO8601DateFormatter()
  340. formatter.formatOptions = [.withFullDate,
  341. .withTime,
  342. .withDashSeparatorInDate,
  343. .withColonSeparatorInTime]
  344. if let lastPumpRecord = lastDeviceStatus?["pump"] as! [String : AnyObject]? {
  345. if let lastPumpTime = formatter.date(from: (lastPumpRecord["clock"] as! String))?.timeIntervalSince1970 {
  346. if let reservoirData = lastPumpRecord["reservoir"] as? Double
  347. {
  348. tableData[5].value = String(format:"%.0f", reservoirData) + "U"
  349. } else {
  350. tableData[5].value = "50+U"
  351. }
  352. if let uploader = lastDeviceStatus?["uploader"] as? [String:AnyObject] {
  353. let upbat = uploader["battery"] as! Double
  354. tableData[4].value = String(format:"%.0f", upbat) + "%"
  355. }
  356. }
  357. }
  358. // Loop
  359. if let lastLoopRecord = lastDeviceStatus?["loop"] as! [String : AnyObject]? {
  360. if let lastLoopTime = formatter.date(from: (lastLoopRecord["timestamp"] as! String))?.timeIntervalSince1970 {
  361. if let failure = lastLoopRecord["failureReason"] {
  362. LoopStatusLabel.text = "⚠"
  363. }
  364. else
  365. {
  366. if let enacted = lastLoopRecord["enacted"] as? [String:AnyObject] {
  367. if let lastTempBasal = enacted["rate"] as? Double {
  368. tableData[2].value = String(format:"%.1f", lastTempBasal)
  369. }
  370. }
  371. if let iobdata = lastLoopRecord["iob"] as? [String:AnyObject] {
  372. tableData[0].value = String(format:"%.1f", (iobdata["iob"] as! Double))
  373. }
  374. if let cobdata = lastLoopRecord["cob"] as? [String:AnyObject] {
  375. tableData[1].value = String(format:"%.0f", cobdata["cob"] as! Double)
  376. }
  377. if let predictdata = lastLoopRecord["predicted"] as? [String:AnyObject] {
  378. let prediction = predictdata["values"] as! [Double]
  379. PredictionLabel.text = String(Int(prediction.last!))
  380. PredictionLabel.textColor = UIColor.systemPurple
  381. predictionData.removeAll()
  382. var i = 1
  383. while i <= 12 {
  384. predictionData.append(prediction[i])
  385. i += 1
  386. }
  387. }
  388. if let loopStatus = lastLoopRecord["recommendedTempBasal"] as? [String:AnyObject] {
  389. if let tempBasalTime = formatter.date(from: (loopStatus["timestamp"] as! String))?.timeIntervalSince1970 {
  390. if tempBasalTime > lastLoopTime {
  391. LoopStatusLabel.text = "⏀"
  392. } else {
  393. LoopStatusLabel.text = "↻"
  394. }
  395. }
  396. } else {
  397. LoopStatusLabel.text = "↻"
  398. }
  399. }
  400. }
  401. }
  402. var oText = "" as String
  403. if let lastOverride = lastDeviceStatus?["override"] as! [String : AnyObject]? {
  404. if let lastOverrideTime = formatter.date(from: (lastOverride["timestamp"] as! String))?.timeIntervalSince1970 {
  405. }
  406. if lastOverride["active"] as! Bool {
  407. let lastCorrection = lastOverride["currentCorrectionRange"] as! [String: AnyObject]
  408. if let multiplier = lastOverride["multiplier"] as? Double {
  409. oText += String(format:"%.1f", multiplier*100)
  410. }
  411. else
  412. {
  413. oText += String(format:"%.1f", 100)
  414. }
  415. oText += "% ("
  416. let minValue = lastCorrection["minValue"] as! Double
  417. let maxValue = lastCorrection["maxValue"] as! Double
  418. oText += bgOutputFormat(bg: minValue, mmol: mmol) + "-" + bgOutputFormat(bg: maxValue, mmol: mmol) + ")"
  419. tableData[3].value = oText
  420. }
  421. }
  422. infoTable.reloadData()
  423. }
  424. // Get last CAGE entry
  425. func loadCage(urlUser: String) {
  426. var urlString = urlUser + "/api/v1/treatments.json?find[eventType]=Site%20Change&count=1"
  427. if token != "" {
  428. urlString = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType]=Site%20Change&count=1"
  429. }
  430. guard let urlData = URL(string: urlString) else {
  431. return
  432. }
  433. var request = URLRequest(url: urlData)
  434. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  435. let task = URLSession.shared.dataTask(with: request) { data, response, error in
  436. if self.consoleLogging == true {print("start cage url")}
  437. guard error == nil else {
  438. return
  439. }
  440. guard let data = data else {
  441. return
  442. }
  443. let decoder = JSONDecoder()
  444. let entriesResponse = try? decoder.decode([cageData].self, from: data)
  445. if let entriesResponse = entriesResponse {
  446. DispatchQueue.main.async {
  447. self.updateCage(data: entriesResponse)
  448. }
  449. } else {
  450. return
  451. }
  452. }
  453. task.resume()
  454. }
  455. // Parse Cage Data
  456. func updateCage(data: [cageData]) {
  457. if consoleLogging == true {print("in updateCage")}
  458. if data.count == 0 {
  459. return
  460. }
  461. let lastCageString = data[0].created_at
  462. let formatter = ISO8601DateFormatter()
  463. formatter.formatOptions = [.withFullDate,
  464. .withTime,
  465. .withDashSeparatorInDate,
  466. .withColonSeparatorInTime]
  467. UserDefaultsRepository.alertCageInsertTime.value = formatter.date(from: (lastCageString))?.timeIntervalSince1970 as! TimeInterval
  468. if let cageTime = formatter.date(from: (lastCageString))?.timeIntervalSince1970 {
  469. let now = NSDate().timeIntervalSince1970
  470. let secondsAgo = now - cageTime
  471. //let days = 24 * 60 * 60
  472. let formatter = DateComponentsFormatter()
  473. formatter.unitsStyle = .positional // Use the appropriate positioning for the current locale
  474. formatter.allowedUnits = [ .hour, .minute ] // Units to display in the formatted string
  475. formatter.zeroFormattingBehavior = [ .pad ] // Pad with zeroes where appropriate for the locale
  476. let formattedDuration = formatter.string(from: secondsAgo)
  477. tableData[7].value = formattedDuration ?? ""
  478. }
  479. infoTable.reloadData()
  480. }
  481. // Get last SAGE entry
  482. func loadSage(urlUser: String) {
  483. var dayComponent = DateComponents()
  484. dayComponent.day = -10 // For removing 10 days
  485. let theCalendar = Calendar.current
  486. let startDate = theCalendar.date(byAdding: dayComponent, to: Date())!
  487. let dateFormatter = DateFormatter()
  488. dateFormatter.dateFormat = "yyyy-MM-dd"
  489. var startDateString = dateFormatter.string(from: startDate)
  490. var urlString = urlUser + "/api/v1/treatments.json?find[eventType]=Sensor%20Start&find[created_at][$gte]=2020-05-31&count=1"
  491. if token != "" {
  492. urlString = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType]=Sensor%20Start&find[created_at][$gte]=2020-05-31&count=1"
  493. }
  494. guard let urlData = URL(string: urlString) else {
  495. return
  496. }
  497. var request = URLRequest(url: urlData)
  498. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  499. let task = URLSession.shared.dataTask(with: request) { data, response, error in
  500. if self.consoleLogging == true {print("start cage url")}
  501. guard error == nil else {
  502. return
  503. }
  504. guard let data = data else {
  505. return
  506. }
  507. let decoder = JSONDecoder()
  508. let entriesResponse = try? decoder.decode([cageData].self, from: data)
  509. if let entriesResponse = entriesResponse {
  510. DispatchQueue.main.async {
  511. self.updateSage(data: entriesResponse)
  512. }
  513. } else {
  514. return
  515. }
  516. }
  517. task.resume()
  518. }
  519. // Parse Sage Data
  520. func updateSage(data: [cageData]) {
  521. if consoleLogging == true {print("in updateSage")}
  522. if data.count == 0 {
  523. return
  524. }
  525. var lastSageString = data[0].created_at
  526. let formatter = ISO8601DateFormatter()
  527. formatter.formatOptions = [.withFullDate,
  528. .withTime,
  529. .withDashSeparatorInDate,
  530. .withColonSeparatorInTime]
  531. UserDefaultsRepository.alertSageInsertTime.value = formatter.date(from: (lastSageString))?.timeIntervalSince1970 as! TimeInterval
  532. if let sageTime = formatter.date(from: (lastSageString as! String))?.timeIntervalSince1970 {
  533. let now = NSDate().timeIntervalSince1970
  534. let secondsAgo = now - sageTime
  535. let days = 24 * 60 * 60
  536. let formatter = DateComponentsFormatter()
  537. formatter.unitsStyle = .positional // Use the appropriate positioning for the current locale
  538. formatter.allowedUnits = [ .day, .hour] // Units to display in the formatted string
  539. formatter.zeroFormattingBehavior = [ .pad ] // Pad with zeroes where appropriate for the locale
  540. let formattedDuration = formatter.string(from: secondsAgo)
  541. tableData[6].value = formattedDuration ?? ""
  542. }
  543. infoTable.reloadData()
  544. }
  545. // Load Current Profile
  546. func loadProfile(urlUser: String) {
  547. let urlString = urlUser + "/api/v1/profile/current.json"
  548. let escapedAddress = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
  549. guard let url = URL(string: escapedAddress!) else {
  550. return
  551. }
  552. var request = URLRequest(url: url)
  553. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  554. let task = URLSession.shared.dataTask(with: request) { data, response, error in
  555. guard error == nil else {
  556. return
  557. }
  558. guard let data = data else {
  559. return
  560. }
  561. let json = try? JSONSerialization.jsonObject(with: data) as! Dictionary<String, Any>
  562. if let json = json {
  563. DispatchQueue.main.async {
  564. self.updateProfile(jsonDeviceStatus: json)
  565. }
  566. } else {
  567. return
  568. }
  569. }
  570. task.resume()
  571. }
  572. // Parse Basal schedule from the profile
  573. func updateProfile(jsonDeviceStatus: Dictionary<String, Any>) {
  574. if jsonDeviceStatus.count == 0 {
  575. return
  576. }
  577. let basal = jsonDeviceStatus[keyPath: "store.Default.basal"] as! NSArray
  578. for i in 0..<basal.count {
  579. let dict = basal[i] as! Dictionary<String, Any>
  580. let thisValue = dict[keyPath: "value"] as! Double
  581. let thisTime = dict[keyPath: "time"] as! String
  582. let thisTimeAsSeconds = dict[keyPath: "timeAsSeconds"] as! Double
  583. let entry = basalProfileStruct(value: thisValue, time: thisTime, timeAsSeconds: thisTimeAsSeconds)
  584. basalProfile.append(entry)
  585. }
  586. }
  587. func createBasalIncrements() {
  588. // remove old entries
  589. // Get the starting time for first BG entry
  590. // cycle through temp basals
  591. // if a temp basal doesn't exist, check for the scheduled basal based on time
  592. }
  593. // Need to figure out the date to pull only last 24 hours
  594. // NOT IMPLEMENTED YET
  595. func loadTempBasals(urlUser: String) {
  596. var dayComponent = DateComponents()
  597. dayComponent.day = -1 // For removing one day (yesterday): -1
  598. let theCalendar = Calendar.current
  599. let yesterday = theCalendar.date(byAdding: dayComponent, to: Date())
  600. let dateFormatter = DateFormatter()
  601. dateFormatter.dateFormat = "yyyy-mm-ddTHH:mm:ss"
  602. //dateFormatter.timeZone = NSTimeZone(name: "UTC")
  603. //let date: NSDate? = dateFormatter.dateFromString("2016-02-29 12:24:26")
  604. //print(date)
  605. var urlStringBasal = urlUser + "/api/v1/treatments.json?find[eventType][$eq]=Temp%20Basal&find[created_at][$gte]=2020-06-02T22:46:26"
  606. if token != "" {
  607. urlStringBasal = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType][$eq]=Temp%20Basal&find[created_at][$gte]=2020-06-02T22:46:26"
  608. }
  609. let escapedAddress = urlStringBasal.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
  610. guard let urlBasal = URL(string: escapedAddress!) else {
  611. return
  612. }
  613. if consoleLogging == true {print("entered 2nd task.")}
  614. var requestBasal = URLRequest(url: urlBasal)
  615. requestBasal.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  616. let basalTask = URLSession.shared.dataTask(with: requestBasal) { data, response, error in
  617. if self.consoleLogging == true {print("in update loop.")}
  618. guard error == nil else {
  619. return
  620. }
  621. guard let data = data else {
  622. return
  623. }
  624. let json = try? JSONSerialization.jsonObject(with: data) as! [[String:AnyObject]]
  625. if let json = json {
  626. DispatchQueue.main.async {
  627. self.updateDeviceStatusDisplay(jsonDeviceStatus: json)
  628. }
  629. }
  630. else
  631. {
  632. return
  633. }
  634. if self.consoleLogging == true {print("finish pump update")}
  635. }
  636. basalTask.resume()
  637. }
  638. // NOT IMPLEMENTED YET
  639. func loadBoluses(urlUser: String){
  640. var calendar = Calendar.current
  641. let today = Date()
  642. let midnight = calendar.startOfDay(for: today)
  643. let yesterday = calendar.date(byAdding: .day, value: -1, to: today)!
  644. let dateFormatter = DateFormatter()
  645. dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
  646. dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  647. let formattedDate = dateFormatter.string(from: yesterday)
  648. var searchString = "find[eventType]=Meal%20Bolus&find[created_at][$gte]=" + formattedDate
  649. var urlBGDataPath: String = urlUser + "/api/v1/treatments.json?"
  650. if token == "" {
  651. urlBGDataPath = urlBGDataPath + searchString
  652. }
  653. else
  654. {
  655. urlBGDataPath = urlBGDataPath + "token=" + token + searchString
  656. }
  657. guard let urlBGData = URL(string: urlBGDataPath) else {
  658. return
  659. }
  660. var request = URLRequest(url: urlBGData)
  661. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  662. let getTask = URLSession.shared.dataTask(with: request) { data, response, error in
  663. if self.consoleLogging == true {print("start meal bolus url")}
  664. guard error == nil else {
  665. return
  666. }
  667. guard let data = data else {
  668. return
  669. }
  670. let decoder = JSONDecoder()
  671. let entriesResponse = try? decoder.decode([sgvData].self, from: data)
  672. if let entriesResponse = entriesResponse {
  673. DispatchQueue.main.async {
  674. // self.ProcessNSData(data: entriesResponse, onlyPullLastRecord: onlyPullLastRecord)
  675. }
  676. }
  677. else
  678. {
  679. return
  680. }
  681. }
  682. getTask.resume()
  683. }
  684. func stringFromTimeInterval(interval: TimeInterval) -> String {
  685. let interval = Int(interval)
  686. let minutes = (interval / 60) % 60
  687. let hours = (interval / 3600)
  688. return String(format: "%02d:%02d", hours, minutes)
  689. }
  690. func createGraph(entries: [sgvData]){
  691. var bgChartEntry = [ChartDataEntry]()
  692. var colors = [NSUIColor]()
  693. var maxBG: Int = 250
  694. for i in 0..<entries.count{
  695. var dateString = String(entries[i].date).prefix(10)
  696. let dateSecondsOnly = Double(String(dateString))!
  697. if entries[i].sgv > maxBG {
  698. maxBG = entries[i].sgv
  699. }
  700. let value = ChartDataEntry(x: Double(entries[i].date), y: Double(entries[i].sgv))
  701. bgChartEntry.append(value)
  702. if Double(entries[i].sgv) >= Double(UserDefaultsRepository.highLine.value) {
  703. colors.append(NSUIColor.yellow)
  704. } else if Double(entries[i].sgv) <= Double(UserDefaultsRepository.lowLine.value) {
  705. colors.append(NSUIColor.red)
  706. } else {
  707. colors.append(NSUIColor.green)
  708. }
  709. }
  710. // Add Prediction Data
  711. if predictionData.count > 0 {
  712. var startingTime = bgChartEntry[bgChartEntry.count - 1].x + 300
  713. var i = 0
  714. // Add 1 hour of predictions
  715. while i < 12 {
  716. var predictionVal = Double(predictionData[i])
  717. // Below can be turned on to prevent out of range opn the graph if needed.
  718. if predictionVal > 400 {
  719. // predictionVal = 400
  720. } else if predictionVal < 0 {
  721. // predictionVal = 0
  722. }
  723. let value = ChartDataEntry(x: startingTime + 5, y: predictionVal)
  724. bgChartEntry.append(value)
  725. colors.append(NSUIColor.systemPurple)
  726. startingTime += 300
  727. i += 1
  728. }
  729. }
  730. let line1 = LineChartDataSet(entries:bgChartEntry, label: "")
  731. line1.circleRadius = 3
  732. line1.circleColors = [NSUIColor.green]
  733. line1.drawCircleHoleEnabled = false
  734. if UserDefaultsRepository.showLines.value {
  735. line1.lineWidth = 2
  736. } else {
  737. line1.lineWidth = 0
  738. }
  739. if UserDefaultsRepository.showDots.value {
  740. line1.drawCirclesEnabled = true
  741. } else {
  742. line1.drawCirclesEnabled = false
  743. }
  744. line1.setDrawHighlightIndicators(false)
  745. line1.valueFont.withSize(50)
  746. for i in 1..<colors.count{
  747. line1.addColor(colors[i])
  748. line1.circleColors.append(colors[i])
  749. }
  750. let data = LineChartData()
  751. data.addDataSet(line1)
  752. data.setValueFont(UIFont(name: UIFont.systemFont(ofSize: 10).fontName, size: 10)!)
  753. data.setDrawValues(false)
  754. //Add lower red line based on low alert value
  755. let ll = ChartLimitLine()
  756. ll.limit = Double(UserDefaultsRepository.lowLine.value)
  757. ll.lineColor = NSUIColor.red
  758. BGChart.rightAxis.addLimitLine(ll)
  759. //Add upper yellow line based on low alert value
  760. let ul = ChartLimitLine()
  761. ul.limit = Double(UserDefaultsRepository.highLine.value)
  762. ul.lineColor = NSUIColor.yellow
  763. BGChart.rightAxis.addLimitLine(ul)
  764. BGChart.xAxis.valueFormatter = ChartXValueFormatter()
  765. BGChart.xAxis.granularity = 1800
  766. BGChart.xAxis.labelTextColor = NSUIColor.label
  767. BGChart.xAxis.labelPosition = XAxis.LabelPosition.bottom
  768. BGChart.rightAxis.labelTextColor = NSUIColor.label
  769. BGChart.rightAxis.axisMinimum = 40
  770. BGChart.leftAxis.axisMinimum = 40
  771. BGChart.rightAxis.axisMaximum = Double(maxBG)
  772. BGChart.leftAxis.axisMaximum = Double(maxBG)
  773. BGChart.leftAxis.enabled = false
  774. BGChart.legend.enabled = false
  775. BGChart.scaleYEnabled = false
  776. BGChart.data = data
  777. BGChart.setExtraOffsets(left: 10, top: 10, right: 10, bottom: 10)
  778. BGChart.setVisibleXRangeMinimum(10)
  779. if firstStart {
  780. BGChart.zoom(scaleX: 18, scaleY: 1, x: 1, y: 1)
  781. firstStart = false
  782. }
  783. // 7000 only shows 30 minutes of the hour predictions, leaving the rest on the right of the screen requiring a scroll
  784. BGChart.moveViewToX(BGChart.chartXMax - 7000)
  785. //24 Hour Small Graph
  786. let line2 = LineChartDataSet(entries:bgChartEntry, label: "Number")
  787. line2.drawCirclesEnabled = false
  788. line2.setDrawHighlightIndicators(false)
  789. line2.lineWidth = 1
  790. for i in 1..<colors.count{
  791. line2.addColor(colors[i])
  792. line2.circleColors.append(colors[i])
  793. }
  794. let data2 = LineChartData()
  795. data2.addDataSet(line2)
  796. BGChartFull.leftAxis.enabled = false
  797. BGChartFull.rightAxis.enabled = false
  798. BGChartFull.xAxis.enabled = false
  799. BGChartFull.legend.enabled = false
  800. BGChartFull.scaleYEnabled = false
  801. BGChartFull.scaleXEnabled = false
  802. BGChartFull.drawGridBackgroundEnabled = false
  803. BGChartFull.data = data2
  804. }
  805. func updateBadge(entries: [sgvData]) {
  806. if entries.count > 0 && UserDefaultsRepository.appBadge.value {
  807. let latestBG = entries[entries.count - 1].sgv
  808. UIApplication.shared.applicationIconBadgeNumber = latestBG
  809. } else {
  810. UIApplication.shared.applicationIconBadgeNumber = 0
  811. }
  812. print("updated badge")
  813. }
  814. func updateBG (entries: [sgvData]) {
  815. if consoleLogging == true {print("in update BG")}
  816. if entries.count > 0 {
  817. let latestEntryi = entries.count - 1
  818. let latestBG = entries[latestEntryi].sgv
  819. let priorBG = entries[latestEntryi - 1].sgv
  820. let deltaBG = latestBG - priorBG as Int
  821. let lastBGTime = entries[latestEntryi].date //NS has different units
  822. let deltaTime = (TimeInterval(Date().timeIntervalSince1970)-lastBGTime) / 60
  823. var userUnit = " mg/dL"
  824. if mmol {
  825. userUnit = " mmol/L"
  826. }
  827. if UserDefaultsRepository.appBadge.value {
  828. UIApplication.shared.applicationIconBadgeNumber = latestBG
  829. } else {
  830. UIApplication.shared.applicationIconBadgeNumber = 0
  831. }
  832. BGText.text = bgOutputFormat(bg: Double(latestBG), mmol: mmol)
  833. MinAgoText.text = String(Int(deltaTime)) + " min ago"
  834. print(String(Int(deltaTime)) + " min ago")
  835. if let directionBG = entries[latestEntryi].direction {
  836. DirectionText.text = bgDirectionGraphic(directionBG)
  837. }
  838. else
  839. {
  840. DirectionText.text = ""
  841. }
  842. if deltaBG < 0 {
  843. self.DeltaText.text = String(deltaBG)
  844. }
  845. else
  846. {
  847. self.DeltaText.text = "+" + String(deltaBG)
  848. }
  849. }
  850. else
  851. {
  852. return
  853. }
  854. checkAlarms(bgs: entries)
  855. }
  856. func bgOutputFormat(bg: Double, mmol: Bool) -> String {
  857. if !mmol {
  858. return String(format:"%.0f", bg)
  859. }
  860. else
  861. {
  862. return String(format:"%.1f", bg / 18.0)
  863. }
  864. }
  865. func bgDirectionGraphic(_ value:String)->String
  866. {
  867. 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":"-"]
  868. graphics:[String:String]=["Flat":"→","DoubleUp":"↑↑","SingleUp":"↑","FortyFiveUp":"↗","FortyFiveDown":"↘︎","SingleDown":"↓","DoubleDown":"↓↓","None":"-","NOT COMPUTABLE":"-","RATE OUT OF RANGE":"-"]
  869. return graphics[value]!
  870. }
  871. // Write calendar. Not implemented yet
  872. func writeCalendar() {
  873. store.requestAccess(to: .event) {(granted, error) in
  874. if !granted { return }
  875. // Create Event info
  876. // eventTitle = BGText.text + " " + DirectionText.text + " " + DeltaText.text + "\nC:" + tableData[1].value + "g I:" + tableData[0].value + "u"
  877. let deltaBG = self.bgData[self.bgData.count - 1].sgv - self.bgData[self.bgData.count - 2].sgv as Int
  878. var deltaString = ""
  879. if deltaBG < 0 {
  880. deltaString = String(deltaBG)
  881. }
  882. else
  883. {
  884. deltaString = "+" + String(deltaBG)
  885. }
  886. let direction = self.bgDirectionGraphic(self.bgData[self.bgData.count - 1].direction ?? "")
  887. var eventTitle = String(self.bgData[self.bgData.count - 1].sgv) + " "
  888. eventTitle += direction + " "
  889. eventTitle += deltaString + "\n"
  890. eventTitle += "C:" + self.tableData[1].value + "g "
  891. eventTitle += "I: " + self.tableData[0].value + "u"
  892. var eventStartDate = Date(timeIntervalSince1970: self.bgData[self.bgData.count - 1].date)
  893. // Delete Last Event
  894. let eventToRemove = self.store.event(withIdentifier: UserDefaultsRepository.savedEventID.value)
  895. if eventToRemove != nil {
  896. do {
  897. try self.store.remove(eventToRemove!, span: .thisEvent, commit: true)
  898. } catch {
  899. // Display error to user
  900. }
  901. }
  902. // Write New Event
  903. var event = EKEvent(eventStore: self.store)
  904. event.title = eventTitle
  905. event.startDate = eventStartDate
  906. event.endDate = event.startDate.addingTimeInterval(60*10) //Add 10 minutes
  907. event.calendar = self.store.calendar(withIdentifier: UserDefaultsRepository.calendarIdentifier.value)
  908. do {
  909. try self.store.save(event, span: .thisEvent, commit: true)
  910. UserDefaultsRepository.savedEventID.value = event.eventIdentifier //save event id to access this particular event later
  911. } catch {
  912. // Display error to user
  913. }
  914. }
  915. }
  916. func checkAlarms(bgs: [sgvData]) {
  917. // Don't check or fire alarms within 1 minute of prior alarm
  918. if checkAlarmTimer.isValid { return }
  919. let date = Date()
  920. let now = date.timeIntervalSince1970
  921. let currentBG = bgs[bgs.count - 1].sgv
  922. let lastBG = bgs[bgs.count - 2].sgv
  923. guard let deltas: [Int] = [
  924. bgs[bgs.count - 1].sgv - bgs[bgs.count - 2].sgv,
  925. bgs[bgs.count - 2].sgv - bgs[bgs.count - 3].sgv,
  926. bgs[bgs.count - 3].sgv - bgs[bgs.count - 4].sgv
  927. ] else {}
  928. let currentBGTime = bgs[bgs.count - 1].date
  929. var alarmTriggered = false
  930. clearOldSnoozes()
  931. // Exit if all is snoozed
  932. if UserDefaultsRepository.alertSnoozeAllIsSnoozed.value {
  933. return
  934. }
  935. // BG Based Alarms
  936. // Check to make sure it is a current reading and has not already triggered alarm from this reading
  937. if now - currentBGTime <= (5*60) && currentBGTime > UserDefaultsRepository.snoozedBGReadingTime.value as! TimeInterval {
  938. // trigger temporary alert first
  939. if UserDefaultsRepository.alertTemporaryActive.value {
  940. if UserDefaultsRepository.alertTemporaryBelow.value {
  941. if currentBG < UserDefaultsRepository.alertTemporaryBG.value {
  942. UserDefaultsRepository.alertTemporaryActive.value = false
  943. AlarmSound.whichAlarm = "Temporary Alert"
  944. triggerAlarm(sound: UserDefaultsRepository.alertTemporarySound.value, snooozedBGReadingTime: currentBGTime)
  945. return
  946. }
  947. } else{
  948. if currentBG > UserDefaultsRepository.alertTemporaryBG.value {
  949. tabBarController?.selectedIndex = 2
  950. AlarmSound.whichAlarm = "Temporary Alert"
  951. triggerAlarm(sound: UserDefaultsRepository.alertTemporarySound.value, snooozedBGReadingTime: currentBGTime)
  952. return
  953. }
  954. }
  955. }
  956. // Check Urgent Low
  957. if UserDefaultsRepository.alertUrgentLowActive.value && !UserDefaultsRepository.alertUrgentLowIsSnoozed.value &&
  958. currentBG <= UserDefaultsRepository.alertUrgentLowBG.value && !UserDefaultsRepository.alertUrgentLowIsSnoozed.value {
  959. AlarmSound.whichAlarm = "Urgent Low Alert"
  960. triggerAlarm(sound: UserDefaultsRepository.alertUrgentLowSound.value, snooozedBGReadingTime: currentBGTime)
  961. return
  962. }
  963. // Check Low
  964. if UserDefaultsRepository.alertLowActive.value && !UserDefaultsRepository.alertUrgentLowIsSnoozed.value &&
  965. currentBG <= UserDefaultsRepository.alertLowBG.value && !UserDefaultsRepository.alertLowIsSnoozed.value {
  966. AlarmSound.whichAlarm = "Low Alert"
  967. triggerAlarm(sound: UserDefaultsRepository.alertLowSound.value, snooozedBGReadingTime: currentBGTime)
  968. return
  969. }
  970. // Check High
  971. if UserDefaultsRepository.alertHighActive.value && !UserDefaultsRepository.alertHighIsSnoozed.value &&
  972. currentBG >= UserDefaultsRepository.alertHighBG.value && !UserDefaultsRepository.alertHighIsSnoozed.value {
  973. AlarmSound.whichAlarm = "High Alert"
  974. triggerAlarm(sound: UserDefaultsRepository.alertHighSound.value, snooozedBGReadingTime: currentBGTime)
  975. return
  976. }
  977. // Check Urgent High
  978. if UserDefaultsRepository.alertUrgentHighActive.value && !UserDefaultsRepository.alertUrgentHighIsSnoozed.value &&
  979. currentBG >= UserDefaultsRepository.alertUrgentHighBG.value && !UserDefaultsRepository.alertUrgentHighIsSnoozed.value {
  980. AlarmSound.whichAlarm = "Urgent High Alert"
  981. triggerAlarm(sound: UserDefaultsRepository.alertUrgentHighSound.value, snooozedBGReadingTime: currentBGTime)
  982. return
  983. }
  984. // Check Fast Drop
  985. if UserDefaultsRepository.alertFastDropActive.value && !UserDefaultsRepository.alertFastDropIsSnoozed.value {
  986. // make sure limit is off or BG is below value
  987. if (!UserDefaultsRepository.alertFastDropUseLimit.value) || (UserDefaultsRepository.alertFastDropUseLimit.value && currentBG < UserDefaultsRepository.alertFastDropBelowBG.value) {
  988. let compare = 0 - UserDefaultsRepository.alertFastDropDelta.value
  989. //check last 2/3/4 readings
  990. if (UserDefaultsRepository.alertFastDropReadings.value == 2 && deltas[0] <= compare)
  991. || (UserDefaultsRepository.alertFastDropReadings.value == 3 && deltas[0] <= compare && deltas[1] <= compare)
  992. || (UserDefaultsRepository.alertFastDropReadings.value == 4 && deltas[0] <= compare && deltas[1] <= compare && deltas[2] <= compare) {
  993. AlarmSound.whichAlarm = "Fast Drop Alert"
  994. triggerAlarm(sound: UserDefaultsRepository.alertFastDropSound.value, snooozedBGReadingTime: currentBGTime)
  995. return
  996. }
  997. }
  998. }
  999. // Check Fast Rise
  1000. if UserDefaultsRepository.alertFastRiseActive.value && !UserDefaultsRepository.alertFastRiseIsSnoozed.value {
  1001. // make sure limit is off or BG is above value
  1002. if (!UserDefaultsRepository.alertFastRiseUseLimit.value) || (UserDefaultsRepository.alertFastRiseUseLimit.value && currentBG > UserDefaultsRepository.alertFastRiseAboveBG.value) {
  1003. let compare = UserDefaultsRepository.alertFastDropDelta.value
  1004. //check last 2/3/4 readings
  1005. if (UserDefaultsRepository.alertFastRiseReadings.value == 2 && deltas[0] >= compare)
  1006. || (UserDefaultsRepository.alertFastRiseReadings.value == 3 && deltas[0] >= compare && deltas[1] >= compare)
  1007. || (UserDefaultsRepository.alertFastRiseReadings.value == 4 && deltas[0] >= compare && deltas[1] >= compare && deltas[2] >= compare) {
  1008. AlarmSound.whichAlarm = "Fast Rise Alert"
  1009. triggerAlarm(sound: UserDefaultsRepository.alertFastRiseSound.value, snooozedBGReadingTime: currentBGTime)
  1010. return
  1011. }
  1012. }
  1013. }
  1014. }
  1015. // These only get checked and fire if a BG reading doesn't fire
  1016. //check for missed reading alert
  1017. if UserDefaultsRepository.alertMissedReadingActive.value && !UserDefaultsRepository.alertMissedReadingIsSnoozed.value && (Double(now - currentBGTime) >= Double(UserDefaultsRepository.alertMissedReading.value * 60)) {
  1018. AlarmSound.whichAlarm = "Missed Reading Alert"
  1019. triggerAlarm(sound: UserDefaultsRepository.alertMissedReadingSound.value, snooozedBGReadingTime: nil)
  1020. return
  1021. }
  1022. // Check Sage
  1023. if UserDefaultsRepository.alertSAGEActive.value {
  1024. let insertTime = Double(UserDefaultsRepository.alertSageInsertTime.value)
  1025. let alertDistance = Double(UserDefaultsRepository.alertSAGE.value * 60 * 60)
  1026. let delta = now - insertTime
  1027. let tenDays = 10 * 24 * 60 * 60
  1028. if Double(tenDays) - Double(delta) <= alertDistance {
  1029. AlarmSound.whichAlarm = "Sensor Change Alert"
  1030. triggerAlarm(sound: UserDefaultsRepository.alertSAGESound.value, snooozedBGReadingTime: nil)
  1031. return
  1032. }
  1033. }
  1034. // Check Cage
  1035. if UserDefaultsRepository.alertCAGEActive.value {
  1036. let insertTime = Double(UserDefaultsRepository.alertCageInsertTime.value)
  1037. let alertDistance = Double(UserDefaultsRepository.alertCAGE.value * 60 * 60)
  1038. let delta = now - insertTime
  1039. let tenDays = 3 * 24 * 60 * 60
  1040. if Double(tenDays) - Double(delta) <= alertDistance {
  1041. AlarmSound.whichAlarm = "Pump Change Alert"
  1042. triggerAlarm(sound: UserDefaultsRepository.alertCAGESound.value, snooozedBGReadingTime: nil)
  1043. return
  1044. }
  1045. }
  1046. }
  1047. func triggerAlarm(sound: String, snooozedBGReadingTime: TimeInterval?)
  1048. {
  1049. guard let snoozer = self.tabBarController!.viewControllers?[2] as? SnoozeViewController else { return }
  1050. snoozer.updateDisplayWhenTriggered(bgVal: BGText.text ?? "", directionVal: DirectionText.text ?? "", deltaVal: DeltaText.text ?? "", minAgoVal: MinAgoText.text ?? "", alertLabelVal: AlarmSound.whichAlarm)
  1051. snoozeTabItem.isEnabled = true;
  1052. tabBarController?.selectedIndex = 2
  1053. if snooozedBGReadingTime != nil {
  1054. UserDefaultsRepository.snoozedBGReadingTime.value = snooozedBGReadingTime
  1055. }
  1056. AlarmSound.setSoundFile(str: sound)
  1057. AlarmSound.play()
  1058. }
  1059. func clearOldSnoozes(){
  1060. let date = Date()
  1061. let now = date.timeIntervalSince1970
  1062. var needsReload: Bool = false
  1063. guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
  1064. if date > UserDefaultsRepository.alertSnoozeAllTime.value ?? date {
  1065. UserDefaultsRepository.alertSnoozeAllTime.setNil(key: "alertSnoozeAllTime")
  1066. UserDefaultsRepository.alertSnoozeAllIsSnoozed.value = false
  1067. alarms.reloadSnoozeTime(key: "alertSnoozeAllTime", setNil: true)
  1068. alarms.reloadIsSnoozed(key: "alertSnoozeAllIsSnoozed", value: false)
  1069. }
  1070. if date > UserDefaultsRepository.alertUrgentLowSnoozedTime.value ?? date {
  1071. UserDefaultsRepository.alertUrgentLowSnoozedTime.setNil(key: "alertUrgentLowSnoozedTime")
  1072. UserDefaultsRepository.alertUrgentLowIsSnoozed.value = false
  1073. alarms.reloadSnoozeTime(key: "alertUrgentLowSnoozedTime", setNil: true)
  1074. alarms.reloadIsSnoozed(key: "alertUrgentLowIsSnoozed", value: false)
  1075. }
  1076. if date > UserDefaultsRepository.alertLowSnoozedTime.value ?? date {
  1077. UserDefaultsRepository.alertLowSnoozedTime.setNil(key: "alertLowSnoozedTime")
  1078. UserDefaultsRepository.alertLowIsSnoozed.value = false
  1079. alarms.reloadSnoozeTime(key: "alertLowSnoozedTime", setNil: true)
  1080. alarms.reloadIsSnoozed(key: "alertLowIsSnoozed", value: false)
  1081. }
  1082. if date > UserDefaultsRepository.alertHighSnoozedTime.value ?? date {
  1083. UserDefaultsRepository.alertHighSnoozedTime.setNil(key: "alertHighSnoozedTime")
  1084. UserDefaultsRepository.alertHighIsSnoozed.value = false
  1085. alarms.reloadSnoozeTime(key: "alertHighSnoozedTime", setNil: true)
  1086. alarms.reloadIsSnoozed(key: "alertHighIsSnoozed", value: false)
  1087. }
  1088. if date > UserDefaultsRepository.alertUrgentHighSnoozedTime.value ?? date {
  1089. UserDefaultsRepository.alertUrgentHighSnoozedTime.setNil(key: "alertUrgentHighSnoozedTime")
  1090. UserDefaultsRepository.alertUrgentHighIsSnoozed.value = false
  1091. alarms.reloadSnoozeTime(key: "alertUrgentHighSnoozedTime", setNil: true)
  1092. alarms.reloadIsSnoozed(key: "alertUrgentHighIsSnoozed", value: false)
  1093. }
  1094. if date > UserDefaultsRepository.alertFastDropSnoozedTime.value ?? date {
  1095. UserDefaultsRepository.alertFastDropSnoozedTime.setNil(key: "alertFastDropSnoozedTime")
  1096. UserDefaultsRepository.alertFastDropIsSnoozed.value = false
  1097. alarms.reloadSnoozeTime(key: "alertFastDropSnoozedTime", setNil: true)
  1098. alarms.reloadIsSnoozed(key: "alertFastDropIsSnoozed", value: false)
  1099. }
  1100. if date > UserDefaultsRepository.alertFastRiseSnoozedTime.value ?? date {
  1101. UserDefaultsRepository.alertFastRiseSnoozedTime.setNil(key: "alertFastRiseSnoozedTime")
  1102. UserDefaultsRepository.alertFastRiseIsSnoozed.value = false
  1103. alarms.reloadSnoozeTime(key: "alertFastRiseSnoozedTime", setNil: true)
  1104. alarms.reloadIsSnoozed(key: "alertFastRiseIsSnoozed", value: false)
  1105. }
  1106. if date > UserDefaultsRepository.alertMissedReadingSnoozedTime.value ?? date {
  1107. UserDefaultsRepository.alertMissedReadingSnoozedTime.setNil(key: "alertMissedReadingSnoozedTime")
  1108. UserDefaultsRepository.alertMissedReadingIsSnoozed.value = false
  1109. alarms.reloadSnoozeTime(key: "alertMissedReadingSnoozedTime", setNil: true)
  1110. alarms.reloadIsSnoozed(key: "alertMissedReadingIsSnoozed", value: false)
  1111. }
  1112. if date > UserDefaultsRepository.alertNotLoopingSnoozedTime.value ?? date {
  1113. UserDefaultsRepository.alertNotLoopingSnoozedTime.setNil(key: "alertNotLoopingSnoozedTime")
  1114. UserDefaultsRepository.alertNotLoopingIsSnoozed.value = false
  1115. alarms.reloadSnoozeTime(key: "alertNotLoopingSnoozedTime", setNil: true)
  1116. alarms.reloadIsSnoozed(key: "alertNotLoopingIsSnoozed", value: false)
  1117. }
  1118. if date > UserDefaultsRepository.alertMissedBolusSnoozedTime.value ?? date {
  1119. UserDefaultsRepository.alertMissedBolusSnoozedTime.setNil(key: "alertMissedBolusSnoozedTime")
  1120. UserDefaultsRepository.alertMissedBolusIsSnoozed.value = false
  1121. alarms.reloadSnoozeTime(key: "alertMissedBolusSnoozedTime", setNil: true)
  1122. alarms.reloadIsSnoozed(key: "alertMissedBolusIsSnoozed", value: false)
  1123. }
  1124. if date > UserDefaultsRepository.alertSAGESnoozedTime.value ?? date {
  1125. UserDefaultsRepository.alertSAGESnoozedTime.setNil(key: "alertSAGESnoozedTime")
  1126. UserDefaultsRepository.alertSAGEIsSnoozed.value = false
  1127. alarms.reloadSnoozeTime(key: "alertSAGESnoozedTime", setNil: true)
  1128. alarms.reloadIsSnoozed(key: "alertSAGEIsSnoozed", value: false)
  1129. }
  1130. if date > UserDefaultsRepository.alertCAGESnoozedTime.value ?? date {
  1131. UserDefaultsRepository.alertCAGESnoozedTime.setNil(key: "alertCAGESnoozedTime")
  1132. UserDefaultsRepository.alertCAGEIsSnoozed.value = false
  1133. alarms.reloadSnoozeTime(key: "alertCAGESnoozedTime", setNil: true)
  1134. alarms.reloadIsSnoozed(key: "alertCAGEIsSnoozed", value: false)
  1135. }
  1136. }
  1137. }