OpenAPS.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import Foundation
  2. import JavaScriptCore
  3. final class OpenAPS {
  4. private let jsWorker = JavaScriptWorker()
  5. private let processQueue = DispatchQueue(label: "OpenAPS.processQueue", qos: .utility)
  6. private let storage: FileStorage
  7. init(storage: FileStorage) {
  8. self.storage = storage
  9. }
  10. func test() {
  11. processQueue.async {
  12. let now = Date()
  13. print("START at \(now)")
  14. let pumphistory = self.loadJSON(name: "pumphistory")
  15. let profile = self.loadJSON(name: "profile")
  16. let basalProfile = self.loadJSON(name: "basal_profile")
  17. let clock = self.loadJSON(name: "clock")
  18. let carbs = self.loadJSON(name: "carbhistory")
  19. let glucose = self.loadJSON(name: "glucose")
  20. let currentTemp = self.loadJSON(name: "temp_basal")
  21. let reservoir = 100
  22. let tsMilliseconds: Double = 1_527_924_300_000
  23. let preferences = self.exportDefaultPreferences()
  24. print("DEFAULT PREFERENCES: \(preferences)")
  25. let autosensResult = self.autosense(
  26. pumpHistory: pumphistory,
  27. profile: profile,
  28. carbs: carbs,
  29. glucose: glucose,
  30. basalprofile: basalProfile,
  31. temptargets: RawJSON.null
  32. )
  33. print("AUTOSENS: \(autosensResult)")
  34. try? self.storage.save(autosensResult, as: Settings.autosense)
  35. let iobResult = self.iob(
  36. pumphistory: pumphistory,
  37. profile: profile,
  38. clock: clock,
  39. autosens: autosensResult,
  40. pumphistory24: RawJSON.null
  41. )
  42. print("IOB: \(iobResult)")
  43. let mealResult = self.meal(
  44. pumphistory: pumphistory,
  45. profile: profile,
  46. basalProfile: basalProfile,
  47. clock: clock,
  48. carbs: carbs,
  49. glucose: glucose
  50. )
  51. print("MEAL: \(mealResult)")
  52. let glucoseStatus = self.glucoseGetLast(glucose: glucose)
  53. print("GLUCOSE STATUS: \(glucoseStatus)")
  54. let suggested = self.determineBasal(
  55. glucoseStatus: glucoseStatus,
  56. currentTemp: currentTemp,
  57. iob: iobResult,
  58. profile: profile,
  59. aurosens: autosensResult,
  60. meal: mealResult,
  61. microBolusAllowed: true,
  62. reservoir: reservoir,
  63. tsMilliseconds: tsMilliseconds
  64. )
  65. print("SUGGESTED: \(suggested)")
  66. let autotunePreppedGlucose = self.autotunePrepare(
  67. pumphistory: pumphistory,
  68. profile: profile,
  69. glucose: glucose,
  70. pumpprofile: profile,
  71. categorizeUamAsBasal: true,
  72. tuneInsulinCurve: false
  73. )
  74. print("AUTOTUNE PREP: \(autotunePreppedGlucose)")
  75. let previousAutotune = try? self.storage.retrieve(Settings.autotune, as: RawJSON.self)
  76. let autotuneResult = self.autotuneRun(
  77. autotunePreparedData: autotunePreppedGlucose,
  78. previousAutotuneResult: previousAutotune ?? profile,
  79. pumpProfile: profile
  80. )
  81. try? self.storage.save(autotuneResult, as: Settings.autotune)
  82. print("AUTOTUNE RESULT: \(autotuneResult)")
  83. let finishDate = Date()
  84. print("FINISH at \(finishDate), duration \(finishDate.timeIntervalSince(now)) s")
  85. }
  86. }
  87. func makeProfile(autotuned: Bool) {
  88. processQueue.async {
  89. print("MAKE PROFILE autotuned \(autotuned)")
  90. let preferences = self.loadFileFromStorage(name: Settings.preferences)
  91. let pumpSettings = self.loadFileFromStorage(name: Settings.settings)
  92. let bgTargets = self.loadFileFromStorage(name: Settings.bgTargets)
  93. let basalProfile = self.loadFileFromStorage(name: Settings.basalProfile)
  94. let isf = self.loadFileFromStorage(name: Settings.insulinSensitivities)
  95. let cr = self.loadFileFromStorage(name: Settings.carbRatios)
  96. let tempTargets = self.loadFileFromStorage(name: Settings.tempTargets)
  97. let model = self.loadFileFromStorage(name: Settings.model)
  98. let autotune = self.loadFileFromStorage(name: Settings.autotune)
  99. let profile = self.makeProfile(
  100. preferences: preferences,
  101. pumpSettings: pumpSettings,
  102. bgTargets: bgTargets,
  103. basalProfile: basalProfile,
  104. isf: isf,
  105. carbRatio: cr,
  106. tempTargets: tempTargets,
  107. model: model,
  108. autotune: autotuned ? autotune : .null
  109. )
  110. print("PROFILE RESULT \n\(profile)")
  111. if autotuned {
  112. try? self.storage.save(profile, as: Settings.profile)
  113. } else {
  114. try? self.storage.save(profile, as: Settings.pumpProfile)
  115. }
  116. }
  117. }
  118. private func iob(pumphistory: JSON, profile: JSON, clock: JSON, autosens: JSON, pumphistory24: JSON) -> RawJSON {
  119. dispatchPrecondition(condition: .onQueue(processQueue))
  120. return jsWorker.inCommonContext { worker in
  121. worker.evaluate(script: Script(name: Bundle.iob))
  122. worker.evaluate(script: Script(name: Prepare.iob))
  123. return worker.call(function: Function.generate, with: [
  124. pumphistory,
  125. profile,
  126. clock,
  127. autosens,
  128. pumphistory24
  129. ])
  130. }
  131. }
  132. private func meal(pumphistory: JSON, profile: JSON, basalProfile: JSON, clock: JSON, carbs: JSON, glucose: JSON) -> RawJSON {
  133. dispatchPrecondition(condition: .onQueue(processQueue))
  134. return jsWorker.inCommonContext { worker in
  135. worker.evaluate(script: Script(name: Bundle.meal))
  136. worker.evaluate(script: Script(name: Prepare.meal))
  137. return worker.call(function: Function.generate, with: [
  138. pumphistory,
  139. profile,
  140. basalProfile,
  141. clock,
  142. carbs,
  143. glucose
  144. ])
  145. }
  146. }
  147. private func autotunePrepare(
  148. pumphistory: JSON,
  149. profile: JSON,
  150. glucose: JSON,
  151. pumpprofile: JSON,
  152. categorizeUamAsBasal: Bool,
  153. tuneInsulinCurve: Bool
  154. ) -> RawJSON {
  155. dispatchPrecondition(condition: .onQueue(processQueue))
  156. return jsWorker.inCommonContext { worker in
  157. worker.evaluate(script: Script(name: Bundle.autotunePrep))
  158. worker.evaluate(script: Script(name: Prepare.autotunePrep))
  159. return worker.call(function: Function.generate, with: [
  160. pumphistory,
  161. profile,
  162. glucose,
  163. pumpprofile,
  164. categorizeUamAsBasal,
  165. tuneInsulinCurve
  166. ])
  167. }
  168. }
  169. private func autotuneRun(
  170. autotunePreparedData: JSON,
  171. previousAutotuneResult: JSON,
  172. pumpProfile: JSON
  173. ) -> RawJSON {
  174. dispatchPrecondition(condition: .onQueue(processQueue))
  175. return jsWorker.inCommonContext { worker in
  176. worker.evaluate(script: Script(name: Bundle.autotuneCore))
  177. worker.evaluate(script: Script(name: Prepare.autotuneCore))
  178. return worker.call(function: Function.generate, with: [
  179. autotunePreparedData,
  180. previousAutotuneResult,
  181. pumpProfile
  182. ])
  183. }
  184. }
  185. private func glucoseGetLast(glucose: JSON) -> RawJSON {
  186. dispatchPrecondition(condition: .onQueue(processQueue))
  187. return jsWorker.inCommonContext { worker in
  188. worker.evaluate(script: Script(name: Bundle.getLastGlucose))
  189. return worker.call(function: Function.freeaps, with: [glucose])
  190. }
  191. }
  192. private func determineBasal(
  193. glucoseStatus: JSON,
  194. currentTemp: JSON,
  195. iob: JSON,
  196. profile: JSON,
  197. aurosens: JSON,
  198. meal: JSON,
  199. microBolusAllowed: Bool,
  200. reservoir: Int,
  201. tsMilliseconds: Double
  202. ) -> RawJSON {
  203. dispatchPrecondition(condition: .onQueue(processQueue))
  204. return jsWorker.inCommonContext { worker in
  205. worker.evaluate(script: Script(name: Bundle.basalSetTemp))
  206. worker.evaluate(script: Script(name: Prepare.determineBasal))
  207. worker.evaluate(script: Script(name: Bundle.determineBasal))
  208. return worker.call(
  209. function: Function.freeaps,
  210. with: [
  211. glucoseStatus,
  212. currentTemp,
  213. iob,
  214. profile,
  215. aurosens,
  216. meal,
  217. Function.tempBasalFunctions,
  218. microBolusAllowed,
  219. reservoir,
  220. tsMilliseconds
  221. ]
  222. )
  223. }
  224. }
  225. private func autosense(
  226. pumpHistory: JSON,
  227. profile: JSON,
  228. carbs: JSON,
  229. glucose: JSON,
  230. basalprofile: JSON,
  231. temptargets: JSON
  232. ) -> RawJSON {
  233. dispatchPrecondition(condition: .onQueue(processQueue))
  234. return jsWorker.inCommonContext { worker in
  235. worker.evaluate(script: Script(name: Bundle.autosens))
  236. worker.evaluate(script: Script(name: Prepare.autosens))
  237. return worker.call(
  238. function: Function.generate,
  239. with: [
  240. pumpHistory,
  241. profile,
  242. carbs,
  243. glucose,
  244. basalprofile,
  245. temptargets
  246. ]
  247. )
  248. }
  249. }
  250. private func exportDefaultPreferences() -> RawJSON {
  251. dispatchPrecondition(condition: .onQueue(processQueue))
  252. return jsWorker.inCommonContext { worker in
  253. worker.evaluate(script: Script(name: Bundle.profile))
  254. worker.evaluate(script: Script(name: Prepare.profile))
  255. return worker.call(function: Function.exportDefaults, with: [])
  256. }
  257. }
  258. private func makeProfile(
  259. preferences: JSON,
  260. pumpSettings: JSON,
  261. bgTargets: JSON,
  262. basalProfile: JSON,
  263. isf: JSON,
  264. carbRatio: JSON,
  265. tempTargets: JSON,
  266. model: JSON,
  267. autotune: JSON
  268. ) -> RawJSON {
  269. dispatchPrecondition(condition: .onQueue(processQueue))
  270. return jsWorker.inCommonContext { worker in
  271. worker.evaluate(script: Script(name: Bundle.profile))
  272. worker.evaluate(script: Script(name: Prepare.profile))
  273. return worker.call(
  274. function: Function.generate,
  275. with: [
  276. preferences,
  277. pumpSettings,
  278. bgTargets,
  279. basalProfile,
  280. isf,
  281. carbRatio,
  282. tempTargets,
  283. model,
  284. autotune
  285. ]
  286. )
  287. }
  288. }
  289. private func loadJSON(name: String) -> String {
  290. try! String(contentsOf: Foundation.Bundle.main.url(forResource: "json/\(name)", withExtension: "json")!)
  291. }
  292. private func loadFileFromStorage(name: String) -> RawJSON {
  293. (try? storage.retrieve(name, as: RawJSON.self)) ?? OpenAPS.defaults(for: name)
  294. }
  295. static func defaults(for file: String) -> RawJSON {
  296. guard let url = Foundation.Bundle.main.url(forResource: "json/defaults/\(file)", withExtension: "") else {
  297. return ""
  298. }
  299. return (try? String(contentsOf: url)) ?? ""
  300. }
  301. }