OpenAPS.swift 15 KB

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