DeviceStatusOpenAPS.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. //
  2. // DeviceStatusOpenAPS.swift
  3. // LoopFollow
  4. //
  5. // Created by Jonas Björkert on 2024-05-19.
  6. // Copyright © 2024 Jon Fawcett. All rights reserved.
  7. //
  8. import Foundation
  9. import UIKit
  10. import HealthKit
  11. extension MainViewController {
  12. func DeviceStatusOpenAPS(formatter: ISO8601DateFormatter, lastDeviceStatus: [String: AnyObject]?, lastLoopRecord: [String: AnyObject]) {
  13. if let lastLoopTime = formatter.date(from: (lastDeviceStatus?["created_at"] as! String))?.timeIntervalSince1970 {
  14. UserDefaultsRepository.alertLastLoopTime.value = lastLoopTime
  15. if lastLoopRecord["failureReason"] != nil {
  16. LoopStatusLabel.text = "X"
  17. latestLoopStatusString = "X"
  18. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Loop Failure: X") }
  19. } else {
  20. var wasEnacted = false
  21. if let enacted = lastLoopRecord["enacted"] as? [String: AnyObject] {
  22. wasEnacted = true
  23. }
  24. var determinedUnit: HKUnit = .millimolesPerLiter
  25. // Determine the unit based on the target value since no unit is provided
  26. if let enacted = lastLoopRecord["enacted"] as? [String: AnyObject],
  27. let enactedTargetValue = enacted["current_target"] as? Double {
  28. if enactedTargetValue > 40 {
  29. determinedUnit = .milligramsPerDeciliter
  30. }
  31. }
  32. /*
  33. Updated
  34. */
  35. if let enactedTimestamp = lastLoopRecord["enacted"]?["timestamp"] as? String,
  36. let enactedTime = formatter.date(from: enactedTimestamp)?.timeIntervalSince1970 {
  37. let formattedTime = Localizer.formatTimestampToLocalString(enactedTime)
  38. infoManager.updateInfoData(type: .updated, value: formattedTime)
  39. }
  40. /*
  41. ISF
  42. */
  43. let profileISF = profileManager.currentISF()
  44. var enactedISF: String?
  45. if let enacted = lastLoopRecord["enacted"] as? [String: AnyObject],
  46. let enactedISFValue = enacted["ISF"] as? Double {
  47. enactedISF = Localizer.formatLocalDouble(enactedISFValue)
  48. }
  49. if let profileISF = profileISF, let enactedISF = enactedISF, profileISF != enactedISF {
  50. infoManager.updateInfoData(type: .isf, value: "\(profileISF) → \(enactedISF)")
  51. } else if let profileISF = profileISF {
  52. infoManager.updateInfoData(type: .isf, value: profileISF)
  53. }
  54. /*
  55. Carb Ratio (CR)
  56. */
  57. let profileCR = profileManager.currentCarbRatio()
  58. var enactedCR: String?
  59. if let reasonString = lastLoopRecord["enacted"]?["reason"] as? String {
  60. let pattern = "CR: (\\d+(?:\\.\\d+)?)"
  61. if let regex = try? NSRegularExpression(pattern: pattern) {
  62. let nsString = reasonString as NSString
  63. if let match = regex.firstMatch(in: reasonString, range: NSRange(location: 0, length: nsString.length)) {
  64. let crString = nsString.substring(with: match.range(at: 1))
  65. if let crValue = Double(crString) {
  66. enactedCR = Localizer.formatToLocalizedString(crValue)
  67. }
  68. }
  69. }
  70. }
  71. if let profileCR = profileCR, let enactedCR = enactedCR, profileCR != enactedCR {
  72. infoManager.updateInfoData(type: .carbRatio, value: "\(profileCR) → \(enactedCR)")
  73. } else if let profileCR = profileCR {
  74. infoManager.updateInfoData(type: .carbRatio, value: profileCR)
  75. }
  76. if let iobdata = lastLoopRecord["iob"] as? [String: AnyObject],
  77. let iobValue = iobdata["iob"] as? Double {
  78. let formattedIOB = String(format: "%.2f", iobValue)
  79. infoManager.updateInfoData(type: .iob, value: formattedIOB)
  80. latestIOB = formattedIOB
  81. }
  82. if let cobdata = lastLoopRecord["enacted"] as? [String: AnyObject],
  83. let cobValue = cobdata["COB"] as? Double {
  84. let formattedCOB = String(format: "%.0f", cobValue)
  85. infoManager.updateInfoData(type: .cob, value: formattedCOB)
  86. latestCOB = formattedCOB
  87. }
  88. if let recbolusdata = lastLoopRecord["enacted"] as? [String: AnyObject],
  89. let insulinReq = recbolusdata["insulinReq"] as? Double {
  90. let formattedRecBolus = String(format: "%.2fU", insulinReq)
  91. infoManager.updateInfoData(type: .recBolus, value: formattedRecBolus)
  92. UserDefaultsRepository.deviceRecBolus.value = insulinReq
  93. } else {
  94. infoManager.updateInfoData(type: .recBolus, value: "N/A")
  95. UserDefaultsRepository.deviceRecBolus.value = 0
  96. }
  97. if let autosensdata = lastLoopRecord["enacted"] as? [String: AnyObject],
  98. let sens = autosensdata["sensitivityRatio"] as? Double {
  99. let formattedSens = String(format: "%.0f", sens * 100.0) + "%"
  100. infoManager.updateInfoData(type: .autosens, value: formattedSens)
  101. }
  102. if let eventualdata = lastLoopRecord["enacted"] as? [String: AnyObject] {
  103. if let eventualBGValue = eventualdata["eventualBG"] as? NSNumber {
  104. let eventualBGStringValue = String(describing: eventualBGValue)
  105. PredictionLabel.text = Localizer.toDisplayUnits(eventualBGStringValue)
  106. }
  107. }
  108. /*
  109. Target
  110. */
  111. let profileTargetHigh = profileManager.currentTargetHigh()
  112. var enactedTarget: String?
  113. if let enacted = lastLoopRecord["enacted"] as? [String: AnyObject],
  114. let enactedTargetValue = enacted["current_target"] as? Double {
  115. enactedTarget = Localizer.toDisplayUnits(String(enactedTargetValue))
  116. }
  117. if let profileTargetHigh = profileTargetHigh, let enactedTarget = enactedTarget, profileTargetHigh != enactedTarget {
  118. infoManager.updateInfoData(type: .target, value: "\(profileTargetHigh) → \(enactedTarget)")
  119. } else if let profileTargetHigh = profileTargetHigh {
  120. infoManager.updateInfoData(type: .target, value: profileTargetHigh)
  121. }
  122. var predictioncolor = UIColor.systemGray
  123. PredictionLabel.textColor = predictioncolor
  124. topPredictionBG = UserDefaultsRepository.minBGScale.value
  125. if let enactdata = lastLoopRecord["enacted"] as? [String: AnyObject],
  126. let predbgdata = enactdata["predBGs"] as? [String: AnyObject] {
  127. let predictionTypes: [(type: String, colorName: String, dataIndex: Int)] = [
  128. ("ZT", "ZT", 12),
  129. ("IOB", "Insulin", 13),
  130. ("COB", "LoopYellow", 14),
  131. ("UAM", "UAM", 15)
  132. ]
  133. var minPredBG = Double.infinity
  134. var maxPredBG = -Double.infinity
  135. for (type, colorName, dataIndex) in predictionTypes {
  136. var predictionData = [ShareGlucoseData]()
  137. if let graphdata = predbgdata[type] as? [Double] {
  138. var predictionTime = lastLoopTime
  139. let toLoad = Int(UserDefaultsRepository.predictionToLoad.value * 12)
  140. for i in 0...toLoad {
  141. if i < graphdata.count {
  142. let predictionValue = graphdata[i]
  143. minPredBG = min(minPredBG, predictionValue)
  144. maxPredBG = max(maxPredBG, predictionValue)
  145. let prediction = ShareGlucoseData(sgv: Int(round(predictionValue)), date: predictionTime, direction: "flat")
  146. predictionData.append(prediction)
  147. predictionTime += 300
  148. }
  149. }
  150. }
  151. let color = UIColor(named: colorName) ?? UIColor.systemPurple
  152. updatePredictionGraphGeneric(
  153. dataIndex: dataIndex,
  154. predictionData: predictionData,
  155. chartLabel: type,
  156. color: color
  157. )
  158. }
  159. if minPredBG != Double.infinity && maxPredBG != -Double.infinity {
  160. let value = "\(Localizer.toDisplayUnits(String(minPredBG)))/\(Localizer.toDisplayUnits(String(maxPredBG)))"
  161. infoManager.updateInfoData(type: .minMax, value: value)
  162. } else {
  163. infoManager.updateInfoData(type: .minMax, value: "N/A")
  164. }
  165. }
  166. if let loopStatus = lastLoopRecord["recommendedTempBasal"] as? [String: AnyObject] {
  167. if let tempBasalTime = formatter.date(from: (loopStatus["timestamp"] as! String))?.timeIntervalSince1970 {
  168. var lastBGTime = lastLoopTime
  169. if bgData.count > 0 {
  170. lastBGTime = bgData[bgData.count - 1].date
  171. }
  172. if tempBasalTime > lastBGTime && !wasEnacted {
  173. LoopStatusLabel.text = "⏀"
  174. latestLoopStatusString = "⏀"
  175. } else {
  176. LoopStatusLabel.text = "↻"
  177. latestLoopStatusString = "↻"
  178. }
  179. }
  180. } else {
  181. LoopStatusLabel.text = "↻"
  182. latestLoopStatusString = "↻"
  183. }
  184. }
  185. evaluateNotLooping(lastLoopTime: lastLoopTime)
  186. }
  187. }
  188. }