DeviceStatus.swift 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. // LoopFollow
  2. // DeviceStatus.swift
  3. // Created by Jonas Björkert.
  4. import Charts
  5. import Foundation
  6. import UIKit
  7. extension MainViewController {
  8. func webLoadNSDeviceStatus() {
  9. Storage.shared.lastLoopingChecked.value = Date()
  10. let parameters = ["count": "1"]
  11. NightscoutUtils.executeDynamicRequest(eventType: .deviceStatus, parameters: parameters) { result in
  12. switch result {
  13. case let .success(json):
  14. if let jsonDeviceStatus = json as? [[String: AnyObject]] {
  15. DispatchQueue.main.async {
  16. self.updateDeviceStatusDisplay(jsonDeviceStatus: jsonDeviceStatus)
  17. }
  18. } else {
  19. self.handleDeviceStatusError()
  20. }
  21. case .failure:
  22. self.handleDeviceStatusError()
  23. }
  24. }
  25. }
  26. private func handleDeviceStatusError() {
  27. LogManager.shared.log(category: .deviceStatus, message: "Device status fetch failed!", limitIdentifier: "Device status fetch failed!")
  28. DispatchQueue.main.async {
  29. TaskScheduler.shared.rescheduleTask(id: .deviceStatus, to: Date().addingTimeInterval(10))
  30. self.evaluateNotLooping()
  31. }
  32. }
  33. func evaluateNotLooping() {
  34. guard let statusStackView = LoopStatusLabel.superview as? UIStackView else { return }
  35. guard let lastLoopTime = Observable.shared.alertLastLoopTime.value, lastLoopTime > 0 else {
  36. return
  37. }
  38. let now = TimeInterval(Date().timeIntervalSince1970)
  39. let nonLoopingTimeThreshold: TimeInterval = 15 * 60
  40. if IsNightscoutEnabled(), (now - lastLoopTime) >= nonLoopingTimeThreshold, lastLoopTime > 0 {
  41. IsNotLooping = true
  42. statusStackView.distribution = .fill
  43. PredictionLabel.isHidden = true
  44. LoopStatusLabel.frame = CGRect(x: 0, y: 0, width: statusStackView.frame.width, height: statusStackView.frame.height)
  45. LoopStatusLabel.textAlignment = .center
  46. LoopStatusLabel.text = "⚠️ Not Looping!"
  47. LoopStatusLabel.textColor = UIColor.systemYellow
  48. LoopStatusLabel.font = UIFont.boldSystemFont(ofSize: 18)
  49. } else {
  50. IsNotLooping = false
  51. statusStackView.distribution = .fillEqually
  52. PredictionLabel.isHidden = false
  53. LoopStatusLabel.textAlignment = .right
  54. LoopStatusLabel.font = UIFont.systemFont(ofSize: 17)
  55. if Storage.shared.forceDarkMode.value {
  56. LoopStatusLabel.textColor = UIColor.white
  57. } else {
  58. LoopStatusLabel.textColor = UIColor.black
  59. }
  60. }
  61. }
  62. // NS Device Status Response Processor
  63. func updateDeviceStatusDisplay(jsonDeviceStatus: [[String: AnyObject]]) {
  64. infoManager.clearInfoData(types: [.iob, .cob, .battery, .pump, .target, .isf, .carbRatio, .updated, .recBolus, .tdd])
  65. // For Loop, clear the current override here - For Trio, it is handled using treatments
  66. if Storage.shared.device.value == "Loop" {
  67. infoManager.clearInfoData(types: [.override])
  68. }
  69. if jsonDeviceStatus.count == 0 {
  70. LogManager.shared.log(category: .deviceStatus, message: "Device status is empty")
  71. TaskScheduler.shared.rescheduleTask(id: .deviceStatus, to: Date().addingTimeInterval(5 * 60))
  72. return
  73. }
  74. // Process the current data first
  75. let lastDeviceStatus = jsonDeviceStatus[0] as [String: AnyObject]?
  76. // pump and uploader
  77. let formatter = ISO8601DateFormatter()
  78. formatter.formatOptions = [.withFullDate,
  79. .withTime,
  80. .withDashSeparatorInDate,
  81. .withColonSeparatorInTime]
  82. if let lastPumpRecord = lastDeviceStatus?["pump"] as! [String: AnyObject]? {
  83. if let lastPumpTime = formatter.date(from: (lastPumpRecord["clock"] as! String))?.timeIntervalSince1970 {
  84. if let reservoirData = lastPumpRecord["reservoir"] as? Double {
  85. latestPumpVolume = reservoirData
  86. infoManager.updateInfoData(type: .pump, value: String(format: "%.0f", reservoirData) + "U")
  87. } else {
  88. latestPumpVolume = 50.0
  89. infoManager.updateInfoData(type: .pump, value: "50+U")
  90. }
  91. if let uploader = lastDeviceStatus?["uploader"] as? [String: AnyObject],
  92. let upbat = uploader["battery"] as? Double
  93. {
  94. let batteryText: String
  95. if let isCharging = uploader["isCharging"] as? Bool, isCharging {
  96. batteryText = "⚡️ " + String(format: "%.0f", upbat) + "%"
  97. } else {
  98. batteryText = String(format: "%.0f", upbat) + "%"
  99. }
  100. infoManager.updateInfoData(type: .battery, value: batteryText)
  101. Observable.shared.deviceBatteryLevel.value = upbat
  102. let timestamp = uploader["timestamp"] as? Date ?? Date()
  103. let currentBattery = DataStructs.batteryStruct(batteryLevel: upbat, timestamp: timestamp)
  104. deviceBatteryData.append(currentBattery)
  105. // store only the last 30 battery readings
  106. if deviceBatteryData.count > 30 {
  107. deviceBatteryData.removeFirst()
  108. }
  109. }
  110. }
  111. }
  112. // Loop - handle new data
  113. if let lastLoopRecord = lastDeviceStatus?["loop"] as! [String: AnyObject]? {
  114. DeviceStatusLoop(formatter: formatter, lastLoopRecord: lastLoopRecord)
  115. var oText = ""
  116. currentOverride = 1.0
  117. if let lastOverride = lastDeviceStatus?["override"] as? [String: AnyObject],
  118. let isActive = lastOverride["active"] as? Bool, isActive
  119. {
  120. if let lastCorrection = lastOverride["currentCorrectionRange"] as? [String: AnyObject],
  121. let minValue = lastCorrection["minValue"] as? Double,
  122. let maxValue = lastCorrection["maxValue"] as? Double
  123. {
  124. if let multiplier = lastOverride["multiplier"] as? Double {
  125. currentOverride = multiplier
  126. oText += String(format: "%.0f%%", multiplier * 100)
  127. } else {
  128. oText += "100%"
  129. }
  130. oText += " ("
  131. oText += Localizer.toDisplayUnits(String(minValue)) + "-" + Localizer.toDisplayUnits(String(maxValue)) + ")"
  132. }
  133. infoManager.updateInfoData(type: .override, value: oText)
  134. } else {
  135. infoManager.clearInfoData(type: .override)
  136. }
  137. }
  138. // OpenAPS - handle new data
  139. if let lastLoopRecord = lastDeviceStatus?["openaps"] as! [String: AnyObject]? {
  140. DeviceStatusOpenAPS(formatter: formatter, lastDeviceStatus: lastDeviceStatus, lastLoopRecord: lastLoopRecord)
  141. }
  142. // Start the timer based on the timestamp
  143. let now = dateTimeUtils.getNowTimeIntervalUTC()
  144. let secondsAgo = now - (Observable.shared.alertLastLoopTime.value ?? 0)
  145. DispatchQueue.main.async {
  146. if secondsAgo >= (20 * 60) {
  147. TaskScheduler.shared.rescheduleTask(
  148. id: .deviceStatus,
  149. to: Date().addingTimeInterval(5 * 60)
  150. )
  151. } else if secondsAgo >= (10 * 60) {
  152. TaskScheduler.shared.rescheduleTask(
  153. id: .deviceStatus,
  154. to: Date().addingTimeInterval(60)
  155. )
  156. } else if secondsAgo >= (7 * 60) {
  157. TaskScheduler.shared.rescheduleTask(
  158. id: .deviceStatus,
  159. to: Date().addingTimeInterval(30)
  160. )
  161. } else if secondsAgo >= (5 * 60) {
  162. TaskScheduler.shared.rescheduleTask(
  163. id: .deviceStatus,
  164. to: Date().addingTimeInterval(10)
  165. )
  166. } else {
  167. let interval = (310 - secondsAgo)
  168. TaskScheduler.shared.rescheduleTask(
  169. id: .deviceStatus,
  170. to: Date().addingTimeInterval(interval)
  171. )
  172. TaskScheduler.shared.rescheduleTask(id: .alarmCheck, to: Date())
  173. }
  174. }
  175. evaluateNotLooping()
  176. LogManager.shared.log(category: .deviceStatus, message: "Update Device Status done", isDebug: true)
  177. }
  178. }