OpenAPS.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  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 loop() {
  11. processQueue.async {
  12. // status
  13. // check it before
  14. // profile
  15. let preferences = self.loadFileFromStorage(name: Settings.preferences)
  16. let pumpSettings = self.loadFileFromStorage(name: Settings.settings)
  17. let bgTargets = self.loadFileFromStorage(name: Settings.bgTargets)
  18. let basalProfile = self.loadFileFromStorage(name: Settings.basalProfile)
  19. let isf = self.loadFileFromStorage(name: Settings.insulinSensitivities)
  20. let cr = self.loadFileFromStorage(name: Settings.carbRatios)
  21. let tempTargets = self.loadFileFromStorage(name: Settings.tempTargets)
  22. let model = self.loadFileFromStorage(name: Settings.model)
  23. let autotune = self.loadFileFromStorage(name: Settings.autotune)
  24. let pumpProfile = self.makeProfile(
  25. preferences: preferences,
  26. pumpSettings: pumpSettings,
  27. bgTargets: bgTargets,
  28. basalProfile: basalProfile,
  29. isf: isf,
  30. carbRatio: cr,
  31. tempTargets: tempTargets,
  32. model: model,
  33. autotune: RawJSON.null
  34. )
  35. let profile = self.makeProfile(
  36. preferences: preferences,
  37. pumpSettings: pumpSettings,
  38. bgTargets: bgTargets,
  39. basalProfile: basalProfile,
  40. isf: isf,
  41. carbRatio: cr,
  42. tempTargets: tempTargets,
  43. model: model,
  44. autotune: autotune.isEmpty ? .null : autotune
  45. )
  46. try? self.storage.save(pumpProfile, as: Settings.pumpProfile)
  47. try? self.storage.save(profile, as: Settings.profile)
  48. // clock
  49. try? self.storage.save(Date(), as: Monitor.clock)
  50. // temp_basal
  51. let tempBasal = self.loadFileFromStorage(name: Monitor.tempBasal)
  52. // meal
  53. let pumpHistory = self.loadFileFromStorage(name: OpenAPS.Monitor.pumpHistory)
  54. let carbs = self.loadFileFromStorage(name: Monitor.carbHistory)
  55. let glucose = self.loadFileFromStorage(name: Monitor.glucose)
  56. let clock = self.loadFileFromStorage(name: Monitor.clock)
  57. let meal = self.meal(
  58. pumphistory: pumpHistory,
  59. profile: profile,
  60. basalProfile: basalProfile,
  61. clock: clock,
  62. carbs: carbs,
  63. glucose: glucose
  64. )
  65. try? self.storage.save(meal, as: Monitor.meal)
  66. // iob
  67. let autosens = self.loadFileFromStorage(name: Settings.autosense)
  68. let iob = self.iob(
  69. pumphistory: pumpHistory,
  70. profile: profile,
  71. clock: clock,
  72. autosens: autosens.isEmpty ? .null : autosens
  73. )
  74. try? self.storage.save(iob, as: Monitor.iob)
  75. // determine-basal
  76. let reservoir = self.loadFileFromStorage(name: Monitor.reservoir)
  77. let suggested = self.determineBasal(
  78. glucose: glucose,
  79. currentTemp: tempBasal,
  80. iob: iob,
  81. profile: profile,
  82. autosens: autosens.isEmpty ? .null : autosens,
  83. meal: meal,
  84. microBolusAllowed: true,
  85. reservoir: reservoir
  86. )
  87. print("SUGGESTED: \(suggested)")
  88. try? self.storage.save(suggested, as: Enact.suggested)
  89. }
  90. }
  91. func test() {
  92. processQueue.async {
  93. let now = Date()
  94. print("START at \(now)")
  95. let pumphistory = self.loadJSON(name: "pumphistory")
  96. let profile = self.loadJSON(name: "profile")
  97. let basalProfile = self.loadJSON(name: "basal_profile")
  98. let clock = self.loadJSON(name: "clock")
  99. let carbs = self.loadJSON(name: "carbhistory")
  100. let glucose = self.loadJSON(name: "glucose")
  101. let currentTemp = self.loadJSON(name: "temp_basal")
  102. let reservoir = 100
  103. let preferences = self.exportDefaultPreferences()
  104. print("DEFAULT PREFERENCES: \(preferences)")
  105. let autosensResult = self.autosense(
  106. pumpHistory: pumphistory,
  107. profile: profile,
  108. carbs: carbs,
  109. glucose: glucose,
  110. basalprofile: basalProfile,
  111. temptargets: RawJSON.null
  112. )
  113. print("AUTOSENS: \(autosensResult)")
  114. try? self.storage.save(autosensResult, as: Settings.autosense)
  115. let iobResult = self.iob(
  116. pumphistory: pumphistory,
  117. profile: profile,
  118. clock: clock,
  119. autosens: autosensResult
  120. )
  121. print("IOB: \(iobResult)")
  122. let mealResult = self.meal(
  123. pumphistory: pumphistory,
  124. profile: profile,
  125. basalProfile: basalProfile,
  126. clock: clock,
  127. carbs: carbs,
  128. glucose: glucose
  129. )
  130. print("MEAL: \(mealResult)")
  131. try? self.storage.save(mealResult, as: Monitor.meal)
  132. let suggested = self.determineBasal(
  133. glucose: glucose,
  134. currentTemp: currentTemp,
  135. iob: iobResult,
  136. profile: profile,
  137. aurosens: autosensResult,
  138. meal: mealResult,
  139. microBolusAllowed: true,
  140. reservoir: reservoir
  141. )
  142. print("SUGGESTED: \(suggested)")
  143. let autotunePreppedGlucose = self.autotunePrepare(
  144. pumphistory: pumphistory,
  145. profile: profile,
  146. glucose: glucose,
  147. pumpprofile: profile,
  148. categorizeUamAsBasal: true,
  149. tuneInsulinCurve: false
  150. )
  151. print("AUTOTUNE PREP: \(autotunePreppedGlucose)")
  152. let previousAutotune = try? self.storage.retrieve(Settings.autotune, as: RawJSON.self)
  153. let autotuneResult = self.autotuneRun(
  154. autotunePreparedData: autotunePreppedGlucose,
  155. previousAutotuneResult: previousAutotune ?? profile,
  156. pumpProfile: profile
  157. )
  158. try? self.storage.save(autotuneResult, as: Settings.autotune)
  159. print("AUTOTUNE RESULT: \(autotuneResult)")
  160. let finishDate = Date()
  161. print("FINISH at \(finishDate), duration \(finishDate.timeIntervalSince(now)) s")
  162. }
  163. }
  164. func makeClock() {
  165. processQueue.async {
  166. try? self.storage.save(Date(), as: Monitor.clock)
  167. }
  168. }
  169. func makeMeal() {
  170. processQueue.async {
  171. let pumphistory = self.loadFileFromStorage(name: Monitor.pumpHistory)
  172. let profile = self.loadFileFromStorage(name: Settings.profile)
  173. let basalProfile = self.loadFileFromStorage(name: Settings.basalProfile)
  174. let clock = self.loadFileFromStorage(name: Monitor.clock)
  175. let carbs = self.loadFileFromStorage(name: Monitor.carbHistory)
  176. let glucose = self.loadFileFromStorage(name: Monitor.glucose)
  177. let mealResult = self.meal(
  178. pumphistory: pumphistory,
  179. profile: profile,
  180. basalProfile: basalProfile,
  181. clock: clock,
  182. carbs: carbs,
  183. glucose: glucose
  184. )
  185. print("MEAL: \(mealResult)")
  186. try? self.storage.save(mealResult, as: Monitor.meal)
  187. }
  188. }
  189. func makeProfile(autotuned: Bool) {
  190. processQueue.async {
  191. print("MAKE PROFILE autotuned \(autotuned)")
  192. let preferences = self.loadFileFromStorage(name: Settings.preferences)
  193. let pumpSettings = self.loadFileFromStorage(name: Settings.settings)
  194. let bgTargets = self.loadFileFromStorage(name: Settings.bgTargets)
  195. let basalProfile = self.loadFileFromStorage(name: Settings.basalProfile)
  196. let isf = self.loadFileFromStorage(name: Settings.insulinSensitivities)
  197. let cr = self.loadFileFromStorage(name: Settings.carbRatios)
  198. let tempTargets = self.loadFileFromStorage(name: Settings.tempTargets)
  199. let model = self.loadFileFromStorage(name: Settings.model)
  200. let autotune = self.loadFileFromStorage(name: Settings.autotune)
  201. let profile = self.makeProfile(
  202. preferences: preferences,
  203. pumpSettings: pumpSettings,
  204. bgTargets: bgTargets,
  205. basalProfile: basalProfile,
  206. isf: isf,
  207. carbRatio: cr,
  208. tempTargets: tempTargets,
  209. model: model,
  210. autotune: autotuned ? autotune : .null
  211. )
  212. print("PROFILE RESULT \n\(profile)")
  213. if autotuned {
  214. try? self.storage.save(profile, as: Settings.profile)
  215. } else {
  216. try? self.storage.save(profile, as: Settings.pumpProfile)
  217. }
  218. }
  219. }
  220. private func iob(pumphistory: JSON, profile: JSON, clock: JSON, autosens: JSON) -> RawJSON {
  221. dispatchPrecondition(condition: .onQueue(processQueue))
  222. return jsWorker.inCommonContext { worker in
  223. worker.evaluate(script: Script(name: Bundle.iob))
  224. worker.evaluate(script: Script(name: Prepare.iob))
  225. return worker.call(function: Function.generate, with: [
  226. pumphistory,
  227. profile,
  228. clock,
  229. autosens
  230. ])
  231. }
  232. }
  233. private func meal(pumphistory: JSON, profile: JSON, basalProfile: JSON, clock: JSON, carbs: JSON, glucose: JSON) -> RawJSON {
  234. dispatchPrecondition(condition: .onQueue(processQueue))
  235. return jsWorker.inCommonContext { worker in
  236. worker.evaluate(script: Script(name: Bundle.meal))
  237. worker.evaluate(script: Script(name: Prepare.meal))
  238. return worker.call(function: Function.generate, with: [
  239. pumphistory,
  240. profile,
  241. clock,
  242. glucose,
  243. basalProfile,
  244. carbs
  245. ])
  246. }
  247. }
  248. private func autotunePrepare(
  249. pumphistory: JSON,
  250. profile: JSON,
  251. glucose: JSON,
  252. pumpprofile: JSON,
  253. categorizeUamAsBasal: Bool,
  254. tuneInsulinCurve: Bool
  255. ) -> RawJSON {
  256. dispatchPrecondition(condition: .onQueue(processQueue))
  257. return jsWorker.inCommonContext { worker in
  258. worker.evaluate(script: Script(name: Bundle.autotunePrep))
  259. worker.evaluate(script: Script(name: Prepare.autotunePrep))
  260. return worker.call(function: Function.generate, with: [
  261. pumphistory,
  262. profile,
  263. glucose,
  264. pumpprofile,
  265. categorizeUamAsBasal,
  266. tuneInsulinCurve
  267. ])
  268. }
  269. }
  270. private func autotuneRun(
  271. autotunePreparedData: JSON,
  272. previousAutotuneResult: JSON,
  273. pumpProfile: JSON
  274. ) -> RawJSON {
  275. dispatchPrecondition(condition: .onQueue(processQueue))
  276. return jsWorker.inCommonContext { worker in
  277. worker.evaluate(script: Script(name: Bundle.autotuneCore))
  278. worker.evaluate(script: Script(name: Prepare.autotuneCore))
  279. return worker.call(function: Function.generate, with: [
  280. autotunePreparedData,
  281. previousAutotuneResult,
  282. pumpProfile
  283. ])
  284. }
  285. }
  286. private func determineBasal(
  287. glucose: JSON,
  288. currentTemp: JSON,
  289. iob: JSON,
  290. profile: JSON,
  291. autosens: JSON,
  292. meal: JSON,
  293. microBolusAllowed: Bool,
  294. reservoir: JSON
  295. ) -> RawJSON {
  296. dispatchPrecondition(condition: .onQueue(processQueue))
  297. return jsWorker.inCommonContext { worker in
  298. worker.evaluate(script: Script(name: Bundle.basalSetTemp))
  299. worker.evaluate(script: Script(name: Bundle.getLastGlucose))
  300. worker.evaluate(script: Script(name: Bundle.determineBasal))
  301. worker.evaluate(script: Script(name: Prepare.determineBasal))
  302. return worker.call(
  303. function: Function.generate,
  304. with: [
  305. iob,
  306. currentTemp,
  307. glucose,
  308. profile,
  309. autosens,
  310. meal,
  311. microBolusAllowed,
  312. reservoir
  313. ]
  314. )
  315. }
  316. }
  317. private func autosense(
  318. pumpHistory: JSON,
  319. profile: JSON,
  320. carbs: JSON,
  321. glucose: JSON,
  322. basalprofile: JSON,
  323. temptargets: JSON
  324. ) -> RawJSON {
  325. dispatchPrecondition(condition: .onQueue(processQueue))
  326. return jsWorker.inCommonContext { worker in
  327. worker.evaluate(script: Script(name: Bundle.autosens))
  328. worker.evaluate(script: Script(name: Prepare.autosens))
  329. return worker.call(
  330. function: Function.generate,
  331. with: [
  332. pumpHistory,
  333. profile,
  334. carbs,
  335. glucose,
  336. basalprofile,
  337. temptargets
  338. ]
  339. )
  340. }
  341. }
  342. private func exportDefaultPreferences() -> RawJSON {
  343. dispatchPrecondition(condition: .onQueue(processQueue))
  344. return jsWorker.inCommonContext { worker in
  345. worker.evaluate(script: Script(name: Bundle.profile))
  346. worker.evaluate(script: Script(name: Prepare.profile))
  347. return worker.call(function: Function.exportDefaults, with: [])
  348. }
  349. }
  350. private func makeProfile(
  351. preferences: JSON,
  352. pumpSettings: JSON,
  353. bgTargets: JSON,
  354. basalProfile: JSON,
  355. isf: JSON,
  356. carbRatio: JSON,
  357. tempTargets: JSON,
  358. model: JSON,
  359. autotune: JSON
  360. ) -> RawJSON {
  361. dispatchPrecondition(condition: .onQueue(processQueue))
  362. return jsWorker.inCommonContext { worker in
  363. worker.evaluate(script: Script(name: Bundle.profile))
  364. worker.evaluate(script: Script(name: Prepare.profile))
  365. return worker.call(
  366. function: Function.generate,
  367. with: [
  368. pumpSettings,
  369. bgTargets,
  370. isf,
  371. basalProfile,
  372. preferences,
  373. carbRatio,
  374. tempTargets,
  375. model,
  376. autotune
  377. ]
  378. )
  379. }
  380. }
  381. private func loadJSON(name: String) -> String {
  382. try! String(contentsOf: Foundation.Bundle.main.url(forResource: "json/\(name)", withExtension: "json")!)
  383. }
  384. private func loadFileFromStorage(name: String) -> RawJSON {
  385. storage.retrieveRaw(name) ?? OpenAPS.defaults(for: name)
  386. }
  387. static func defaults(for file: String) -> RawJSON {
  388. guard let url = Foundation.Bundle.main.url(forResource: "json/defaults/\(file)", withExtension: "") else {
  389. return ""
  390. }
  391. return (try? String(contentsOf: url)) ?? ""
  392. }
  393. }