|
|
@@ -1,19 +1,130 @@
|
|
|
import Foundation
|
|
|
+import Swinject
|
|
|
import WatchConnectivity
|
|
|
|
|
|
protocol WatchManager {}
|
|
|
|
|
|
-final class BaseWatchManager: NSObject, WatchManager {
|
|
|
- var session: WCSession
|
|
|
+final class BaseWatchManager: NSObject, WatchManager, Injectable {
|
|
|
+ private let session: WCSession
|
|
|
+ private var state = WatchState()
|
|
|
+ private let processQueue = DispatchQueue(label: "BaseWatchManager.processQueue")
|
|
|
|
|
|
- init(session: WCSession = .default) {
|
|
|
+ @Injected() private var broadcaster: Broadcaster!
|
|
|
+ @Injected() private var settingsManager: SettingsManager!
|
|
|
+ @Injected() private var glucoseStorage: GlucoseStorage!
|
|
|
+ @Injected() private var apsManager: APSManager!
|
|
|
+ @Injected() private var storage: FileStorage!
|
|
|
+
|
|
|
+ init(resolver: Resolver, session: WCSession = .default) {
|
|
|
self.session = session
|
|
|
super.init()
|
|
|
+ injectServices(resolver)
|
|
|
|
|
|
if WCSession.isSupported() {
|
|
|
session.delegate = self
|
|
|
session.activate()
|
|
|
}
|
|
|
+
|
|
|
+ broadcaster.register(GlucoseObserver.self, observer: self)
|
|
|
+ broadcaster.register(SuggestionObserver.self, observer: self)
|
|
|
+ broadcaster.register(SettingsObserver.self, observer: self)
|
|
|
+ broadcaster.register(PumpHistoryObserver.self, observer: self)
|
|
|
+ broadcaster.register(PumpSettingsObserver.self, observer: self)
|
|
|
+ broadcaster.register(BasalProfileObserver.self, observer: self)
|
|
|
+ broadcaster.register(TempTargetsObserver.self, observer: self)
|
|
|
+ broadcaster.register(CarbsObserver.self, observer: self)
|
|
|
+ broadcaster.register(EnactedSuggestionObserver.self, observer: self)
|
|
|
+ broadcaster.register(PumpBatteryObserver.self, observer: self)
|
|
|
+ broadcaster.register(PumpReservoirObserver.self, observer: self)
|
|
|
+
|
|
|
+ configureState()
|
|
|
+ }
|
|
|
+
|
|
|
+ private func configureState() {
|
|
|
+ processQueue.async {
|
|
|
+ let glucoseValues = self.glucoseText()
|
|
|
+ self.state.glucose = glucoseValues.glucose
|
|
|
+ self.state.trend = glucoseValues.trend
|
|
|
+ self.state.delta = glucoseValues.delta
|
|
|
+ self.state.glucoseDate = self.glucoseStorage.recent().last?.dateString
|
|
|
+ self.state.lastLoopDate = self.apsManager.lastLoopDate
|
|
|
+ self.state.bolusIncrement = self.settingsManager.preferences.bolusIncrement
|
|
|
+ self.state.maxCOB = self.settingsManager.preferences.maxCOB
|
|
|
+ self.state.maxBolus = self.settingsManager.pumpSettings.maxBolus
|
|
|
+ self.state.carbsRequired = self.suggestion?.carbsReq
|
|
|
+
|
|
|
+ let inslinRequired = self.suggestion?.insulinReq ?? 0
|
|
|
+ self.state.bolusRecommended = self.apsManager
|
|
|
+ .roundBolus(amount: max(inslinRequired * self.settingsManager.settings.insulinReqFraction, 0))
|
|
|
+
|
|
|
+ self.state.iob = self.suggestion?.iob
|
|
|
+ self.state.cob = self.suggestion?.cob
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func sendState() {
|
|
|
+ dispatchPrecondition(condition: .onQueue(processQueue))
|
|
|
+ guard let data = try? JSONEncoder().encode(state) else {
|
|
|
+ warning(.service, "Cannot encode watch state")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ guard session.isReachable else {
|
|
|
+ warning(.service, "WCSession is not reachable")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ session.sendMessageData(data, replyHandler: nil) { error in
|
|
|
+ warning(.service, "Cannot send message to watch", error: error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func glucoseText() -> (glucose: String, trend: String, delta: String) {
|
|
|
+ let glucose = glucoseStorage.recent()
|
|
|
+
|
|
|
+ guard let lastGlucose = glucose.last, let glucoseValue = lastGlucose.glucose else { return ("--", "--", "--") }
|
|
|
+
|
|
|
+ let delta = glucose.count >= 2 ? glucoseValue - (glucose[glucose.count - 2].glucose ?? 0) : nil
|
|
|
+
|
|
|
+ let units = settingsManager.settings.units
|
|
|
+ let glucoseText = glucoseFormatter
|
|
|
+ .string(from: Double(
|
|
|
+ units == .mmolL ? glucoseValue
|
|
|
+ .asMmolL : Decimal(glucoseValue)
|
|
|
+ ) as NSNumber)! + " " + NSLocalizedString(units.rawValue, comment: "units")
|
|
|
+ let directionText = lastGlucose.direction?.symbol ?? "↔︎"
|
|
|
+ let deltaText = delta
|
|
|
+ .map {
|
|
|
+ self.deltaFormatter
|
|
|
+ .string(from: Double(
|
|
|
+ units == .mmolL ? $0
|
|
|
+ .asMmolL : Decimal($0)
|
|
|
+ ) as NSNumber)!
|
|
|
+ } ?? "--"
|
|
|
+
|
|
|
+ return (glucoseText, directionText, deltaText)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var glucoseFormatter: NumberFormatter {
|
|
|
+ let formatter = NumberFormatter()
|
|
|
+ formatter.numberStyle = .decimal
|
|
|
+ formatter.maximumFractionDigits = 0
|
|
|
+ if settingsManager.settings.units == .mmolL {
|
|
|
+ formatter.minimumFractionDigits = 1
|
|
|
+ formatter.maximumFractionDigits = 1
|
|
|
+ }
|
|
|
+ formatter.roundingMode = .halfUp
|
|
|
+ return formatter
|
|
|
+ }
|
|
|
+
|
|
|
+ private var deltaFormatter: NumberFormatter {
|
|
|
+ let formatter = NumberFormatter()
|
|
|
+ formatter.numberStyle = .decimal
|
|
|
+ formatter.maximumFractionDigits = 2
|
|
|
+ formatter.positivePrefix = "+"
|
|
|
+ return formatter
|
|
|
+ }
|
|
|
+
|
|
|
+ private var suggestion: Suggestion? {
|
|
|
+ storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -23,12 +134,79 @@ extension BaseWatchManager: WCSessionDelegate {
|
|
|
func sessionDidDeactivate(_: WCSession) {}
|
|
|
|
|
|
func session(_: WCSession, activationDidCompleteWith state: WCSessionActivationState, error _: Error?) {
|
|
|
- print("ASDF state \(state.rawValue)")
|
|
|
+ debug(.service, "WCSession is activated: \(state == .activated)")
|
|
|
}
|
|
|
|
|
|
- func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
|
|
|
- print("ASDF message \(message)")
|
|
|
- session.sendMessage(["message": "It works!"], replyHandler: nil) { _ in
|
|
|
+ func session(_ session: WCSession, didReceiveMessage _: [String: Any]) {
|
|
|
+ session.sendMessage(["message": "It works!"], replyHandler: nil) { _ in }
|
|
|
+ }
|
|
|
+
|
|
|
+ func session(_: WCSession, didReceiveMessageData _: Data) {}
|
|
|
+
|
|
|
+ func sessionReachabilityDidChange(_ session: WCSession) {
|
|
|
+ print("WCSession reacanility: \(session.isReachable)")
|
|
|
+ if session.isReachable {
|
|
|
+ processQueue.async {
|
|
|
+ self.sendState()
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+extension BaseWatchManager:
|
|
|
+ GlucoseObserver,
|
|
|
+ SuggestionObserver,
|
|
|
+ SettingsObserver,
|
|
|
+ PumpHistoryObserver,
|
|
|
+ PumpSettingsObserver,
|
|
|
+ BasalProfileObserver,
|
|
|
+ TempTargetsObserver,
|
|
|
+ CarbsObserver,
|
|
|
+ EnactedSuggestionObserver,
|
|
|
+ PumpBatteryObserver,
|
|
|
+ PumpReservoirObserver
|
|
|
+{
|
|
|
+ func glucoseDidUpdate(_: [BloodGlucose]) {
|
|
|
+ configureState()
|
|
|
+ }
|
|
|
+
|
|
|
+ func suggestionDidUpdate(_: Suggestion) {
|
|
|
+ configureState()
|
|
|
+ }
|
|
|
+
|
|
|
+ func settingsDidChange(_: FreeAPSSettings) {
|
|
|
+ configureState()
|
|
|
+ }
|
|
|
+
|
|
|
+ func pumpHistoryDidUpdate(_: [PumpHistoryEvent]) {
|
|
|
+ // TODO:
|
|
|
+ }
|
|
|
+
|
|
|
+ func pumpSettingsDidChange(_: PumpSettings) {
|
|
|
+ configureState()
|
|
|
+ }
|
|
|
+
|
|
|
+ func basalProfileDidChange(_: [BasalProfileEntry]) {
|
|
|
+ // TODO:
|
|
|
+ }
|
|
|
+
|
|
|
+ func tempTargetsDidUpdate(_: [TempTarget]) {
|
|
|
+ configureState()
|
|
|
+ }
|
|
|
+
|
|
|
+ func carbsDidUpdate(_: [CarbsEntry]) {
|
|
|
+ // TODO:
|
|
|
+ }
|
|
|
+
|
|
|
+ func enactedSuggestionDidUpdate(_: Suggestion) {
|
|
|
+ // TODO:
|
|
|
+ }
|
|
|
+
|
|
|
+ func pumpBatteryDidChange(_: Battery) {
|
|
|
+ // TODO:
|
|
|
+ }
|
|
|
+
|
|
|
+ func pumpReservoirDidChange(_: Decimal) {
|
|
|
+ // TODO:
|
|
|
+ }
|
|
|
+}
|