DeviceStatus.swift 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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. let parameters: [String: String] = ["count": "1"]
  15. NightscoutUtils.executeDynamicRequest(eventType: .deviceStatus, parameters: parameters) { result in
  16. switch result {
  17. case .success(let json):
  18. if let jsonDeviceStatus = json as? [[String: AnyObject]] {
  19. DispatchQueue.main.async {
  20. self.updateDeviceStatusDisplay(jsonDeviceStatus: jsonDeviceStatus)
  21. }
  22. } else {
  23. self.handleDeviceStatusError()
  24. }
  25. case .failure:
  26. self.handleDeviceStatusError()
  27. }
  28. }
  29. }
  30. private func handleDeviceStatusError() {
  31. DispatchQueue.main.async {
  32. TaskScheduler.shared.rescheduleTask(
  33. id: .deviceStatus,
  34. to: Date().addingTimeInterval(10)
  35. )
  36. }
  37. }
  38. func evaluateNotLooping(lastLoopTime: TimeInterval) {
  39. if let statusStackView = LoopStatusLabel.superview as? UIStackView {
  40. if ((TimeInterval(Date().timeIntervalSince1970) - lastLoopTime) / 60) > 15 {
  41. IsNotLooping = true
  42. // Change the distribution to 'fill' to allow manual resizing of arranged subviews
  43. statusStackView.distribution = .fill
  44. // Hide PredictionLabel and expand LoopStatusLabel to fill the entire stack view
  45. PredictionLabel.isHidden = true
  46. LoopStatusLabel.frame = CGRect(x: 0, y: 0, width: statusStackView.frame.width, height: statusStackView.frame.height)
  47. // Update LoopStatusLabel's properties to display Not Looping
  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. // Restore the original distribution and visibility of labels
  55. statusStackView.distribution = .fillEqually
  56. PredictionLabel.isHidden = false
  57. // Reset LoopStatusLabel's properties
  58. LoopStatusLabel.textAlignment = .right
  59. LoopStatusLabel.font = UIFont.systemFont(ofSize: 17)
  60. if UserDefaultsRepository.forceDarkMode.value {
  61. LoopStatusLabel.textColor = UIColor.white
  62. } else {
  63. LoopStatusLabel.textColor = UIColor.black
  64. }
  65. }
  66. }
  67. latestLoopTime = lastLoopTime
  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. return
  74. }
  75. //Process the current data first
  76. let lastDeviceStatus = jsonDeviceStatus[0] as [String : AnyObject]?
  77. //pump and uploader
  78. let formatter = ISO8601DateFormatter()
  79. formatter.formatOptions = [.withFullDate,
  80. .withTime,
  81. .withDashSeparatorInDate,
  82. .withColonSeparatorInTime]
  83. if let lastPumpRecord = lastDeviceStatus?["pump"] as! [String : AnyObject]? {
  84. if let lastPumpTime = formatter.date(from: (lastPumpRecord["clock"] as! String))?.timeIntervalSince1970 {
  85. if let reservoirData = lastPumpRecord["reservoir"] as? Double {
  86. latestPumpVolume = reservoirData
  87. infoManager.updateInfoData(type: .pump, value: String(format: "%.0f", reservoirData) + "U")
  88. } else {
  89. latestPumpVolume = 50.0
  90. infoManager.updateInfoData(type: .pump, value: "50+U")
  91. }
  92. if let uploader = lastDeviceStatus?["uploader"] as? [String: AnyObject],
  93. let upbat = uploader["battery"] as? Double {
  94. infoManager.updateInfoData(type: .battery, value: String(format: "%.0f", upbat) + "%")
  95. UserDefaultsRepository.deviceBatteryLevel.value = upbat
  96. }
  97. }
  98. }
  99. // Loop - handle new data
  100. if let lastLoopRecord = lastDeviceStatus?["loop"] as! [String : AnyObject]? {
  101. DeviceStatusLoop(formatter: formatter, lastLoopRecord: lastLoopRecord)
  102. var oText = ""
  103. currentOverride = 1.0
  104. if let lastOverride = lastDeviceStatus?["override"] as? [String: AnyObject],
  105. let isActive = lastOverride["active"] as? Bool, isActive {
  106. if let lastCorrection = lastOverride["currentCorrectionRange"] as? [String: AnyObject],
  107. let minValue = lastCorrection["minValue"] as? Double,
  108. let maxValue = lastCorrection["maxValue"] as? Double {
  109. if let multiplier = lastOverride["multiplier"] as? Double {
  110. currentOverride = multiplier
  111. oText += String(format: "%.0f%%", (multiplier * 100))
  112. } else {
  113. oText += "100%"
  114. }
  115. oText += " ("
  116. oText += Localizer.toDisplayUnits(String(minValue)) + "-" + Localizer.toDisplayUnits(String(maxValue)) + ")"
  117. }
  118. infoManager.updateInfoData(type: .override, value: oText)
  119. } else {
  120. infoManager.clearInfoData(type: .override)
  121. }
  122. }
  123. // OpenAPS - handle new data
  124. if let lastLoopRecord = lastDeviceStatus?["openaps"] as! [String : AnyObject]? {
  125. DeviceStatusOpenAPS(formatter: formatter, lastDeviceStatus: lastDeviceStatus, lastLoopRecord: lastLoopRecord)
  126. }
  127. // Start the timer based on the timestamp
  128. let now = dateTimeUtils.getNowTimeIntervalUTC()
  129. let secondsAgo = now - latestLoopTime
  130. DispatchQueue.main.async {
  131. if secondsAgo >= (20 * 60) {
  132. TaskScheduler.shared.rescheduleTask(
  133. id: .deviceStatus,
  134. to: Date().addingTimeInterval(5 * 60)
  135. )
  136. } else if secondsAgo >= (10 * 60) {
  137. TaskScheduler.shared.rescheduleTask(
  138. id: .deviceStatus,
  139. to: Date().addingTimeInterval(60)
  140. )
  141. } else if secondsAgo >= (7 * 60) {
  142. TaskScheduler.shared.rescheduleTask(
  143. id: .deviceStatus,
  144. to: Date().addingTimeInterval(30)
  145. )
  146. } else if secondsAgo >= (5 * 60) {
  147. TaskScheduler.shared.rescheduleTask(
  148. id: .deviceStatus,
  149. to: Date().addingTimeInterval(10)
  150. )
  151. } else {
  152. let interval = (310 - secondsAgo)
  153. TaskScheduler.shared.rescheduleTask(
  154. id: .deviceStatus,
  155. to: Date().addingTimeInterval(interval)
  156. )
  157. }
  158. }
  159. }
  160. }