| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- import Foundation
- import WatchConnectivity
- /// WatchState manages the communication between the Watch app and the iPhone app using WatchConnectivity.
- /// It handles glucose data synchronization and sending treatment requests (bolus, carbs) to the phone.
- @Observable final class WatchState: NSObject, WCSessionDelegate {
- // MARK: - Properties
- /// The WatchConnectivity session instance used for communication
- private var session: WCSession?
- /// Indicates if the paired iPhone is currently reachable
- var isReachable = false
- var currentGlucose: String = "--"
- var trend: String? = ""
- var delta: String? = "--"
- var glucoseValues: [(date: Date, glucose: Double)] = []
- var cob: String? = "--"
- var iob: String? = "--"
- var lastLoopTime: String? = "--"
- var overridePresets: [OverridePresetWatch] = []
- var tempTargetPresets: [TempTargetPresetWatch] = []
- /// treatments inputs
- /// used to store carbs for combined meal-bolus-treatments
- var carbsAmount: Int = 0
- var fatAmount: Int = 0
- var proteinAmount: Int = 0
- var bolusAmount = 0.0
- var activeBolusAmount = 0.0
- var confirmationProgress = 0.0
- var bolusProgress: Double = 0.0
- override init() {
- super.init()
- setupSession()
- }
- /// Configures the WatchConnectivity session if supported on the device
- private func setupSession() {
- if WCSession.isSupported() {
- let session = WCSession.default
- session.delegate = self
- session.activate()
- self.session = session
- } else {
- print("⌚️ WCSession is not supported on this device")
- }
- }
- // MARK: - Send Data to Phone
- /// Sends a bolus insulin request to the paired iPhone
- /// - Parameters:
- /// - amount: The insulin amount to be delivered
- func sendBolusRequest(_ amount: Decimal) {
- guard let session = session, session.isReachable else { return }
- let message: [String: Any] = [
- "bolus": amount
- ]
- session.sendMessage(message, replyHandler: nil) { error in
- print("Error sending bolus request: \(error.localizedDescription)")
- }
- }
- /// Sends a carbohydrate entry request to the paired iPhone
- /// - Parameters:
- /// - amount: The amount of carbs in grams
- /// - date: The timestamp for the carb entry (defaults to current time)
- func sendCarbsRequest(_ amount: Int, _ date: Date = Date()) {
- guard let session = session, session.isReachable else { return }
- let message: [String: Any] = [
- "carbs": amount,
- "date": date.timeIntervalSince1970
- ]
- session.sendMessage(message, replyHandler: nil) { error in
- print("Error sending carbs request: \(error.localizedDescription)")
- }
- }
- /// Sends a meal and bolus insulin combo request to the paired iPhone
- /// - Parameters:
- /// - amount: The insulin amount to be delivered
- /// - isExternal: Indicates if the bolus is from an external source
- func sendMealBolusComboRequest(carbsAmount _: Decimal, bolusAmount: Decimal, _ date: Date = Date()) {
- guard let session = session, session.isReachable else { return }
- let message: [String: Any] = [
- "bolus": bolusAmount,
- "carbs": bolusAmount,
- "date": date.timeIntervalSince1970
- ]
- session.sendMessage(message, replyHandler: nil) { error in
- print("Error sending meal bolus combo request: \(error.localizedDescription)")
- }
- }
- func sendCancelOverrideRequest() {
- guard let session = session, session.isReachable else { return }
- let message: [String: Any] = [
- "cancelOverride": true
- ]
- session.sendMessage(message, replyHandler: nil) { error in
- print("⌚️ Error sending cancel override request: \(error.localizedDescription)")
- }
- }
- func sendActivateOverrideRequest(presetName: String) {
- guard let session = session, session.isReachable else { return }
- let message: [String: Any] = [
- "activateOverride": presetName
- ]
- session.sendMessage(message, replyHandler: nil) { error in
- print("⌚️ Error sending activate override request: \(error.localizedDescription)")
- }
- }
- func sendCancelTempTargetRequest() {
- guard let session = session, session.isReachable else { return }
- let message: [String: Any] = [
- "cancelTempTarget": true
- ]
- session.sendMessage(message, replyHandler: nil) { error in
- print("⌚️ Error sending cancel temp target request: \(error.localizedDescription)")
- }
- }
- func sendActivateTempTargetRequest(presetName: String) {
- guard let session = session, session.isReachable else { return }
- let message: [String: Any] = [
- "activateTempTarget": presetName
- ]
- session.sendMessage(message, replyHandler: nil) { error in
- print("⌚️ Error sending activate temp target request: \(error.localizedDescription)")
- }
- }
- func sendCancelBolusRequest() {
- guard let session = session, session.isReachable else { return }
- let message: [String: Any] = [
- "cancelBolus": true
- ]
- session.sendMessage(message, replyHandler: nil) { error in
- print("Error sending cancel bolus request: \(error.localizedDescription)")
- }
- }
- // MARK: - WCSessionDelegate
- /// Called when the session has completed activation
- /// Updates the reachability status and logs the activation state
- func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
- DispatchQueue.main.async {
- if let error = error {
- print("⌚️ Watch session activation failed: \(error.localizedDescription)")
- return
- }
- print("⌚️ Watch session activated with state: \(activationState.rawValue)")
- self.isReachable = session.isReachable
- print("⌚️ Watch isReachable after activation: \(session.isReachable)")
- }
- }
- /// Handles incoming messages from the paired iPhone
- /// Updates local glucose data, trend, and delta information
- func session(_: WCSession, didReceiveMessage message: [String: Any]) {
- print("⌚️ Watch received message: \(message)")
- DispatchQueue.main.async { [weak self] in
- guard let self = self else { return }
- if let currentGlucose = message["currentGlucose"] as? String {
- self.currentGlucose = currentGlucose
- }
- if let trend = message["trend"] as? String {
- self.trend = trend
- }
- if let delta = message["delta"] as? String {
- self.delta = delta
- }
- if let iob = message["iob"] as? String {
- self.iob = iob
- }
- if let cob = message["cob"] as? String {
- self.cob = cob
- }
- if let lastLoopTime = message["lastLoopTime"] as? String {
- self.lastLoopTime = lastLoopTime
- }
- if let glucoseData = message["glucoseValues"] as? [[String: Any]] {
- self.glucoseValues = glucoseData.compactMap { data in
- guard let glucose = data["glucose"] as? Double,
- let timestamp = data["date"] as? TimeInterval
- else { return nil }
- return (Date(timeIntervalSince1970: timestamp), glucose)
- }
- .sorted { $0.date < $1.date }
- }
- if let overrideData = message["overridePresets"] as? [[String: Any]] {
- self.overridePresets = overrideData.compactMap { data in
- guard let name = data["name"] as? String,
- let isEnabled = data["isEnabled"] as? Bool
- else { return nil }
- return OverridePresetWatch(name: name, isEnabled: isEnabled)
- }
- }
- if let tempTargetData = message["tempTargetPresets"] as? [[String: Any]] {
- self.tempTargetPresets = tempTargetData.compactMap { data in
- guard let name = data["name"] as? String,
- let isEnabled = data["isEnabled"] as? Bool
- else { return nil }
- return TempTargetPresetWatch(name: name, isEnabled: isEnabled)
- }
- }
- if let bolusProgress = message["bolusProgress"] as? Double {
- self.bolusProgress = bolusProgress
- }
- }
- }
- /// Called when the reachability status of the paired iPhone changes
- /// Updates the local reachability status
- func sessionReachabilityDidChange(_ session: WCSession) {
- DispatchQueue.main.async {
- self.isReachable = session.isReachable
- print("⌚️ Watch reachability changed: \(session.isReachable)")
- }
- }
- }
|