DeviceStatus.swift 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. //
  2. // DeviceStatus.swift
  3. // LoopFollow
  4. //
  5. // Created by Jonas Björkert on 2023-10-05.
  6. // Copyright © 2023 Jon Fawcett. All rights reserved.
  7. //
  8. import Foundation
  9. import UIKit
  10. import Charts
  11. extension MainViewController {
  12. // NS Device Status Web Call
  13. func webLoadNSDeviceStatus() {
  14. if UserDefaultsRepository.debugLog.value {
  15. self.writeDebugLog(value: "Download: device status")
  16. }
  17. let parameters: [String: String] = ["count": "288"]
  18. NightscoutUtils.executeDynamicRequest(eventType: .deviceStatus, parameters: parameters) { result in
  19. switch result {
  20. case .success(let json):
  21. if let jsonDeviceStatus = json as? [[String: AnyObject]] {
  22. DispatchQueue.main.async {
  23. self.updateDeviceStatusDisplay(jsonDeviceStatus: jsonDeviceStatus)
  24. }
  25. } else {
  26. self.handleDeviceStatusError()
  27. }
  28. case .failure:
  29. self.handleDeviceStatusError()
  30. }
  31. }
  32. }
  33. private func handleDeviceStatusError() {
  34. if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
  35. globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
  36. //self.sendNotification(title: "Nightscout Error", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
  37. }
  38. DispatchQueue.main.async {
  39. if self.deviceStatusTimer.isValid {
  40. self.deviceStatusTimer.invalidate()
  41. }
  42. self.startDeviceStatusTimer(time: 10)
  43. }
  44. }
  45. func evaluateNotLooping(lastLoopTime: TimeInterval) {
  46. if let statusStackView = LoopStatusLabel.superview as? UIStackView {
  47. if ((TimeInterval(Date().timeIntervalSince1970) - lastLoopTime) / 60) > 15 {
  48. IsNotLooping = true
  49. // Change the distribution to 'fill' to allow manual resizing of arranged subviews
  50. statusStackView.distribution = .fill
  51. // Hide PredictionLabel and expand LoopStatusLabel to fill the entire stack view
  52. PredictionLabel.isHidden = true
  53. LoopStatusLabel.frame = CGRect(x: 0, y: 0, width: statusStackView.frame.width, height: statusStackView.frame.height)
  54. // Update LoopStatusLabel's properties to display Not Looping
  55. LoopStatusLabel.textAlignment = .center
  56. LoopStatusLabel.text = "⚠️ Not Looping!"
  57. LoopStatusLabel.textColor = UIColor.systemYellow
  58. LoopStatusLabel.font = UIFont.boldSystemFont(ofSize: 18)
  59. } else {
  60. IsNotLooping = false
  61. // Restore the original distribution and visibility of labels
  62. statusStackView.distribution = .fillEqually
  63. PredictionLabel.isHidden = false
  64. // Reset LoopStatusLabel's properties
  65. LoopStatusLabel.textAlignment = .right
  66. LoopStatusLabel.font = UIFont.systemFont(ofSize: 17)
  67. if UserDefaultsRepository.forceDarkMode.value {
  68. LoopStatusLabel.textColor = UIColor.white
  69. } else {
  70. LoopStatusLabel.textColor = UIColor.black
  71. }
  72. }
  73. }
  74. latestLoopTime = lastLoopTime
  75. }
  76. // NS Device Status Response Processor
  77. func updateDeviceStatusDisplay(jsonDeviceStatus: [[String:AnyObject]]) {
  78. self.clearLastInfoData(index: 0)
  79. self.clearLastInfoData(index: 1)
  80. self.clearLastInfoData(index: 3)
  81. self.clearLastInfoData(index: 4)
  82. self.clearLastInfoData(index: 5)
  83. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: device status") }
  84. if jsonDeviceStatus.count == 0 {
  85. return
  86. }
  87. //Process the current data first
  88. let lastDeviceStatus = jsonDeviceStatus[0] as [String : AnyObject]?
  89. //pump and uploader
  90. let formatter = ISO8601DateFormatter()
  91. formatter.formatOptions = [.withFullDate,
  92. .withTime,
  93. .withDashSeparatorInDate,
  94. .withColonSeparatorInTime]
  95. if let lastPumpRecord = lastDeviceStatus?["pump"] as! [String : AnyObject]? {
  96. if let lastPumpTime = formatter.date(from: (lastPumpRecord["clock"] as! String))?.timeIntervalSince1970 {
  97. if let reservoirData = lastPumpRecord["reservoir"] as? Double {
  98. latestPumpVolume = reservoirData
  99. tableData[5].value = String(format:"%.0f", reservoirData) + "U"
  100. } else {
  101. latestPumpVolume = 50.0
  102. tableData[5].value = "50+U"
  103. }
  104. if let uploader = lastDeviceStatus?["uploader"] as? [String:AnyObject] {
  105. let upbat = uploader["battery"] as! Double
  106. tableData[4].value = String(format:"%.0f", upbat) + "%"
  107. UserDefaultsRepository.deviceBatteryLevel.value = upbat
  108. }
  109. }
  110. }
  111. // Loop - handle new data
  112. if let lastLoopRecord = lastDeviceStatus?["loop"] as! [String : AnyObject]? {
  113. DeviceStatusLoop(formatter: formatter, lastLoopRecord: lastLoopRecord)
  114. }
  115. // OpenAPS - handle new data
  116. if let lastLoopRecord = lastDeviceStatus?["openaps"] as! [String : AnyObject]? {
  117. DeviceStatusOpenAPS(formatter: formatter, lastDeviceStatus: lastDeviceStatus, lastLoopRecord: lastLoopRecord)
  118. }
  119. var oText = "" as String
  120. currentOverride = 1.0
  121. if let lastOverride = lastDeviceStatus?["override"] as! [String : AnyObject]? {
  122. if lastOverride["active"] as! Bool {
  123. let lastCorrection = lastOverride["currentCorrectionRange"] as! [String: AnyObject]
  124. if let multiplier = lastOverride["multiplier"] as? Double {
  125. currentOverride = multiplier
  126. oText += String(format: "%.0f%%", (multiplier * 100))
  127. }
  128. else
  129. {
  130. oText += "100%"
  131. }
  132. oText += " ("
  133. let minValue = lastCorrection["minValue"] as! Double
  134. let maxValue = lastCorrection["maxValue"] as! Double
  135. oText += bgUnits.toDisplayUnits(String(minValue)) + "-" + bgUnits.toDisplayUnits(String(maxValue)) + ")"
  136. tableData[3].value = oText
  137. }
  138. }
  139. infoTable.reloadData()
  140. // Start the timer based on the timestamp
  141. let now = dateTimeUtils.getNowTimeIntervalUTC()
  142. let secondsAgo = now - latestLoopTime
  143. DispatchQueue.main.async {
  144. // if Loop is overdue over: 20:00, re-attempt every 5 minutes
  145. if secondsAgo >= (20 * 60) {
  146. self.startDeviceStatusTimer(time: (5 * 60))
  147. print("started 5 minute device status timer")
  148. // if the Loop is overdue: 10:00-19:59, re-attempt every minute
  149. } else if secondsAgo >= (10 * 60) {
  150. self.startDeviceStatusTimer(time: 60)
  151. print("started 1 minute device status timer")
  152. // if the Loop is overdue: 7:00-9:59, re-attempt every 30 seconds
  153. } else if secondsAgo >= (7 * 60) {
  154. self.startDeviceStatusTimer(time: 30)
  155. print("started 30 second device status timer")
  156. // if the Loop is overdue: 5:00-6:59 re-attempt every 10 seconds
  157. } else if secondsAgo >= (5 * 60) {
  158. self.startDeviceStatusTimer(time: 10)
  159. print("started 10 second device status timer")
  160. // We have a current Loop. Set timer to 5:10 from last reading
  161. } else {
  162. self.startDeviceStatusTimer(time: 310 - secondsAgo)
  163. let timerVal = 310 - secondsAgo
  164. print("started 5:10 device status timer: \(timerVal)")
  165. }
  166. }
  167. }
  168. }