CalibrationService.swift 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import Foundation
  2. import LibreTransmitter
  3. import Swinject
  4. struct Calibration: JSON, Hashable, Identifiable {
  5. let x: Double
  6. let y: Double
  7. var date = Date()
  8. static let zero = Calibration(x: 0, y: 0)
  9. var id = UUID()
  10. }
  11. protocol CalibrationService {
  12. var slope: Double { get }
  13. var intercept: Double { get }
  14. var calibrations: [Calibration] { get }
  15. func addCalibration(_ calibration: Calibration)
  16. func removeCalibration(_ calibration: Calibration)
  17. func removeAllCalibrations()
  18. func removeLast()
  19. func calibrate(value: Double) -> Double
  20. }
  21. final class BaseCalibrationService: CalibrationService, Injectable {
  22. private enum Config {
  23. static let minSlope = 0.8
  24. static let maxSlope = 1.25
  25. static let minIntercept = -100.0
  26. static let maxIntercept = 100.0
  27. static let maxValue = 500.0
  28. static let minValue = 0.0
  29. }
  30. @Injected() var storage: FileStorage!
  31. @Injected() var notificationCenter: NotificationCenter!
  32. private var lifetime = Lifetime()
  33. private(set) var calibrations: [Calibration] = [] {
  34. didSet {
  35. storage.save(calibrations, as: OpenAPS.FreeAPS.calibrations)
  36. }
  37. }
  38. init(resolver: Resolver) {
  39. injectServices(resolver)
  40. calibrations = storage.retrieve(OpenAPS.FreeAPS.calibrations, as: [Calibration].self) ?? []
  41. subscribe()
  42. }
  43. private func subscribe() {
  44. notificationCenter.publisher(for: .newSensorDetected)
  45. .sink { [weak self] _ in
  46. self?.removeAllCalibrations()
  47. }
  48. .store(in: &lifetime)
  49. }
  50. var slope: Double {
  51. guard calibrations.count >= 2 else {
  52. return 1
  53. }
  54. let xs = calibrations.map(\.x)
  55. let ys = calibrations.map(\.y)
  56. let sum1 = average(multiply(xs, ys)) - average(xs) * average(ys)
  57. let sum2 = average(multiply(xs, xs)) - pow(average(xs), 2)
  58. let slope = sum1 / sum2
  59. return min(max(slope, Config.minSlope), Config.maxSlope)
  60. }
  61. var intercept: Double {
  62. guard calibrations.count >= 1 else {
  63. return 0
  64. }
  65. let xs = calibrations.map(\.x)
  66. let ys = calibrations.map(\.y)
  67. let intercept = average(ys) - slope * average(xs)
  68. return min(max(intercept, Config.minIntercept), Config.maxIntercept)
  69. }
  70. func calibrate(value: Double) -> Double {
  71. linearRegression(value)
  72. }
  73. func addCalibration(_ calibration: Calibration) {
  74. calibrations.append(calibration)
  75. }
  76. func removeCalibration(_ calibration: Calibration) {
  77. calibrations.removeAll { $0 == calibration }
  78. }
  79. func removeAllCalibrations() {
  80. calibrations.removeAll()
  81. }
  82. func removeLast() {
  83. calibrations.removeLast()
  84. }
  85. private func average(_ input: [Double]) -> Double {
  86. input.reduce(0, +) / Double(input.count)
  87. }
  88. private func multiply(_ a: [Double], _ b: [Double]) -> [Double] {
  89. zip(a, b).map(*)
  90. }
  91. private func linearRegression(_ x: Double) -> Double {
  92. (intercept + slope * x).clamped(Config.minValue ... Config.maxValue)
  93. }
  94. }