DeviceStatus.swift 8.5 KB

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