DeviceStatus.swift 7.7 KB

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