| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855 |
- //
- // NightScout.swift
- // LoopFollow
- //
- // Created by Jon Fawcett on 6/16/20.
- // Copyright © 2020 Jon Fawcett. All rights reserved.
- //
- import Foundation
- import UIKit
- extension MainViewController {
- //NS Carbs Struct
- struct carbsData: Codable {
- var carbs: Double?
- }
- //NS Cage Struct
- struct cageData: Codable {
- var created_at: String
- }
-
- //NS Basal Profile Struct
- struct basalProfileStruct: Codable {
- var value: Double
- var time: String
- var timeAsSeconds: Double
- }
-
- struct NSProfile: Decodable {
- struct Store: Decodable {
- struct BasalEntry: Decodable {
- let value: Double
- let time: String
- let timeAsSeconds: Double
- }
-
- let basal: [BasalEntry]
- }
-
- let store: [String: Store]
- }
-
- //NS Basal Data Struct
- struct basalGraphStruct: Codable {
- var basalRate: Double
- var date: TimeInterval
- }
-
- //NS Bolus Data Struct
- struct bolusGraphStruct: Codable {
- var value: Double
- var date: TimeInterval
- var sgv: Int
- }
-
- //NS Bolus Data Struct
- struct carbGraphStruct: Codable {
- var value: Double
- var date: TimeInterval
- var sgv: Int
- var absorptionTime: Int
- }
-
- func isStaleData() -> Bool {
- if bgData.count > 0 {
- let now = dateTimeUtils.getNowTimeIntervalUTC()
- let lastReadingTime = bgData.last!.date
- let secondsAgo = now - lastReadingTime
- if secondsAgo >= 20*60 {
- return true
- } else {
- return false
- }
- } else {
- return false
- }
- }
-
- // Dex Share Web Call
- func webLoadDexShare() {
- // Dexcom Share only returns 24 hrs of data as of now
- // Requesting more just for consistency with NS
- let graphHours = 24 * UserDefaultsRepository.downloadDays.value
- var count = graphHours * 12
- dexShare?.fetchData(count) { (err, result) -> () in
-
- // TODO: add error checking
- if(err == nil) {
- var data = result!
-
- // If Dex data is old, load from NS instead
- let latestDate = data[0].date
- let now = dateTimeUtils.getNowTimeIntervalUTC()
- if (latestDate + 330) < now && UserDefaultsRepository.url.value != "" {
- self.webLoadNSBGData()
- print("dex didn't load, triggered NS attempt")
- return
- }
-
- // Dexcom only returns 24 hrs of data. If we need more, call NS.
- if graphHours > 24 && UserDefaultsRepository.url.value != "" {
- self.webLoadNSBGData(dexData: data)
- } else {
- self.ProcessDexBGData(data: data, sourceName: "Dexcom")
- }
- } else {
- // If we get an error, immediately try to pull NS BG Data
- if UserDefaultsRepository.url.value != "" {
- self.webLoadNSBGData()
- }
-
- if globalVariables.dexVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
- globalVariables.dexVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
- DispatchQueue.main.async {
- //self.sendNotification(title: "Dexcom Share Error", body: "Please double check user name and password, internet connection, and sharing status.")
- }
- }
- }
- }
- }
-
- // NS BG Data Web call
- func webLoadNSBGData(dexData: [ShareGlucoseData] = []) {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: BG") }
-
- // This kicks it out in the instance where dexcom fails but they aren't using NS &&
- if UserDefaultsRepository.url.value == "" {
- self.startBGTimer(time: 10)
- return
- }
-
- var parameters: [String: String] = [:]
- let utcISODateFormatter = ISO8601DateFormatter()
- let date = Calendar.current.date(byAdding: .day, value: -1 * UserDefaultsRepository.downloadDays.value, to: Date())!
- parameters["count"] = "1000"
- parameters["find[dateString][$gte]"] = utcISODateFormatter.string(from: date)
-
- NightscoutUtils.executeRequest(eventType: .sgv, parameters: parameters) { (result: Result<[ShareGlucoseData], Error>) in
- switch result {
- case .success(let entriesResponse):
- var nsData = entriesResponse
- DispatchQueue.main.async {
- // transform NS data to look like Dex data
- for i in 0..<nsData.count {
- // convert the NS timestamp to seconds instead of milliseconds
- nsData[i].date /= 1000
- nsData[i].date.round(FloatingPointRoundingRule.toNearestOrEven)
- }
- print(nsData.count)
-
- //Avoid duplicate entries messing up the graph, only use one reading per 5 minutes.
- let graphHours = 24 * UserDefaultsRepository.downloadDays.value
- let points = graphHours * 12 + 1
- var nsData2 = [ShareGlucoseData]()
- let timestamp = Date().timeIntervalSince1970
- for i in 0..<points {
- //Starting with "now" and then step 5 minutes back in time
- let target = timestamp - Double(i) * 60 * 5
- //Find the reading closest to the target, but not too far away
- let closest = nsData.filter{ abs($0.date - target) < 3 * 60 }.min { abs($0.date - target) < abs($1.date - target) }
- //If a reading is found, add it to the new array
- if let item = closest {
- nsData2.append(item)
- }
- }
- print(nsData2.count)
-
- // merge NS and Dex data if needed; use recent Dex data and older NS data
- var sourceName = "Nightscout"
- if !dexData.isEmpty {
- let oldestDexDate = dexData[dexData.count - 1].date
- var itemsToRemove = 0
- while itemsToRemove < nsData2.count && nsData2[itemsToRemove].date >= oldestDexDate {
- itemsToRemove += 1
- }
- nsData2.removeFirst(itemsToRemove)
- nsData2 = dexData + nsData2
- sourceName = "Dexcom"
- }
- // trigger the processor for the data after downloading.
- self.ProcessDexBGData(data: nsData2, sourceName: sourceName)
- }
- case .failure(let error):
- print("Failed to fetch data: \(error)")
- if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
- globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
- //self.sendNotification(title: "Nightscout Error", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
- }
- DispatchQueue.main.async {
- if self.bgTimer.isValid {
- self.bgTimer.invalidate()
- }
- self.startBGTimer(time: 10)
- }
- // if we have Dex data, use it
- if !dexData.isEmpty {
- self.ProcessDexBGData(data: dexData, sourceName: "Dexcom")
- }
- return
- }
- }
- }
-
- // Dexcom BG Data Response processor
- func ProcessDexBGData(data: [ShareGlucoseData], sourceName: String){
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: BG") }
-
- let graphHours = 24 * UserDefaultsRepository.downloadDays.value
-
- let pullDate = data[data.count - 1].date
- let latestDate = data[0].date
- let now = dateTimeUtils.getNowTimeIntervalUTC()
-
- // Start the BG timer based on the reading
- let secondsAgo = now - latestDate
-
- DispatchQueue.main.async {
- // if reading is overdue over: 20:00, re-attempt every 5 minutes
- if secondsAgo >= (20 * 60) {
- self.startBGTimer(time: (5 * 60))
- print("##### started 5 minute bg timer")
-
- // if the reading is overdue: 10:00-19:59, re-attempt every minute
- } else if secondsAgo >= (10 * 60) {
- self.startBGTimer(time: 60)
- print("##### started 1 minute bg timer")
-
- // if the reading is overdue: 7:00-9:59, re-attempt every 30 seconds
- } else if secondsAgo >= (7 * 60) {
- self.startBGTimer(time: 30)
- print("##### started 30 second bg timer")
-
- // if the reading is overdue: 5:00-6:59 re-attempt every 10 seconds
- } else if secondsAgo >= (5 * 60) {
- self.startBGTimer(time: 10)
- print("##### started 10 second bg timer")
-
- // We have a current reading. Set timer to 5:10 from last reading
- } else {
- self.startBGTimer(time: 300 - secondsAgo + Double(UserDefaultsRepository.bgUpdateDelay.value))
- let timerVal = 310 - secondsAgo
- print("##### started 5:10 bg timer: \(timerVal)")
- self.updateBadge(val: data[0].sgv)
- if UserDefaultsRepository.speakBG.value {
- self.speakBG(currentValue: data[0].sgv, previousValue: data[1].sgv)
- }
- }
- }
-
- bgData.removeAll()
-
- // loop through the data so we can reverse the order to oldest first for the graph
- for i in 0..<data.count {
- let dateString = data[data.count - 1 - i].date
- if dateString >= dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) {
- let sgvValue = data[data.count - 1 - i].sgv
-
- // Skip the current iteration if the sgv value is over 600
- // First time a user starts a G7, they get a value of 4000
- if sgvValue > 600 {
- continue
- }
-
- let reading = ShareGlucoseData(sgv: sgvValue, date: dateString, direction: data[data.count - 1 - i].direction)
- bgData.append(reading)
- }
- }
- viewUpdateNSBG(sourceName: sourceName)
- }
-
- // NS BG Data Front end updater
- func viewUpdateNSBG (sourceName: String) {
- DispatchQueue.main.async {
- if UserDefaultsRepository.debugLog.value {
- self.writeDebugLog(value: "Display: BG")
- self.writeDebugLog(value: "Num BG: " + self.bgData.count.description)
- }
- let entries = self.bgData
- if entries.count < 1 { return }
-
- self.updateBGGraph()
- self.updateStats()
-
- let latestEntryi = entries.count - 1
- let latestBG = entries[latestEntryi].sgv
- let priorBG = entries[latestEntryi - 1].sgv
- let deltaBG = latestBG - priorBG as Int
- let lastBGTime = entries[latestEntryi].date
-
- let deltaTime = (TimeInterval(Date().timeIntervalSince1970)-lastBGTime) / 60
- var userUnit = " mg/dL"
- if self.mmol {
- userUnit = " mmol/L"
- }
-
- self.serverText.text = sourceName
-
- var snoozerBG = ""
- var snoozerDirection = ""
- var snoozerDelta = ""
-
- self.BGText.text = bgUnits.toDisplayUnits(String(latestBG))
- snoozerBG = bgUnits.toDisplayUnits(String(latestBG))
- self.setBGTextColor()
-
- if let directionBG = entries[latestEntryi].direction {
- self.DirectionText.text = self.bgDirectionGraphic(directionBG)
- snoozerDirection = self.bgDirectionGraphic(directionBG)
- self.latestDirectionString = self.bgDirectionGraphic(directionBG)
- }
- else
- {
- self.DirectionText.text = ""
- snoozerDirection = ""
- self.latestDirectionString = ""
- }
-
- if deltaBG < 0 {
- self.DeltaText.text = bgUnits.toDisplayUnits(String(deltaBG))
- snoozerDelta = bgUnits.toDisplayUnits(String(deltaBG))
- self.latestDeltaString = String(deltaBG)
- }
- else
- {
- self.DeltaText.text = "+" + bgUnits.toDisplayUnits(String(deltaBG))
- snoozerDelta = "+" + bgUnits.toDisplayUnits(String(deltaBG))
- self.latestDeltaString = "+" + String(deltaBG)
- }
-
- // Snoozer Display
- guard let snoozer = self.tabBarController!.viewControllers?[2] as? SnoozeViewController else { return }
- snoozer.BGLabel.text = snoozerBG
- snoozer.DirectionLabel.text = snoozerDirection
- snoozer.DeltaLabel.text = snoozerDelta
-
- }
-
- }
-
- // NS Device Status Web Call
- func webLoadNSDeviceStatus() {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: device status") }
- let urlUser = UserDefaultsRepository.url.value
-
-
- // NS Api is not working to find by greater than date
- var urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?count=288"
- if token != "" {
- urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?count=288&token=" + token
- }
- let escapedAddress = urlStringDeviceStatus.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
- guard let urlDeviceStatus = URL(string: escapedAddress!) else {
- if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
- globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
- //self.sendNotification(title: "Nightscout Failure", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
- }
- DispatchQueue.main.async {
- if self.deviceStatusTimer.isValid {
- self.deviceStatusTimer.invalidate()
- }
- self.startDeviceStatusTimer(time: 10)
- }
-
- return
- }
-
-
- var requestDeviceStatus = URLRequest(url: urlDeviceStatus)
- requestDeviceStatus.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
-
-
- let deviceStatusTask = URLSession.shared.dataTask(with: requestDeviceStatus) { data, response, error in
-
- guard error == nil else {
- if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
- globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
- //self.sendNotification(title: "Nightscout Error", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
- }
- DispatchQueue.main.async {
- if self.deviceStatusTimer.isValid {
- self.deviceStatusTimer.invalidate()
- }
- self.startDeviceStatusTimer(time: 10)
- }
- return
- }
-
- guard let data = data else {
- if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
- globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
- //self.sendNotification(title: "Nightscout Error", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
- }
- DispatchQueue.main.async {
- if self.deviceStatusTimer.isValid {
- self.deviceStatusTimer.invalidate()
- }
- self.startDeviceStatusTimer(time: 10)
- }
- return
- }
-
-
- let json = try? (JSONSerialization.jsonObject(with: data) as? [[String:AnyObject]])
- if let json = json {
- DispatchQueue.main.async {
- self.updateDeviceStatusDisplay(jsonDeviceStatus: json)
- }
- } else {
- if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
- globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
- //self.sendNotification(title: "Nightscout Error", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
- }
- DispatchQueue.main.async {
- if self.deviceStatusTimer.isValid {
- self.deviceStatusTimer.invalidate()
- }
- self.startDeviceStatusTimer(time: 10)
- }
- return
- }
- }
- deviceStatusTask.resume()
-
- }
-
- // NS Device Status Response Processor
- func updateDeviceStatusDisplay(jsonDeviceStatus: [[String:AnyObject]]) {
- self.clearLastInfoData(index: 0)
- self.clearLastInfoData(index: 1)
- self.clearLastInfoData(index: 3)
- self.clearLastInfoData(index: 4)
- self.clearLastInfoData(index: 5)
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: device status") }
- if jsonDeviceStatus.count == 0 {
- return
- }
-
- //Process the current data first
- let lastDeviceStatus = jsonDeviceStatus[0] as [String : AnyObject]?
-
- //pump and uploader
- let formatter = ISO8601DateFormatter()
- formatter.formatOptions = [.withFullDate,
- .withTime,
- .withDashSeparatorInDate,
- .withColonSeparatorInTime]
- if let lastPumpRecord = lastDeviceStatus?["pump"] as! [String : AnyObject]? {
- if let lastPumpTime = formatter.date(from: (lastPumpRecord["clock"] as! String))?.timeIntervalSince1970 {
- if let reservoirData = lastPumpRecord["reservoir"] as? Double {
- latestPumpVolume = reservoirData
- tableData[5].value = String(format:"%.0f", reservoirData) + "U"
- } else {
- latestPumpVolume = 50.0
- tableData[5].value = "50+U"
- }
-
- if let uploader = lastDeviceStatus?["uploader"] as? [String:AnyObject] {
- let upbat = uploader["battery"] as! Double
- tableData[4].value = String(format:"%.0f", upbat) + "%"
- UserDefaultsRepository.deviceBatteryLevel.value = upbat
- }
- }
- }
-
- // Loop
- if let lastLoopRecord = lastDeviceStatus?["loop"] as! [String : AnyObject]? {
- //print("Loop: \(lastLoopRecord)")
- if let lastLoopTime = formatter.date(from: (lastLoopRecord["timestamp"] as! String))?.timeIntervalSince1970 {
- UserDefaultsRepository.alertLastLoopTime.value = lastLoopTime
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "lastLoopTime: " + String(lastLoopTime)) }
- if let failure = lastLoopRecord["failureReason"] {
- LoopStatusLabel.text = "X"
- latestLoopStatusString = "X"
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Loop Failure: X") }
- } else {
- var wasEnacted = false
- if let enacted = lastLoopRecord["enacted"] as? [String:AnyObject] {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Loop: Was Enacted") }
- wasEnacted = true
- if let lastTempBasal = enacted["rate"] as? Double {
- }
- }
- if let iobdata = lastLoopRecord["iob"] as? [String:AnyObject] {
- tableData[0].value = String(format:"%.2f", (iobdata["iob"] as! Double))
- latestIOB = String(format:"%.2f", (iobdata["iob"] as! Double))
- }
- if let cobdata = lastLoopRecord["cob"] as? [String:AnyObject] {
- tableData[1].value = String(format:"%.0f", cobdata["cob"] as! Double)
- latestCOB = String(format:"%.0f", cobdata["cob"] as! Double)
- }
- if let predictdata = lastLoopRecord["predicted"] as? [String:AnyObject] {
- let prediction = predictdata["values"] as! [Double]
- PredictionLabel.text = bgUnits.toDisplayUnits(String(Int(prediction.last!)))
- PredictionLabel.textColor = UIColor.systemPurple
- if UserDefaultsRepository.downloadPrediction.value && latestLoopTime < lastLoopTime {
- predictionData.removeAll()
- var predictionTime = lastLoopTime
- let toLoad = Int(UserDefaultsRepository.predictionToLoad.value * 12)
- var i = 0
- while i <= toLoad {
- if i < prediction.count {
- let sgvValue = Int(round(prediction[i]))
- // Skip values higher than 600
- if sgvValue <= 600 {
- let prediction = ShareGlucoseData(sgv: sgvValue, date: predictionTime, direction: "flat")
- predictionData.append(prediction)
- }
- predictionTime += 300
- }
- i += 1
- }
-
- let predMin = prediction.min()
- let predMax = prediction.max()
- tableData[9].value = bgUnits.toDisplayUnits(String(predMin!)) + "/" + bgUnits.toDisplayUnits(String(predMax!))
-
- updatePredictionGraph()
- }
- }
- if let recBolus = lastLoopRecord["recommendedBolus"] as? Double {
- tableData[8].value = String(format:"%.2fU", recBolus)
- }
- if let loopStatus = lastLoopRecord["recommendedTempBasal"] as? [String:AnyObject] {
- if let tempBasalTime = formatter.date(from: (loopStatus["timestamp"] as! String))?.timeIntervalSince1970 {
- var lastBGTime = lastLoopTime
- if bgData.count > 0 {
- lastBGTime = bgData[bgData.count - 1].date
- }
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "tempBasalTime: " + String(tempBasalTime)) }
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "lastBGTime: " + String(lastBGTime)) }
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "wasEnacted: " + String(wasEnacted)) }
- if tempBasalTime > lastBGTime && !wasEnacted {
- LoopStatusLabel.text = "⏀"
- latestLoopStatusString = "⏀"
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Open Loop: recommended temp. temp time > bg time, was not enacted") }
- } else {
- LoopStatusLabel.text = "↻"
- latestLoopStatusString = "↻"
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Looping: recommended temp, but temp time is < bg time and/or was enacted") }
- }
- }
- } else {
- LoopStatusLabel.text = "↻"
- latestLoopStatusString = "↻"
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Looping: no recommended temp") }
- }
-
- }
-
- if ((TimeInterval(Date().timeIntervalSince1970) - lastLoopTime) / 60) > 15 {
- LoopStatusLabel.text = "⚠"
- latestLoopStatusString = "⚠"
- }
- latestLoopTime = lastLoopTime
- } // end lastLoopTime
- } // end lastLoop Record
- if let lastLoopRecord = lastDeviceStatus?["openaps"] as! [String : AnyObject]? {
- if let lastLoopTime = formatter.date(from: (lastDeviceStatus?["created_at"] as! String))?.timeIntervalSince1970 {
- UserDefaultsRepository.alertLastLoopTime.value = lastLoopTime
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "lastLoopTime: " + String(lastLoopTime)) }
- if let failure = lastLoopRecord["failureReason"] {
- LoopStatusLabel.text = "X"
- latestLoopStatusString = "X"
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Loop Failure: X") }
- } else {
- var wasEnacted = false
- if let enacted = lastLoopRecord["enacted"] as? [String:AnyObject] {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Loop: Was Enacted") }
- wasEnacted = true
- if let lastTempBasal = enacted["rate"] as? Double {
-
- }
- }
- if let iobdata = lastLoopRecord["iob"] as? [String:AnyObject] {
- tableData[0].value = String(format:"%.2f", (iobdata["iob"] as! Double))
- latestIOB = String(format:"%.2f", (iobdata["iob"] as! Double))
- }
- if let cobdata = lastLoopRecord["enacted"] as? [String:AnyObject] {
- tableData[1].value = String(format:"%.1f", cobdata["COB"] as! Double)
- latestCOB = String(format:"%.1f", cobdata["COB"] as! Double)
- }
- if let recbolusdata = lastLoopRecord["enacted"] as? [String:AnyObject] {
- tableData[8].value = String(format:"%.2fU", recbolusdata["insulinReq"] as! Double)
- }
- if let autosensdata = lastLoopRecord["enacted"] as? [String:AnyObject] {
- let sens = autosensdata["sensitivityRatio"] as! Double * 100.0
- tableData[11].value = String(format:"%.0f", sens) + "%"
- }
-
- //Picks COB prediction if available, else UAM, else IOB, else ZT
- //Ideal is to predict all 4 in Loop Follow but this is a quick start
- var graphtype = ""
- var predictioncolor = UIColor.systemGray
- PredictionLabel.textColor = predictioncolor
- if let enactdata = lastLoopRecord["enacted"] as? [String:AnyObject] {
- if let predbgdata = enactdata["predBGs"] as? [String:AnyObject] {
- if predbgdata["COB"] != nil {
- graphtype="COB"
- predictioncolor = UIColor.systemYellow
- PredictionLabel.textColor = predictioncolor
- }
- else if predbgdata["UAM"] != nil {
- graphtype="UAM"
- predictioncolor = UIColor.systemOrange
- PredictionLabel.textColor = predictioncolor
- }
- else if predbgdata["IOB"] != nil {
- graphtype="IOB"
- predictioncolor = UIColor.systemBlue
- PredictionLabel.textColor = predictioncolor
- }
- else {
- graphtype="ZT"
- predictioncolor = UIColor.systemGreen
- PredictionLabel.textColor = predictioncolor
- }
-
- let graphdata = predbgdata[graphtype] as! [Double]
-
- if let eventualdata = lastLoopRecord["enacted"] as? [String:AnyObject] {
- if let eventualBGValue = eventualdata["eventualBG"] as? NSNumber {
- let eventualBGStringValue = String(describing: eventualBGValue)
- PredictionLabel.text = bgUnits.toDisplayUnits(eventualBGStringValue)
- }
- }
-
- if UserDefaultsRepository.downloadPrediction.value && latestLoopTime < lastLoopTime {
- predictionData.removeAll()
- var predictionTime = lastLoopTime
- let toLoad = Int(UserDefaultsRepository.predictionToLoad.value * 12)
- var i = 0
- while i <= toLoad {
- if i < graphdata.count {
- let prediction = ShareGlucoseData(sgv: Int(round(graphdata[i])), date: predictionTime, direction: "flat")
- predictionData.append(prediction)
- predictionTime += 300
- }
- i += 1
- }
-
- }
-
- let predMin = graphdata.min()
- let predMax = graphdata.max()
- tableData[9].value = bgUnits.toDisplayUnits(String(predMin!)) + "/" + bgUnits.toDisplayUnits(String(predMax!))
-
- updatePredictionGraph(color: predictioncolor)
- }
- }
-
- if let loopStatus = lastLoopRecord["recommendedTempBasal"] as? [String:AnyObject] {
- if let tempBasalTime = formatter.date(from: (loopStatus["timestamp"] as! String))?.timeIntervalSince1970 {
- var lastBGTime = lastLoopTime
- if bgData.count > 0 {
- lastBGTime = bgData[bgData.count - 1].date
- }
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "tempBasalTime: " + String(tempBasalTime)) }
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "lastBGTime: " + String(lastBGTime)) }
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "wasEnacted: " + String(wasEnacted)) }
- if tempBasalTime > lastBGTime && !wasEnacted {
- LoopStatusLabel.text = "⏀"
- latestLoopStatusString = "⏀"
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Open Loop: recommended temp. temp time > bg time, was not enacted") }
- } else {
- LoopStatusLabel.text = "↻"
- latestLoopStatusString = "↻"
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Looping: recommended temp, but temp time is < bg time and/or was enacted") }
- }
- }
- } else {
- LoopStatusLabel.text = "↻"
- latestLoopStatusString = "↻"
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Looping: no recommended temp") }
- }
-
- }
- if ((TimeInterval(Date().timeIntervalSince1970) - lastLoopTime) / 60) > 15 {
- LoopStatusLabel.text = "⚠"
- latestLoopStatusString = "⚠"
- }
- latestLoopTime = lastLoopTime
- }
- }
-
- var oText = "" as String
- currentOverride = 1.0
- if let lastOverride = lastDeviceStatus?["override"] as! [String : AnyObject]? {
- if lastOverride["active"] as! Bool {
-
- let lastCorrection = lastOverride["currentCorrectionRange"] as! [String: AnyObject]
- if let multiplier = lastOverride["multiplier"] as? Double {
- currentOverride = multiplier
- oText += String(format: "%.0f%%", (multiplier * 100))
- }
- else
- {
- oText += "100%"
- }
- oText += " ("
- let minValue = lastCorrection["minValue"] as! Double
- let maxValue = lastCorrection["maxValue"] as! Double
- oText += bgUnits.toDisplayUnits(String(minValue)) + "-" + bgUnits.toDisplayUnits(String(maxValue)) + ")"
-
- tableData[3].value = oText
- }
- }
-
- infoTable.reloadData()
-
- // Start the timer based on the timestamp
- let now = dateTimeUtils.getNowTimeIntervalUTC()
- let secondsAgo = now - latestLoopTime
-
- DispatchQueue.main.async {
- // if Loop is overdue over: 20:00, re-attempt every 5 minutes
- if secondsAgo >= (20 * 60) {
- self.startDeviceStatusTimer(time: (5 * 60))
- print("started 5 minute device status timer")
-
- // if the Loop is overdue: 10:00-19:59, re-attempt every minute
- } else if secondsAgo >= (10 * 60) {
- self.startDeviceStatusTimer(time: 60)
- print("started 1 minute device status timer")
-
- // if the Loop is overdue: 7:00-9:59, re-attempt every 30 seconds
- } else if secondsAgo >= (7 * 60) {
- self.startDeviceStatusTimer(time: 30)
- print("started 30 second device status timer")
-
- // if the Loop is overdue: 5:00-6:59 re-attempt every 10 seconds
- } else if secondsAgo >= (5 * 60) {
- self.startDeviceStatusTimer(time: 10)
- print("started 10 second device status timer")
-
- // We have a current Loop. Set timer to 5:10 from last reading
- } else {
- self.startDeviceStatusTimer(time: 310 - secondsAgo)
- let timerVal = 310 - secondsAgo
- print("started 5:10 device status timer: \(timerVal)")
- }
- }
- }
-
- // NS Carbs Today Web Call
- func webLoadNSCarbsToday() {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: Carbs Today") }
- let urlUser = UserDefaultsRepository.url.value
- let now = Date()
- let timeZone = TimeZone.current
- var calendar = Calendar(identifier: .gregorian)
- calendar.timeZone = timeZone
- var dateComponents = calendar.dateComponents(in: timeZone, from: now)
- dateComponents.hour = 0
- dateComponents.minute = 0
- dateComponents.second = 0
- guard let date = dateComponents.date else { fatalError("Invalid date components") }
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
- dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
- let utcDateString = dateFormatter.string(from: date)
- var urlString = urlUser + "/api/v1/treatments.json?count=1000&find[eventType]=Carb+Correction&find[created_at][$gte]=" + utcDateString
- if token != "" {
- urlString = urlUser + "/api/v1/treatments.json?token=" + token + "&count=1000&find[eventType]=Carb+Correction&find[created_at][$gte]=" + utcDateString
- }
- guard let urlData = URL(string: urlString) else {
- return
- }
- var request = URLRequest(url: urlData)
- request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
- let task = URLSession.shared.dataTask(with: request) { data, response, error in
- guard error == nil else {
- return
- }
- guard let data = data else {
- return
- }
- let decoder = JSONDecoder()
- let entriesResponse = try? decoder.decode([carbsData].self, from: data)
- if let entriesResponse = entriesResponse {
- DispatchQueue.main.async {
- self.updateCarbsToday(data: entriesResponse)
- }
- } else {
- return
- }
- }
- task.resume()
- }
- // NS CarbsToday Response Processor
- func updateCarbsToday(data: [carbsData]) {
- self.clearLastInfoData(index: 10)
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: carbs") }
- if data.count == 0 {
- return
- }
- let totalCarbs = data.reduce(0.0) { $0 + ($1.carbs ?? 0.0) }
- let resultString = String(format: "%.0f", totalCarbs)
- tableData[10].value = resultString
- infoTable.reloadData()
- }
- // NS Cage Web Call
- func webLoadNSCage() {
- let parameters: [String: String] = ["find[eventType]": NightscoutUtils.EventType.cage.rawValue, "count": "1"]
- NightscoutUtils.executeRequest(eventType: .cage, parameters: parameters) { (result: Result<[cageData], Error>) in
- switch result {
- case .success(let data):
- self.updateCage(data: data)
- case .failure(let error):
- print("Error: \(error.localizedDescription)")
- }
- }
- }
-
- // NS Cage Response Processor
- func updateCage(data: [cageData]) {
- self.clearLastInfoData(index: 7)
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: CAGE") }
- if data.count == 0 {
- return
- }
-
- let lastCageString = data[0].created_at
-
- let formatter = ISO8601DateFormatter()
- formatter.formatOptions = [.withFullDate,
- .withTime,
- .withDashSeparatorInDate,
- .withColonSeparatorInTime]
- UserDefaultsRepository.alertCageInsertTime.value = formatter.date(from: (lastCageString))?.timeIntervalSince1970 as! TimeInterval
- if let cageTime = formatter.date(from: (lastCageString))?.timeIntervalSince1970 {
- let now = dateTimeUtils.getNowTimeIntervalUTC()
- let secondsAgo = now - cageTime
- //let days = 24 * 60 * 60
-
- let formatter = DateComponentsFormatter()
- formatter.unitsStyle = .positional // Use the appropriate positioning for the current locale
- formatter.allowedUnits = [ .day, .hour ] // Units to display in the formatted string
- formatter.zeroFormattingBehavior = [ .pad ] // Pad with zeroes where appropriate for the locale
-
- let formattedDuration = formatter.string(from: secondsAgo)
- tableData[7].value = formattedDuration ?? ""
- }
- infoTable.reloadData()
- }
-
- // NS Sage Web Call
- func webLoadNSSage() {
- let lastDateString = dateTimeUtils.nowMinus10DaysTimeInterval()
-
- let parameters: [String: String] = [
- "find[eventType]": NightscoutUtils.EventType.sage.rawValue,
- "find[created_at][$gte]": lastDateString,
- "count": "1"
- ]
-
- NightscoutUtils.executeRequest(eventType: .sage, parameters: parameters) { (result: Result<[cageData], Error>) in
- switch result {
- case .success(let data):
- DispatchQueue.main.async {
- self.updateSage(data: data)
- }
- case .failure(let error):
- print("Failed to fetch data: \(error.localizedDescription)")
- }
- }
- }
-
- // NS Sage Response Processor
- func updateSage(data: [cageData]) {
- self.clearLastInfoData(index: 6)
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process/Display: SAGE") }
- if data.count == 0 {
- return
- }
-
- var lastSageString = data[0].created_at
-
- let formatter = ISO8601DateFormatter()
- formatter.formatOptions = [.withFullDate,
- .withTime,
- .withDashSeparatorInDate,
- .withColonSeparatorInTime]
- UserDefaultsRepository.alertSageInsertTime.value = formatter.date(from: (lastSageString))?.timeIntervalSince1970 as! TimeInterval
- if UserDefaultsRepository.alertAutoSnoozeCGMStart.value && (dateTimeUtils.getNowTimeIntervalUTC() - UserDefaultsRepository.alertSageInsertTime.value < 7200){
- let snoozeTime = Date(timeIntervalSince1970: UserDefaultsRepository.alertSageInsertTime.value + 7200)
- UserDefaultsRepository.alertSnoozeAllTime.value = snoozeTime
- UserDefaultsRepository.alertSnoozeAllIsSnoozed.value = true
- guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
- alarms.reloadIsSnoozed(key: "alertSnoozeAllIsSnoozed", value: true)
- alarms.reloadSnoozeTime(key: "alertSnoozeAllTime", setNil: false, value: snoozeTime)
- }
-
- if let sageTime = formatter.date(from: (lastSageString as! String))?.timeIntervalSince1970 {
- let now = dateTimeUtils.getNowTimeIntervalUTC()
- let secondsAgo = now - sageTime
- let days = 24 * 60 * 60
-
- let formatter = DateComponentsFormatter()
- formatter.unitsStyle = .positional // Use the appropriate positioning for the current locale
- formatter.allowedUnits = [ .day, .hour] // Units to display in the formatted string
- formatter.zeroFormattingBehavior = [ .pad ] // Pad with zeroes where appropriate for the locale
-
- let formattedDuration = formatter.string(from: secondsAgo)
- tableData[6].value = formattedDuration ?? ""
- }
- infoTable.reloadData()
- }
-
- // NS Profile Web Call
- func webLoadNSProfile() {
- NightscoutUtils.executeRequest(eventType: .profile, parameters: [:]) { (result: Result<NSProfile, Error>) in
- switch result {
- case .success(let profileData):
- self.updateProfile(profileData: profileData)
- case .failure(let error):
- print("Error fetching profile data: \(error.localizedDescription)")
- }
- }
- }
-
- // NS Profile Response Processor
- func updateProfile(profileData: NSProfile) {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: profile") }
-
- guard let store = profileData.store["default"] ?? profileData.store["Default"] else {
- return
- }
-
- basalProfile.removeAll()
- for basalEntry in store.basal {
- let entry = basalProfileStruct(value: basalEntry.value, time: basalEntry.time, timeAsSeconds: basalEntry.timeAsSeconds)
- basalProfile.append(entry)
- }
-
- // Don't process the basal or draw the graph until after the BG has been fully processeed and drawn
- if firstGraphLoad { return }
- var basalSegments: [DataStructs.basalProfileSegment] = []
-
- let graphHours = 24 * UserDefaultsRepository.downloadDays.value
- // Build scheduled basal segments from right to left by
- // moving pointers to the current midnight and current basal
- var midnight = dateTimeUtils.getTimeIntervalMidnightToday()
- var basalProfileIndex = basalProfile.count - 1
- var start = midnight + basalProfile[basalProfileIndex].timeAsSeconds
- var end = dateTimeUtils.getNowTimeIntervalUTC()
- // Move back until we're in the graph range
- while start > end {
- basalProfileIndex -= 1
- start = midnight + basalProfile[basalProfileIndex].timeAsSeconds
- }
- // Add records while they're still within the graph
- let graphStart = dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours)
- while end >= graphStart {
- let entry = DataStructs.basalProfileSegment(
- basalRate: basalProfile[basalProfileIndex].value, startDate: start, endDate: end)
- basalSegments.append(entry)
-
- basalProfileIndex -= 1
- if basalProfileIndex < 0 {
- basalProfileIndex = basalProfile.count - 1
- midnight = midnight.advanced(by: -24*60*60)
- }
- end = start - 1
- start = midnight + basalProfile[basalProfileIndex].timeAsSeconds
- }
- // reverse the result to get chronological order
- basalSegments.reverse()
-
- var firstPass = true
- // Runs the scheduled basal to the end of the prediction line
- var predictionEndTime = dateTimeUtils.getNowTimeIntervalUTC() + (3600 * UserDefaultsRepository.predictionToLoad.value)
- basalScheduleData.removeAll()
-
- for i in 0..<basalSegments.count {
- let timeStart = dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours)
-
- // This processed everything after the first one.
- if firstPass == false
- && basalSegments[i].startDate <= predictionEndTime {
- let startDot = basalGraphStruct(basalRate: basalSegments[i].basalRate, date: basalSegments[i].startDate)
- basalScheduleData.append(startDot)
- var endDate = basalSegments[i].endDate
-
- // if it's the last one needed, set it to end at the prediction end time
- if endDate > predictionEndTime || i == basalSegments.count - 1 {
- endDate = Double(predictionEndTime)
- }
- let endDot = basalGraphStruct(basalRate: basalSegments[i].basalRate, date: endDate)
- basalScheduleData.append(endDot)
- }
-
- // we need to manually set the first one
- // Check that this is the first one and there are no existing entries
- if firstPass == true {
- // check that the timestamp is > the current entry and < the next entry
- if timeStart >= basalSegments[i].startDate && timeStart < basalSegments[i].endDate {
- // Set the start time to match the BG start
- let startDot = basalGraphStruct(basalRate: basalSegments[i].basalRate, date: Double(timeStart + (60 * 5)))
- basalScheduleData.append(startDot)
-
- // set the enddot where the next one will start
- var endDate = basalSegments[i].endDate
- let endDot = basalGraphStruct(basalRate: basalSegments[i].basalRate, date: endDate)
- basalScheduleData.append(endDot)
- firstPass = false
- }
- }
- }
-
- if UserDefaultsRepository.graphBasal.value {
- updateBasalScheduledGraph()
- }
-
- }
-
- // NS Treatments Web Call
- // Downloads Basal, Bolus, Carbs, BG Check, Notes, Overrides
- func WebLoadNSTreatments() {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: Treatments") }
- if !UserDefaultsRepository.downloadTreatments.value { return }
-
- let graphHours = 24 * UserDefaultsRepository.downloadDays.value
- let startTimeString = dateTimeUtils.nowMinusNHoursTimeInterval(N: graphHours)
-
- let parameters: [String: String] = ["find[created_at][$gte]": startTimeString]
-
- NightscoutUtils.executeDynamicRequest(eventType: .treatments, parameters: parameters) { (result: Result<Any, Error>) in
- switch result {
- case .success(let data):
- if let entries = data as? [[String: AnyObject]] {
- DispatchQueue.main.async {
- self.updateTreatments(entries: entries)
- }
- } else {
- print("Error: Unexpected data structure")
- }
- case .failure(let error):
- print("Error: \(error.localizedDescription)")
- }
- }
- }
-
- // Process and split out treatments to individual tasks
- func updateTreatments(entries: [[String:AnyObject]]) {
-
- var tempBasal: [[String:AnyObject]] = []
- var bolus: [[String:AnyObject]] = []
- var carbs: [[String:AnyObject]] = []
- var temporaryOverride: [[String:AnyObject]] = []
- var note: [[String:AnyObject]] = []
- var bgCheck: [[String:AnyObject]] = []
- var suspendPump: [[String:AnyObject]] = []
- var resumePump: [[String:AnyObject]] = []
- var pumpSiteChange: [[String:AnyObject]] = []
- var cgmSensorStart: [[String:AnyObject]] = []
-
- for i in 0..<entries.count {
- let entry = entries[i] as [String : AnyObject]?
- switch entry?["eventType"] as! String {
- case "Temp Basal":
- tempBasal.append(entry!)
- case "Correction Bolus":
- bolus.append(entry!)
- case "Bolus":
- bolus.append(entry!)
- case "SMB":
- bolus.append(entry!)
- case "Meal Bolus":
- carbs.append(entry!)
- bolus.append(entry!)
- case "Carb Correction":
- carbs.append(entry!)
- case "Temporary Override":
- temporaryOverride.append(entry!)
- case "Temporary Target":
- temporaryOverride.append(entry!)
- case "Note":
- note.append(entry!)
- print("Note: \(String(describing: entry))")
- case "BG Check":
- bgCheck.append(entry!)
- case "Suspend Pump":
- suspendPump.append(entry!)
- case "Resume Pump":
- resumePump.append(entry!)
- case "Pump Site Change":
- pumpSiteChange.append(entry!)
- case "Sensor Start":
- cgmSensorStart.append(entry!)
- default:
- print("No Match: \(String(describing: entry))")
- }
- }
- // end of for loop
-
- if tempBasal.count > 0 {
- processNSBasals(entries: tempBasal)
- } else {
- if basalData.count < 0 {
- clearOldTempBasal()
- }
- }
- if bolus.count > 0 {
- processNSBolus(entries: bolus)
- } else {
- if bolusData.count > 0 {
- clearOldBolus()
- }
- }
- if carbs.count > 0 {
- processNSCarbs(entries: carbs)
- } else {
- if carbData.count > 0 {
- clearOldCarb()
- }
- }
- if bgCheck.count > 0 {
- processNSBGCheck(entries: bgCheck)
- } else {
- if bgCheckData.count > 0 {
- clearOldBGCheck()
- }
- }
- if temporaryOverride.count > 0 {
- processNSOverrides(entries: temporaryOverride)
- } else {
- if overrideGraphData.count > 0 {
- clearOldOverride()
- }
- }
- if suspendPump.count > 0 {
- processSuspendPump(entries: suspendPump)
- } else {
- if suspendGraphData.count > 0 {
- clearOldSuspend()
- }
- }
- if resumePump.count > 0 {
- processResumePump(entries: resumePump)
- } else {
- if resumeGraphData.count > 0 {
- clearOldResume()
- }
- }
- if cgmSensorStart.count > 0 {
- processSensorStart(entries: cgmSensorStart)
- } else {
- if sensorStartGraphData.count > 0 {
- clearOldSensor()
- }
- }
- if note.count > 0 {
- processNotes(entries: note)
- } else {
- if noteGraphData.count > 0 {
- clearOldNotes()
- }
- }
- }
-
- func clearOldTempBasal()
- {
- basalData.removeAll()
- updateBasalGraph()
- }
-
- func clearOldBolus()
- {
- bolusData.removeAll()
- updateBolusGraph()
- }
-
- func clearOldCarb()
- {
- carbData.removeAll()
- updateCarbGraph()
- }
-
- func clearOldBGCheck()
- {
- bgCheckData.removeAll()
- updateBGCheckGraph()
- }
-
- func clearOldOverride()
- {
- overrideGraphData.removeAll()
- updateOverrideGraph()
- }
-
- func clearOldSuspend()
- {
- suspendGraphData.removeAll()
- updateSuspendGraph()
- }
-
- func clearOldResume()
- {
- resumeGraphData.removeAll()
- updateResumeGraph()
- }
-
- func clearOldSensor()
- {
- sensorStartGraphData.removeAll()
- updateSensorStart()
- }
-
- func clearOldNotes()
- {
- noteGraphData.removeAll()
- updateNotes()
- }
-
- // NS Temp Basal Response Processor
- func processNSBasals(entries: [[String:AnyObject]]) {
- self.clearLastInfoData(index: 2)
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Basal") }
- // due to temp basal durations, we're going to destroy the array and load everything each cycle for the time being.
- basalData.removeAll()
-
- var lastEndDot = 0.0
-
- var tempArray = entries
- tempArray.reverse()
- for i in 0..<tempArray.count {
- let currentEntry = tempArray[i] as [String : AnyObject]?
- var basalDate: String
- if currentEntry?["timestamp"] != nil {
- basalDate = currentEntry?["timestamp"] as! String
- } else if currentEntry?["created_at"] != nil {
- basalDate = currentEntry?["created_at"] as! String
- } else {
- continue
- }
- var strippedZone = String(basalDate.dropLast())
- strippedZone = strippedZone.replacingOccurrences(of: "\\.\\d+", with: "", options: .regularExpression)
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
- dateFormatter.locale = Locale(identifier: "en_US")
- dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
- guard let dateString = dateFormatter.date(from: strippedZone) else { continue }
- let dateTimeStamp = dateString.timeIntervalSince1970
- guard let basalRate = currentEntry?["absolute"] as? Double else {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "ERROR: Null Basal entry")}
- continue
- }
-
- let midnightTime = dateTimeUtils.getTimeIntervalMidnightToday()
- // Setting end dots
- var duration = 0.0
- do {
- duration = try currentEntry?["duration"] as! Double
- } catch {
- print("No Duration Found")
- }
-
- // 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
- if i > 0 {
- let priorEntry = tempArray[i - 1] as [String : AnyObject]?
- var priorBasalDate: String
- if priorEntry?["timestamp"] != nil {
- priorBasalDate = priorEntry?["timestamp"] as! String
- } else if currentEntry?["created_at"] != nil {
- priorBasalDate = priorEntry?["created_at"] as! String
- } else {
- continue
- }
- var priorStrippedZone = String(priorBasalDate.dropLast())
- priorStrippedZone = priorStrippedZone.replacingOccurrences(of: "\\.\\d+", with: "", options: .regularExpression)
- let priorDateFormatter = DateFormatter()
- priorDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
- priorDateFormatter.locale = Locale(identifier: "en_US")
- priorDateFormatter.timeZone = TimeZone(abbreviation: "UTC")
- guard let priorDateString = dateFormatter.date(from: priorStrippedZone) else { continue }
- let priorDateTimeStamp = priorDateString.timeIntervalSince1970
- let priorDuration = priorEntry?["duration"] as? Double ?? 0.0
- // 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
- if Double( dateTimeStamp - priorDateTimeStamp ) > Double( (priorDuration * 60) + 15 ) {
-
- var scheduled = 0.0
- var midGap = false
- var midGapTime: TimeInterval = 0
- var midGapValue: Double = 0
- // cycle through basal profiles.
- // TODO figure out how to deal with profile changes that happen mid-gap
- for b in 0..<self.basalScheduleData.count {
-
- if (priorDateTimeStamp + (priorDuration * 60)) >= basalScheduleData[b].date {
- scheduled = basalScheduleData[b].basalRate
-
- // deal with mid-gap scheduled basal change
- // don't do it on the last scheudled basal entry
- if b < self.basalScheduleData.count - 1 {
- if dateTimeStamp > self.basalScheduleData[b + 1].date {
- // midGap = true
- // TODO: finish this to handle mid-gap items without crashing from overlapping entries
- midGapTime = self.basalScheduleData[b + 1].date
- midGapValue = self.basalScheduleData[b + 1].basalRate
- }
- }
-
- }
-
- }
-
- // Make the starting dot at the last ending dot
- let startDot = basalGraphStruct(basalRate: scheduled, date: Double(priorDateTimeStamp + (priorDuration * 60)))
- basalData.append(startDot)
-
-
- if midGap {
- // Make the ending dot at the new scheduled basal
- let endDot1 = basalGraphStruct(basalRate: scheduled, date: Double(midGapTime))
- basalData.append(endDot1)
- // Make the starting dot at the scheduled Time
- let startDot2 = basalGraphStruct(basalRate: midGapValue, date: Double(midGapTime))
- basalData.append(startDot2)
- // Make the ending dot at the new basal value
- let endDot2 = basalGraphStruct(basalRate: midGapValue, date: Double(dateTimeStamp))
- basalData.append(endDot2)
-
- } else {
- // Make the ending dot at the new starting dot
- let endDot = basalGraphStruct(basalRate: scheduled, date: Double(dateTimeStamp))
- basalData.append(endDot)
- }
-
- }
- }
-
- // Make the starting dot
- let startDot = basalGraphStruct(basalRate: basalRate, date: Double(dateTimeStamp))
- basalData.append(startDot)
-
- // Make the ending dot
- // If it's the last one and has no duration, extend it for 30 minutes past the start. Otherwise set ending at duration
- // duration is already set to 0 if there is no duration set on it.
- //if i == tempArray.count - 1 && dateTimeStamp + duration <= dateTimeUtils.getNowTimeIntervalUTC() {
- if i == tempArray.count - 1 && duration == 0.0 {
- lastEndDot = dateTimeStamp + (30 * 60)
- latestBasal = String(format:"%.2f", basalRate)
- } else {
- lastEndDot = dateTimeStamp + (duration * 60)
- latestBasal = String(format:"%.2f", basalRate)
- }
-
- // Double check for overlaps of incorrectly ended TBRs and sent it to end when the next one starts if it finds a discrepancy
- if i < tempArray.count - 1 {
- let nextEntry = tempArray[i + 1] as [String : AnyObject]?
- var nextBasalDate: String
- if nextEntry?["timestamp"] != nil {
- nextBasalDate = nextEntry?["timestamp"] as! String
- } else if currentEntry?["created_at"] != nil {
- nextBasalDate = nextEntry?["created_at"] as! String
- } else {
- continue
- }
- var nextStrippedZone = String(nextBasalDate.dropLast())
- nextStrippedZone = nextStrippedZone.replacingOccurrences(of: "\\.\\d+", with: "", options: .regularExpression)
- let nextDateFormatter = DateFormatter()
- nextDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
- nextDateFormatter.locale = Locale(identifier: "en_US")
- nextDateFormatter.timeZone = TimeZone(abbreviation: "UTC")
- guard let nextDateString = dateFormatter.date(from: nextStrippedZone) else { continue }
- let nextDateTimeStamp = nextDateString.timeIntervalSince1970
- if nextDateTimeStamp < (dateTimeStamp + (duration * 60)) {
- lastEndDot = nextDateTimeStamp
- }
- }
-
- let endDot = basalGraphStruct(basalRate: basalRate, date: Double(lastEndDot))
- basalData.append(endDot)
-
-
- }
-
- // If last basal was prior to right now, we need to create one last scheduled entry
- if lastEndDot <= dateTimeUtils.getNowTimeIntervalUTC() {
- var scheduled = 0.0
- // cycle through basal profiles.
- // TODO figure out how to deal with profile changes that happen mid-gap
- for b in 0..<self.basalProfile.count {
- let scheduleTimeYesterday = self.basalProfile[b].timeAsSeconds + dateTimeUtils.getTimeIntervalMidnightYesterday()
- let scheduleTimeToday = self.basalProfile[b].timeAsSeconds + dateTimeUtils.getTimeIntervalMidnightToday()
- // check the prior temp ending to the profile seconds from midnight
- print("yesterday " + String(scheduleTimeYesterday))
- print("today " + String(scheduleTimeToday))
- if lastEndDot >= scheduleTimeToday {
- scheduled = basalProfile[b].value
- }
- }
-
- latestBasal = String(format:"%.2f", scheduled)
- // Make the starting dot at the last ending dot
- let startDot = basalGraphStruct(basalRate: scheduled, date: Double(lastEndDot))
- basalData.append(startDot)
-
- // Make the ending dot 10 minutes after now
- let endDot = basalGraphStruct(basalRate: scheduled, date: Double(Date().timeIntervalSince1970 + (60 * 10)))
- basalData.append(endDot)
-
- }
- tableData[2].value = latestBasal
- infoTable.reloadData()
- if UserDefaultsRepository.graphBasal.value {
- updateBasalGraph()
- }
- infoTable.reloadData()
- }
- // NS Meal Bolus Response Processor
- func processNSBolus(entries: [[String:AnyObject]]) {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Bolus") }
- // because it's a small array, we're going to destroy and reload every time.
- bolusData.removeAll()
- var lastFoundIndex = 0
- for i in 0..<entries.count {
- let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
- var bolusDate: String
- if currentEntry?["timestamp"] != nil {
- bolusDate = currentEntry?["timestamp"] as! String
- } else if currentEntry?["created_at"] != nil {
- bolusDate = currentEntry?["created_at"] as! String
- } else {
- continue
- }
-
- // fix to remove millisecond (after period in timestamp) for FreeAPS users
- var strippedZone = String(bolusDate.dropLast())
- strippedZone = strippedZone.components(separatedBy: ".")[0]
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
- dateFormatter.locale = Locale(identifier: "en_US")
- dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
- guard let dateString = dateFormatter.date(from: strippedZone) else { continue }
- let dateTimeStamp = dateString.timeIntervalSince1970
- guard let bolus = currentEntry?["insulin"] as? Double else { continue }
- let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
- lastFoundIndex = sgv.foundIndex
-
- if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
- // Make the dot
- let dot = bolusGraphStruct(value: bolus, date: Double(dateTimeStamp), sgv: Int(sgv.sgv + 20))
- bolusData.append(dot)
- }
-
- }
-
- if UserDefaultsRepository.graphBolus.value {
- updateBolusGraph()
- }
-
- }
-
- // NS Carb Bolus Response Processor
- func processNSCarbs(entries: [[String:AnyObject]]) {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Carbs") }
- // because it's a small array, we're going to destroy and reload every time.
- carbData.removeAll()
- var lastFoundIndex = 0
- var lastFoundBolus = 0
- for i in 0..<entries.count {
- let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
- var carbDate: String
- if currentEntry?["timestamp"] != nil {
- carbDate = currentEntry?["timestamp"] as! String
- } else if currentEntry?["created_at"] != nil {
- carbDate = currentEntry?["created_at"] as! String
- } else {
- continue
- }
-
-
- let absorptionTime = currentEntry?["absorptionTime"] as? Int ?? 0
-
- // Fix for FreeAPS milliseconds in timestamp
- var strippedZone = String(carbDate.dropLast())
- strippedZone = strippedZone.components(separatedBy: ".")[0]
-
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
- dateFormatter.locale = Locale(identifier: "en_US")
- dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
- guard let dateString = dateFormatter.date(from: strippedZone) else { continue }
- var dateTimeStamp = dateString.timeIntervalSince1970
-
- guard let carbs = currentEntry?["carbs"] as? Double else {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "ERROR: Null Carb entry")}
- continue
- }
- let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
- lastFoundIndex = sgv.foundIndex
-
- var offset = -50
- if sgv.sgv < Double(topBG - 100) {
- let bolusTime = findNearestBolusbyTime(timeWithin: 300, needle: dateTimeStamp, haystack: bolusData, startingIndex: lastFoundBolus)
- lastFoundBolus = bolusTime.foundIndex
-
- if bolusTime.offset {
- offset = 70
- } else {
- offset = 20
- }
- }
-
- if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
- // Make the dot
- let dot = carbGraphStruct(value: Double(carbs), date: Double(dateTimeStamp), sgv: Int(sgv.sgv + Double(offset)), absorptionTime: absorptionTime)
- carbData.append(dot)
- }
-
-
-
- }
-
- if UserDefaultsRepository.graphCarbs.value {
- updateCarbGraph()
- }
-
-
- }
-
- // NS Suspend Pump Response Processor
- func processSuspendPump(entries: [[String:AnyObject]]) {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Suspend Pump") }
- // because it's a small array, we're going to destroy and reload every time.
- suspendGraphData.removeAll()
- var lastFoundIndex = 0
- for i in 0..<entries.count {
- let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
- var date: String
- if currentEntry?["timestamp"] != nil {
- date = currentEntry?["timestamp"] as! String
- } else if currentEntry?["created_at"] != nil {
- date = currentEntry?["created_at"] as! String
- } else {
- return
- }
- // Fix for FreeAPS milliseconds in timestamp
- var strippedZone = String(date.dropLast())
- strippedZone = strippedZone.components(separatedBy: ".")[0]
-
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
- dateFormatter.locale = Locale(identifier: "en_US")
- dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
- let dateString = dateFormatter.date(from: strippedZone)
- let dateTimeStamp = dateString!.timeIntervalSince1970
- let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
- lastFoundIndex = sgv.foundIndex
-
- if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
- // Make the dot
- let dot = DataStructs.timestampOnlyStruct(date: Double(dateTimeStamp), sgv: Int(sgv.sgv))
- suspendGraphData.append(dot)
- }
- }
- if UserDefaultsRepository.graphOtherTreatments.value {
- updateSuspendGraph()
- }
-
- }
-
- // NS Resume Pump Response Processor
- func processResumePump(entries: [[String:AnyObject]]) {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Resume Pump") }
- // because it's a small array, we're going to destroy and reload every time.
- resumeGraphData.removeAll()
- var lastFoundIndex = 0
- for i in 0..<entries.count {
- let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
- var date: String
- if currentEntry?["timestamp"] != nil {
- date = currentEntry?["timestamp"] as! String
- } else if currentEntry?["created_at"] != nil {
- date = currentEntry?["created_at"] as! String
- } else {
- return
- }
- // Fix for FreeAPS milliseconds in timestamp
- var strippedZone = String(date.dropLast())
- strippedZone = strippedZone.components(separatedBy: ".")[0]
-
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
- dateFormatter.locale = Locale(identifier: "en_US")
- dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
- let dateString = dateFormatter.date(from: strippedZone)
- let dateTimeStamp = dateString!.timeIntervalSince1970
- let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
- lastFoundIndex = sgv.foundIndex
-
- if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
- // Make the dot
- let dot = DataStructs.timestampOnlyStruct(date: Double(dateTimeStamp), sgv: Int(sgv.sgv))
- resumeGraphData.append(dot)
- }
- }
- if UserDefaultsRepository.graphOtherTreatments.value {
- updateResumeGraph()
- }
-
- }
-
- // NS Sensor Start Response Processor
- func processSensorStart(entries: [[String:AnyObject]]) {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Sensor Start") }
- // because it's a small array, we're going to destroy and reload every time.
- sensorStartGraphData.removeAll()
- var lastFoundIndex = 0
- for i in 0..<entries.count {
- let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
- var date: String
- if currentEntry?["timestamp"] != nil {
- date = currentEntry?["timestamp"] as! String
- } else if currentEntry?["created_at"] != nil {
- date = currentEntry?["created_at"] as! String
- } else {
- return
- }
- // Fix for FreeAPS milliseconds in timestamp
- var strippedZone = String(date.dropLast())
- strippedZone = strippedZone.components(separatedBy: ".")[0]
-
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
- dateFormatter.locale = Locale(identifier: "en_US")
- dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
- let dateString = dateFormatter.date(from: strippedZone)
- let dateTimeStamp = dateString!.timeIntervalSince1970
- let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
- lastFoundIndex = sgv.foundIndex
-
- if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
- // Make the dot
- let dot = DataStructs.timestampOnlyStruct(date: Double(dateTimeStamp), sgv: Int(sgv.sgv))
- sensorStartGraphData.append(dot)
- }
- }
- if UserDefaultsRepository.graphOtherTreatments.value {
- updateSensorStart()
- }
-
- }
-
- // NS Note Response Processor
- func processNotes(entries: [[String:AnyObject]]) {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Notes") }
- // because it's a small array, we're going to destroy and reload every time.
- noteGraphData.removeAll()
- var lastFoundIndex = 0
- for i in 0..<entries.count {
- let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
- var date: String
- if currentEntry?["timestamp"] != nil {
- date = currentEntry?["timestamp"] as! String
- } else if currentEntry?["created_at"] != nil {
- date = currentEntry?["created_at"] as! String
- } else {
- return
- }
- // Fix for FreeAPS milliseconds in timestamp
- var strippedZone = String(date.dropLast())
- strippedZone = strippedZone.components(separatedBy: ".")[0]
-
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
- dateFormatter.locale = Locale(identifier: "en_US")
- dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
- let dateString = dateFormatter.date(from: strippedZone)
- let dateTimeStamp = dateString!.timeIntervalSince1970
- let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
- lastFoundIndex = sgv.foundIndex
-
- guard let thisNote = currentEntry?["notes"] as? String else { continue }
-
- if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
- // Make the dot
- let dot = DataStructs.noteStruct(date: Double(dateTimeStamp), sgv: Int(sgv.sgv), note: thisNote)
- noteGraphData.append(dot)
- }
- }
- if UserDefaultsRepository.graphOtherTreatments.value {
- updateNotes()
- }
-
- }
-
- // NS BG Check Response Processor
- func processNSBGCheck(entries: [[String:AnyObject]]) {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: BG Check") }
- // because it's a small array, we're going to destroy and reload every time.
- bgCheckData.removeAll()
- for i in 0..<entries.count {
- let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
- var date: String
- if currentEntry?["timestamp"] != nil {
- date = currentEntry?["timestamp"] as! String
- } else if currentEntry?["created_at"] != nil {
- date = currentEntry?["created_at"] as! String
- } else {
- return
- }
- // Fix for FreeAPS milliseconds in timestamp
- var strippedZone = String(date.dropLast())
- strippedZone = strippedZone.components(separatedBy: ".")[0]
-
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
- dateFormatter.locale = Locale(identifier: "en_US")
- dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
- let dateString = dateFormatter.date(from: strippedZone)
- let dateTimeStamp = dateString!.timeIntervalSince1970
-
- guard let sgv = currentEntry?["glucose"] as? Int else {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "ERROR: Non-Int Glucose entry")}
- continue
- }
-
- if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
- // Make the dot
- //let dot = ShareGlucoseData(value: Double(carbs), date: Double(dateTimeStamp), sgv: Int(sgv.sgv))
- let dot = ShareGlucoseData(sgv: sgv, date: Double(dateTimeStamp), direction: "")
- bgCheckData.append(dot)
- }
-
-
-
- }
-
- if UserDefaultsRepository.graphOtherTreatments.value {
- updateBGCheckGraph()
- }
-
-
- }
-
- // NS Override Response Processor
- func processNSOverrides(entries: [[String:AnyObject]]) {
- if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Overrides") }
- // because it's a small array, we're going to destroy and reload every time.
- overrideGraphData.removeAll()
- for i in 0..<entries.count {
- let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
- var date: String
- if currentEntry?["timestamp"] != nil {
- date = currentEntry?["timestamp"] as! String
- } else if currentEntry?["created_at"] != nil {
- date = currentEntry?["created_at"] as! String
- } else {
- return
- }
- // Fix for FreeAPS milliseconds in timestamp
- var strippedZone = String(date.dropLast())
- strippedZone = strippedZone.components(separatedBy: ".")[0]
-
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
- dateFormatter.locale = Locale(identifier: "en_US")
- dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
- let dateString = dateFormatter.date(from: strippedZone)
- var dateTimeStamp = dateString!.timeIntervalSince1970
- let graphHours = 24 * UserDefaultsRepository.downloadDays.value
- if dateTimeStamp < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) {
- dateTimeStamp = dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours)
- }
-
- var multiplier: Double = 1.0
- if currentEntry?["insulinNeedsScaleFactor"] != nil {
- multiplier = currentEntry?["insulinNeedsScaleFactor"] as! Double
- }
- var duration: Double = 5.0
- if let durationType = currentEntry?["durationType"] as? String {
- if i == entries.count - 1 {
- duration = dateTimeUtils.getNowTimeIntervalUTC() - dateTimeStamp + (60 * 60)
- }
- } else {
- duration = (currentEntry?["duration"] as? Double)!
- duration = duration * 60
- }
-
- // Skip overrides that aren't 5 minutes long. This prevents overlapping that causes bars to not display.
- if duration < 300 { continue }
-
- guard let enteredBy = currentEntry?["enteredBy"] as? String else { continue }
- guard let reason = currentEntry?["reason"] as? String else { continue }
-
- var range: [Int] = []
- if let ranges = currentEntry?["correctionRange"] as? [Int] {
- if ranges.count == 2 {
- guard let low = ranges[0] as? Int else { continue }
- guard let high = ranges[1] as? Int else { continue }
- range.append(low)
- range.append(high)
- }
-
- } else {
- let low = currentEntry?["targetBottom"] as? Int
- let high = currentEntry?["targetTop"] as? Int
-
- if (low == nil && high != nil) || (low != nil && high == nil) {
- continue
- }
-
- if let l = low {
- range.append(l)
- }
-
- if let h = high {
- range.append(h)
- }
- }
-
- let endDate = dateTimeStamp + (duration)
- let dot = DataStructs.overrideStruct(insulNeedsScaleFactor: multiplier, date: dateTimeStamp, endDate: endDate, duration: duration, correctionRange: range, enteredBy: enteredBy, reason: reason, sgv: -20)
- overrideGraphData.append(dot)
-
-
- }
- if UserDefaultsRepository.graphOtherTreatments.value {
- updateOverrideGraph()
- }
-
- }
- }
|