| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 |
- //
- // FirstViewController.swift
- // LoopFollow
- //
- // Created by Jon Fawcett on 6/1/20.
- // Copyright © 2020 Jon Fawcett. All rights reserved.
- //
- import UIKit
- import Charts
- class MainViewController: UIViewController, UITableViewDataSource {
-
- @IBOutlet weak var BGText: UILabel!
- @IBOutlet weak var DeltaText: UILabel!
- @IBOutlet weak var DirectionText: UILabel!
- @IBOutlet weak var BGChart: LineChartView!
- @IBOutlet weak var BGChartFull: LineChartView!
- @IBOutlet weak var MinAgoText: UILabel!
- @IBOutlet weak var infoTable: UITableView!
-
- //NS BG Struct
- struct sgvData: Codable {
- var sgv: Int
- var date: TimeInterval
- var direction: String?
- }
-
- // Data Table Struct
- struct infoData {
- var name: String
- var value: String
- }
-
- // Variables for BG Charts
- public var numPoints: Int = 13
- public var linePlotData: [Double] = []
- public var linePlotDataTime: [Double] = []
- var firstStart: Bool = true
-
- // Vars for NS Pull
- var graphHours:Int=24
- var mmol = false as Bool
- var urlUser = UserDefaultsRepository.url.value as String
- var token = "" as String
- var defaults : UserDefaults?
- let consoleLogging = true
- var timeofLastBGUpdate = 0 as TimeInterval
-
- var backgroundTask = BackgroundTask()
-
- // Refresh NS Data
- var timer = Timer()
- // check every 30 Seconds whether new bgvalues should be retrieved
- let timeInterval: TimeInterval = 30.0
-
- // Info Table Setup
- var tableData = [
- infoData(name: "IOB", value: ""), //0
- infoData(name: "COB", value: ""), //1
- infoData(name: "Basal", value: ""), //2
- infoData(name: "Override", value: ""), //3
- infoData(name: "Battery", value: ""), //4
- infoData(name: "Pump", value: ""), //5
- infoData(name: "Loop", value: ""), //6
- infoData(name: "SAGE", value: ""), //7
- infoData(name: "CAGE", value: "") //8
- ]
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- UNUserNotificationCenter.current().requestAuthorization(options: .badge) { (granted, error) in
- if error != nil {
- // success!
- }
- }
-
- // ToDo: Should continue running in background
- // stop timer when app enters in background, start is again when becomes active
- let notificationCenter = NotificationCenter.default
- notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
- notificationCenter.addObserver(self, selector: #selector(appCameToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
-
- //Bind info data
- infoTable.rowHeight = 25
- infoTable.dataSource = self
- }
-
- override func viewWillAppear(_ animated: Bool) {
- Reload()
- }
-
-
- // Info Table Functions
- func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return tableData.count
- }
-
- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell", for: indexPath)
- let values = tableData[indexPath.row]
- cell.textLabel?.text = values.name
- cell.detailTextLabel?.text = values.value
- return cell
- }
-
- // Timer
- fileprivate func startTimer() {
- timer = Timer.scheduledTimer(timeInterval: timeInterval,
- target: self,
- selector: #selector(MainViewController.timerDidEnd(_:)),
- userInfo: nil,
- repeats: true)
- }
-
- @objc func appMovedToBackground() {
- // timer.invalidate()
- backgroundTask.startBackgroundTask()
- }
- @objc func appCameToForeground() {
- backgroundTask.stopBackgroundTask()
- //timer.invalidate()
- startTimer()
- loadBGData(urlUser: urlUser)
- loadDeviceStatus(urlUser: urlUser)
-
- }
-
- // check whether new Values should be retrieved
- @objc func timerDidEnd(_ timer:Timer) {
- print("timer ended")
- Reload()
- }
-
- func Reload() {
- loadBGData(urlUser: urlUser)
- clearLastInfoData()
- loadDeviceStatus(urlUser: urlUser)
- loadTempBasals(urlUser: urlUser)
- }
-
- // Main NS Data Pull
- func loadBGData(urlUser: String) {
- let points = String(self.graphHours * 12 + 1)
- var urlBGDataPath: String = urlUser + "/api/v1/entries/sgv.json?"
- if token == "" {
- urlBGDataPath = urlBGDataPath + "count=" + points
- }
- else
- {
- urlBGDataPath = urlBGDataPath + "token=" + token + "&count=" + points
- }
- guard let urlBGData = URL(string: urlBGDataPath) else {
- return
- }
- var request = URLRequest(url: urlBGData)
- request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
-
- let getBGTask = URLSession.shared.dataTask(with: request) { data, response, error in
- if self.consoleLogging == true {print("start bg url")}
- guard error == nil else {
- return
- }
- guard let data = data else {
- return
- }
-
- let decoder = JSONDecoder()
- let entriesResponse = try? decoder.decode([sgvData].self, from: data)
- if let entriesResponse = entriesResponse {
- DispatchQueue.main.async {
- if self.backgroundTask.player.isPlaying {
- self.updateBadge(entries: entriesResponse)
- } else {
- self.updateBG(entries: entriesResponse)
- self.createGraph(entries: entriesResponse)
- }
-
-
- }
- }
- else
- {
-
- return
- }
- }
- getBGTask.resume()
- }
-
- //Clear the info data before next pull
- func clearLastInfoData(){
- for i in 0..<tableData.count{
- tableData[i].value = ""
- }
- }
-
-
-
- // NS Device Status Pull
- func loadDeviceStatus(urlUser: String) {
- var urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?count=1"
- if token != "" {
- urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?token=" + token + "&count=1"
- }
-
- let escapedAddress = urlStringDeviceStatus.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
- guard let urlDeviceStatus = URL(string: escapedAddress!) else {
- return
- }
-
- if consoleLogging == true {print("entered 2nd task.")}
- var requestDeviceStatus = URLRequest(url: urlDeviceStatus)
- requestDeviceStatus.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
- let deviceStatusTask = URLSession.shared.dataTask(with: requestDeviceStatus) { data, response, error in
- if self.consoleLogging == true {print("in update loop.")}
- guard error == nil else {
- return
- }
- guard let data = data else {
-
- return
- }
-
- let json = try? JSONSerialization.jsonObject(with: data) as! [[String:AnyObject]]
-
- if let json = json {
- DispatchQueue.main.async {
- self.updateDeviceStatusDisplay(jsonDeviceStatus: json)
- }
- }
- else
- {
-
- return
- }
- if self.consoleLogging == true {print("finish pump update")}
- }
- deviceStatusTask.resume()
- }
-
- // Need to figure out the date to pull only last 24 hours
- func loadTempBasals(urlUser: String) {
- var dayComponent = DateComponents()
- dayComponent.day = -1 // For removing one day (yesterday): -1
- let theCalendar = Calendar.current
-
- let yesterday = theCalendar.date(byAdding: dayComponent, to: Date())
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-mm-ddTHH:mm:ss"
- //dateFormatter.timeZone = NSTimeZone(name: "UTC")
- //let date: NSDate? = dateFormatter.dateFromString("2016-02-29 12:24:26")
- //print(date)
-
- var urlStringBasal = urlUser + "/api/v1/treatments.json?find[eventType][$eq]=Temp%20Basal&find[created_at][$gte]=2020-06-02T22:46:26"
- if token != "" {
- urlStringBasal = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType][$eq]=Temp%20Basal&find[created_at][$gte]=2020-06-02T22:46:26"
- }
-
- let escapedAddress = urlStringBasal.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
- guard let urlBasal = URL(string: escapedAddress!) else {
- return
- }
-
- if consoleLogging == true {print("entered 2nd task.")}
- var requestBasal = URLRequest(url: urlBasal)
- requestBasal.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
- let basalTask = URLSession.shared.dataTask(with: requestBasal) { data, response, error in
- if self.consoleLogging == true {print("in update loop.")}
- guard error == nil else {
- return
- }
- guard let data = data else {
-
- return
- }
-
- let json = try? JSONSerialization.jsonObject(with: data) as! [[String:AnyObject]]
-
- if let json = json {
- DispatchQueue.main.async {
- self.updateDeviceStatusDisplay(jsonDeviceStatus: json)
- }
- }
- else
- {
-
- return
- }
- if self.consoleLogging == true {print("finish pump update")}
- }
- basalTask.resume()
- }
-
- // Parse Device Status Data
- func updateDeviceStatusDisplay(jsonDeviceStatus: [[String:AnyObject]]) {
- if consoleLogging == true {print("in updatePump")}
- if jsonDeviceStatus.count == 0 {
- return
- }
-
- //only grabbing one record since ns sorts by {created_at : -1}
- 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
- {
- tableData[5].value = String(format:"%.0f", reservoirData) + "U"
- } else {
- tableData[5].value = "50+U"
-
- }
-
- if let uploader = lastDeviceStatus?["uploader"] as? [String:AnyObject] {
- let upbat = uploader["battery"] as! Double
- // BatteryText.text! += " " + String(format:"%.0f", upbat) + "%"
- tableData[4].value = String(format:"%.0f", upbat) + "%"
- }
-
- }
- }
-
- if let lastLoopRecord = lastDeviceStatus?["loop"] as! [String : AnyObject]? {
- if let lastLoopTime = formatter.date(from: (lastLoopRecord["timestamp"] as! String))?.timeIntervalSince1970 {
- if let failure = lastLoopRecord["failureReason"] {
-
- //LoopStatusText.text! += " Failure "
- }
- else
- {
- if let enacted = lastLoopRecord["enacted"] as? [String:AnyObject] {
- if let lastTempBasal = enacted["rate"] as? Double {
- tableData[2].value = String(format:"%.1f", lastTempBasal)
- }
- }
- if let iobdata = lastLoopRecord["iob"] as? [String:AnyObject] {
- tableData[0].value = String(format:"%.1f", (iobdata["iob"] as! Double))
- }
- if let cobdata = lastLoopRecord["cob"] as? [String:AnyObject] {
- tableData[1].value = String(format:"%.0f", cobdata["cob"] as! Double)
- }
- if let predictdata = lastLoopRecord["predicted"] as? [String:AnyObject] {
- // let prediction = predictdata["values"] as! [Double]
- // loopStatusText += " EBG " + bgOutputFormat(bg: prediction.last!, mmol: mmol)
- }
-
- }
- }
-
- }
-
- var oText = "" as String
-
- if let lastOverride = lastDeviceStatus?["override"] as! [String : AnyObject]? {
- if let lastOverrideTime = formatter.date(from: (lastOverride["timestamp"] as! String))?.timeIntervalSince1970 {
- }
- if lastOverride["active"] as! Bool {
-
- let lastCorrection = lastOverride["currentCorrectionRange"] as! [String: AnyObject]
- if let multiplier = lastOverride["multiplier"] as? Double {
- oText += String(format:"%.1f", multiplier*100)
- }
- else
- {
- oText += String(format:"%.1f", 100)
- }
- oText += "% ("
- let minValue = lastCorrection["minValue"] as! Double
- let maxValue = lastCorrection["maxValue"] as! Double
- oText += bgOutputFormat(bg: minValue, mmol: mmol) + "-" + bgOutputFormat(bg: maxValue, mmol: mmol) + ")"
-
- tableData[3].value = oText
- }
- }
-
- infoTable.reloadData()
- }
-
- func stringFromTimeInterval(interval: TimeInterval) -> String {
- let interval = Int(interval)
- let minutes = (interval / 60) % 60
- let hours = (interval / 3600)
- return String(format: "%02d:%02d", hours, minutes)
- }
- func createGraph(entries: [sgvData]){
- var bgChartEntry = [ChartDataEntry]()
- var colors = [NSUIColor]()
- for i in 0..<entries.count{
- var dateString = String(entries[entries.count - 1 - i].date).prefix(10)
- let dateSecondsOnly = Double(String(dateString))!
-
- let value = ChartDataEntry(x: Double(dateSecondsOnly), y: Double(entries[entries.count - 1 - i].sgv))
- bgChartEntry.append(value)
-
- if Double(entries[entries.count - 1 - i].sgv) > 180 {
- colors.append(NSUIColor.yellow)
- } else if Double(entries[entries.count - 1 - i].sgv) < 70 {
- colors.append(NSUIColor.red)
- } else {
- colors.append(NSUIColor.green)
- }
- }
- let line1 = LineChartDataSet(entries:bgChartEntry, label: "")
- line1.circleRadius = 3
- line1.circleColors = [NSUIColor.green]
- line1.drawCircleHoleEnabled = false
- if UserDefaultsRepository.showLines.value {
- line1.lineWidth = 2
- } else {
- line1.lineWidth = 0
- }
- if UserDefaultsRepository.showDots.value {
- line1.drawCirclesEnabled = true
- } else {
- line1.drawCirclesEnabled = false
- }
- line1.setDrawHighlightIndicators(false)
- line1.valueFont.withSize(50)
-
- for i in 1..<colors.count{
- line1.addColor(colors[i])
- line1.circleColors.append(colors[i])
- }
-
- let data = LineChartData()
- data.addDataSet(line1)
- data.setValueFont(UIFont(name: UIFont.systemFont(ofSize: 10).fontName, size: 10)!)
- data.setDrawValues(false)
- BGChart.xAxis.valueFormatter = ChartXValueFormatter()
- BGChart.xAxis.granularity = 1800
- BGChart.xAxis.labelTextColor = NSUIColor.white
- BGChart.rightAxis.labelTextColor = NSUIColor.white
- BGChart.leftAxis.enabled = false
- BGChart.legend.enabled = false
- BGChart.scaleYEnabled = false
- BGChart.data = data
- BGChart.setExtraOffsets(left: 10, top: 10, right: 10, bottom: 10)
- BGChart.setVisibleXRangeMinimum(10)
- if firstStart {
- BGChart.zoom(scaleX: 20, scaleY: 1, x: 1, y: 1)
- firstStart = false
- }
- BGChart.moveViewToX(BGChart.chartXMax)
-
- //24 Hour Small Graph
- let line2 = LineChartDataSet(entries:bgChartEntry, label: "Number")
- line2.drawCirclesEnabled = false
- line2.lineWidth = 1
- for i in 1..<colors.count{
- line2.addColor(colors[i])
- line2.circleColors.append(colors[i])
- }
-
- let data2 = LineChartData()
- data2.addDataSet(line2)
- BGChartFull.leftAxis.enabled = false
- BGChartFull.rightAxis.enabled = false
- BGChartFull.xAxis.enabled = false
- BGChartFull.legend.enabled = false
- BGChartFull.scaleYEnabled = false
- BGChartFull.scaleXEnabled = false
- BGChartFull.drawGridBackgroundEnabled = false
- BGChartFull.data = data2
-
-
- }
-
- func updateBadge(entries: [sgvData]) {
- UIApplication.shared.applicationIconBadgeNumber = 0
- if entries.count > 0 {
- let latestBG = entries[0].sgv
- UIApplication.shared.applicationIconBadgeNumber = latestBG
- }
- print("updated badge")
- }
-
- func updateBG (entries: [sgvData]) {
- if consoleLogging == true {print("in update BG")}
- UIApplication.shared.applicationIconBadgeNumber = 0
- if entries.count > 0 {
- let latestBG = entries[0].sgv
- let priorBG = entries[1].sgv
- let deltaBG = latestBG - priorBG as Int
- let lastBGTime = entries[0].date / 1000 //NS has different units
- let deltaTime = (TimeInterval(Date().timeIntervalSince1970)-lastBGTime) / 60
- var userUnit = " mg/dL"
- if mmol {
- userUnit = " mmol/L"
- }
- UIApplication.shared.applicationIconBadgeNumber = latestBG
- BGText.text = bgOutputFormat(bg: Double(latestBG), mmol: mmol)
- MinAgoText.text = String(Int(deltaTime)) + " min ago"
- print(String(Int(deltaTime)) + " min ago")
- if let directionBG = entries[0].direction {
- DirectionText.text = bgDirectionGraphic(directionBG)
- }
- else
- {
- DirectionText.text = ""
- }
-
- if deltaBG < 0 {
- self.DeltaText.text = String(deltaBG)
- }
- else
- {
- self.DeltaText.text = "+" + String(deltaBG)
- }
-
- }
- else
- {
-
- return
- }
-
-
- if consoleLogging == true {print("end update bg")}
- }
- func bgOutputFormat(bg: Double, mmol: Bool) -> String {
- if !mmol {
- return String(format:"%.0f", bg)
- }
- else
- {
- return String(format:"%.1f", bg / 18.0)
- }
- }
-
- func bgDirectionGraphic(_ value:String)->String {
- let graphics:[String:String]=["Flat":"\u{2192}","DoubleUp":"\u{21C8}","SingleUp":"\u{2191}","FortyFiveUp":"\u{2197}\u{FE0E}","FortyFiveDown":"\u{2198}\u{FE0E}","SingleDown":"\u{2193}","DoubleDown":"\u{21CA}","None":"-","NOT COMPUTABLE":"-","RATE OUT OF RANGE":"-"]
-
-
- return graphics[value]!
- }
-
- }
|