NightScout.swift 76 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735
  1. //
  2. // NightScout.swift
  3. // LoopFollow
  4. //
  5. // Created by Jon Fawcett on 6/16/20.
  6. // Copyright © 2020 Jon Fawcett. All rights reserved.
  7. //
  8. import Foundation
  9. import UIKit
  10. extension MainViewController {
  11. //NS Cage Struct
  12. struct cageData: Codable {
  13. var created_at: String
  14. }
  15. //NS Basal Profile Struct
  16. struct basalProfileStruct: Codable {
  17. var value: Double
  18. var time: String
  19. var timeAsSeconds: Double
  20. }
  21. //NS Basal Data Struct
  22. struct basalGraphStruct: Codable {
  23. var basalRate: Double
  24. var date: TimeInterval
  25. }
  26. //NS Bolus Data Struct
  27. struct bolusGraphStruct: Codable {
  28. var value: Double
  29. var date: TimeInterval
  30. var sgv: Int
  31. }
  32. //NS Bolus Data Struct
  33. struct carbGraphStruct: Codable {
  34. var value: Double
  35. var date: TimeInterval
  36. var sgv: Int
  37. var absorptionTime: Int
  38. }
  39. func isStaleData() -> Bool {
  40. if bgData.count > 0 {
  41. let now = dateTimeUtils.getNowTimeIntervalUTC()
  42. let lastReadingTime = bgData.last!.date
  43. let secondsAgo = now - lastReadingTime
  44. if secondsAgo >= 20*60 {
  45. return true
  46. } else {
  47. return false
  48. }
  49. } else {
  50. return false
  51. }
  52. }
  53. // Dex Share Web Call
  54. func webLoadDexShare(onlyPullLastRecord: Bool = false) {
  55. var count = 288
  56. if onlyPullLastRecord { count = 1 }
  57. dexShare?.fetchData(count) { (err, result) -> () in
  58. // TODO: add error checking
  59. if(err == nil) {
  60. var data = result!
  61. self.ProcessNSBGData(data: data, onlyPullLastRecord: onlyPullLastRecord)
  62. } else {
  63. // If we get an error, immediately try to pull NS BG Data
  64. self.webLoadNSBGData(onlyPullLastRecord: onlyPullLastRecord)
  65. if globalVariables.dexVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
  66. globalVariables.dexVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
  67. DispatchQueue.main.async {
  68. //self.sendNotification(title: "Dexcom Share Error", body: "Please double check user name and password, internet connection, and sharing status.")
  69. }
  70. }
  71. }
  72. }
  73. }
  74. // NS BG Data Web call
  75. func webLoadNSBGData(onlyPullLastRecord: Bool = false) {
  76. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: BG") }
  77. // Set the count= in the url either to pull 24 hours or only the last record
  78. var points = "1"
  79. if !onlyPullLastRecord {
  80. points = String(self.graphHours * 12 + 1)
  81. }
  82. // URL processor
  83. var urlBGDataPath: String = UserDefaultsRepository.url.value + "/api/v1/entries/sgv.json?"
  84. if token == "" {
  85. urlBGDataPath = urlBGDataPath + "count=" + points
  86. } else {
  87. urlBGDataPath = urlBGDataPath + "token=" + token + "&count=" + points
  88. }
  89. guard let urlBGData = URL(string: urlBGDataPath) else {
  90. if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
  91. globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
  92. //self.sendNotification(title: "Nightscout Error", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
  93. }
  94. DispatchQueue.main.async {
  95. if self.bgTimer.isValid {
  96. self.bgTimer.invalidate()
  97. }
  98. self.startBGTimer(time: 10)
  99. }
  100. return
  101. }
  102. var request = URLRequest(url: urlBGData)
  103. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  104. // Downloader
  105. let getBGTask = URLSession.shared.dataTask(with: request) { data, response, error in
  106. guard error == nil else {
  107. if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
  108. globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
  109. //self.sendNotification(title: "Nightscout Error", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
  110. }
  111. DispatchQueue.main.async {
  112. if self.bgTimer.isValid {
  113. self.bgTimer.invalidate()
  114. }
  115. self.startBGTimer(time: 10)
  116. }
  117. return
  118. }
  119. guard let data = data else {
  120. if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
  121. globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
  122. //self.sendNotification(title: "Nightscout Error", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
  123. }
  124. DispatchQueue.main.async {
  125. if self.bgTimer.isValid {
  126. self.bgTimer.invalidate()
  127. }
  128. self.startBGTimer(time: 10)
  129. }
  130. return
  131. }
  132. let decoder = JSONDecoder()
  133. let entriesResponse = try? decoder.decode([ShareGlucoseData].self, from: data)
  134. if let entriesResponse = entriesResponse {
  135. DispatchQueue.main.async {
  136. // trigger the processor for the data after downloading.
  137. self.ProcessNSBGData(data: entriesResponse, onlyPullLastRecord: onlyPullLastRecord, isNS: true)
  138. }
  139. } else {
  140. if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
  141. globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
  142. //self.sendNotification(title: "Nightscout Failure", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
  143. }
  144. DispatchQueue.main.async {
  145. if self.bgTimer.isValid {
  146. self.bgTimer.invalidate()
  147. }
  148. self.startBGTimer(time: 10)
  149. }
  150. return
  151. }
  152. }
  153. getBGTask.resume()
  154. }
  155. // NS BG Data Response processor
  156. func ProcessNSBGData(data: [ShareGlucoseData], onlyPullLastRecord: Bool, isNS: Bool = false){
  157. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: BG") }
  158. var pullDate = data[data.count - 1].date
  159. if isNS {
  160. pullDate = data[data.count - 1].date / 1000
  161. pullDate.round(FloatingPointRoundingRule.toNearestOrEven)
  162. }
  163. var latestDate = data[0].date
  164. if isNS {
  165. latestDate = data[0].date / 1000
  166. latestDate.round(FloatingPointRoundingRule.toNearestOrEven)
  167. }
  168. let now = dateTimeUtils.getNowTimeIntervalUTC()
  169. if !isNS && (latestDate + 330) < now {
  170. webLoadNSBGData(onlyPullLastRecord: onlyPullLastRecord)
  171. print("dex didn't load, triggered NS attempt")
  172. return
  173. }
  174. // Start the BG timer based on the reading
  175. let secondsAgo = now - latestDate
  176. DispatchQueue.main.async {
  177. // if reading is overdue over: 20:00, re-attempt every 5 minutes
  178. if secondsAgo >= (20 * 60) {
  179. self.startBGTimer(time: (5 * 60))
  180. print("##### started 5 minute bg timer")
  181. // if the reading is overdue: 10:00-19:59, re-attempt every minute
  182. } else if secondsAgo >= (10 * 60) {
  183. self.startBGTimer(time: 60)
  184. print("##### started 1 minute bg timer")
  185. // if the reading is overdue: 7:00-9:59, re-attempt every 30 seconds
  186. } else if secondsAgo >= (7 * 60) {
  187. self.startBGTimer(time: 30)
  188. print("##### started 30 second bg timer")
  189. // if the reading is overdue: 5:00-6:59 re-attempt every 10 seconds
  190. } else if secondsAgo >= (5 * 60) {
  191. self.startBGTimer(time: 10)
  192. print("##### started 10 second bg timer")
  193. // We have a current reading. Set timer to 5:10 from last reading
  194. } else {
  195. self.startBGTimer(time: 300 - secondsAgo + Double(UserDefaultsRepository.bgUpdateDelay.value))
  196. let timerVal = 310 - secondsAgo
  197. print("##### started 5:10 bg timer: \(timerVal)")
  198. }
  199. }
  200. // 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
  201. if !onlyPullLastRecord {
  202. bgData.removeAll()
  203. } else if bgData[bgData.count - 1].date != pullDate {
  204. bgData.removeFirst()
  205. if data.count > 0 && UserDefaultsRepository.speakBG.value {
  206. speakBG(sgv: data[data.count - 1].sgv)
  207. }
  208. } else {
  209. if data.count > 0 {
  210. self.updateBadge(val: data[data.count - 1].sgv)
  211. }
  212. return
  213. }
  214. // 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.
  215. for i in 0..<data.count{
  216. var dateString = data[data.count - 1 - i].date
  217. if isNS {
  218. dateString = data[data.count - 1 - i].date / 1000
  219. dateString.round(FloatingPointRoundingRule.toNearestOrEven)
  220. }
  221. if dateString >= dateTimeUtils.getTimeInterval24HoursAgo() {
  222. let reading = ShareGlucoseData(sgv: data[data.count - 1 - i].sgv, date: dateString, direction: data[data.count - 1 - i].direction)
  223. bgData.append(reading)
  224. }
  225. }
  226. viewUpdateNSBG(isNS: isNS)
  227. }
  228. // NS BG Data Front end updater
  229. func viewUpdateNSBG (isNS: Bool) {
  230. DispatchQueue.main.async {
  231. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Display: BG") }
  232. guard let snoozer = self.tabBarController!.viewControllers?[2] as? SnoozeViewController else { return }
  233. let entries = self.bgData
  234. if entries.count > 0 {
  235. let latestEntryi = entries.count - 1
  236. let latestBG = entries[latestEntryi].sgv
  237. let priorBG = entries[latestEntryi - 1].sgv
  238. let deltaBG = latestBG - priorBG as Int
  239. let lastBGTime = entries[latestEntryi].date
  240. let deltaTime = (TimeInterval(Date().timeIntervalSince1970)-lastBGTime) / 60
  241. var userUnit = " mg/dL"
  242. if self.mmol {
  243. userUnit = " mmol/L"
  244. }
  245. // TODO: remove testing feature to color code arrow based on NS vs Dex
  246. if isNS {
  247. self.serverText.text = "Nightscout"
  248. } else {
  249. self.serverText.text = "Dexcom"
  250. }
  251. self.BGText.text = bgUnits.toDisplayUnits(String(latestBG))
  252. snoozer.BGLabel.text = bgUnits.toDisplayUnits(String(latestBG))
  253. self.setBGTextColor()
  254. if let directionBG = entries[latestEntryi].direction {
  255. self.DirectionText.text = self.bgDirectionGraphic(directionBG)
  256. snoozer.DirectionLabel.text = self.bgDirectionGraphic(directionBG)
  257. self.latestDirectionString = self.bgDirectionGraphic(directionBG)
  258. }
  259. else
  260. {
  261. self.DirectionText.text = ""
  262. snoozer.DirectionLabel.text = ""
  263. self.latestDirectionString = ""
  264. }
  265. if deltaBG < 0 {
  266. self.DeltaText.text = bgUnits.toDisplayUnits(String(deltaBG))
  267. snoozer.DeltaLabel.text = bgUnits.toDisplayUnits(String(deltaBG))
  268. self.latestDeltaString = String(deltaBG)
  269. }
  270. else
  271. {
  272. self.DeltaText.text = "+" + bgUnits.toDisplayUnits(String(deltaBG))
  273. snoozer.DeltaLabel.text = "+" + bgUnits.toDisplayUnits(String(deltaBG))
  274. self.latestDeltaString = "+" + String(deltaBG)
  275. }
  276. self.updateBadge(val: latestBG)
  277. }
  278. else
  279. {
  280. return
  281. }
  282. self.updateBGGraph()
  283. self.updateStats()
  284. }
  285. }
  286. // NS Device Status Web Call
  287. func webLoadNSDeviceStatus() {
  288. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: device status") }
  289. let urlUser = UserDefaultsRepository.url.value
  290. // NS Api is not working to find by greater than date
  291. var urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?count=288"
  292. if token != "" {
  293. urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?count=288&token=" + token
  294. }
  295. let escapedAddress = urlStringDeviceStatus.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
  296. guard let urlDeviceStatus = URL(string: escapedAddress!) else {
  297. if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
  298. globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
  299. //self.sendNotification(title: "Nightscout Failure", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
  300. }
  301. DispatchQueue.main.async {
  302. if self.deviceStatusTimer.isValid {
  303. self.deviceStatusTimer.invalidate()
  304. }
  305. self.startDeviceStatusTimer(time: 10)
  306. }
  307. return
  308. }
  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. guard error == nil else {
  313. if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
  314. globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
  315. //self.sendNotification(title: "Nightscout Error", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
  316. }
  317. DispatchQueue.main.async {
  318. if self.deviceStatusTimer.isValid {
  319. self.deviceStatusTimer.invalidate()
  320. }
  321. self.startDeviceStatusTimer(time: 10)
  322. }
  323. return
  324. }
  325. guard let data = data else {
  326. if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
  327. globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
  328. //self.sendNotification(title: "Nightscout Error", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
  329. }
  330. DispatchQueue.main.async {
  331. if self.deviceStatusTimer.isValid {
  332. self.deviceStatusTimer.invalidate()
  333. }
  334. self.startDeviceStatusTimer(time: 10)
  335. }
  336. return
  337. }
  338. let json = try? (JSONSerialization.jsonObject(with: data) as? [[String:AnyObject]])
  339. if let json = json {
  340. DispatchQueue.main.async {
  341. self.updateDeviceStatusDisplay(jsonDeviceStatus: json)
  342. }
  343. } else {
  344. if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
  345. globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
  346. //self.sendNotification(title: "Nightscout Error", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
  347. }
  348. DispatchQueue.main.async {
  349. if self.deviceStatusTimer.isValid {
  350. self.deviceStatusTimer.invalidate()
  351. }
  352. self.startDeviceStatusTimer(time: 10)
  353. }
  354. return
  355. }
  356. }
  357. deviceStatusTask.resume()
  358. }
  359. // NS Device Status Response Processor
  360. func updateDeviceStatusDisplay(jsonDeviceStatus: [[String:AnyObject]]) {
  361. self.clearLastInfoData(index: 0)
  362. self.clearLastInfoData(index: 1)
  363. self.clearLastInfoData(index: 3)
  364. self.clearLastInfoData(index: 4)
  365. self.clearLastInfoData(index: 5)
  366. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: device status") }
  367. if jsonDeviceStatus.count == 0 {
  368. return
  369. }
  370. //Process the current data first
  371. let lastDeviceStatus = jsonDeviceStatus[0] as [String : AnyObject]?
  372. //pump and uploader
  373. let formatter = ISO8601DateFormatter()
  374. formatter.formatOptions = [.withFullDate,
  375. .withTime,
  376. .withDashSeparatorInDate,
  377. .withColonSeparatorInTime]
  378. if let lastPumpRecord = lastDeviceStatus?["pump"] as! [String : AnyObject]? {
  379. if let lastPumpTime = formatter.date(from: (lastPumpRecord["clock"] as! String))?.timeIntervalSince1970 {
  380. if let reservoirData = lastPumpRecord["reservoir"] as? Double {
  381. latestPumpVolume = reservoirData
  382. tableData[5].value = String(format:"%.0f", reservoirData) + "U"
  383. } else {
  384. latestPumpVolume = 50.0
  385. tableData[5].value = "50+U"
  386. }
  387. if let uploader = lastDeviceStatus?["uploader"] as? [String:AnyObject] {
  388. let upbat = uploader["battery"] as! Double
  389. tableData[4].value = String(format:"%.0f", upbat) + "%"
  390. }
  391. }
  392. }
  393. // Loop
  394. if let lastLoopRecord = lastDeviceStatus?["loop"] as! [String : AnyObject]? {
  395. //print("Loop: \(lastLoopRecord)")
  396. if let lastLoopTime = formatter.date(from: (lastLoopRecord["timestamp"] as! String))?.timeIntervalSince1970 {
  397. UserDefaultsRepository.alertLastLoopTime.value = lastLoopTime
  398. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "lastLoopTime: " + String(lastLoopTime)) }
  399. if let failure = lastLoopRecord["failureReason"] {
  400. LoopStatusLabel.text = "X"
  401. latestLoopStatusString = "X"
  402. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Loop Failure: X") }
  403. } else {
  404. var wasEnacted = false
  405. if let enacted = lastLoopRecord["enacted"] as? [String:AnyObject] {
  406. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Loop: Was Enacted") }
  407. wasEnacted = true
  408. if let lastTempBasal = enacted["rate"] as? Double {
  409. }
  410. }
  411. if let iobdata = lastLoopRecord["iob"] as? [String:AnyObject] {
  412. tableData[0].value = String(format:"%.2f", (iobdata["iob"] as! Double))
  413. latestIOB = String(format:"%.2f", (iobdata["iob"] as! Double))
  414. }
  415. if let cobdata = lastLoopRecord["cob"] as? [String:AnyObject] {
  416. tableData[1].value = String(format:"%.0f", cobdata["cob"] as! Double)
  417. latestCOB = String(format:"%.0f", cobdata["cob"] as! Double)
  418. }
  419. if let predictdata = lastLoopRecord["predicted"] as? [String:AnyObject] {
  420. let prediction = predictdata["values"] as! [Int]
  421. PredictionLabel.text = bgUnits.toDisplayUnits(String(Int(prediction.last!)))
  422. PredictionLabel.textColor = UIColor.systemPurple
  423. if UserDefaultsRepository.downloadPrediction.value && latestLoopTime < lastLoopTime {
  424. predictionData.removeAll()
  425. var predictionTime = lastLoopTime
  426. let toLoad = Int(UserDefaultsRepository.predictionToLoad.value * 12)
  427. var i = 0
  428. while i <= toLoad {
  429. if i < prediction.count {
  430. let prediction = ShareGlucoseData(sgv: prediction[i], date: predictionTime, direction: "flat")
  431. predictionData.append(prediction)
  432. predictionTime += 300
  433. }
  434. i += 1
  435. }
  436. let predMin = prediction.min()
  437. let predMax = prediction.max()
  438. tableData[9].value = bgUnits.toDisplayUnits(String(predMin!)) + "/" + bgUnits.toDisplayUnits(String(predMax!))
  439. updatePredictionGraph()
  440. }
  441. }
  442. if let recBolus = lastLoopRecord["recommendedBolus"] as? Double {
  443. tableData[8].value = String(format:"%.2fU", recBolus)
  444. }
  445. if let loopStatus = lastLoopRecord["recommendedTempBasal"] as? [String:AnyObject] {
  446. if let tempBasalTime = formatter.date(from: (loopStatus["timestamp"] as! String))?.timeIntervalSince1970 {
  447. var lastBGTime = lastLoopTime
  448. if bgData.count > 0 {
  449. lastBGTime = bgData[bgData.count - 1].date
  450. }
  451. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "tempBasalTime: " + String(tempBasalTime)) }
  452. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "lastBGTime: " + String(lastBGTime)) }
  453. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "wasEnacted: " + String(wasEnacted)) }
  454. if tempBasalTime > lastBGTime && !wasEnacted {
  455. LoopStatusLabel.text = "⏀"
  456. latestLoopStatusString = "⏀"
  457. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Open Loop: recommended temp. temp time > bg time, was not enacted") }
  458. } else {
  459. LoopStatusLabel.text = "↻"
  460. latestLoopStatusString = "↻"
  461. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Looping: recommended temp, but temp time is < bg time and/or was enacted") }
  462. }
  463. }
  464. } else {
  465. LoopStatusLabel.text = "↻"
  466. latestLoopStatusString = "↻"
  467. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Looping: no recommended temp") }
  468. }
  469. }
  470. if ((TimeInterval(Date().timeIntervalSince1970) - lastLoopTime) / 60) > 15 {
  471. LoopStatusLabel.text = "⚠"
  472. latestLoopStatusString = "⚠"
  473. }
  474. latestLoopTime = lastLoopTime
  475. } // end lastLoopTime
  476. } // end lastLoop Record
  477. var oText = "" as String
  478. currentOverride = 1.0
  479. if let lastOverride = lastDeviceStatus?["override"] as! [String : AnyObject]? {
  480. if let lastOverrideTime = formatter.date(from: (lastOverride["timestamp"] as! String))?.timeIntervalSince1970 {
  481. }
  482. if lastOverride["active"] as! Bool {
  483. let lastCorrection = lastOverride["currentCorrectionRange"] as! [String: AnyObject]
  484. if let multiplier = lastOverride["multiplier"] as? Double {
  485. currentOverride = multiplier
  486. oText += String(format: "%.0f%%", (multiplier * 100))
  487. }
  488. else
  489. {
  490. oText += "100%"
  491. }
  492. oText += " ("
  493. let minValue = lastCorrection["minValue"] as! Double
  494. let maxValue = lastCorrection["maxValue"] as! Double
  495. oText += bgUnits.toDisplayUnits(String(minValue)) + "-" + bgUnits.toDisplayUnits(String(maxValue)) + ")"
  496. tableData[3].value = oText
  497. }
  498. }
  499. infoTable.reloadData()
  500. // Start the timer based on the timestamp
  501. let now = dateTimeUtils.getNowTimeIntervalUTC()
  502. let secondsAgo = now - latestLoopTime
  503. DispatchQueue.main.async {
  504. // if Loop is overdue over: 20:00, re-attempt every 5 minutes
  505. if secondsAgo >= (20 * 60) {
  506. self.startDeviceStatusTimer(time: (5 * 60))
  507. print("started 5 minute device status timer")
  508. // if the Loop is overdue: 10:00-19:59, re-attempt every minute
  509. } else if secondsAgo >= (10 * 60) {
  510. self.startDeviceStatusTimer(time: 60)
  511. print("started 1 minute device status timer")
  512. // if the Loop is overdue: 7:00-9:59, re-attempt every 30 seconds
  513. } else if secondsAgo >= (7 * 60) {
  514. self.startDeviceStatusTimer(time: 30)
  515. print("started 30 second device status timer")
  516. // if the Loop is overdue: 5:00-6:59 re-attempt every 10 seconds
  517. } else if secondsAgo >= (5 * 60) {
  518. self.startDeviceStatusTimer(time: 10)
  519. print("started 10 second device status timer")
  520. // We have a current Loop. Set timer to 5:10 from last reading
  521. } else {
  522. self.startDeviceStatusTimer(time: 310 - secondsAgo)
  523. let timerVal = 310 - secondsAgo
  524. print("started 5:10 device status timer: \(timerVal)")
  525. }
  526. }
  527. }
  528. // NS Cage Web Call
  529. func webLoadNSCage() {
  530. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: CAGE") }
  531. let urlUser = UserDefaultsRepository.url.value
  532. var urlString = urlUser + "/api/v1/treatments.json?find[eventType]=Site%20Change&count=1"
  533. if token != "" {
  534. urlString = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType]=Site%20Change&count=1"
  535. }
  536. guard let urlData = URL(string: urlString) else {
  537. return
  538. }
  539. var request = URLRequest(url: urlData)
  540. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  541. let task = URLSession.shared.dataTask(with: request) { data, response, error in
  542. guard error == nil else {
  543. return
  544. }
  545. guard let data = data else {
  546. return
  547. }
  548. let decoder = JSONDecoder()
  549. let entriesResponse = try? decoder.decode([cageData].self, from: data)
  550. if let entriesResponse = entriesResponse {
  551. DispatchQueue.main.async {
  552. self.updateCage(data: entriesResponse)
  553. }
  554. } else {
  555. return
  556. }
  557. }
  558. task.resume()
  559. }
  560. // NS Cage Response Processor
  561. func updateCage(data: [cageData]) {
  562. self.clearLastInfoData(index: 7)
  563. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: CAGE") }
  564. if data.count == 0 {
  565. return
  566. }
  567. let lastCageString = data[0].created_at
  568. let formatter = ISO8601DateFormatter()
  569. formatter.formatOptions = [.withFullDate,
  570. .withTime,
  571. .withDashSeparatorInDate,
  572. .withColonSeparatorInTime]
  573. UserDefaultsRepository.alertCageInsertTime.value = formatter.date(from: (lastCageString))?.timeIntervalSince1970 as! TimeInterval
  574. if let cageTime = formatter.date(from: (lastCageString))?.timeIntervalSince1970 {
  575. let now = dateTimeUtils.getNowTimeIntervalUTC()
  576. let secondsAgo = now - cageTime
  577. //let days = 24 * 60 * 60
  578. let formatter = DateComponentsFormatter()
  579. formatter.unitsStyle = .positional // Use the appropriate positioning for the current locale
  580. formatter.allowedUnits = [ .day, .hour ] // Units to display in the formatted string
  581. formatter.zeroFormattingBehavior = [ .pad ] // Pad with zeroes where appropriate for the locale
  582. let formattedDuration = formatter.string(from: secondsAgo)
  583. tableData[7].value = formattedDuration ?? ""
  584. }
  585. infoTable.reloadData()
  586. }
  587. // NS Sage Web Call
  588. func webLoadNSSage() {
  589. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: SAGE") }
  590. let lastDateString = dateTimeUtils.nowMinus10DaysTimeInterval()
  591. let urlUser = UserDefaultsRepository.url.value
  592. var urlString = urlUser + "/api/v1/treatments.json?find[eventType]=Sensor%20Start&find[created_at][$gte]=" + lastDateString + "&count=1"
  593. if token != "" {
  594. urlString = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType]=Sensor%20Start&find[created_at][$gte]=" + lastDateString + "&count=1"
  595. }
  596. guard let urlData = URL(string: urlString) else {
  597. return
  598. }
  599. var request = URLRequest(url: urlData)
  600. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  601. let task = URLSession.shared.dataTask(with: request) { data, response, error in
  602. guard error == nil else {
  603. return
  604. }
  605. guard let data = data else {
  606. return
  607. }
  608. let decoder = JSONDecoder()
  609. let entriesResponse = try? decoder.decode([cageData].self, from: data)
  610. if let entriesResponse = entriesResponse {
  611. DispatchQueue.main.async {
  612. self.updateSage(data: entriesResponse)
  613. }
  614. } else {
  615. return
  616. }
  617. }
  618. task.resume()
  619. }
  620. // NS Sage Response Processor
  621. func updateSage(data: [cageData]) {
  622. self.clearLastInfoData(index: 6)
  623. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process/Display: SAGE") }
  624. if data.count == 0 {
  625. return
  626. }
  627. var lastSageString = data[0].created_at
  628. let formatter = ISO8601DateFormatter()
  629. formatter.formatOptions = [.withFullDate,
  630. .withTime,
  631. .withDashSeparatorInDate,
  632. .withColonSeparatorInTime]
  633. UserDefaultsRepository.alertSageInsertTime.value = formatter.date(from: (lastSageString))?.timeIntervalSince1970 as! TimeInterval
  634. if let sageTime = formatter.date(from: (lastSageString as! String))?.timeIntervalSince1970 {
  635. let now = dateTimeUtils.getNowTimeIntervalUTC()
  636. let secondsAgo = now - sageTime
  637. let days = 24 * 60 * 60
  638. let formatter = DateComponentsFormatter()
  639. formatter.unitsStyle = .positional // Use the appropriate positioning for the current locale
  640. formatter.allowedUnits = [ .day, .hour] // Units to display in the formatted string
  641. formatter.zeroFormattingBehavior = [ .pad ] // Pad with zeroes where appropriate for the locale
  642. let formattedDuration = formatter.string(from: secondsAgo)
  643. tableData[6].value = formattedDuration ?? ""
  644. }
  645. infoTable.reloadData()
  646. }
  647. // NS Profile Web Call
  648. func webLoadNSProfile() {
  649. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: profile") }
  650. let urlUser = UserDefaultsRepository.url.value
  651. var urlString = urlUser + "/api/v1/profile/current.json"
  652. if token != "" {
  653. urlString = urlUser + "/api/v1/profile/current.json?token=" + token
  654. }
  655. let escapedAddress = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
  656. guard let url = URL(string: escapedAddress!) else {
  657. return
  658. }
  659. var request = URLRequest(url: url)
  660. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  661. let task = URLSession.shared.dataTask(with: request) { data, response, error in
  662. guard error == nil else {
  663. return
  664. }
  665. guard let data = data else {
  666. return
  667. }
  668. let json = try? JSONSerialization.jsonObject(with: data) as! Dictionary<String, Any>
  669. if let json = json {
  670. DispatchQueue.main.async {
  671. self.updateProfile(jsonDeviceStatus: json)
  672. }
  673. } else {
  674. return
  675. }
  676. }
  677. task.resume()
  678. }
  679. // NS Profile Response Processor
  680. func updateProfile(jsonDeviceStatus: Dictionary<String, Any>) {
  681. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: profile") }
  682. if jsonDeviceStatus.count == 0 {
  683. return
  684. }
  685. if jsonDeviceStatus[keyPath: "message"] != nil { return }
  686. let basal = try jsonDeviceStatus[keyPath: "store.Default.basal"] as! NSArray
  687. basalProfile.removeAll()
  688. for i in 0..<basal.count {
  689. let dict = basal[i] as! Dictionary<String, Any>
  690. do {
  691. let thisValue = try dict[keyPath: "value"] as! Double
  692. let thisTime = dict[keyPath: "time"] as! String
  693. let thisTimeAsSeconds = dict[keyPath: "timeAsSeconds"] as! Double
  694. let entry = basalProfileStruct(value: thisValue, time: thisTime, timeAsSeconds: thisTimeAsSeconds)
  695. basalProfile.append(entry)
  696. } catch {
  697. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "ERROR: profile wrapped in quotes") }
  698. }
  699. }
  700. // Don't process the basal or draw the graph until after the BG has been fully processeed and drawn
  701. if firstGraphLoad { return }
  702. // Make temporary array with all values of yesterday and today
  703. let yesterdayStart = dateTimeUtils.getTimeIntervalMidnightYesterday()
  704. let todayStart = dateTimeUtils.getTimeIntervalMidnightToday()
  705. var basal2Day: [DataStructs.basal2DayProfile] = []
  706. // Run twice to add in order yesterday then today.
  707. for p in 0..<basalProfile.count {
  708. let start = yesterdayStart + basalProfile[p].timeAsSeconds
  709. var end = yesterdayStart
  710. // set the endings 1 second before the next one starts
  711. if p < basalProfile.count - 1 {
  712. end = yesterdayStart + basalProfile[p + 1].timeAsSeconds - 1
  713. } else {
  714. // set the end 1 second before midnight
  715. end = yesterdayStart + 86399
  716. }
  717. let entry = DataStructs.basal2DayProfile(basalRate: basalProfile[p].value, startDate: start, endDate: end)
  718. basal2Day.append(entry)
  719. }
  720. for p in 0..<basalProfile.count {
  721. let start = todayStart + basalProfile[p].timeAsSeconds
  722. var end = todayStart
  723. // set the endings 1 second before the next one starts
  724. if p < basalProfile.count - 1 {
  725. end = todayStart + basalProfile[p + 1].timeAsSeconds - 1
  726. } else {
  727. // set the end 1 second before midnight
  728. end = todayStart + 86399
  729. }
  730. let entry = DataStructs.basal2DayProfile(basalRate: basalProfile[p].value, startDate: start, endDate: end)
  731. basal2Day.append(entry)
  732. }
  733. var firstPass = true
  734. // Runs the scheduled basal to the end of the prediction line
  735. var predictionEndTime = dateTimeUtils.getNowTimeIntervalUTC() + (3600 * UserDefaultsRepository.predictionToLoad.value)
  736. basalScheduleData.removeAll()
  737. for i in 0..<basal2Day.count {
  738. let timeYesterday = dateTimeUtils.getTimeInterval24HoursAgo()
  739. // This processed everything after the first one.
  740. if firstPass == false
  741. && basal2Day[i].startDate <= predictionEndTime {
  742. let startDot = basalGraphStruct(basalRate: basal2Day[i].basalRate, date: basal2Day[i].startDate)
  743. basalScheduleData.append(startDot)
  744. var endDate = basal2Day[i].endDate
  745. // if it's the last one needed, set it to end at the prediction end time
  746. if endDate > predictionEndTime || i == basal2Day.count - 1 {
  747. endDate = Double(predictionEndTime)
  748. }
  749. let endDot = basalGraphStruct(basalRate: basal2Day[i].basalRate, date: endDate)
  750. basalScheduleData.append(endDot)
  751. }
  752. // we need to manually set the first one
  753. // Check that this is the first one and there are no existing entries
  754. if firstPass == true {
  755. // check that the timestamp is > the current entry and < the next entry
  756. if timeYesterday >= basal2Day[i].startDate && timeYesterday < basal2Day[i].endDate {
  757. // Set the start time to match the BG start
  758. let startDot = basalGraphStruct(basalRate: basal2Day[i].basalRate, date: Double(dateTimeUtils.getTimeInterval24HoursAgo() + (60 * 5)))
  759. basalScheduleData.append(startDot)
  760. // set the enddot where the next one will start
  761. var endDate = basal2Day[i].endDate
  762. let endDot = basalGraphStruct(basalRate: basal2Day[i].basalRate, date: endDate)
  763. basalScheduleData.append(endDot)
  764. firstPass = false
  765. }
  766. }
  767. }
  768. if UserDefaultsRepository.graphBasal.value {
  769. updateBasalScheduledGraph()
  770. }
  771. }
  772. // NS Treatments Web Call
  773. // Downloads Basal, Bolus, Carbs, BG Check, Notes, Overrides
  774. func WebLoadNSTreatments() {
  775. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: Treatments") }
  776. if !UserDefaultsRepository.downloadTreatments.value { return }
  777. let yesterdayString = dateTimeUtils.nowMinus24HoursTimeInterval()
  778. var urlString = UserDefaultsRepository.url.value + "/api/v1/treatments.json?find[created_at][$gte]=" + yesterdayString
  779. if token != "" {
  780. urlString = UserDefaultsRepository.url.value + "/api/v1/treatments.json?token=" + token + "&find[created_at][$gte]=" + yesterdayString
  781. }
  782. guard let urlData = URL(string: urlString) else {
  783. return
  784. }
  785. var request = URLRequest(url: urlData)
  786. request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
  787. let task = URLSession.shared.dataTask(with: request) { data, response, error in
  788. guard error == nil else {
  789. return
  790. }
  791. guard let data = data else {
  792. return
  793. }
  794. let json = try? (JSONSerialization.jsonObject(with: data) as? [[String:AnyObject]])
  795. if let json = json {
  796. DispatchQueue.main.async {
  797. self.updateTreatments(entries: json)
  798. }
  799. } else {
  800. return
  801. }
  802. }
  803. task.resume()
  804. }
  805. // Process and split out treatments to individual tasks
  806. func updateTreatments(entries: [[String:AnyObject]]) {
  807. var tempBasal: [[String:AnyObject]] = []
  808. var bolus: [[String:AnyObject]] = []
  809. var carbs: [[String:AnyObject]] = []
  810. var temporaryOverride: [[String:AnyObject]] = []
  811. var note: [[String:AnyObject]] = []
  812. var bgCheck: [[String:AnyObject]] = []
  813. var suspendPump: [[String:AnyObject]] = []
  814. var resumePump: [[String:AnyObject]] = []
  815. var pumpSiteChange: [[String:AnyObject]] = []
  816. var cgmSensorStart: [[String:AnyObject]] = []
  817. for i in 0..<entries.count {
  818. let entry = entries[i] as [String : AnyObject]?
  819. switch entry?["eventType"] as! String {
  820. case "Temp Basal":
  821. tempBasal.append(entry!)
  822. case "Correction Bolus":
  823. bolus.append(entry!)
  824. case "Meal Bolus":
  825. carbs.append(entry!)
  826. case "Temporary Override":
  827. temporaryOverride.append(entry!)
  828. case "Note":
  829. note.append(entry!)
  830. print("Note: \(String(describing: entry))")
  831. case "BG Check":
  832. bgCheck.append(entry!)
  833. case "Suspend Pump":
  834. suspendPump.append(entry!)
  835. case "Resume Pump":
  836. resumePump.append(entry!)
  837. case "Pump Site Change":
  838. pumpSiteChange.append(entry!)
  839. case "Sensor Start":
  840. cgmSensorStart.append(entry!)
  841. default:
  842. print("No Match: \(String(describing: entry))")
  843. }
  844. }
  845. // end of for loop
  846. if tempBasal.count > 0 {
  847. processNSBasals(entries: tempBasal)
  848. } else {
  849. if basalData.count < 0 {
  850. clearOldTempBasal()
  851. }
  852. }
  853. if bolus.count > 0 {
  854. processNSBolus(entries: bolus)
  855. } else {
  856. if bolusData.count > 0 {
  857. clearOldBolus()
  858. }
  859. }
  860. if carbs.count > 0 {
  861. processNSCarbs(entries: carbs)
  862. } else {
  863. if carbData.count > 0 {
  864. clearOldCarb()
  865. }
  866. }
  867. if bgCheck.count > 0 {
  868. processNSBGCheck(entries: bgCheck)
  869. } else {
  870. if bgCheckData.count > 0 {
  871. clearOldBGCheck()
  872. }
  873. }
  874. if temporaryOverride.count > 0 {
  875. processNSOverrides(entries: temporaryOverride)
  876. } else {
  877. if overrideGraphData.count > 0 {
  878. clearOldOverride()
  879. }
  880. }
  881. if suspendPump.count > 0 {
  882. processSuspendPump(entries: suspendPump)
  883. } else {
  884. if suspendGraphData.count > 0 {
  885. clearOldSuspend()
  886. }
  887. }
  888. if resumePump.count > 0 {
  889. processResumePump(entries: resumePump)
  890. } else {
  891. if resumeGraphData.count > 0 {
  892. clearOldResume()
  893. }
  894. }
  895. if cgmSensorStart.count > 0 {
  896. processSensorStart(entries: cgmSensorStart)
  897. } else {
  898. if sensorStartGraphData.count > 0 {
  899. clearOldSensor()
  900. }
  901. }
  902. if note.count > 0 {
  903. processNotes(entries: note)
  904. } else {
  905. if noteGraphData.count > 0 {
  906. clearOldNotes()
  907. }
  908. }
  909. }
  910. func clearOldTempBasal()
  911. {
  912. for i in 0..<basalData.count {
  913. if basalData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() {
  914. basalData.remove(at: i)
  915. }
  916. }
  917. updateBasalGraph()
  918. }
  919. func clearOldBolus()
  920. {
  921. for i in 0..<bolusData.count {
  922. if bolusData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() {
  923. bolusData.remove(at: i)
  924. }
  925. }
  926. updateBolusGraph()
  927. }
  928. func clearOldCarb()
  929. {
  930. for i in 0..<carbData.count {
  931. if carbData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() {
  932. carbData.remove(at: i)
  933. }
  934. }
  935. updateCarbGraph()
  936. }
  937. func clearOldBGCheck()
  938. {
  939. for i in 0..<bgCheckData.count {
  940. if bgCheckData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() {
  941. bgCheckData.remove(at: i)
  942. }
  943. }
  944. updateBGCheckGraph()
  945. }
  946. func clearOldOverride()
  947. {
  948. for i in 0..<overrideGraphData.count {
  949. if overrideGraphData[i].endDate < dateTimeUtils.getTimeInterval24HoursAgo() {
  950. overrideGraphData.remove(at: i)
  951. }
  952. }
  953. updateOverrideGraph()
  954. }
  955. func clearOldSuspend()
  956. {
  957. for i in 0..<suspendGraphData.count {
  958. if suspendGraphData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() {
  959. suspendGraphData.remove(at: i)
  960. }
  961. }
  962. updateSuspendGraph()
  963. }
  964. func clearOldResume()
  965. {
  966. for i in 0..<resumeGraphData.count {
  967. if resumeGraphData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() {
  968. resumeGraphData.remove(at: i)
  969. }
  970. }
  971. updateResumeGraph()
  972. }
  973. func clearOldSensor()
  974. {
  975. for i in 0..<sensorStartGraphData.count {
  976. if sensorStartGraphData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() {
  977. sensorStartGraphData.remove(at: i)
  978. }
  979. }
  980. updateSensorStart()
  981. }
  982. func clearOldNotes()
  983. {
  984. for i in 0..<noteGraphData.count {
  985. if noteGraphData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() {
  986. noteGraphData.remove(at: i)
  987. }
  988. }
  989. updateNotes()
  990. }
  991. // NS Temp Basal Response Processor
  992. func processNSBasals(entries: [[String:AnyObject]]) {
  993. self.clearLastInfoData(index: 2)
  994. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Basal") }
  995. // due to temp basal durations, we're going to destroy the array and load everything each cycle for the time being.
  996. basalData.removeAll()
  997. var lastEndDot = 0.0
  998. var tempArray = entries
  999. tempArray.reverse()
  1000. for i in 0..<tempArray.count {
  1001. let currentEntry = tempArray[i] as [String : AnyObject]?
  1002. var basalDate: String
  1003. if currentEntry?["timestamp"] != nil {
  1004. basalDate = currentEntry?["timestamp"] as! String
  1005. } else if currentEntry?["created_at"] != nil {
  1006. basalDate = currentEntry?["created_at"] as! String
  1007. } else {
  1008. return
  1009. }
  1010. var strippedZone = String(basalDate.dropLast())
  1011. strippedZone = strippedZone.replacingOccurrences(of: "\\.\\d+", with: "", options: .regularExpression)
  1012. let dateFormatter = DateFormatter()
  1013. dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  1014. dateFormatter.locale = Locale(identifier: "en_US")
  1015. dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  1016. let dateString = dateFormatter.date(from: strippedZone)
  1017. let dateTimeStamp = dateString!.timeIntervalSince1970
  1018. guard let basalRate = currentEntry?["absolute"] as? Double else {
  1019. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "ERROR: Null Basal entry")}
  1020. continue
  1021. }
  1022. let midnightTime = dateTimeUtils.getTimeIntervalMidnightToday()
  1023. // Setting end dots
  1024. var duration = 0.0
  1025. do {
  1026. duration = try currentEntry?["duration"] as! Double
  1027. } catch {
  1028. print("No Duration Found")
  1029. }
  1030. // This adds scheduled basal wherever there is a break between temps. can't check the prior ending on the first item. it is 24 hours old, so it isn't important for display anyway
  1031. if i > 0 {
  1032. let priorEntry = tempArray[i - 1] as [String : AnyObject]?
  1033. var priorBasalDate: String
  1034. if priorEntry?["timestamp"] != nil {
  1035. priorBasalDate = priorEntry?["timestamp"] as! String
  1036. } else if currentEntry?["created_at"] != nil {
  1037. priorBasalDate = priorEntry?["created_at"] as! String
  1038. } else {
  1039. continue
  1040. }
  1041. var priorStrippedZone = String(priorBasalDate.dropLast())
  1042. priorStrippedZone = priorStrippedZone.replacingOccurrences(of: "\\.\\d+", with: "", options: .regularExpression)
  1043. let priorDateFormatter = DateFormatter()
  1044. priorDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  1045. priorDateFormatter.locale = Locale(identifier: "en_US")
  1046. priorDateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  1047. let priorDateString = dateFormatter.date(from: priorStrippedZone)
  1048. let priorDateTimeStamp = priorDateString!.timeIntervalSince1970
  1049. let priorDuration = priorEntry?["duration"] as! Double
  1050. // if difference between time stamps is greater than the duration of the last entry, there is a gap. Give a 15 second leeway on the timestamp
  1051. if Double( dateTimeStamp - priorDateTimeStamp ) > Double( (priorDuration * 60) + 15 ) {
  1052. var scheduled = 0.0
  1053. var midGap = false
  1054. var midGapTime: TimeInterval = 0
  1055. var midGapValue: Double = 0
  1056. // cycle through basal profiles.
  1057. // TODO figure out how to deal with profile changes that happen mid-gap
  1058. for b in 0..<self.basalScheduleData.count {
  1059. if (priorDateTimeStamp + (priorDuration * 60)) >= basalScheduleData[b].date {
  1060. scheduled = basalScheduleData[b].basalRate
  1061. // deal with mid-gap scheduled basal change
  1062. // don't do it on the last scheudled basal entry
  1063. if b < self.basalScheduleData.count - 1 {
  1064. if dateTimeStamp > self.basalScheduleData[b + 1].date {
  1065. // midGap = true
  1066. // TODO: finish this to handle mid-gap items without crashing from overlapping entries
  1067. midGapTime = self.basalScheduleData[b + 1].date
  1068. midGapValue = self.basalScheduleData[b + 1].basalRate
  1069. }
  1070. }
  1071. }
  1072. }
  1073. // Make the starting dot at the last ending dot
  1074. let startDot = basalGraphStruct(basalRate: scheduled, date: Double(priorDateTimeStamp + (priorDuration * 60)))
  1075. basalData.append(startDot)
  1076. if midGap {
  1077. // Make the ending dot at the new scheduled basal
  1078. let endDot1 = basalGraphStruct(basalRate: scheduled, date: Double(midGapTime))
  1079. basalData.append(endDot1)
  1080. // Make the starting dot at the scheduled Time
  1081. let startDot2 = basalGraphStruct(basalRate: midGapValue, date: Double(midGapTime))
  1082. basalData.append(startDot2)
  1083. // Make the ending dot at the new basal value
  1084. let endDot2 = basalGraphStruct(basalRate: midGapValue, date: Double(dateTimeStamp))
  1085. basalData.append(endDot2)
  1086. } else {
  1087. // Make the ending dot at the new starting dot
  1088. let endDot = basalGraphStruct(basalRate: scheduled, date: Double(dateTimeStamp))
  1089. basalData.append(endDot)
  1090. }
  1091. }
  1092. }
  1093. // Make the starting dot
  1094. let startDot = basalGraphStruct(basalRate: basalRate, date: Double(dateTimeStamp))
  1095. basalData.append(startDot)
  1096. // Make the ending dot
  1097. // If it's the last one and has no duration, extend it for 30 minutes past the start. Otherwise set ending at duration
  1098. // duration is already set to 0 if there is no duration set on it.
  1099. //if i == tempArray.count - 1 && dateTimeStamp + duration <= dateTimeUtils.getNowTimeIntervalUTC() {
  1100. if i == tempArray.count - 1 && duration == 0.0 {
  1101. lastEndDot = dateTimeStamp + (30 * 60)
  1102. latestBasal = String(format:"%.2f", basalRate)
  1103. } else {
  1104. lastEndDot = dateTimeStamp + (duration * 60)
  1105. latestBasal = String(format:"%.2f", basalRate)
  1106. }
  1107. // Double check for overlaps of incorrectly ended TBRs and sent it to end when the next one starts if it finds a discrepancy
  1108. if i < tempArray.count - 1 {
  1109. let nextEntry = tempArray[i + 1] as [String : AnyObject]?
  1110. var nextBasalDate: String
  1111. if nextEntry?["timestamp"] != nil {
  1112. nextBasalDate = nextEntry?["timestamp"] as! String
  1113. } else if currentEntry?["created_at"] != nil {
  1114. nextBasalDate = nextEntry?["created_at"] as! String
  1115. } else {
  1116. continue
  1117. }
  1118. var nextStrippedZone = String(nextBasalDate.dropLast())
  1119. nextStrippedZone = nextStrippedZone.replacingOccurrences(of: "\\.\\d+", with: "", options: .regularExpression)
  1120. let nextDateFormatter = DateFormatter()
  1121. nextDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  1122. nextDateFormatter.locale = Locale(identifier: "en_US")
  1123. nextDateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  1124. let nextDateString = dateFormatter.date(from: nextStrippedZone)
  1125. let nextDateTimeStamp = nextDateString!.timeIntervalSince1970
  1126. if nextDateTimeStamp < (dateTimeStamp + (duration * 60)) {
  1127. lastEndDot = nextDateTimeStamp
  1128. }
  1129. }
  1130. let endDot = basalGraphStruct(basalRate: basalRate, date: Double(lastEndDot))
  1131. basalData.append(endDot)
  1132. }
  1133. // If last basal was prior to right now, we need to create one last scheduled entry
  1134. if lastEndDot <= dateTimeUtils.getNowTimeIntervalUTC() {
  1135. var scheduled = 0.0
  1136. // cycle through basal profiles.
  1137. // TODO figure out how to deal with profile changes that happen mid-gap
  1138. for b in 0..<self.basalProfile.count {
  1139. let scheduleTimeYesterday = self.basalProfile[b].timeAsSeconds + dateTimeUtils.getTimeIntervalMidnightYesterday()
  1140. let scheduleTimeToday = self.basalProfile[b].timeAsSeconds + dateTimeUtils.getTimeIntervalMidnightToday()
  1141. // check the prior temp ending to the profile seconds from midnight
  1142. print("yesterday " + String(scheduleTimeYesterday))
  1143. print("today " + String(scheduleTimeToday))
  1144. if lastEndDot >= scheduleTimeToday {
  1145. scheduled = basalProfile[b].value
  1146. }
  1147. }
  1148. latestBasal = String(format:"%.2f", scheduled)
  1149. // Make the starting dot at the last ending dot
  1150. let startDot = basalGraphStruct(basalRate: scheduled, date: Double(lastEndDot))
  1151. basalData.append(startDot)
  1152. // Make the ending dot 10 minutes after now
  1153. let endDot = basalGraphStruct(basalRate: scheduled, date: Double(Date().timeIntervalSince1970 + (60 * 10)))
  1154. basalData.append(endDot)
  1155. }
  1156. tableData[2].value = latestBasal
  1157. infoTable.reloadData()
  1158. if UserDefaultsRepository.graphBasal.value {
  1159. updateBasalGraph()
  1160. }
  1161. infoTable.reloadData()
  1162. }
  1163. // NS Meal Bolus Response Processor
  1164. func processNSBolus(entries: [[String:AnyObject]]) {
  1165. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Bolus") }
  1166. // because it's a small array, we're going to destroy and reload every time.
  1167. bolusData.removeAll()
  1168. var lastFoundIndex = 0
  1169. for i in 0..<entries.count {
  1170. let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
  1171. var bolusDate: String
  1172. if currentEntry?["timestamp"] != nil {
  1173. bolusDate = currentEntry?["timestamp"] as! String
  1174. } else if currentEntry?["created_at"] != nil {
  1175. bolusDate = currentEntry?["created_at"] as! String
  1176. } else {
  1177. return
  1178. }
  1179. // fix to remove millisecond (after period in timestamp) for FreeAPS users
  1180. var strippedZone = String(bolusDate.dropLast())
  1181. strippedZone = strippedZone.components(separatedBy: ".")[0]
  1182. let dateFormatter = DateFormatter()
  1183. dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  1184. dateFormatter.locale = Locale(identifier: "en_US")
  1185. dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  1186. let dateString = dateFormatter.date(from: strippedZone)
  1187. let dateTimeStamp = dateString!.timeIntervalSince1970
  1188. guard let bolus = currentEntry?["insulin"] as? Double else { continue }
  1189. let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
  1190. lastFoundIndex = sgv.foundIndex
  1191. if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
  1192. // Make the dot
  1193. let dot = bolusGraphStruct(value: bolus, date: Double(dateTimeStamp), sgv: Int(sgv.sgv + 20))
  1194. bolusData.append(dot)
  1195. }
  1196. }
  1197. if UserDefaultsRepository.graphBolus.value {
  1198. updateBolusGraph()
  1199. }
  1200. }
  1201. // NS Carb Bolus Response Processor
  1202. func processNSCarbs(entries: [[String:AnyObject]]) {
  1203. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Carbs") }
  1204. // because it's a small array, we're going to destroy and reload every time.
  1205. carbData.removeAll()
  1206. var lastFoundIndex = 0
  1207. var lastFoundBolus = 0
  1208. for i in 0..<entries.count {
  1209. let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
  1210. var carbDate: String
  1211. if currentEntry?["timestamp"] != nil {
  1212. carbDate = currentEntry?["timestamp"] as! String
  1213. } else if currentEntry?["created_at"] != nil {
  1214. carbDate = currentEntry?["created_at"] as! String
  1215. } else {
  1216. return
  1217. }
  1218. let absorptionTime = currentEntry?["absorptionTime"] as? Int ?? 0
  1219. // Fix for FreeAPS milliseconds in timestamp
  1220. var strippedZone = String(carbDate.dropLast())
  1221. strippedZone = strippedZone.components(separatedBy: ".")[0]
  1222. let dateFormatter = DateFormatter()
  1223. dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  1224. dateFormatter.locale = Locale(identifier: "en_US")
  1225. dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  1226. let dateString = dateFormatter.date(from: strippedZone)
  1227. var dateTimeStamp = dateString!.timeIntervalSince1970
  1228. guard let carbs = currentEntry?["carbs"] as? Double else {
  1229. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "ERROR: Null Carb entry")}
  1230. break
  1231. }
  1232. let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
  1233. lastFoundIndex = sgv.foundIndex
  1234. var offset = -50
  1235. if sgv.sgv < Double(topBG - 100) {
  1236. let bolusTime = findNearestBolusbyTime(timeWithin: 300, needle: dateTimeStamp, haystack: bolusData, startingIndex: lastFoundBolus)
  1237. lastFoundBolus = bolusTime.foundIndex
  1238. if bolusTime.offset {
  1239. offset = 70
  1240. } else {
  1241. offset = 20
  1242. }
  1243. }
  1244. if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
  1245. // Make the dot
  1246. let dot = carbGraphStruct(value: Double(carbs), date: Double(dateTimeStamp), sgv: Int(sgv.sgv + Double(offset)), absorptionTime: absorptionTime)
  1247. carbData.append(dot)
  1248. }
  1249. }
  1250. if UserDefaultsRepository.graphCarbs.value {
  1251. updateCarbGraph()
  1252. }
  1253. }
  1254. // NS Suspend Pump Response Processor
  1255. func processSuspendPump(entries: [[String:AnyObject]]) {
  1256. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Suspend Pump") }
  1257. // because it's a small array, we're going to destroy and reload every time.
  1258. suspendGraphData.removeAll()
  1259. var lastFoundIndex = 0
  1260. for i in 0..<entries.count {
  1261. let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
  1262. var date: String
  1263. if currentEntry?["timestamp"] != nil {
  1264. date = currentEntry?["timestamp"] as! String
  1265. } else if currentEntry?["created_at"] != nil {
  1266. date = currentEntry?["created_at"] as! String
  1267. } else {
  1268. return
  1269. }
  1270. // Fix for FreeAPS milliseconds in timestamp
  1271. var strippedZone = String(date.dropLast())
  1272. strippedZone = strippedZone.components(separatedBy: ".")[0]
  1273. let dateFormatter = DateFormatter()
  1274. dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  1275. dateFormatter.locale = Locale(identifier: "en_US")
  1276. dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  1277. let dateString = dateFormatter.date(from: strippedZone)
  1278. let dateTimeStamp = dateString!.timeIntervalSince1970
  1279. let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
  1280. lastFoundIndex = sgv.foundIndex
  1281. if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
  1282. // Make the dot
  1283. let dot = DataStructs.timestampOnlyStruct(date: Double(dateTimeStamp), sgv: Int(sgv.sgv))
  1284. suspendGraphData.append(dot)
  1285. }
  1286. }
  1287. if UserDefaultsRepository.graphOtherTreatments.value {
  1288. updateSuspendGraph()
  1289. }
  1290. }
  1291. // NS Resume Pump Response Processor
  1292. func processResumePump(entries: [[String:AnyObject]]) {
  1293. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Resume Pump") }
  1294. // because it's a small array, we're going to destroy and reload every time.
  1295. resumeGraphData.removeAll()
  1296. var lastFoundIndex = 0
  1297. for i in 0..<entries.count {
  1298. let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
  1299. var date: String
  1300. if currentEntry?["timestamp"] != nil {
  1301. date = currentEntry?["timestamp"] as! String
  1302. } else if currentEntry?["created_at"] != nil {
  1303. date = currentEntry?["created_at"] as! String
  1304. } else {
  1305. return
  1306. }
  1307. // Fix for FreeAPS milliseconds in timestamp
  1308. var strippedZone = String(date.dropLast())
  1309. strippedZone = strippedZone.components(separatedBy: ".")[0]
  1310. let dateFormatter = DateFormatter()
  1311. dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  1312. dateFormatter.locale = Locale(identifier: "en_US")
  1313. dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  1314. let dateString = dateFormatter.date(from: strippedZone)
  1315. let dateTimeStamp = dateString!.timeIntervalSince1970
  1316. let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
  1317. lastFoundIndex = sgv.foundIndex
  1318. if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
  1319. // Make the dot
  1320. let dot = DataStructs.timestampOnlyStruct(date: Double(dateTimeStamp), sgv: Int(sgv.sgv))
  1321. resumeGraphData.append(dot)
  1322. }
  1323. }
  1324. if UserDefaultsRepository.graphOtherTreatments.value {
  1325. updateResumeGraph()
  1326. }
  1327. }
  1328. // NS Sensor Start Response Processor
  1329. func processSensorStart(entries: [[String:AnyObject]]) {
  1330. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Sensor Start") }
  1331. // because it's a small array, we're going to destroy and reload every time.
  1332. sensorStartGraphData.removeAll()
  1333. var lastFoundIndex = 0
  1334. for i in 0..<entries.count {
  1335. let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
  1336. var date: String
  1337. if currentEntry?["timestamp"] != nil {
  1338. date = currentEntry?["timestamp"] as! String
  1339. } else if currentEntry?["created_at"] != nil {
  1340. date = currentEntry?["created_at"] as! String
  1341. } else {
  1342. return
  1343. }
  1344. // Fix for FreeAPS milliseconds in timestamp
  1345. var strippedZone = String(date.dropLast())
  1346. strippedZone = strippedZone.components(separatedBy: ".")[0]
  1347. let dateFormatter = DateFormatter()
  1348. dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  1349. dateFormatter.locale = Locale(identifier: "en_US")
  1350. dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  1351. let dateString = dateFormatter.date(from: strippedZone)
  1352. let dateTimeStamp = dateString!.timeIntervalSince1970
  1353. let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
  1354. lastFoundIndex = sgv.foundIndex
  1355. if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
  1356. // Make the dot
  1357. let dot = DataStructs.timestampOnlyStruct(date: Double(dateTimeStamp), sgv: Int(sgv.sgv))
  1358. sensorStartGraphData.append(dot)
  1359. }
  1360. }
  1361. if UserDefaultsRepository.graphOtherTreatments.value {
  1362. updateSensorStart()
  1363. }
  1364. }
  1365. // NS Note Response Processor
  1366. func processNotes(entries: [[String:AnyObject]]) {
  1367. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Notes") }
  1368. // because it's a small array, we're going to destroy and reload every time.
  1369. noteGraphData.removeAll()
  1370. var lastFoundIndex = 0
  1371. for i in 0..<entries.count {
  1372. let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
  1373. var date: String
  1374. if currentEntry?["timestamp"] != nil {
  1375. date = currentEntry?["timestamp"] as! String
  1376. } else if currentEntry?["created_at"] != nil {
  1377. date = currentEntry?["created_at"] as! String
  1378. } else {
  1379. return
  1380. }
  1381. // Fix for FreeAPS milliseconds in timestamp
  1382. var strippedZone = String(date.dropLast())
  1383. strippedZone = strippedZone.components(separatedBy: ".")[0]
  1384. let dateFormatter = DateFormatter()
  1385. dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  1386. dateFormatter.locale = Locale(identifier: "en_US")
  1387. dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  1388. let dateString = dateFormatter.date(from: strippedZone)
  1389. let dateTimeStamp = dateString!.timeIntervalSince1970
  1390. let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
  1391. lastFoundIndex = sgv.foundIndex
  1392. guard let thisNote = currentEntry?["notes"] as? String else { continue }
  1393. if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
  1394. // Make the dot
  1395. let dot = DataStructs.noteStruct(date: Double(dateTimeStamp), sgv: Int(sgv.sgv), note: thisNote)
  1396. noteGraphData.append(dot)
  1397. }
  1398. }
  1399. if UserDefaultsRepository.graphOtherTreatments.value {
  1400. updateNotes()
  1401. }
  1402. }
  1403. // NS BG Check Response Processor
  1404. func processNSBGCheck(entries: [[String:AnyObject]]) {
  1405. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: BG Check") }
  1406. // because it's a small array, we're going to destroy and reload every time.
  1407. bgCheckData.removeAll()
  1408. for i in 0..<entries.count {
  1409. let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
  1410. var date: String
  1411. if currentEntry?["timestamp"] != nil {
  1412. date = currentEntry?["timestamp"] as! String
  1413. } else if currentEntry?["created_at"] != nil {
  1414. date = currentEntry?["created_at"] as! String
  1415. } else {
  1416. return
  1417. }
  1418. // Fix for FreeAPS milliseconds in timestamp
  1419. var strippedZone = String(date.dropLast())
  1420. strippedZone = strippedZone.components(separatedBy: ".")[0]
  1421. let dateFormatter = DateFormatter()
  1422. dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  1423. dateFormatter.locale = Locale(identifier: "en_US")
  1424. dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  1425. let dateString = dateFormatter.date(from: strippedZone)
  1426. let dateTimeStamp = dateString!.timeIntervalSince1970
  1427. guard let sgv = currentEntry?["glucose"] as? Int else {
  1428. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "ERROR: Non-Int Glucose entry")}
  1429. continue
  1430. }
  1431. if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
  1432. // Make the dot
  1433. //let dot = ShareGlucoseData(value: Double(carbs), date: Double(dateTimeStamp), sgv: Int(sgv.sgv))
  1434. let dot = ShareGlucoseData(sgv: sgv, date: Double(dateTimeStamp), direction: "")
  1435. bgCheckData.append(dot)
  1436. }
  1437. }
  1438. if UserDefaultsRepository.graphOtherTreatments.value {
  1439. updateBGCheckGraph()
  1440. }
  1441. }
  1442. // NS Override Response Processor
  1443. func processNSOverrides(entries: [[String:AnyObject]]) {
  1444. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Overrides") }
  1445. // because it's a small array, we're going to destroy and reload every time.
  1446. overrideGraphData.removeAll()
  1447. for i in 0..<entries.count {
  1448. let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
  1449. var date: String
  1450. if currentEntry?["timestamp"] != nil {
  1451. date = currentEntry?["timestamp"] as! String
  1452. } else if currentEntry?["created_at"] != nil {
  1453. date = currentEntry?["created_at"] as! String
  1454. } else {
  1455. return
  1456. }
  1457. // Fix for FreeAPS milliseconds in timestamp
  1458. var strippedZone = String(date.dropLast())
  1459. strippedZone = strippedZone.components(separatedBy: ".")[0]
  1460. let dateFormatter = DateFormatter()
  1461. dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
  1462. dateFormatter.locale = Locale(identifier: "en_US")
  1463. dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
  1464. let dateString = dateFormatter.date(from: strippedZone)
  1465. var dateTimeStamp = dateString!.timeIntervalSince1970
  1466. if dateTimeStamp < dateTimeUtils.getTimeInterval24HoursAgo() {
  1467. dateTimeStamp = dateTimeUtils.getTimeInterval24HoursAgo()
  1468. }
  1469. var multiplier: Double = 1.0
  1470. if currentEntry?["insulinNeedsScaleFactor"] != nil {
  1471. multiplier = currentEntry?["insulinNeedsScaleFactor"] as! Double
  1472. }
  1473. var duration: Double = 5.0
  1474. if let durationType = currentEntry?["durationType"] as? String {
  1475. duration = dateTimeUtils.getNowTimeIntervalUTC() - dateTimeStamp + (60 * 60)
  1476. } else {
  1477. duration = (currentEntry?["duration"] as? Double)!
  1478. duration = duration * 60
  1479. }
  1480. guard let enteredBy = currentEntry?["enteredBy"] as? String else { continue }
  1481. guard let reason = currentEntry?["reason"] as? String else { continue }
  1482. var range: [Int] = []
  1483. if let ranges = currentEntry?["correctionRange"] as? [Int] {
  1484. if ranges.count == 2 {
  1485. guard let low = ranges[0] as? Int else { continue }
  1486. guard let high = ranges[1] as? Int else { continue }
  1487. range.append(low)
  1488. range.append(high)
  1489. }
  1490. }
  1491. let endDate = dateTimeStamp + (duration)
  1492. let dot = DataStructs.overrideStruct(insulNeedsScaleFactor: multiplier, date: dateTimeStamp, endDate: endDate, duration: duration, correctionRange: range, enteredBy: enteredBy, reason: reason, sgv: -20)
  1493. overrideGraphData.append(dot)
  1494. }
  1495. if UserDefaultsRepository.graphOtherTreatments.value {
  1496. updateOverrideGraph()
  1497. }
  1498. }
  1499. }