From 1ef522fad51501d8d327e8c495ef507fbad60ae5 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 20 Jun 2023 20:06:05 -0500 Subject: [PATCH 01/19] Updating to use DisplayGlucosePreference --- LibreTransmitter.xcodeproj/project.pbxproj | 62 ++++++-- .../xcschemes/Shared-watchOS.xcscheme | 2 +- .../xcshareddata/xcschemes/Shared.xcscheme | 2 +- LibreTransmitter/LibreGlucose.swift | 58 -------- .../LibreTransmitterManagerV3.swift | 94 +++++------- LibreTransmitter/NotificationHelper.swift | 137 +++++++++--------- .../Observables/GlucoseInfo.swift | 13 +- .../LibreTransmitterSetupViewController.swift | 9 +- .../LibreTransmitterManager+UI.swift | 8 +- .../Views/Settings/GlucoseSettingsView.swift | 14 +- .../Settings/NotificationSettingsView.swift | 20 +-- .../Views/Settings/SettingsView.swift | 81 +++++------ .../Views/Setup/BluetoothSelection.swift | 4 - 13 files changed, 216 insertions(+), 288 deletions(-) diff --git a/LibreTransmitter.xcodeproj/project.pbxproj b/LibreTransmitter.xcodeproj/project.pbxproj index 71239c0..574b368 100644 --- a/LibreTransmitter.xcodeproj/project.pbxproj +++ b/LibreTransmitter.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 53; objects = { /* Begin PBXBuildFile section */ @@ -718,8 +718,9 @@ 432B0E7F1CDFC3C50045347B /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "LoopKit Authors"; TargetAttributes = { 432B0E871CDFC3C50045347B = { @@ -1040,6 +1041,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1109,6 +1111,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1138,7 +1141,8 @@ ); MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -1153,16 +1157,23 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = LibreTransmitter/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = com.mddub.LibreTransmitter; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1175,16 +1186,23 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = LibreTransmitter/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = com.mddub.LibreTransmitter; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1208,12 +1226,19 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = LibreTransmitterUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PRODUCT_BUNDLE_IDENTIFIER = com.loopkit.LibreTransmitterUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1239,12 +1264,19 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = LibreTransmitterUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PRODUCT_BUNDLE_IDENTIFIER = com.loopkit.LibreTransmitterUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1268,7 +1300,11 @@ INFOPLIST_FILE = LibreTransmitterPlugin/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MACH_O_TYPE = mh_dylib; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; @@ -1301,7 +1337,11 @@ INFOPLIST_FILE = LibreTransmitterPlugin/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MACH_O_TYPE = mh_dylib; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_FAST_MATH = YES; diff --git a/LibreTransmitter.xcodeproj/xcshareddata/xcschemes/Shared-watchOS.xcscheme b/LibreTransmitter.xcodeproj/xcshareddata/xcschemes/Shared-watchOS.xcscheme index 442faa1..4d87245 100644 --- a/LibreTransmitter.xcodeproj/xcshareddata/xcschemes/Shared-watchOS.xcscheme +++ b/LibreTransmitter.xcodeproj/xcshareddata/xcschemes/Shared-watchOS.xcscheme @@ -1,6 +1,6 @@ String { - guard let glucoseUnit = UserDefaults.standard.mmGlucoseUnit else { - logger.debug("glucose unit was not recognized, aborting") - return "Unknown" - } - - var stringValue = [String]() - - var diff = newValue.glucoseDouble - oldValue.glucoseDouble - let sign = diff < 0 ? "-" : "+" - - if diff == 0 { - stringValue.append( "\(sign) 0") - } else { - diff = abs(diff) - let asObj = LibreGlucose( - unsmoothedGlucose: diff, - glucoseDouble: diff, - timestamp: Date()) - if let formatted = dynamicFormatter?.string(from: asObj.quantity, for: glucoseUnit) { - stringValue.append( "\(sign) \(formatted)") - } - } - - return stringValue.joined(separator: ",") - } -} - extension LibreGlucose { static func calculateSlope(current: Self, last: Self) -> Double { if current.timestamp == last.timestamp { diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index 512a6f8..09f6647 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -6,7 +6,7 @@ import Foundation import LoopKit -// import LoopKitUI +import LoopKitUI import UIKit import UserNotifications import Combine @@ -22,6 +22,8 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega public let isOnboarded = true // No distinction between created and onboarded + private var alertsUnitPreference = DisplayGlucosePreference(displayGlucoseUnit: .milligramsPerDeciliter) + public var hasValidSensorSession: Bool { lastConnected != nil } @@ -142,10 +144,11 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega defer { logger.debug("sending glucose notification") - NotificationHelper.sendGlucoseNotitifcationIfNeeded(glucose: newValue, - oldValue: oldValue, - trend: trend, - battery: proxy?.metadata?.batteryString ?? "n/a") + NotificationHelper.sendGlucoseNotificationIfNeeded(glucose: newValue, + oldValue: oldValue, + trend: trend, + battery: proxy?.metadata?.batteryString ?? "n/a", + glucoseFormatter: alertsUnitPreference.formatter) // once we have a new glucose value, we can update the isalarming property if let activeAlarms = UserDefaults.standard.glucoseSchedules?.getActiveAlarms(newValue.glucoseDouble) { @@ -162,7 +165,7 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega } - logger.debug("latestBackfill set, newvalue is \(newValue.description)") + logger.debug("latestBackfill set, newvalue is \(newValue.glucose)") if let oldValue { // the idea here is to use the diff between the old and the new glucose to calculate slope and direction, rather than using trend from the glucose value. @@ -256,14 +259,6 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega public var sensorInfoObservable = SensorInfo() public var glucoseInfoObservable = GlucoseInfo() - var longDateFormatter: DateFormatter = ({ - let df = DateFormatter() - df.dateStyle = .long - df.timeStyle = .long - df.doesRelativeDateFormatting = true - return df - })() - var dateFormatter: DateFormatter = ({ let df = DateFormatter() df.dateStyle = .long @@ -418,41 +413,19 @@ extension LibreTransmitterManagerV3 { } - let formatter = QuantityFormatter() - let preferredUnit = UserDefaults.standard.mmGlucoseUnit ?? .millimolesPerLiter - if let d = self.latestBackfill { self.logger.debug("will set glucoseInfoObservable") - - formatter.setPreferredNumberFormatter(for: .millimolesPerLiter) - self.glucoseInfoObservable.glucoseMMOL = formatter.string(from: d.quantity, for: .millimolesPerLiter) ?? "-" - - formatter.setPreferredNumberFormatter(for: .milligramsPerDeciliter) - self.glucoseInfoObservable.glucoseMGDL = formatter.string(from: d.quantity, for: .milligramsPerDeciliter) ?? "-" - - // backward compat - if preferredUnit == .millimolesPerLiter { - self.glucoseInfoObservable.glucose = self.glucoseInfoObservable.glucoseMMOL - } else if preferredUnit == .milligramsPerDeciliter { - self.glucoseInfoObservable.glucose = self.glucoseInfoObservable.glucoseMGDL - } - - self.glucoseInfoObservable.date = self.longDateFormatter.string(from: d.timestamp) + self.glucoseInfoObservable.glucose = d.quantity + self.glucoseInfoObservable.date = d.timestamp } if let d = self.latestPrediction { - formatter.setPreferredNumberFormatter(for: .millimolesPerLiter) - self.glucoseInfoObservable.predictionMMOL = formatter.string(from: d.quantity, for: .millimolesPerLiter) ?? "-" - - formatter.setPreferredNumberFormatter(for: .milligramsPerDeciliter) - self.glucoseInfoObservable.predictionMGDL = formatter.string(from: d.quantity, for: .milligramsPerDeciliter) ?? "-" - self.glucoseInfoObservable.predictionDate = self.longDateFormatter.string(from: d.timestamp) + self.glucoseInfoObservable.prediction = d.quantity + self.glucoseInfoObservable.predictionDate = d.timestamp } else { - self.glucoseInfoObservable.predictionMMOL = "" - self.glucoseInfoObservable.predictionMGDL = "" - self.glucoseInfoObservable.predictionDate = "" - + self.glucoseInfoObservable.prediction = nil + self.glucoseInfoObservable.predictionDate = nil } } @@ -492,22 +465,20 @@ extension LibreTransmitterManagerV3 { } logger.debug("tried creating trendarrow using \(glucoses.count) elements for trend calc") - return - glucoses - .filterDateRange(startDate, nil) - .compactMap { - return NewGlucoseSample( - date: $0.startDate, - quantity: $0.quantity, - condition: nil, - trend: trend, - trendRate: nil, - isDisplayOnly: false, - wasUserEntered: false, - syncIdentifier: $0.syncId, - device: self.proxy?.device) - } - + return glucoses + .filterDateRange(startDate, nil) + .compactMap { + return NewGlucoseSample( + date: $0.startDate, + quantity: $0.quantity, + condition: nil, + trend: trend, + trendRate: nil, + isDisplayOnly: false, + wasUserEntered: false, + syncIdentifier: $0.syncId, + device: self.proxy?.device) + } } public var calibrationData: SensorData.CalibrationInfo? { @@ -518,3 +489,10 @@ extension LibreTransmitterManagerV3 { proxy?.activePluginType?.smallImage ?? UIImage(named: "libresensor", in: Bundle.current, compatibleWith: nil)! } } + + +extension LibreTransmitterManagerV3: DisplayGlucoseUnitObserver { + public func unitDidChange(to displayGlucoseUnit: HKUnit) { + self.alertsUnitPreference.unitDidChange(to: displayGlucoseUnit) + } +} diff --git a/LibreTransmitter/NotificationHelper.swift b/LibreTransmitter/NotificationHelper.swift index 646ebc9..7e7d8ec 100644 --- a/LibreTransmitter/NotificationHelper.swift +++ b/LibreTransmitter/NotificationHelper.swift @@ -142,13 +142,6 @@ public enum NotificationHelper { } } - static func ensureCanSendGlucoseNotification(_ completion: @escaping (_ unit: HKUnit) -> Void ) { - ensureCanSendNotification { - if let glucoseUnit = UserDefaults.standard.mmGlucoseUnit, GlucoseUnitIsSupported(unit: glucoseUnit) { - completion(glucoseUnit) - } - } - } } // MARK: Sensor related notification sendouts @@ -370,7 +363,7 @@ public extension NotificationHelper { private static var glucoseNotifyCalledCount = 0 - static func sendGlucoseNotitifcationIfNeeded(glucose: LibreGlucose, oldValue: LibreGlucose?, trend: GlucoseTrend?, battery: String?) { + static func sendGlucoseNotificationIfNeeded(glucose: LibreGlucose, oldValue: LibreGlucose?, trend: GlucoseTrend?, battery: String?, glucoseFormatter: QuantityFormatter) { glucoseNotifyCalledCount &+= 1 let shouldSendGlucoseAlternatingTimes = glucoseNotifyCalledCount != 0 && UserDefaults.standard.mmNotifyEveryXTimes != 0 @@ -391,84 +384,88 @@ public extension NotificationHelper { // even if glucose notifications are disabled in the UI if shouldSend || alarm.isAlarming() { - sendGlucoseNotitifcation(glucose: glucose, oldValue: oldValue, - alarm: alarm, isSnoozed: isSnoozed, - trend: trend, showPhoneBattery: shouldShowPhoneBattery, - transmitterBattery: transmitterBattery) + sendGlucoseNotification(glucose: glucose, oldValue: oldValue, + glucoseFormatter: glucoseFormatter, + alarm: alarm, isSnoozed: isSnoozed, + trend: trend, showPhoneBattery: shouldShowPhoneBattery, + transmitterBattery: transmitterBattery) } else { logger.debug("\(#function) not sending glucose, shouldSend and alarmIsActive was false") return } } - private static func sendGlucoseNotitifcation(glucose: LibreGlucose, oldValue: LibreGlucose?, - alarm: GlucoseScheduleAlarmResult = .none, - isSnoozed: Bool = false, - trend: GlucoseTrend?, - showPhoneBattery: Bool = false, - transmitterBattery: String?) { - ensureCanSendGlucoseNotification { _ in - let content = UNMutableNotificationContent() - let glucoseDesc = glucose.description - var titles = [String]() - var body = [String]() - var body2 = [String]() - - var isCritical = false - switch alarm { - case .none: - titles.append("Glucose") - case .low: - titles.append("LOWALERT!") - isCritical = true - case .high: - titles.append("HIGHALERT!") - isCritical = true - } - - if isSnoozed { - titles.append("(Snoozed)") - } else if alarm.isAlarming() { - content.sound = .default - vibrateIfNeeded() - } - titles.append(glucoseDesc) - - body.append("Glucose: \(glucoseDesc)") + private static func sendGlucoseNotification(glucose: LibreGlucose, oldValue: LibreGlucose?, + glucoseFormatter: QuantityFormatter, + alarm: GlucoseScheduleAlarmResult = .none, + isSnoozed: Bool = false, + trend: GlucoseTrend?, + showPhoneBattery: Bool = false, + transmitterBattery: String?) { + let content = UNMutableNotificationContent() + let glucoseDesc = glucoseFormatter.string(from: glucose.quantity)! + var titles = [String]() + var body = [String]() + var body2 = [String]() + + var isCritical = false + switch alarm { + case .none: + titles.append("Glucose") + case .low: + titles.append("LOWALERT!") + isCritical = true + case .high: + titles.append("HIGHALERT!") + isCritical = true + } + + if isSnoozed { + titles.append("(Snoozed)") + } else if alarm.isAlarming() { + content.sound = .default + vibrateIfNeeded() + } + titles.append(glucoseDesc) - if let oldValue { - body.append( LibreGlucose.glucoseDiffDesc(oldValue: oldValue, newValue: glucose)) - } + body.append("Glucose: \(glucoseDesc)") - if let trend = trend?.localizedDescription { - body.append("\(trend)") + if let oldValue { + let diff = Double(glucose.glucose - oldValue.glucose) + if diff >= 0 { + body.append("+") } + body.append( glucoseFormatter.string(from: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: diff))!) + } - if showPhoneBattery { - if !UIDevice.current.isBatteryMonitoringEnabled { - UIDevice.current.isBatteryMonitoringEnabled = true - } + if let trend = trend?.localizedDescription { + body.append("\(trend)") + } - let battery = Double(UIDevice.current.batteryLevel * 100 ).roundTo(places: 1) - body2.append("Phone: \(battery)%") + if showPhoneBattery { + if !UIDevice.current.isBatteryMonitoringEnabled { + UIDevice.current.isBatteryMonitoringEnabled = true } - if let transmitterBattery { - body2.append("Transmitter: \(transmitterBattery)") - } + let battery = Double(UIDevice.current.batteryLevel * 100 ).roundTo(places: 1) + body2.append("Phone: \(battery)%") + } - // these are texts that naturally fit on their own line in the body - var body2s = "" - if !body2.isEmpty { - body2s = "\n" + body2.joined(separator: "\n") - } + if let transmitterBattery { + body2.append("Transmitter: \(transmitterBattery)") + } - content.title = titles.joined(separator: " ") - content.body = body.joined(separator: ", ") + body2s - addRequest(identifier: .glucocoseNotifications, - content: content, - deleteOld: true, isCritical: isCritical && !isSnoozed) + // these are texts that naturally fit on their own line in the body + var body2s = "" + if !body2.isEmpty { + body2s = "\n" + body2.joined(separator: "\n") } + + content.title = titles.joined(separator: " ") + content.body = body.joined(separator: ", ") + body2s + addRequest(identifier: .glucocoseNotifications, + content: content, + deleteOld: true, isCritical: isCritical && !isSnoozed) } private static var lastBatteryWarning: Date? diff --git a/LibreTransmitter/Observables/GlucoseInfo.swift b/LibreTransmitter/Observables/GlucoseInfo.swift index 983f95d..2b51dd1 100644 --- a/LibreTransmitter/Observables/GlucoseInfo.swift +++ b/LibreTransmitter/Observables/GlucoseInfo.swift @@ -7,20 +7,17 @@ // import Foundation +import HealthKit public class GlucoseInfo: ObservableObject, Equatable, Hashable { - @Published public var glucose = "" // dynamic based users preference - @Published public var glucoseMMOL = "" - @Published public var glucoseMGDL = "" - @Published public var date = "" + @Published public var glucose: HKQuantity? + @Published public var date: Date? @Published public var checksum = "" // @Published var entryErrors = "" - @Published public var prediction = "" - @Published public var predictionMMOL = "" - @Published public var predictionMGDL = "" - @Published public var predictionDate = "" + @Published public var prediction: HKQuantity? + @Published public var predictionDate: Date? public static func ==(lhs: GlucoseInfo, rhs: GlucoseInfo) -> Bool { lhs.glucose == rhs.glucose && lhs.date == rhs.date && diff --git a/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift b/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift index b1085c6..eff386f 100644 --- a/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift +++ b/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift @@ -21,17 +21,16 @@ class LibreTransmitterSetupViewController: UINavigationController, CGMManagerOnb lazy var cgmManager: LibreTransmitterManagerV3? = LibreTransmitterManagerV3() - var modeSelection: UIHostingController! - - init() { + init(displayGlucosePreference: DisplayGlucosePreference) { SelectionState.shared.selectedStringIdentifier = UserDefaults.standard.preSelectedDevice let cancelNotifier = GenericObservableObject() let saveNotifier = GenericObservableObject() - modeSelection = UIHostingController(rootView: ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier)) + let myView = ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier) + .environmentObject(displayGlucosePreference) - super.init(rootViewController: modeSelection) + super.init(rootViewController: UIHostingController(rootView: myView)) cancelNotifier.listenOnce { [weak self] in self?.cancel() diff --git a/LibreTransmitterUI/LibreTransmitterManager+UI.swift b/LibreTransmitterUI/LibreTransmitterManager+UI.swift index 77d4f4b..f8f462e 100644 --- a/LibreTransmitterUI/LibreTransmitterManager+UI.swift +++ b/LibreTransmitterUI/LibreTransmitterManager+UI.swift @@ -24,12 +24,12 @@ extension LibreTransmitterManagerV3: CGMManagerUI { nil } - public static func setupViewController(bluetoothProvider: BluetoothProvider, displayGlucoseUnitObservable: DisplayGlucoseUnitObservable, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> SetupUIResult { + public static func setupViewController(bluetoothProvider: BluetoothProvider, displayGlucosePreference: DisplayGlucosePreference, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool, prefersToSkipUserInteraction: Bool) -> SetupUIResult { - return .userInteractionRequired(LibreTransmitterSetupViewController()) + return .userInteractionRequired(LibreTransmitterSetupViewController(displayGlucosePreference: displayGlucosePreference)) } - public func settingsViewController(bluetoothProvider: BluetoothProvider, displayGlucoseUnitObservable: DisplayGlucoseUnitObservable, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> CGMManagerViewController { + public func settingsViewController(bluetoothProvider: BluetoothProvider, displayGlucosePreference: DisplayGlucosePreference, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> CGMManagerViewController { let doneNotifier = GenericObservableObject() let wantToTerminateNotifier = GenericObservableObject() @@ -39,7 +39,7 @@ extension LibreTransmitterManagerV3: CGMManagerUI { let wantToRestablishConnectionNotifier = GenericObservableObject() let settings = SettingsView.asHostedViewController( - displayGlucoseUnitObservable: displayGlucoseUnitObservable, + displayGlucosePreference: displayGlucosePreference, notifyComplete: doneNotifier, notifyDelete: wantToTerminateNotifier, notifyReset: wantToResetCGMManagerNotifier, notifyReconnect:wantToRestablishConnectionNotifier, transmitterInfoObservable: self.transmitterInfoObservable, diff --git a/LibreTransmitterUI/Views/Settings/GlucoseSettingsView.swift b/LibreTransmitterUI/Views/Settings/GlucoseSettingsView.swift index 0afa41c..eafb8d9 100644 --- a/LibreTransmitterUI/Views/Settings/GlucoseSettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/GlucoseSettingsView.swift @@ -15,18 +15,6 @@ struct GlucoseSettingsView: View { @State private var presentableStatus: StatusMessage? - private var glucoseUnit: HKUnit - - public init(glucoseUnit: HKUnit) { - if let savedGlucoseUnit = UserDefaults.standard.mmGlucoseUnit { - self.glucoseUnit = savedGlucoseUnit - } else { - self.glucoseUnit = glucoseUnit - UserDefaults.standard.mmGlucoseUnit = glucoseUnit - } - - } - @AppStorage("com.loopkit.libreSyncToNs") var mmSyncToNS: Bool = true @AppStorage("com.loopkit.libreBackfillFromHistory") var mmBackfillFromHistory: Bool = true @AppStorage("com.loopkit.libreshouldPersistSensorData") var shouldPersistSensorData: Bool = false @@ -78,6 +66,6 @@ struct GlucoseSettingsView: View { struct GlucoseSettingsView_Previews: PreviewProvider { static var previews: some View { - GlucoseSettingsView(glucoseUnit: HKUnit.millimolesPerLiter) + GlucoseSettingsView() } } diff --git a/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift b/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift index 4c297c6..85c99d9 100644 --- a/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift @@ -10,26 +10,16 @@ import SwiftUI import Combine import LibreTransmitter import HealthKit +import LoopKitUI struct NotificationSettingsView: View { + @EnvironmentObject private var displayGlucosePreference: DisplayGlucosePreference @State private var presentableStatus: StatusMessage? - private var glucoseUnit: HKUnit - private let glucoseSegments = [HKUnit.millimolesPerLiter, HKUnit.milligramsPerDeciliter] private lazy var glucoseSegmentStrings = self.glucoseSegments.map({ $0.localizedShortUnitString }) - public init(glucoseUnit: HKUnit) { - if let savedGlucoseUnit = UserDefaults.standard.mmGlucoseUnit { - self.glucoseUnit = savedGlucoseUnit - } else { - self.glucoseUnit = glucoseUnit - UserDefaults.standard.mmGlucoseUnit = glucoseUnit - } - - } - private enum Key: String { // case glucoseSchedules = "com.loopkit.libreglucoseschedules" @@ -74,6 +64,10 @@ struct NotificationSettingsView: View { static let formatter = NumberFormatter() + init() { + + } + var glucoseVisibilitySection : some View { Section(header: Text(LocalizedString("Glucose Notification visibility", comment: "Text describing header for notification visibility in notificationsettingsview")) ) { Toggle(LocalizedString("Always Notify Glucose", comment: "Text describing always notify glucose option in notificationsettingsview"), isOn: $mmAlwaysDisplayGlucose) @@ -142,6 +136,6 @@ struct NotificationSettingsView: View { struct NotificationSettingsView_Previews: PreviewProvider { static var previews: some View { - NotificationSettingsView(glucoseUnit: HKUnit.millimolesPerLiter) + NotificationSettingsView() } } diff --git a/LibreTransmitterUI/Views/Settings/SettingsView.swift b/LibreTransmitterUI/Views/Settings/SettingsView.swift index bee129e..ef4f0ca 100644 --- a/LibreTransmitterUI/Views/Settings/SettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/SettingsView.swift @@ -15,6 +15,7 @@ import LoopKitUI import UniformTypeIdentifiers public struct SettingsItem: View { + @State var title: String = "" // we don't want this to change after it is set @Binding var detail: String @@ -47,8 +48,16 @@ public struct SettingsItem: View { } struct SettingsView: View { + @EnvironmentObject private var displayGlucosePreference: DisplayGlucosePreference + + var longDateFormatter: DateFormatter = ({ + let df = DateFormatter() + df.dateStyle = .long + df.timeStyle = .long + df.doesRelativeDateFormatting = true + return df + })() - @ObservedObject private var displayGlucoseUnitObservable: DisplayGlucoseUnitObservable @ObservedObject private var transmitterInfo: LibreTransmitter.TransmitterInfo @ObservedObject private var sensorInfo: LibreTransmitter.SensorInfo @@ -58,7 +67,7 @@ struct SettingsView: View { @ObservedObject private var notifyDelete: GenericObservableObject @ObservedObject private var notifyReset: GenericObservableObject @ObservedObject private var notifyReconnect: GenericObservableObject - + @State private var presentableStatus: StatusMessage? @ObservedObject var alarmStatus: LibreTransmitter.AlarmStatus @@ -67,7 +76,7 @@ struct SettingsView: View { // @Environment(\.presentationMode) var presentationMode static func asHostedViewController( - displayGlucoseUnitObservable: DisplayGlucoseUnitObservable, + displayGlucosePreference: DisplayGlucosePreference, notifyComplete: GenericObservableObject, notifyDelete: GenericObservableObject, notifyReset: GenericObservableObject, @@ -75,9 +84,9 @@ struct SettingsView: View { transmitterInfoObservable: LibreTransmitter.TransmitterInfo, sensorInfoObervable: LibreTransmitter.SensorInfo, glucoseInfoObservable: LibreTransmitter.GlucoseInfo, - alarmStatus: LibreTransmitter.AlarmStatus) -> DismissibleHostingController { - DismissibleHostingController(rootView: self.init( - displayGlucoseUnitObservable: displayGlucoseUnitObservable, + alarmStatus: LibreTransmitter.AlarmStatus) -> DismissibleHostingController + { + let view = self.init( transmitterInfo: transmitterInfoObservable, sensorInfo: sensorInfoObervable, glucoseMeasurement: glucoseInfoObservable, @@ -86,12 +95,13 @@ struct SettingsView: View { notifyReset: notifyReset, notifyReconnect: notifyReconnect, alarmStatus: alarmStatus - - )) + ).environmentObject(displayGlucosePreference) + return DismissibleHostingController(rootView: view) } + private var glucoseUnit: HKUnit { - displayGlucoseUnitObservable.displayGlucoseUnit + displayGlucosePreference.unit } static let formatter = NumberFormatter() @@ -104,8 +114,11 @@ struct SettingsView: View { headerSection snoozeSection measurementSection - if !glucoseMeasurement.predictionDate.isEmpty { - predictionSection + if let date = glucoseMeasurement.predictionDate, let prediction = glucoseMeasurement.prediction { + Section(header: Text(LocalizedString("Last Blood Sugar prediction", comment: "Text describing header for Blood Sugar prediction section"))) { + SettingsItem(title: "CurrentBG", detail: displayGlucosePreference.format(prediction)) + SettingsItem(title: "Date", detail: longDateFormatter.string(from: date) ) + } } NavigationLink(destination: deviceInfoSection) { @@ -122,13 +135,6 @@ struct SettingsView: View { destructSection }.listStyle(InsetGroupedListStyle()) - .onAppear { - // only override savedglucose unit if we haven't saved this locally before - if UserDefaults.standard.mmGlucoseUnit == nil { - UserDefaults.standard.mmGlucoseUnit = glucoseUnit - } - - } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { doneButton @@ -151,28 +157,19 @@ struct SettingsView: View { } var measurementSection : some View { - Section(header: Text(LocalizedString("Last measurement", comment: "Text describing header for last measurement section"))) { - if glucoseUnit == .millimolesPerLiter { - SettingsItem(title: "Glucose", detail: $glucoseMeasurement.glucoseMMOL) - } else if glucoseUnit == .milligramsPerDeciliter { - SettingsItem(title: "Glucose", detail: $glucoseMeasurement.glucoseMGDL) - } - - SettingsItem(title: "Date", detail: $glucoseMeasurement.date ) - SettingsItem(title: "Sensor Footer checksum", detail: $glucoseMeasurement.checksum ) + var glucoseText: String = "" + var glucoseDateText: String = "" + if let glucose = glucoseMeasurement.glucose { + glucoseText = displayGlucosePreference.format(glucose) + } + if let date = glucoseMeasurement.date { + glucoseDateText = longDateFormatter.string(from: date) } - } - - var predictionSection : some View { - Section(header: Text(LocalizedString("Last Blood Sugar prediction", comment: "Text describing header for Blood Sugar prediction section"))) { - if glucoseUnit == .millimolesPerLiter { - SettingsItem(title: "CurrentBG", detail: $glucoseMeasurement.predictionMMOL) - } else if glucoseUnit == .milligramsPerDeciliter { - SettingsItem(title: "CurrentBG", detail: $glucoseMeasurement.predictionMGDL) - } - - SettingsItem(title: "Date", detail: $glucoseMeasurement.predictionDate ) + return Section(header: Text(LocalizedString("Last measurement", comment: "Text describing header for last measurement section"))) { + SettingsItem(title: "Glucose", detail: glucoseText) + SettingsItem(title: "Date", detail: glucoseDateText ) + SettingsItem(title: "Sensor Footer checksum", detail: $glucoseMeasurement.checksum ) } } @@ -271,11 +268,11 @@ struct SettingsView: View { } } - NavigationLink(destination: GlucoseSettingsView(glucoseUnit: self.glucoseUnit)) { + NavigationLink(destination: GlucoseSettingsView()) { SettingsItem(title: "Glucose Settings") } - NavigationLink(destination: NotificationSettingsView(glucoseUnit: self.glucoseUnit)) { + NavigationLink(destination: NotificationSettingsView()) { SettingsItem(title: "Notifications") } @@ -324,7 +321,7 @@ struct SettingsView: View { var showProgress : Bool { - if let expiresAt = sensorInfo.expiresAt,let activatedAt = sensorInfo.activatedAt { + if let expiresAt = sensorInfo.expiresAt { return expiresAt.timeIntervalSinceNow > 0 } @@ -463,6 +460,6 @@ struct SettingsView: View { struct SettingsOverview_Previews: PreviewProvider { static var previews: some View { - NotificationSettingsView(glucoseUnit: HKUnit.millimolesPerLiter) + NotificationSettingsView() } } diff --git a/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift b/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift index 861bbee..64e33bc 100644 --- a/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift +++ b/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift @@ -197,10 +197,6 @@ struct BluetoothSelection: View { private var searcher: BluetoothSearchManager! - /*static func asHostedViewController() -> UIHostingController { - UIHostingController(rootView: self.init()) - }*/ - // Should contain all discovered and compatible devices // This list is expected to contain 10 or 20 items at the most @State var allDevices = [SomePeripheral]() From 7b678f2c91e01d717f486a7587ee97f8742944b5 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 20 Jun 2023 21:56:42 -0500 Subject: [PATCH 02/19] cleanup --- Common/Settings/GlucoseSchedules.swift | 3 --- .../Views/Settings/NotificationSettingsView.swift | 4 ---- 2 files changed, 7 deletions(-) diff --git a/Common/Settings/GlucoseSchedules.swift b/Common/Settings/GlucoseSchedules.swift index 5f26362..dd69bc5 100644 --- a/Common/Settings/GlucoseSchedules.swift +++ b/Common/Settings/GlucoseSchedules.swift @@ -147,9 +147,6 @@ class GlucoseSchedule: Codable, CustomStringConvertible { var highAlarm: Double? var enabled: Bool? - init() { - } - // glucose schedules are stored as standalone datecomponents (i.e. offsets) // this takes the current start of day and adds those offsets, // and returns a Dateinterval with those offsets applied diff --git a/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift b/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift index 85c99d9..ee088bf 100644 --- a/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift @@ -64,10 +64,6 @@ struct NotificationSettingsView: View { static let formatter = NumberFormatter() - init() { - - } - var glucoseVisibilitySection : some View { Section(header: Text(LocalizedString("Glucose Notification visibility", comment: "Text describing header for notification visibility in notificationsettingsview")) ) { Toggle(LocalizedString("Always Notify Glucose", comment: "Text describing always notify glucose option in notificationsettingsview"), isOn: $mmAlwaysDisplayGlucose) From 09aa2569bb7bf05fe221d7b44562f36061f36988 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 21 Jun 2023 15:39:00 -0500 Subject: [PATCH 03/19] Fix warnings --- LibreTransmitter/LibreTransmitterManagerV3.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index 09f6647..434be5b 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -109,9 +109,9 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega "## LibreTransmitterManager", "Testdata: foo", "lastConnected: \(String(describing: lastConnected))", - "Connection state: \(self.proxy?.connectionStateString)", - "Sensor state: \(proxy?.sensorData?.state.description)", - "transmitterbattery: \(proxy?.metadata?.batteryString)", + "Connection state: \(String(describing: self.proxy?.connectionStateString))", + "Sensor state: \(String(describing: proxy?.sensorData?.state.description))", + "transmitterbattery: \(String(describing: proxy?.metadata?.batteryString))", "SensorData: \(getPersistedSensorDataForDebug())", "providesBLEHeartbeat: \(providesBLEHeartbeat)", "Metainfo::\n\(AppMetaData.allProperties)", @@ -293,11 +293,11 @@ extension LibreTransmitterManagerV3 { if let predicted = allGlucoses.predictBloodSugar(glucosePredictionMinutes) { let currentBg = predicted.calibratedGlucose(calibrationInfo: calibration) let bgDate = predicted.date.addingTimeInterval(60 * -glucosePredictionMinutes) - return LibreGlucose(unsmoothedGlucose: currentBg, glucoseDouble: currentBg, timestamp: bgDate) logger.debug("Predicted glucose (not used) was: \(currentBg)") + return LibreGlucose(unsmoothedGlucose: currentBg, glucoseDouble: currentBg, timestamp: bgDate) } else { - return nil logger.debug("Tried to predict glucose value but failed!") + return nil } } From 75b0a14312edc08666ffabb66d0ac5e18b9fa44d Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 21 Jun 2023 17:16:16 -0500 Subject: [PATCH 04/19] Adding demo plugin --- Info.plist | 30 +++ LibreDemoPlugin/LibreDemoCGMManager.swift | 16 ++ LibreDemoPlugin/LibreDemoPlugin.h | 19 ++ LibreDemoPlugin/LibreDemoPlugin.swift | 42 ++++ LibreTransmitter.xcodeproj/project.pbxproj | 206 ++++++++++++++++++ .../SensorPairing/SensorPairing.swift | 2 +- .../SensorPairing/SensorPairingService.swift | 4 +- .../Views/Setup/Libre2DirectSetup.swift | 8 +- 8 files changed, 317 insertions(+), 10 deletions(-) create mode 100644 Info.plist create mode 100644 LibreDemoPlugin/LibreDemoCGMManager.swift create mode 100644 LibreDemoPlugin/LibreDemoPlugin.h create mode 100644 LibreDemoPlugin/LibreDemoPlugin.swift diff --git a/Info.plist b/Info.plist new file mode 100644 index 0000000..302c611 --- /dev/null +++ b/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSHumanReadableCopyright + Copyright © 2023 LoopKit Authors + NSPrincipalClass + LibreDemoPlugin + com.loopkit.Loop.CGMManagerDisplayName + Libre Demo CGM + com.loopkit.Loop.CGMManagerIdentifier + LibreDemoCGMManager + + diff --git a/LibreDemoPlugin/LibreDemoCGMManager.swift b/LibreDemoPlugin/LibreDemoCGMManager.swift new file mode 100644 index 0000000..b1a98e8 --- /dev/null +++ b/LibreDemoPlugin/LibreDemoCGMManager.swift @@ -0,0 +1,16 @@ +// +// LibreDemoCGMManager.swift +// LibreTransmitter +// +// Created by Pete Schwamb on 6/21/23. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import LibreTransmitter + +class LibreDemoCGMManager: LibreTransmitterManagerV3 { + public var managerIdentifier = "LibreDemoCGMManager" + + +} diff --git a/LibreDemoPlugin/LibreDemoPlugin.h b/LibreDemoPlugin/LibreDemoPlugin.h new file mode 100644 index 0000000..02f957b --- /dev/null +++ b/LibreDemoPlugin/LibreDemoPlugin.h @@ -0,0 +1,19 @@ +// +// LibreTransmitterDemo.h +// LibreTransmitterDemo +// +// Created by Pete Schwamb on 6/21/23. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +#import + +//! Project version number for LibreTransmitterDemo. +FOUNDATION_EXPORT double LibreTransmitterDemoVersionNumber; + +//! Project version string for LibreTransmitterDemo. +FOUNDATION_EXPORT const unsigned char LibreTransmitterDemoVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/LibreDemoPlugin/LibreDemoPlugin.swift b/LibreDemoPlugin/LibreDemoPlugin.swift new file mode 100644 index 0000000..9ff2f74 --- /dev/null +++ b/LibreDemoPlugin/LibreDemoPlugin.swift @@ -0,0 +1,42 @@ +// +// LibreDemoPlugin.swift +// LibreTransmitter +// +// Created by Pete Schwamb on 6/21/23. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import LoopKitUI +import LibreTransmitter +import LibreTransmitterUI + +import os.log + +class LibreDemoPlugin: NSObject, CGMManagerUIPlugin { + + private let log = OSLog(category: "LibreDemoPlugin") + + public var pumpManagerType: PumpManagerUI.Type? { + nil + } + + public var cgmManagerType: CGMManagerUI.Type? { + LibreDemoCGMManager.self + } + + override init() { + super.init() + log.default("Instantiated") + LibreTransmitter.AppMetaData.allProperties = allProperties + + } + + let prefix = "com-loopkit-libre" + let bundle = Bundle(for: LibreTransmitterPlugin.self) + + var allProperties: String { + bundle.infoDictionary?.compactMap { + $0.key.starts(with: prefix) ? "\($0.key): \($0.value)" : nil + }.joined(separator: "\n") ?? "none" + } +} diff --git a/LibreTransmitter.xcodeproj/project.pbxproj b/LibreTransmitter.xcodeproj/project.pbxproj index 574b368..c9bcc6d 100644 --- a/LibreTransmitter.xcodeproj/project.pbxproj +++ b/LibreTransmitter.xcodeproj/project.pbxproj @@ -120,6 +120,12 @@ B40BF26623ABD4E700A43CEE /* LibreTransmitterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26523ABD4E700A43CEE /* LibreTransmitterPlugin.swift */; }; B40BF26823ABD55200A43CEE /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26723ABD55200A43CEE /* OSLog.swift */; }; B40BF26923ABD55200A43CEE /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26723ABD55200A43CEE /* OSLog.swift */; }; + C1BDBAED2A4397E200A787D1 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26723ABD55200A43CEE /* OSLog.swift */; }; + C1BDBAEE2A4397E200A787D1 /* LibreTransmitterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26523ABD4E700A43CEE /* LibreTransmitterPlugin.swift */; }; + C1BDBAF02A4397E200A787D1 /* LibreTransmitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 432B0E881CDFC3C50045347B /* LibreTransmitter.framework */; }; + C1BDBAF12A4397E200A787D1 /* LibreTransmitterUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43A8EC82210E664300A81379 /* LibreTransmitterUI.framework */; }; + C1BDBAF52A4397E200A787D1 /* LibreTransmitter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 432B0E881CDFC3C50045347B /* LibreTransmitter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1BDBAF62A4397E200A787D1 /* LibreTransmitterUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43A8EC82210E664300A81379 /* LibreTransmitterUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -144,6 +150,20 @@ remoteGlobalIDString = 432B0E871CDFC3C50045347B; remoteInfo = LibreTransmitter; }; + C1BDBAE92A4397E200A787D1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 432B0E7F1CDFC3C50045347B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 432B0E871CDFC3C50045347B; + remoteInfo = LibreTransmitter; + }; + C1BDBAEB2A4397E200A787D1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 432B0E7F1CDFC3C50045347B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43A8EC81210E664300A81379; + remoteInfo = LibreTransmitterUI; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -159,6 +179,18 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + C1BDBAF42A4397E200A787D1 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + C1BDBAF52A4397E200A787D1 /* LibreTransmitter.framework in Embed Frameworks */, + C1BDBAF62A4397E200A787D1 /* LibreTransmitterUI.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -293,6 +325,11 @@ B40BF26423ABD4E600A43CEE /* LibreTransmitterPlugin-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LibreTransmitterPlugin-Bridging-Header.h"; sourceTree = ""; }; B40BF26523ABD4E700A43CEE /* LibreTransmitterPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreTransmitterPlugin.swift; sourceTree = ""; }; B40BF26723ABD55200A43CEE /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; + C1BDBAE22A4397AA00A787D1 /* LibreDemoPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LibreDemoPlugin.h; sourceTree = ""; }; + C1BDBAFA2A4397E200A787D1 /* LibreDemoPlugin.loopplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LibreDemoPlugin.loopplugin; sourceTree = BUILT_PRODUCTS_DIR; }; + C1BDBAFB2A4397E200A787D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = /Users/pete/dev/LoopWorkspace/LibreTransmitter/Info.plist; sourceTree = ""; }; + C1BDBAFC2A43985000A787D1 /* LibreDemoPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreDemoPlugin.swift; sourceTree = ""; }; + C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreDemoCGMManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -324,6 +361,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1BDBAEF2A4397E200A787D1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C1BDBAF02A4397E200A787D1 /* LibreTransmitter.framework in Frameworks */, + C1BDBAF12A4397E200A787D1 /* LibreTransmitterUI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -542,6 +588,7 @@ 432B0E961CDFC3C50045347B /* LibreTransmitterTests */, 43A8EC83210E664300A81379 /* LibreTransmitterUI */, B40BF25F23ABD47400A43CEE /* LibreTransmitterPlugin */, + C1BDBAE12A4397AA00A787D1 /* LibreDemoPlugin */, 432B0E891CDFC3C50045347B /* Products */, 43A8EC7A210E661300A81379 /* Frameworks */, ); @@ -553,6 +600,7 @@ 432B0E881CDFC3C50045347B /* LibreTransmitter.framework */, 43A8EC82210E664300A81379 /* LibreTransmitterUI.framework */, B40BF25E23ABD47400A43CEE /* LibreTransmitterPlugin.loopplugin */, + C1BDBAFA2A4397E200A787D1 /* LibreDemoPlugin.loopplugin */, ); name = Products; sourceTree = ""; @@ -632,6 +680,17 @@ path = LibreTransmitterPlugin; sourceTree = ""; }; + C1BDBAE12A4397AA00A787D1 /* LibreDemoPlugin */ = { + isa = PBXGroup; + children = ( + C1BDBAFB2A4397E200A787D1 /* Info.plist */, + C1BDBAE22A4397AA00A787D1 /* LibreDemoPlugin.h */, + C1BDBAFC2A43985000A787D1 /* LibreDemoPlugin.swift */, + C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */, + ); + path = LibreDemoPlugin; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -712,6 +771,27 @@ productReference = B40BF25E23ABD47400A43CEE /* LibreTransmitterPlugin.loopplugin */; productType = "com.apple.product-type.bundle"; }; + C1BDBAE72A4397E200A787D1 /* LibreDemoPlugin */ = { + isa = PBXNativeTarget; + buildConfigurationList = C1BDBAF72A4397E200A787D1 /* Build configuration list for PBXNativeTarget "LibreDemoPlugin" */; + buildPhases = ( + C1BDBAEC2A4397E200A787D1 /* Sources */, + C1BDBAEF2A4397E200A787D1 /* Frameworks */, + C1BDBAF22A4397E200A787D1 /* Resources */, + C1BDBAF32A4397E200A787D1 /* Run script capture-build-details */, + C1BDBAF42A4397E200A787D1 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + C1BDBAE82A4397E200A787D1 /* PBXTargetDependency */, + C1BDBAEA2A4397E200A787D1 /* PBXTargetDependency */, + ); + name = LibreDemoPlugin; + productName = LibreTransmitterPlugin; + productReference = C1BDBAFA2A4397E200A787D1 /* LibreDemoPlugin.loopplugin */; + productType = "com.apple.product-type.bundle"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -772,6 +852,7 @@ 432B0E871CDFC3C50045347B /* LibreTransmitter */, 43A8EC81210E664300A81379 /* LibreTransmitterUI */, B40BF25D23ABD47400A43CEE /* LibreTransmitterPlugin */, + C1BDBAE72A4397E200A787D1 /* LibreDemoPlugin */, ); }; /* End PBXProject section */ @@ -811,6 +892,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1BDBAF22A4397E200A787D1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -832,6 +920,24 @@ shellPath = /bin/sh; shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\"${SRCROOT}/Scripts/capture-build-details.sh\"\n\n"; }; + C1BDBAF32A4397E200A787D1 /* Run script capture-build-details */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run script capture-build-details"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\"${SRCROOT}/Scripts/capture-build-details.sh\"\n\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -945,6 +1051,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1BDBAEC2A4397E200A787D1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1BDBAED2A4397E200A787D1 /* OSLog.swift in Sources */, + C1BDBAEE2A4397E200A787D1 /* LibreTransmitterPlugin.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -963,6 +1078,16 @@ target = 432B0E871CDFC3C50045347B /* LibreTransmitter */; targetProxy = A9E521F6225E949400EDDEF2 /* PBXContainerItemProxy */; }; + C1BDBAE82A4397E200A787D1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 432B0E871CDFC3C50045347B /* LibreTransmitter */; + targetProxy = C1BDBAE92A4397E200A787D1 /* PBXContainerItemProxy */; + }; + C1BDBAEA2A4397E200A787D1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43A8EC81210E664300A81379 /* LibreTransmitterUI */; + targetProxy = C1BDBAEB2A4397E200A787D1 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -1356,6 +1481,78 @@ }; name = Release; }; + C1BDBAF82A4397E200A787D1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.loopkit.LibreDemoPlugin; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "LibreTransmitterPlugin/LibreTransmitterPlugin-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + WRAPPER_EXTENSION = loopplugin; + }; + name = Debug; + }; + C1BDBAF92A4397E200A787D1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.loopkit.LibreDemoPlugin; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "LibreTransmitterPlugin/LibreTransmitterPlugin-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + WRAPPER_EXTENSION = loopplugin; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1395,6 +1592,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + C1BDBAF72A4397E200A787D1 /* Build configuration list for PBXNativeTarget "LibreDemoPlugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C1BDBAF82A4397E200A787D1 /* Debug */, + C1BDBAF92A4397E200A787D1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 432B0E7F1CDFC3C50045347B /* Project object */; diff --git a/LibreTransmitterUI/SensorPairing/SensorPairing.swift b/LibreTransmitterUI/SensorPairing/SensorPairing.swift index 7ee3691..f5e8525 100644 --- a/LibreTransmitterUI/SensorPairing/SensorPairing.swift +++ b/LibreTransmitterUI/SensorPairing/SensorPairing.swift @@ -81,5 +81,5 @@ class SensorPairingInfo: ObservableObject, Codable { } protocol SensorPairingProtocol { - func pairSensor() -> AnyPublisher + func pairSensor() } diff --git a/LibreTransmitterUI/SensorPairing/SensorPairingService.swift b/LibreTransmitterUI/SensorPairing/SensorPairingService.swift index 1c53319..41ba4a0 100644 --- a/LibreTransmitterUI/SensorPairing/SensorPairingService.swift +++ b/LibreTransmitterUI/SensorPairing/SensorPairingService.swift @@ -46,7 +46,7 @@ class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, SensorPairing private let unlockCode: UInt32 = 42 // 42 - @discardableResult func pairSensor() -> AnyPublisher { + func pairSensor(){ if NFCTagReaderSession.readingAvailable { accessQueue.async { self.session = NFCTagReaderSession(pollingOption: .iso15693, delegate: self, queue: self.nfcQueue) @@ -54,8 +54,6 @@ class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, SensorPairing self.session?.begin() } } - - return readingsSubject.eraseToAnyPublisher() } public var publisher: AnyPublisher { diff --git a/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift b/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift index 899ca63..77b66c6 100644 --- a/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift +++ b/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift @@ -29,8 +29,7 @@ struct Libre2DirectSetup: View { @ObservedObject public var saveNotifier: GenericObservableObject public var isMockedSensor = false - - + func pairMockedSensor() { let info = FakeSensorPairingData().fakeSensorPairingInfo() logger.debug("Sending fake sensor pairinginfo: \(info.description)") @@ -38,8 +37,6 @@ struct Libre2DirectSetup: View { DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { receivePairingInfo(info) } - - } func pairSensor() { @@ -95,7 +92,7 @@ struct Libre2DirectSetup: View { // only relevant for launch through settings, as selectionstate can be persisted // we need to enforce libre2 by removing any selected third party transmitter SelectionState.shared.selectedStringIdentifier = nil - print(" paried and set selected UID to: \(SelectionState.shared.selectedUID?.hex)") + print("Paired and set selected UID to: \(String(describing: SelectionState.shared.selectedUID?.hex))") saveNotifier.notify() NotificationHelper.sendLibre2FirectFinishedSetupNotifcation() @@ -137,7 +134,6 @@ struct Libre2DirectSetup: View { .navigationBarBackButtonHidden(true) .navigationBarItems(leading: cancelButton) // the pair button does the save process for us! //, trailing: saveButton) .onReceive(service.publisher, perform: receivePairingInfo) - //.onReceive(service.errorPublisher, perform: receiveError) .alert(item: $presentableStatus) { status in Alert(title: Text(status.title), message: Text(status.message), dismissButton: .default(Text("Got it!"))) From d35dbc792e578336c4a3bdddcadaa06607c6b22c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 22 Jun 2023 19:15:12 -0500 Subject: [PATCH 05/19] Provide alternate pairing service when running with demo --- Info.plist | 2 +- LibreDemoPlugin/LibreDemoCGMManager.swift | 11 +++- .../MockSensorPairingService.swift | 24 ++++++++ LibreSensor/SensorContents/PreLibre2.swift | 6 -- .../SensorPairing/FakeSensorPairingData.swift | 7 ++- .../SensorPairing/SensorPairing.swift | 32 +++++----- .../SensorPairing/SensorPairingService.swift | 58 +++++++++++-------- LibreTransmitter.xcodeproj/project.pbxproj | 24 +++++--- .../LibreTransmitterManagerV3.swift | 14 +++-- .../LibreTransmitterSetupViewController.swift | 23 +++----- .../LibreTransmitterManager+UI.swift | 11 +++- .../Views/Settings/SettingsView.swift | 11 +++- LibreTransmitterUI/Views/Setup/AuthView.swift | 8 ++- .../Views/Setup/BluetoothSelection.swift | 2 +- .../Views/Setup/Libre2DirectSetup.swift | 25 +++----- .../Views/Setup/ModeSelectionView.swift | 21 ++----- 16 files changed, 162 insertions(+), 117 deletions(-) create mode 100644 LibreDemoPlugin/MockSensorPairingService.swift rename {LibreTransmitterUI => LibreSensor}/SensorPairing/FakeSensorPairingData.swift (95%) rename {LibreTransmitterUI => LibreSensor}/SensorPairing/SensorPairing.swift (75%) rename {LibreTransmitterUI => LibreSensor}/SensorPairing/SensorPairingService.swift (87%) diff --git a/Info.plist b/Info.plist index 302c611..42ce65a 100644 --- a/Info.plist +++ b/Info.plist @@ -21,7 +21,7 @@ NSHumanReadableCopyright Copyright © 2023 LoopKit Authors NSPrincipalClass - LibreDemoPlugin + LibreDemoPlugin.LibreDemoPlugin com.loopkit.Loop.CGMManagerDisplayName Libre Demo CGM com.loopkit.Loop.CGMManagerIdentifier diff --git a/LibreDemoPlugin/LibreDemoCGMManager.swift b/LibreDemoPlugin/LibreDemoCGMManager.swift index b1a98e8..ad759fd 100644 --- a/LibreDemoPlugin/LibreDemoCGMManager.swift +++ b/LibreDemoPlugin/LibreDemoCGMManager.swift @@ -8,9 +8,18 @@ import Foundation import LibreTransmitter +import LibreTransmitterUI +import LoopKit +import LoopKitUI class LibreDemoCGMManager: LibreTransmitterManagerV3 { - public var managerIdentifier = "LibreDemoCGMManager" + public override var managerIdentifier: String { + "LibreDemoCGMManager" + } + + public override var service: SensorPairingProtocol { + return MockSensorPairingService() + } } diff --git a/LibreDemoPlugin/MockSensorPairingService.swift b/LibreDemoPlugin/MockSensorPairingService.swift new file mode 100644 index 0000000..570ac9f --- /dev/null +++ b/LibreDemoPlugin/MockSensorPairingService.swift @@ -0,0 +1,24 @@ +// +// MockSensorPairingService.swift +// LibreDemoPlugin +// +// Created by Pete Schwamb on 6/22/23. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import LibreTransmitter +import Combine + +class MockSensorPairingService: SensorPairingProtocol { + + private var readingsSubject = PassthroughSubject() + + public var publisher: AnyPublisher { + readingsSubject.eraseToAnyPublisher() + } + + func pairSensor() throws { + print("Here") + } +} diff --git a/LibreSensor/SensorContents/PreLibre2.swift b/LibreSensor/SensorContents/PreLibre2.swift index 70f3db5..f14545e 100644 --- a/LibreSensor/SensorContents/PreLibre2.swift +++ b/LibreSensor/SensorContents/PreLibre2.swift @@ -272,12 +272,6 @@ public extension Libre2 { } } -extension UInt16 { - init(_ byte0: UInt8, _ byte1: UInt8) { - self = Data([byte1, byte0]).withUnsafeBytes { $0.load(as: UInt16.self) } - } -} - extension Libre2 { enum Example { static let sensorInfo: [UInt8] = [ diff --git a/LibreTransmitterUI/SensorPairing/FakeSensorPairingData.swift b/LibreSensor/SensorPairing/FakeSensorPairingData.swift similarity index 95% rename from LibreTransmitterUI/SensorPairing/FakeSensorPairingData.swift rename to LibreSensor/SensorPairing/FakeSensorPairingData.swift index b5f000d..ade05a9 100644 --- a/LibreTransmitterUI/SensorPairing/FakeSensorPairingData.swift +++ b/LibreSensor/SensorPairing/FakeSensorPairingData.swift @@ -10,15 +10,18 @@ import Foundation // https://github.com/NightscoutFoundation/xDrip/blob/579d365a94cb1fa2ad28b692efb05036928a5dd3/wear/src/main/java/com/eveningoutpost/dexdrip/NFCReaderX.java#L95 -struct FakeSensorPairingData { +public struct FakeSensorPairingData { let de_new_packet: [UInt8] = [0x87, 0x88, 0xd2, 0xe8, 0xdd, 0x28, 0x9b, 0x95, 0xb5, 0x9d, 0xe1, 0x1f, 0x47, 0x2c, 0x61, 0x4f, 0xcb, 0x81, 0x5e, 0xc8, 0x36, 0x4a, 0x4c, 0x1f, 0xa4, 0xc8, 0x59, 0x81, 0x72, 0xbf, 0x9e, 0xae, 0xa4, 0x1b, 0x51, 0x5b, 0xb9, 0x9f, 0x6a, 0x6b, 0xf4, 0x55, 0x78, 0xe1, 0xa3, 0x4f, 0x3a, 0x60, 0x49, 0x8f, 0x1f, 0xcb, 0xdf, 0x2e, 0xdc, 0xbe, 0x59, 0xc9, 0x28, 0x8e, 0xf4, 0x83, 0x16, 0x11, 0xa, 0xd2, 0x74, 0x6f, 0x9d, 0xbf, 0x29, 0x44, 0x37, 0x8d, 0xe9, 0xf7, 0x47, 0x1, 0x2b, 0x3, 0x5e, 0x9b, 0x72, 0x25, 0x1f, 0x82, 0x11, 0xb5, 0xdb, 0x19, 0x42, 0x9c, 0xfe, 0x91, 0x63, 0x94, 0xf7, 0x14, 0x67, 0xb6, 0x25, 0xf3, 0xf9, 0xee, 0x30, 0x54, 0xa4, 0x89, 0x2b, 0xa8, 0xe4, 0x6f, 0x7a, 0x4f, 0xa3, 0xdc, 0xc0, 0x43, 0xfc, 0x38, 0x1c, 0x32, 0x76, 0x1b, 0x17, 0xb6, 0x81, 0x87, 0xf8, 0xd3, 0x97, 0xca, 0xd5, 0x67, 0xf6, 0x4a, 0xbd, 0x6f, 0x2b, 0x90, 0xd6, 0xd2, 0x4e, 0x96, 0x87, 0x98, 0xcf, 0xf6, 0x82, 0xb3, 0x7b, 0xf1, 0xd4, 0xf2, 0x3b, 0xb3, 0xc3, 0x76, 0x33, 0xe5, 0xa3, 0xe9, 0x27, 0xde, 0x6a, 0x21, 0xc2, 0xb2, 0xfc, 0x2, 0x87, 0xb1, 0x55, 0x7c, 0xc9, 0xe0, 0x5b, 0x9f, 0x63, 0x61, 0x67, 0x18, 0x3d, 0xe9, 0x92, 0x1f, 0xed, 0xad, 0x41, 0xee, 0x8d, 0xd7, 0x5e, 0x3d, 0x4b, 0xa4, 0x20, 0xfa, 0x6c, 0xc, 0xf7, 0x68, 0xe5, 0xfb, 0x90, 0xc6, 0x54, 0x49, 0x4d, 0xfe, 0x1e, 0xa3, 0x25, 0x2b, 0xa5, 0x6f, 0xf9, 0xc0, 0xce, 0x18, 0x67, 0x6e, 0x33, 0xc1, 0x43, 0x53, 0x35, 0x44, 0x52, 0x91, 0xd2, 0x8, 0x5a, 0x9d, 0x18, 0xea, 0x2d, 0xcb, 0x11, 0x2b, 0xe0, 0xb, 0xe3, 0x84, 0x18, 0x54, 0xc0, 0xc1, 0x74, 0xfb, 0x53, 0x4d, 0x3a, 0x29, 0x56, 0x6d, 0xce, 0x7e, 0x28, 0x4, 0xf, 0xd4, 0xb7, 0xaa, 0x19, 0x4f, 0x5f, 0x60, 0x5a, 0x59, 0x9, 0x89, 0xa3, 0xed, 0x24, 0xcc, 0x6f, 0x88, 0xf8, 0x53, 0xd7, 0xe3, 0x74, 0x7, 0x6d, 0xe1, 0x6e, 0xe9, 0xed, 0x64, 0xf, 0x46, 0x58, 0xe, 0x8f, 0x30, 0x6b, 0xdb, 0xd6, 0xbd, 0x56, 0xe0, 0x89, 0x87, 0x51, 0x4e, 0xad, 0xe3, 0x63, 0xf, 0x18, 0x41, 0x45, 0x52, 0xdd, 0x3e, 0x21, 0xe, 0x74, 0x6b, 0xd9, 0xcf, 0x4f, 0xa3, 0x94, 0x62, 0xff, 0x6a, 0x52, 0xbe, 0x15, 0x37, 0xbb, 0xad, 0xd4, 0x63, 0x28, 0x23, 0x26, 0x60, 0x90, 0xe7, 0xcd, 0xf6] var de_new_patch_uid : [UInt8] = [0xd6, 0xf1, 0x0f, 0x01, 0x00, 0xa4, 0x07, 0xe0] let de_new_patch_info : [UInt8] = [0x9d, 0x08, 0x30, 0x01, 0x9c, 0x16]; let sensorName = "3MH000GUR5W" + + public init() { + } - func fakeSensorPairingInfo() -> SensorPairingInfo{ + public func fakeSensorPairingInfo() -> SensorPairingInfo{ SensorPairingInfo(uuid: Data(de_new_patch_uid), patchInfo: Data(de_new_patch_info), fram: Data(de_new_packet), streamingEnabled: true, initialIdentificationStrategy: .byFakeSensorName, sensorName: sensorName) } } diff --git a/LibreTransmitterUI/SensorPairing/SensorPairing.swift b/LibreSensor/SensorPairing/SensorPairing.swift similarity index 75% rename from LibreTransmitterUI/SensorPairing/SensorPairing.swift rename to LibreSensor/SensorPairing/SensorPairing.swift index f5e8525..2d2a8f4 100644 --- a/LibreTransmitterUI/SensorPairing/SensorPairing.swift +++ b/LibreSensor/SensorPairing/SensorPairing.swift @@ -6,22 +6,21 @@ // import Foundation import Combine -import LibreTransmitter -class SensorPairingInfo: ObservableObject, Codable { - @Published private(set) var uuid: Data - @Published private(set) var patchInfo: Data - @Published private(set) var fram: Data - @Published private(set) var streamingEnabled: Bool - @Published private(set) var initialIdentificationStrategy: Libre2IdentificationStrategy = .byUid +public class SensorPairingInfo: ObservableObject, Codable { + @Published public var uuid: Data + @Published public var patchInfo: Data + @Published public var fram: Data + @Published public var streamingEnabled: Bool + @Published public var initialIdentificationStrategy: Libre2IdentificationStrategy = .byUid - @Published private(set) var sensorName : String? = nil + @Published public var sensorName : String? = nil enum CodingKeys: CodingKey { case uuid, patchInfo, fram, streamingEnabled, initialIdentificationStrategy, sensorName } - func encode(to encoder: Encoder) throws { + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(uuid, forKey: .uuid) @@ -33,7 +32,7 @@ class SensorPairingInfo: ObservableObject, Codable { } - required init(from decoder: Decoder) throws { + public required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) uuid = try container.decode(Data.self, forKey: .uuid) @@ -58,15 +57,15 @@ class SensorPairingInfo: ObservableObject, Codable { self.sensorName = sensorName } - var sensorData: SensorData? { + public var sensorData: SensorData? { SensorData(bytes: [UInt8](self.fram)) } - var calibrationData: SensorData.CalibrationInfo? { + public var calibrationData: SensorData.CalibrationInfo? { sensorData?.calibrationData } - var description: String { + public var description: String { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted @@ -80,6 +79,9 @@ class SensorPairingInfo: ObservableObject, Codable { } -protocol SensorPairingProtocol { - func pairSensor() +public protocol SensorPairingProtocol { + var publisher: AnyPublisher { get } + + func pairSensor() throws + } diff --git a/LibreTransmitterUI/SensorPairing/SensorPairingService.swift b/LibreSensor/SensorPairing/SensorPairingService.swift similarity index 87% rename from LibreTransmitterUI/SensorPairing/SensorPairingService.swift rename to LibreSensor/SensorPairing/SensorPairingService.swift index 41ba4a0..20f686a 100644 --- a/LibreTransmitterUI/SensorPairing/SensorPairingService.swift +++ b/LibreSensor/SensorPairing/SensorPairingService.swift @@ -17,26 +17,38 @@ public enum PairingError: Error { case wrongSensorType case decryptionError case noPatchInfo - - public var errorDescription: String { + case nfcNotSupported +} + +extension PairingError: LocalizedError { + public var errorDescription: String? { switch self { case .noTagInfo: - return "Could not get tag info" + return LocalizedString("Could not get tag info", comment: "error description for PairingError.noTagInfo") case .noSensorData: - return "Could not get sensor data" + return LocalizedString("Could not get sensor data", comment: "error description for PairingError.noSensorData") case .wrongSensorType: - return "Wrong sensor type detected" + return LocalizedString("Wrong sensor type detected", comment: "error description for PairingError.wrongSensorType") case .decryptionError: - return "Could not decrypt sensor contents" + return LocalizedString("Could not decrypt sensor contents", comment: "error description for PairingError.decryptionError") case .noPatchInfo: - return "Could not get patch info" + return LocalizedString("Could not get patch info", comment: "error description for PairingError.noPatchInfo") + case .nfcNotSupported: + return LocalizedString("Phone NFC not supported!", comment: "error description for PairingError.nfcNotSupported") } + } + public var recoverySuggestion: String? { + switch self { + case .nfcNotSupported: + return LocalizedString("Your phone or app is not enabled for NFC communications, which is needed to pair to libre2 sensors", comment: "Recovery suggestion for PairingError.nfcNotSupported") + default: + return nil + } } - } -class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, SensorPairingProtocol { +public class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, SensorPairingProtocol { private var session: NFCTagReaderSession? private var readingsSubject = PassthroughSubject() private var errorSubject = PassthroughSubject() @@ -46,7 +58,12 @@ class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, SensorPairing private let unlockCode: UInt32 = 42 // 42 - func pairSensor(){ + public func pairSensor() throws { + if !Features.phoneNFCAvailable { + throw PairingError.nfcNotSupported + } + print("Asked to pair sensor! phoneNFCAvailable: \(Features.phoneNFCAvailable)") + if NFCTagReaderSession.readingAvailable { accessQueue.async { self.session = NFCTagReaderSession(pollingOption: .iso15693, delegate: self, queue: self.nfcQueue) @@ -76,17 +93,17 @@ class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, SensorPairing } } - internal func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) { + public func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) { } - internal func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) { + public func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) { if let error = error as? NFCReaderError, error.code != .readerSessionInvalidationErrorUserCanceled { session.invalidate(errorMessage: "Connection failure: \(error.localizedDescription)") self.sendError(error) } } - internal func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { + public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { guard let firstTag = tags.first else { return } guard case .iso15693(let tag) = firstTag else { return } @@ -105,7 +122,7 @@ class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, SensorPairing tag.getSystemInfo(requestFlags: [.address, .highDataRate]) { result in switch result { case .failure: - session.invalidate(errorMessage: PairingError.noTagInfo.errorDescription) + session.invalidate(errorMessage: PairingError.noTagInfo.localizedDescription) self.sendError(PairingError.noTagInfo) return case .success: @@ -141,7 +158,7 @@ class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, SensorPairing // patchInfo should have length 6, which sometimes is not the case, as there are occuring crashes in nfcCommand and Libre2BLEUtilities.streamingUnlockPayload guard patchInfo.count >= 6 else { - session.invalidate(errorMessage: PairingError.noPatchInfo.errorDescription) + session.invalidate(errorMessage: PairingError.noPatchInfo.localizedDescription) return } @@ -164,13 +181,13 @@ class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, SensorPairing guard sensorUID.count == 8 && patchInfo.count == 6 && fram.count == 344 else { // self.readingsSubject.send(completion: .failure(LibreError.noSensorData)) - session.invalidate(errorMessage: PairingError.noSensorData.errorDescription) + session.invalidate(errorMessage: PairingError.noSensorData.localizedDescription) self.sendError(PairingError.noSensorData) return } guard sensorType == .libre2 else { - session.invalidate(errorMessage: PairingError.wrongSensorType.errorDescription) + session.invalidate(errorMessage: PairingError.wrongSensorType.localizedDescription) self.sendError(PairingError.noSensorData) return } @@ -187,15 +204,10 @@ class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, SensorPairing return } catch { print("problem decrypting") - session.invalidate(errorMessage: PairingError.decryptionError.errorDescription) + session.invalidate(errorMessage: PairingError.decryptionError.localizedDescription) self.sendError(PairingError.decryptionError) - } - - - } - } } } diff --git a/LibreTransmitter.xcodeproj/project.pbxproj b/LibreTransmitter.xcodeproj/project.pbxproj index c9bcc6d..1db242c 100644 --- a/LibreTransmitter.xcodeproj/project.pbxproj +++ b/LibreTransmitter.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 2709A6F329E5E0D3004355A3 /* FakeSensorPairingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2709A6F229E5E0D3004355A3 /* FakeSensorPairingData.swift */; }; 270C1429266047490063405B /* NotificationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 270C1428266047490063405B /* NotificationSettingsView.swift */; }; 271A39F52975844B0005FEDA /* NotificationHelperOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271A39F42975844B0005FEDA /* NotificationHelperOverride.swift */; }; 2735A6E029637DEA00D4E868 /* LibreTransmitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 432B0E881CDFC3C50045347B /* LibreTransmitter.framework */; }; @@ -21,8 +20,6 @@ 2746C73C26D91FC900E31BD9 /* Libre2DirectTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C73B26D91FC900E31BD9 /* Libre2DirectTransmitter.swift */; }; 2746C73F26DCF83700E31BD9 /* Features.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C73D26DCF83400E31BD9 /* Features.swift */; }; 2746C74226DD0F8800E31BD9 /* Libre2DirectSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C74126DD0F8800E31BD9 /* Libre2DirectSetup.swift */; }; - 2746C74526DF636900E31BD9 /* SensorPairingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C74426DF636900E31BD9 /* SensorPairingService.swift */; }; - 2746C74726DF63C800E31BD9 /* SensorPairing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C74626DF63C800E31BD9 /* SensorPairing.swift */; }; 274E71D3297ED77300FCFECD /* AuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274E71D2297ED77300FCFECD /* AuthView.swift */; }; 274E71D52986D4A600FCFECD /* CriticalAlarmsVolumeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274E71D42986D4A600FCFECD /* CriticalAlarmsVolumeView.swift */; }; 275786AB26753CC400845D0E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 275786AA26753CC400845D0E /* SettingsView.swift */; }; @@ -126,6 +123,12 @@ C1BDBAF12A4397E200A787D1 /* LibreTransmitterUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43A8EC82210E664300A81379 /* LibreTransmitterUI.framework */; }; C1BDBAF52A4397E200A787D1 /* LibreTransmitter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 432B0E881CDFC3C50045347B /* LibreTransmitter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C1BDBAF62A4397E200A787D1 /* LibreTransmitterUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43A8EC82210E664300A81379 /* LibreTransmitterUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1BDBAFF2A44FFD800A787D1 /* MockSensorPairingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BDBAFE2A44FFD800A787D1 /* MockSensorPairingService.swift */; }; + C1BDBB002A45051B00A787D1 /* LibreDemoPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BDBAFC2A43985000A787D1 /* LibreDemoPlugin.swift */; }; + C1BDBB022A45064F00A787D1 /* LibreDemoCGMManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */; }; + C1BDBB032A45158A00A787D1 /* SensorPairingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C74426DF636900E31BD9 /* SensorPairingService.swift */; }; + C1BDBB042A45159100A787D1 /* SensorPairing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C74626DF63C800E31BD9 /* SensorPairing.swift */; }; + C1BDBB052A45159400A787D1 /* FakeSensorPairingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2709A6F229E5E0D3004355A3 /* FakeSensorPairingData.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -330,6 +333,7 @@ C1BDBAFB2A4397E200A787D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = /Users/pete/dev/LoopWorkspace/LibreTransmitter/Info.plist; sourceTree = ""; }; C1BDBAFC2A43985000A787D1 /* LibreDemoPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreDemoPlugin.swift; sourceTree = ""; }; C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreDemoCGMManager.swift; sourceTree = ""; }; + C1BDBAFE2A44FFD800A787D1 /* MockSensorPairingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSensorPairingService.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -428,6 +432,7 @@ isa = PBXGroup; children = ( 27850BA7256724F00020D109 /* SensorContents */, + 2746C74326DF635300E31BD9 /* SensorPairing */, 27850BA3256724EF0020D109 /* GlucoseAlgorithm */, 27850BA0256724EF0020D109 /* LibreError.swift */, 27850BA2256724EF0020D109 /* LimitedQueue.swift */, @@ -647,7 +652,6 @@ 43A8EC83210E664300A81379 /* LibreTransmitterUI */ = { isa = PBXGroup; children = ( - 2746C74326DF635300E31BD9 /* SensorPairing */, 27850D2125672DFB0020D109 /* Controllers */, 27850D3E25672DFB0020D109 /* Graphics */, 27850D4825672DFB0020D109 /* Views */, @@ -687,6 +691,7 @@ C1BDBAE22A4397AA00A787D1 /* LibreDemoPlugin.h */, C1BDBAFC2A43985000A787D1 /* LibreDemoPlugin.swift */, C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */, + C1BDBAFE2A44FFD800A787D1 /* MockSensorPairingService.swift */, ); path = LibreDemoPlugin; sourceTree = ""; @@ -964,13 +969,16 @@ 27850D0E25672CA60020D109 /* ConcreteGlucoseDisplayable.swift in Sources */, 27850DC525672F640020D109 /* GlucoseFromRaw.swift in Sources */, 27850CEB25672C0C0020D109 /* DoubleExtensions.swift in Sources */, + C1BDBB042A45159100A787D1 /* SensorPairing.swift in Sources */, 27850CEF25672C0C0020D109 /* CollectionExtensions.swift in Sources */, 27850DB825672F4B0020D109 /* Calibration.swift in Sources */, 27850CED25672C0C0020D109 /* DateExtensions.swift in Sources */, 27850DBE25672F530020D109 /* LibreError.swift in Sources */, 27DD8F24268FA13F00649010 /* HashableClass.swift in Sources */, + C1BDBB052A45159400A787D1 /* FakeSensorPairingData.swift in Sources */, 27850D9F25672F190020D109 /* LibreTransmitterProxyProtocol.swift in Sources */, 27850DCC25672F770020D109 /* Measurement.swift in Sources */, + C1BDBB032A45158A00A787D1 /* SensorPairingService.swift in Sources */, 27850DCB25672F770020D109 /* SensorSerialNumber.swift in Sources */, 27850DA525672F1C0020D109 /* LibreTransmitterProxyManager.swift in Sources */, 27850CF725672C0C0020D109 /* UserDefaults+Bluetooth.swift in Sources */, @@ -1023,7 +1031,6 @@ 27850CF625672C0C0020D109 /* UserDefaults+Alarmsettings.swift in Sources */, 27850CEE25672C0C0020D109 /* DateExtensions.swift in Sources */, 27850CF425672C0C0020D109 /* UserDefaults+GlucoseSettings.swift in Sources */, - 2746C74726DF63C800E31BD9 /* SensorPairing.swift in Sources */, 27A5912726E0E32C00A7EE36 /* ModeSelectionView.swift in Sources */, 2746C73F26DCF83700E31BD9 /* Features.swift in Sources */, 27850CEC25672C0C0020D109 /* DoubleExtensions.swift in Sources */, @@ -1033,8 +1040,6 @@ 43A8EC91210E676500A81379 /* LibreTransmitterManager+UI.swift in Sources */, 27850CFA25672C0C0020D109 /* UIApplication+metadata.swift in Sources */, 27850CF025672C0C0020D109 /* CollectionExtensions.swift in Sources */, - 2746C74526DF636900E31BD9 /* SensorPairingService.swift in Sources */, - 2709A6F329E5E0D3004355A3 /* FakeSensorPairingData.swift in Sources */, 277773A72639EF2B00431547 /* BlueButtonStyle.swift in Sources */, 27850CFC25672C0C0020D109 /* GlucoseSchedules.swift in Sources */, 27DD8F2A269106A100649010 /* ViewExtensions.swift in Sources */, @@ -1056,7 +1061,10 @@ buildActionMask = 2147483647; files = ( C1BDBAED2A4397E200A787D1 /* OSLog.swift in Sources */, + C1BDBAFF2A44FFD800A787D1 /* MockSensorPairingService.swift in Sources */, + C1BDBB002A45051B00A787D1 /* LibreDemoPlugin.swift in Sources */, C1BDBAEE2A4397E200A787D1 /* LibreTransmitterPlugin.swift in Sources */, + C1BDBB022A45064F00A787D1 /* LibreDemoCGMManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1495,6 +1503,7 @@ COMBINE_HIDPI_IMAGES = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Info.plist; + INFOPLIST_KEY_NSPrincipalClass = LibreDemoPlugin; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( @@ -1532,6 +1541,7 @@ COMBINE_HIDPI_IMAGES = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Info.plist; + INFOPLIST_KEY_NSPrincipalClass = LibreDemoPlugin; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index 434be5b..d0f94fb 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -15,7 +15,7 @@ import CoreBluetooth import HealthKit import os.log -public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { +open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { public typealias GlucoseArrayWithPrediction = (trends: [LibreGlucose], historical: [LibreGlucose], prediction: [LibreGlucose]) public lazy var logger = Logger(forType: Self.self) @@ -185,7 +185,9 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega } - public var managerIdentifier = "LibreTransmitterManagerV3" + open var managerIdentifier: String { + "LibreTransmitterManagerV3" + } public required convenience init?(rawState: CGMManager.RawStateValue) { @@ -207,7 +209,7 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega UserDefaults.standard.mmSyncToNs } - public init() { + public required init() { lastConnected = nil logger.debug("LibreTransmitterManager will be created now") @@ -271,8 +273,10 @@ public final class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelega var lastDirectUpdate: Date? internal var countTimesWithoutData: Int = 0 - + open var service: SensorPairingProtocol { + return SensorPairingService() + } } // MARK: - Convenience functions @@ -427,9 +431,7 @@ extension LibreTransmitterManagerV3 { self.glucoseInfoObservable.prediction = nil self.glucoseInfoObservable.predictionDate = nil } - } - } func getStartDateForFilter() -> Date? { diff --git a/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift b/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift index eff386f..fc27d37 100644 --- a/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift +++ b/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift @@ -19,15 +19,17 @@ class LibreTransmitterSetupViewController: UINavigationController, CGMManagerOnb fileprivate lazy var logger = Logger(forType: Self.self) - lazy var cgmManager: LibreTransmitterManagerV3? = LibreTransmitterManagerV3() + var cgmManager: LibreTransmitterManagerV3 - init(displayGlucosePreference: DisplayGlucosePreference) { + init(displayGlucosePreference: DisplayGlucosePreference, cgmManager: LibreTransmitterManagerV3) { SelectionState.shared.selectedStringIdentifier = UserDefaults.standard.preSelectedDevice + self.cgmManager = cgmManager + let cancelNotifier = GenericObservableObject() let saveNotifier = GenericObservableObject() - let myView = ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier) + let myView = ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, service: cgmManager.service) .environmentObject(displayGlucosePreference) super.init(rootViewController: UIHostingController(rootView: myView)) @@ -42,10 +44,6 @@ class LibreTransmitterSetupViewController: UINavigationController, CGMManagerOnb } - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - } - deinit { logger.debug("LibreTransmitterSetupViewController() deinit was called") // cgmManager = nil @@ -87,14 +85,9 @@ class LibreTransmitterSetupViewController: UINavigationController, CGMManagerOnb // stored both preSelectedDevice and selectedUID ! } - if let cgmManager { - logger.debug("Setupcontroller Saving from setup") - cgmManagerOnboardingDelegate?.cgmManagerOnboarding(didCreateCGMManager: cgmManager) - cgmManagerOnboardingDelegate?.cgmManagerOnboarding(didOnboardCGMManager: cgmManager) - - } else { - logger.debug("Setupcontroller not Saving from setup") - } + logger.debug("Setupcontroller Saving from setup") + cgmManagerOnboardingDelegate?.cgmManagerOnboarding(didCreateCGMManager: cgmManager) + cgmManagerOnboardingDelegate?.cgmManagerOnboarding(didOnboardCGMManager: cgmManager) completionDelegate?.completionNotifyingDidComplete(self) } diff --git a/LibreTransmitterUI/LibreTransmitterManager+UI.swift b/LibreTransmitterUI/LibreTransmitterManager+UI.swift index f8f462e..35c05c5 100644 --- a/LibreTransmitterUI/LibreTransmitterManager+UI.swift +++ b/LibreTransmitterUI/LibreTransmitterManager+UI.swift @@ -24,9 +24,12 @@ extension LibreTransmitterManagerV3: CGMManagerUI { nil } - public static func setupViewController(bluetoothProvider: BluetoothProvider, displayGlucosePreference: DisplayGlucosePreference, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool, prefersToSkipUserInteraction: Bool) -> SetupUIResult { + public static func setupViewController(bluetoothProvider: BluetoothProvider, displayGlucosePreference: DisplayGlucosePreference, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool, prefersToSkipUserInteraction: Bool) -> SetupUIResult + { + let cgmManager = self.init() + let vc = LibreTransmitterSetupViewController(displayGlucosePreference: displayGlucosePreference, cgmManager: cgmManager) - return .userInteractionRequired(LibreTransmitterSetupViewController(displayGlucosePreference: displayGlucosePreference)) + return .userInteractionRequired(vc) } public func settingsViewController(bluetoothProvider: BluetoothProvider, displayGlucosePreference: DisplayGlucosePreference, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> CGMManagerViewController { @@ -45,7 +48,9 @@ extension LibreTransmitterManagerV3: CGMManagerUI { transmitterInfoObservable: self.transmitterInfoObservable, sensorInfoObervable: self.sensorInfoObservable, glucoseInfoObservable: self.glucoseInfoObservable, - alarmStatus: self.alarmStatus) + alarmStatus: self.alarmStatus, + service: self.service + ) let nav = CGMManagerSettingsNavigationViewController(rootViewController: settings) nav.navigationItem.largeTitleDisplayMode = .always diff --git a/LibreTransmitterUI/Views/Settings/SettingsView.swift b/LibreTransmitterUI/Views/Settings/SettingsView.swift index ef4f0ca..64efe75 100644 --- a/LibreTransmitterUI/Views/Settings/SettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/SettingsView.swift @@ -75,6 +75,8 @@ struct SettingsView: View { // @State private var showingExporter = false // @Environment(\.presentationMode) var presentationMode + var service: SensorPairingProtocol + static func asHostedViewController( displayGlucosePreference: DisplayGlucosePreference, notifyComplete: GenericObservableObject, @@ -84,7 +86,9 @@ struct SettingsView: View { transmitterInfoObservable: LibreTransmitter.TransmitterInfo, sensorInfoObervable: LibreTransmitter.SensorInfo, glucoseInfoObservable: LibreTransmitter.GlucoseInfo, - alarmStatus: LibreTransmitter.AlarmStatus) -> DismissibleHostingController + alarmStatus: LibreTransmitter.AlarmStatus, + service: SensorPairingProtocol + ) -> DismissibleHostingController { let view = self.init( transmitterInfo: transmitterInfoObservable, @@ -94,7 +98,8 @@ struct SettingsView: View { notifyDelete: notifyDelete, notifyReset: notifyReset, notifyReconnect: notifyReconnect, - alarmStatus: alarmStatus + alarmStatus: alarmStatus, + service: service ).environmentObject(displayGlucosePreference) return DismissibleHostingController(rootView: view) } @@ -220,7 +225,7 @@ struct SettingsView: View { var sensorChangeSection: some View { Section { - NavigationLink(destination: AuthView(completeNotifier: notifyComplete, notifyReset: notifyReset, notifyReconnect: notifyReconnect)) { + NavigationLink(destination: AuthView(completeNotifier: notifyComplete, notifyReset: notifyReset, notifyReconnect: notifyReconnect, service: service)) { /*Button("Change Sensor") { }.foregroundColor(.blue)*/ SettingsItem(title: "Change Sensor").foregroundColor(.blue) diff --git a/LibreTransmitterUI/Views/Setup/AuthView.swift b/LibreTransmitterUI/Views/Setup/AuthView.swift index b87d3f7..d97e3ee 100644 --- a/LibreTransmitterUI/Views/Setup/AuthView.swift +++ b/LibreTransmitterUI/Views/Setup/AuthView.swift @@ -8,6 +8,8 @@ import SwiftUI import LoopKitUI +import LibreTransmitter + // this view should only be called when setting up a new device in an existing cgmmanager struct AuthView: View { @@ -21,6 +23,8 @@ struct AuthView: View { @State private var isAuthenticated = false @State private var hasSetupListeners = false + + var service: SensorPairingProtocol var exclamation: Image { Image(systemName: "exclamationmark.triangle.fill") @@ -37,7 +41,7 @@ struct AuthView: View { Text(LocalizedString("Authenticated", comment: "Text confirming user is authenticated in AuthView")) .transition(AnyTransition.opacity.combined(with: .move(edge: .bottom))) - NavigationLink(destination: ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier), isActive: $isNavigationActive) { + NavigationLink(destination: ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, service: service), isActive: $isNavigationActive) { Button(action: { self.notifyReset.notify() self.isNavigationActive = true @@ -155,6 +159,6 @@ struct AuthView: View { struct AuthView_Previews: PreviewProvider { static var previews: some View { - AuthView(completeNotifier: GenericObservableObject(), notifyReset: GenericObservableObject(), notifyReconnect: GenericObservableObject()) + AuthView(completeNotifier: GenericObservableObject(), notifyReset: GenericObservableObject(), notifyReconnect: GenericObservableObject(), service: SensorPairingService()) } } diff --git a/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift b/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift index 64e33bc..4243f85 100644 --- a/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift +++ b/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift @@ -379,7 +379,7 @@ extension BluetoothSelection { struct BluetoothSelection_Previews: PreviewProvider { static var previews: some View { - var testData = SelectionState.shared + let testData = SelectionState.shared testData.selectedStringIdentifier = "device4" return BluetoothSelection(debugMode: true, cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject()) diff --git a/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift b/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift index 77b66c6..ef3b3f0 100644 --- a/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift +++ b/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift @@ -21,14 +21,13 @@ struct Libre2DirectSetup: View { @State private var presentableStatus: StatusMessage? @State private var showPairingInfo = false - @State private var service = SensorPairingService() @State private var pairingInfo = SensorPairingInfo() @ObservedObject public var cancelNotifier: GenericObservableObject @ObservedObject public var saveNotifier: GenericObservableObject - public var isMockedSensor = false + var service: SensorPairingProtocol func pairMockedSensor() { let info = FakeSensorPairingData().fakeSensorPairingInfo() @@ -40,21 +39,15 @@ struct Libre2DirectSetup: View { } func pairSensor() { - - guard !isMockedSensor else { - pairMockedSensor() - return - } - - if !Features.phoneNFCAvailable { - presentableStatus = StatusMessage(title: "Phone NFC required!", message: "Your phone or app is not enabled for NFC communications, which is needed to pair to libre2 sensors") - return - } - print("Asked to pair sensor! phoneNFCAvailable: \(Features.phoneNFCAvailable)") - showPairingInfo = false - service.pairSensor() + showPairingInfo = false + do { + try service.pairSensor() + } catch { + let message = (error as? LocalizedError)?.recoverySuggestion ?? error.localizedDescription + presentableStatus = StatusMessage(title: error.localizedDescription, message: message) + } } func receivePairingInfo(_ info: SensorPairingInfo) { @@ -143,7 +136,7 @@ struct Libre2DirectSetup: View { struct Libre2DirectSetup_Previews: PreviewProvider { static var previews: some View { - Libre2DirectSetup(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject()) + Libre2DirectSetup(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), service: SensorPairingService()) } } #endif diff --git a/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift b/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift index e368540..2e54c20 100644 --- a/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift +++ b/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift @@ -8,31 +8,20 @@ import SwiftUI import LoopKitUI +import LibreTransmitter struct ModeSelectionView: View { @ObservedObject public var cancelNotifier: GenericObservableObject @ObservedObject public var saveNotifier: GenericObservableObject - - var supportsFakeSensor = Features.supportsFakeSensor + + var service: SensorPairingProtocol var modeSelectSection : some View { Section(header: Text(LocalizedString("Connection options", comment: "Text describing options for connecting to sensor or transmitter"))) { - if supportsFakeSensor { - NavigationLink(destination: Libre2DirectSetup(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, isMockedSensor: true)) { - - SettingsItem(title: LocalizedString("Fake Libre 2 Direct", comment: "Fake Libre 2 connection option")) - .actionButtonStyle(.primary) - .padding([.top, .bottom], 8) - - } - } - #if canImport(CoreNFC) - - - NavigationLink(destination: Libre2DirectSetup(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier)) { + NavigationLink(destination: Libre2DirectSetup(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, service: service)) { SettingsItem(title: LocalizedString("Libre 2 Direct", comment: "Libre 2 connection option")) .actionButtonStyle(.primary) @@ -88,6 +77,6 @@ struct ModeSelectionView: View { struct ModeSelectionView_Previews: PreviewProvider { static var previews: some View { - ModeSelectionView(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject()) + ModeSelectionView(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), service: SensorPairingService()) } } From 136d4589d1bae8626695cf87b9c7c459c8e3409b Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 23 Jun 2023 13:02:50 -0500 Subject: [PATCH 06/19] Pairing for demo working --- Bluetooth/BluetoothSearch.swift | 2 +- .../LibreTransmitterProxyManager.swift | 33 ++------------- Features.swift | 16 -------- LibreDemoPlugin/LibreDemoCGMManager.swift | 4 +- .../MockSensorPairingService.swift | 16 +++++++- LibreSensor/SensorContents/Sensor.swift | 9 +--- .../SensorPairing/FakeSensorPairingData.swift | 2 +- LibreSensor/SensorPairing/SensorPairing.swift | 13 ++---- .../SensorPairing/SensorPairingService.swift | 2 - .../LibreTransmitterManagerV3.swift | 2 +- LibreTransmitter/NotificationHelper.swift | 2 +- .../LibreTransmitterSetupViewController.swift | 4 +- .../LibreTransmitterManager+UI.swift | 2 +- .../Views/Settings/SettingsView.swift | 8 ++-- LibreTransmitterUI/Views/Setup/AuthView.swift | 6 +-- .../Views/Setup/Libre2DirectSetup.swift | 41 +++++++++---------- .../Views/Setup/ModeSelectionView.swift | 30 ++++++-------- .../Views/Utilities/ViewExtensions.swift | 3 +- 18 files changed, 71 insertions(+), 124 deletions(-) diff --git a/Bluetooth/BluetoothSearch.swift b/Bluetooth/BluetoothSearch.swift index dd1c450..15de1eb 100644 --- a/Bluetooth/BluetoothSearch.swift +++ b/Bluetooth/BluetoothSearch.swift @@ -140,7 +140,7 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph case .poweredOn: // we don't want this to start scanning right away, but rather wait until the view has appeared // this means that the view is responsible for calling scanForCompatibleDevices it self - // scanForCompatibleDevices() // power was switched on, while app is running -> reconnect. + scanForCompatibleDevices() // power was switched on, while app is running -> reconnect. break @unknown default: diff --git a/Bluetooth/Transmitter/LibreTransmitterProxyManager.swift b/Bluetooth/Transmitter/LibreTransmitterProxyManager.swift index f5d1841..833c103 100644 --- a/Bluetooth/Transmitter/LibreTransmitterProxyManager.swift +++ b/Bluetooth/Transmitter/LibreTransmitterProxyManager.swift @@ -449,28 +449,15 @@ public final class LibreTransmitterProxyManager: NSObject, CBCentralManagerDeleg var foundUUID = manufacturerData.subdata(in: 2..<8) foundUUID.append(contentsOf: [0x07, 0xe0]) - logger.debug("ManufacturerData: \(manufacturerData), found uid: \(foundUUID)") + logger.debug("ManufacturerData: \(manufacturerData.hex), found uid: \(foundUUID.hex)") guard foundUUID == selectedUid && Libre2DirectTransmitter.canSupportPeripheral(peripheral) else { return false } - - return true } - private func verifyLibre2ByName(peripheral: CBPeripheral, name: String) -> Bool { - - // This method is not as robust, and should only be used in cases where manufacturerdata is not available, such as when using the tzachi-dar simulator - guard peripheral.name?.contains(name) == true else { - return false - } - return Libre2DirectTransmitter.canSupportPeripheral(peripheral) - - - } - public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) { dispatchPrecondition(condition: .onQueue(managerQueue)) @@ -488,22 +475,10 @@ public final class LibreTransmitterProxyManager: NSObject, CBCentralManagerDeleg logger.debug("preselected sensor is: \(String(describing:sensor))") - if let sensor = UserDefaults.standard.preSelectedSensor, let name = sensor.sensorName, sensor.initialIdentificationStrategy == .byFakeSensorName { - logger.debug("Verifiying libre2 connection using sensor name") - if !verifyLibre2ByName(peripheral: peripheral, name: name) { - logger.debug("failed Verifiying libre2 connection using sensor name") - return - } - - } else { - logger.debug("Verifiying libre2 connection using manufacturerData") - if !verifyLibre2ManufacturerData(peripheral: peripheral, selectedUid: selectedUid, advertisementData: advertisementData) { - logger.debug("failed Verifiying libre2 connection using manufacturerData") - return - } + if !verifyLibre2ManufacturerData(peripheral: peripheral, selectedUid: selectedUid, advertisementData: advertisementData) { + logger.debug("failed Verifiying libre2 connection using manufacturerData") + return } - - // next time we search via bluetooth, let's identify the sensor with its bluetooth identifier UserDefaults.standard.preSelectedUid = nil diff --git a/Features.swift b/Features.swift index 713e950..e5f56f1 100644 --- a/Features.swift +++ b/Features.swift @@ -8,9 +8,7 @@ import Foundation -#if canImport(CoreNFC) import CoreNFC -#endif public final class Features { @@ -21,21 +19,7 @@ public final class Features { static public var allowsEditingFactoryCalibrationData = false - // Only to be used with this program, running on a linux amd64 system or rasberry pi - // https://github.com/tzachi-dar/gatt#this-is-a-program-for-creating-a-simulation-for-libre-2-for-xdrip - static public var supportsFakeSensor = false - static var phoneNFCAvailable: Bool { - #if canImport(CoreNFC) - if NSClassFromString("NFCNDEFReaderSession") == nil { - return false - - } - return NFCNDEFReaderSession.readingAvailable - #else - return false - #endif } - } diff --git a/LibreDemoPlugin/LibreDemoCGMManager.swift b/LibreDemoPlugin/LibreDemoCGMManager.swift index ad759fd..6032637 100644 --- a/LibreDemoPlugin/LibreDemoCGMManager.swift +++ b/LibreDemoPlugin/LibreDemoCGMManager.swift @@ -17,9 +17,7 @@ class LibreDemoCGMManager: LibreTransmitterManagerV3 { "LibreDemoCGMManager" } - - public override var service: SensorPairingProtocol { + public override var pairingService: SensorPairingProtocol { return MockSensorPairingService() } - } diff --git a/LibreDemoPlugin/MockSensorPairingService.swift b/LibreDemoPlugin/MockSensorPairingService.swift index 570ac9f..f407126 100644 --- a/LibreDemoPlugin/MockSensorPairingService.swift +++ b/LibreDemoPlugin/MockSensorPairingService.swift @@ -9,16 +9,30 @@ import Foundation import LibreTransmitter import Combine +import os.log class MockSensorPairingService: SensorPairingProtocol { + fileprivate lazy var logger = Logger(forType: Self.self) + private var readingsSubject = PassthroughSubject() public var publisher: AnyPublisher { readingsSubject.eraseToAnyPublisher() } + private func sendUpdate(_ info: SensorPairingInfo) { + DispatchQueue.main.async { [weak self] in + self?.readingsSubject.send(info) + } + } + func pairSensor() throws { - print("Here") + let info = FakeSensorPairingData().fakeSensorPairingInfo() + logger.debug("Sending fake sensor pairinginfo: \(info.description)") + //delay a bit to simulate a real tag readout + DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { + self.sendUpdate(info) + } } } diff --git a/LibreSensor/SensorContents/Sensor.swift b/LibreSensor/SensorContents/Sensor.swift index 2815dbe..e76f307 100644 --- a/LibreSensor/SensorContents/Sensor.swift +++ b/LibreSensor/SensorContents/Sensor.swift @@ -60,10 +60,6 @@ public struct CalibrationToSensorMapping: Codable { self.reverseFooterCRC = reverseFooterCRC } } -public enum Libre2IdentificationStrategy: Int, Codable { - case byUid = 0 //default - case byFakeSensorName = 1 //only when used with tzachi-dar-simulator -} public struct Sensor: Codable { public let uuid: Data @@ -81,8 +77,6 @@ public struct Sensor: Codable { public var unlockCount: Int - var initialIdentificationStrategy: Libre2IdentificationStrategy - var sensorName : String? /* @@ -116,7 +110,7 @@ public struct Sensor: Codable { } } */ - public init(uuid: Data, patchInfo: Data, maxAge: Int, unlockCount: Int = 0, initialIdentificationStrategy: Libre2IdentificationStrategy = .byUid, sensorName: String? = nil) { + public init(uuid: Data, patchInfo: Data, maxAge: Int, unlockCount: Int = 0, sensorName: String? = nil) { self.uuid = uuid self.patchInfo = patchInfo @@ -129,7 +123,6 @@ public struct Sensor: Codable { self.unlockCount = 0 self.maxAge = maxAge // self.calibrationInfo = calibrationInfo - self.initialIdentificationStrategy = initialIdentificationStrategy self.sensorName = sensorName } diff --git a/LibreSensor/SensorPairing/FakeSensorPairingData.swift b/LibreSensor/SensorPairing/FakeSensorPairingData.swift index ade05a9..aa9d992 100644 --- a/LibreSensor/SensorPairing/FakeSensorPairingData.swift +++ b/LibreSensor/SensorPairing/FakeSensorPairingData.swift @@ -22,6 +22,6 @@ public struct FakeSensorPairingData { } public func fakeSensorPairingInfo() -> SensorPairingInfo{ - SensorPairingInfo(uuid: Data(de_new_patch_uid), patchInfo: Data(de_new_patch_info), fram: Data(de_new_packet), streamingEnabled: true, initialIdentificationStrategy: .byFakeSensorName, sensorName: sensorName) + SensorPairingInfo(uuid: Data(de_new_patch_uid), patchInfo: Data(de_new_patch_info), fram: Data(de_new_packet), streamingEnabled: true, sensorName: sensorName) } } diff --git a/LibreSensor/SensorPairing/SensorPairing.swift b/LibreSensor/SensorPairing/SensorPairing.swift index 2d2a8f4..a0b99cc 100644 --- a/LibreSensor/SensorPairing/SensorPairing.swift +++ b/LibreSensor/SensorPairing/SensorPairing.swift @@ -13,12 +13,11 @@ public class SensorPairingInfo: ObservableObject, Codable { @Published public var patchInfo: Data @Published public var fram: Data @Published public var streamingEnabled: Bool - @Published public var initialIdentificationStrategy: Libre2IdentificationStrategy = .byUid - + @Published public var sensorName : String? = nil enum CodingKeys: CodingKey { - case uuid, patchInfo, fram, streamingEnabled, initialIdentificationStrategy, sensorName + case uuid, patchInfo, fram, streamingEnabled, sensorName } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -27,7 +26,6 @@ public class SensorPairingInfo: ObservableObject, Codable { try container.encode(patchInfo, forKey: .patchInfo) try container.encode(fram, forKey: .fram) try container.encode(streamingEnabled, forKey: .streamingEnabled) - try container.encode(initialIdentificationStrategy, forKey: .initialIdentificationStrategy) try container.encode(sensorName, forKey: .sensorName) @@ -40,20 +38,15 @@ public class SensorPairingInfo: ObservableObject, Codable { fram = try container.decode(Data.self, forKey: .fram) streamingEnabled = try container.decode(Bool.self, forKey: .streamingEnabled) - initialIdentificationStrategy = try container.decode(Libre2IdentificationStrategy.self, forKey: .initialIdentificationStrategy) sensorName = try container.decode(String?.self, forKey: .sensorName) - - - } - public init(uuid: Data=Data(), patchInfo: Data=Data(), fram: Data=Data(), streamingEnabled: Bool = false, initialIdentificationStrategy: Libre2IdentificationStrategy = .byUid, sensorName: String? = nil ) { + public init(uuid: Data=Data(), patchInfo: Data=Data(), fram: Data=Data(), streamingEnabled: Bool = false, sensorName: String? = nil ) { self.uuid = uuid self.patchInfo = patchInfo self.fram = fram self.streamingEnabled = streamingEnabled - self.initialIdentificationStrategy = initialIdentificationStrategy self.sensorName = sensorName } diff --git a/LibreSensor/SensorPairing/SensorPairingService.swift b/LibreSensor/SensorPairing/SensorPairingService.swift index 20f686a..2bd996e 100644 --- a/LibreSensor/SensorPairing/SensorPairingService.swift +++ b/LibreSensor/SensorPairing/SensorPairingService.swift @@ -5,7 +5,6 @@ // Created by Reimar Metzen on 06.07.21. // -#if canImport(CoreNFC) import Foundation import Combine import CoreNFC @@ -392,4 +391,3 @@ private enum Subcommand: UInt8, CustomStringConvertible { } } } -#endif diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index d0f94fb..f6b5a57 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -274,7 +274,7 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { internal var countTimesWithoutData: Int = 0 - open var service: SensorPairingProtocol { + open var pairingService: SensorPairingProtocol { return SensorPairingService() } } diff --git a/LibreTransmitter/NotificationHelper.swift b/LibreTransmitter/NotificationHelper.swift index 7e7d8ec..8060014 100644 --- a/LibreTransmitter/NotificationHelper.swift +++ b/LibreTransmitter/NotificationHelper.swift @@ -146,7 +146,7 @@ public enum NotificationHelper { // MARK: Sensor related notification sendouts public extension NotificationHelper { - static func sendLibre2FirectFinishedSetupNotifcation() { + static func sendLibre2DirectFinishedSetupNotifcation() { ensureCanSendNotification { let content = UNMutableNotificationContent() content.title = "Libre 2 Direct Setup Complete" diff --git a/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift b/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift index fc27d37..a952523 100644 --- a/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift +++ b/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift @@ -29,7 +29,7 @@ class LibreTransmitterSetupViewController: UINavigationController, CGMManagerOnb let cancelNotifier = GenericObservableObject() let saveNotifier = GenericObservableObject() - let myView = ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, service: cgmManager.service) + let myView = ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, pairingService: cgmManager.pairingService) .environmentObject(displayGlucosePreference) super.init(rootViewController: UIHostingController(rootView: myView)) @@ -73,7 +73,7 @@ class LibreTransmitterSetupViewController: UINavigationController, CGMManagerOnb } else if let newUID = SelectionState.shared.selectedUID { // this one is only temporary, // as we don't know the bluetooth identifier during nfc setup - logger.debug("Setupcontroller will set new libre2 device to \(newUID)") + logger.debug("Setupcontroller will set new libre2 device to \(newUID.hex)") UserDefaults.standard.preSelectedUid = newUID SelectionState.shared.selectedUID = nil diff --git a/LibreTransmitterUI/LibreTransmitterManager+UI.swift b/LibreTransmitterUI/LibreTransmitterManager+UI.swift index 35c05c5..3574754 100644 --- a/LibreTransmitterUI/LibreTransmitterManager+UI.swift +++ b/LibreTransmitterUI/LibreTransmitterManager+UI.swift @@ -49,7 +49,7 @@ extension LibreTransmitterManagerV3: CGMManagerUI { sensorInfoObervable: self.sensorInfoObservable, glucoseInfoObservable: self.glucoseInfoObservable, alarmStatus: self.alarmStatus, - service: self.service + pairingService: self.pairingService ) let nav = CGMManagerSettingsNavigationViewController(rootViewController: settings) diff --git a/LibreTransmitterUI/Views/Settings/SettingsView.swift b/LibreTransmitterUI/Views/Settings/SettingsView.swift index 64efe75..336c252 100644 --- a/LibreTransmitterUI/Views/Settings/SettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/SettingsView.swift @@ -75,7 +75,7 @@ struct SettingsView: View { // @State private var showingExporter = false // @Environment(\.presentationMode) var presentationMode - var service: SensorPairingProtocol + var pairingService: SensorPairingProtocol static func asHostedViewController( displayGlucosePreference: DisplayGlucosePreference, @@ -87,7 +87,7 @@ struct SettingsView: View { sensorInfoObervable: LibreTransmitter.SensorInfo, glucoseInfoObservable: LibreTransmitter.GlucoseInfo, alarmStatus: LibreTransmitter.AlarmStatus, - service: SensorPairingProtocol + pairingService: SensorPairingProtocol ) -> DismissibleHostingController { let view = self.init( @@ -99,7 +99,7 @@ struct SettingsView: View { notifyReset: notifyReset, notifyReconnect: notifyReconnect, alarmStatus: alarmStatus, - service: service + pairingService: pairingService ).environmentObject(displayGlucosePreference) return DismissibleHostingController(rootView: view) } @@ -225,7 +225,7 @@ struct SettingsView: View { var sensorChangeSection: some View { Section { - NavigationLink(destination: AuthView(completeNotifier: notifyComplete, notifyReset: notifyReset, notifyReconnect: notifyReconnect, service: service)) { + NavigationLink(destination: AuthView(completeNotifier: notifyComplete, notifyReset: notifyReset, notifyReconnect: notifyReconnect, pairingService: pairingService)) { /*Button("Change Sensor") { }.foregroundColor(.blue)*/ SettingsItem(title: "Change Sensor").foregroundColor(.blue) diff --git a/LibreTransmitterUI/Views/Setup/AuthView.swift b/LibreTransmitterUI/Views/Setup/AuthView.swift index d97e3ee..a8638db 100644 --- a/LibreTransmitterUI/Views/Setup/AuthView.swift +++ b/LibreTransmitterUI/Views/Setup/AuthView.swift @@ -24,7 +24,7 @@ struct AuthView: View { @State private var isAuthenticated = false @State private var hasSetupListeners = false - var service: SensorPairingProtocol + var pairingService: SensorPairingProtocol var exclamation: Image { Image(systemName: "exclamationmark.triangle.fill") @@ -41,7 +41,7 @@ struct AuthView: View { Text(LocalizedString("Authenticated", comment: "Text confirming user is authenticated in AuthView")) .transition(AnyTransition.opacity.combined(with: .move(edge: .bottom))) - NavigationLink(destination: ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, service: service), isActive: $isNavigationActive) { + NavigationLink(destination: ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, pairingService: pairingService), isActive: $isNavigationActive) { Button(action: { self.notifyReset.notify() self.isNavigationActive = true @@ -159,6 +159,6 @@ struct AuthView: View { struct AuthView_Previews: PreviewProvider { static var previews: some View { - AuthView(completeNotifier: GenericObservableObject(), notifyReset: GenericObservableObject(), notifyReconnect: GenericObservableObject(), service: SensorPairingService()) + AuthView(completeNotifier: GenericObservableObject(), notifyReset: GenericObservableObject(), notifyReconnect: GenericObservableObject(), pairingService: SensorPairingService()) } } diff --git a/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift b/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift index ef3b3f0..0e7b7df 100644 --- a/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift +++ b/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift @@ -12,38 +12,27 @@ import LoopKitUI import LoopKit import os.log -#if canImport(CoreNFC) - fileprivate var logger = Logger(forType: "Libre2DirectSetup") struct Libre2DirectSetup: View { @State private var presentableStatus: StatusMessage? @State private var showPairingInfo = false - - + @State private var isPairing = false @State private var pairingInfo = SensorPairingInfo() @ObservedObject public var cancelNotifier: GenericObservableObject @ObservedObject public var saveNotifier: GenericObservableObject - var service: SensorPairingProtocol - - func pairMockedSensor() { - let info = FakeSensorPairingData().fakeSensorPairingInfo() - logger.debug("Sending fake sensor pairinginfo: \(info.description)") - //delay a bit to simulate a real tag readout - DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { - receivePairingInfo(info) - } - } + var pairingService: SensorPairingProtocol func pairSensor() { showPairingInfo = false + isPairing = true do { - try service.pairSensor() + try pairingService.pairSensor() } catch { let message = (error as? LocalizedError)?.recoverySuggestion ?? error.localizedDescription presentableStatus = StatusMessage(title: error.localizedDescription, message: message) @@ -56,6 +45,7 @@ struct Libre2DirectSetup: View { pairingInfo = info + isPairing = false showPairingInfo = true // calibrationdata must always be extracted from the full nfc scan @@ -77,7 +67,7 @@ struct Libre2DirectSetup: View { let max = info.sensorData?.maxMinutesWearTime ?? 0 - let sensor = Sensor(uuid: info.uuid, patchInfo: info.patchInfo, maxAge: max, initialIdentificationStrategy: info.initialIdentificationStrategy, sensorName: info.sensorName) + let sensor = Sensor(uuid: info.uuid, patchInfo: info.patchInfo, maxAge: max, sensorName: info.sensorName) UserDefaults.standard.preSelectedSensor = sensor SelectionState.shared.selectedUID = pairingInfo.uuid @@ -87,7 +77,7 @@ struct Libre2DirectSetup: View { SelectionState.shared.selectedStringIdentifier = nil print("Paired and set selected UID to: \(String(describing: SelectionState.shared.selectedUID?.hex))") saveNotifier.notify() - NotificationHelper.sendLibre2FirectFinishedSetupNotifcation() + NotificationHelper.sendLibre2DirectFinishedSetupNotifcation() } @@ -117,16 +107,26 @@ struct Libre2DirectSetup: View { }) { VStack(spacing: 10) { - Button("Pair Sensor & connect") { + Button { pairSensor() + } label: { + if isPairing { + HStack(spacing: 10) { + ProgressView() + Text(LocalizedString("Pairing...", comment: "Button title for pairing sensor when pairing")) + } + } else { + Text(LocalizedString("Pair Sensor", comment: "Button title for pairing sensor")) + } } .actionButtonStyle(.primary) + .disabled(isPairing) }.padding() } .navigationTitle("Libre 2 Setup") .navigationBarBackButtonHidden(true) .navigationBarItems(leading: cancelButton) // the pair button does the save process for us! //, trailing: saveButton) - .onReceive(service.publisher, perform: receivePairingInfo) + .onReceive(pairingService.publisher, perform: receivePairingInfo) .alert(item: $presentableStatus) { status in Alert(title: Text(status.title), message: Text(status.message), dismissButton: .default(Text("Got it!"))) @@ -136,7 +136,6 @@ struct Libre2DirectSetup: View { struct Libre2DirectSetup_Previews: PreviewProvider { static var previews: some View { - Libre2DirectSetup(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), service: SensorPairingService()) + Libre2DirectSetup(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), pairingService: SensorPairingService()) } } -#endif diff --git a/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift b/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift index 2e54c20..299103f 100644 --- a/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift +++ b/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift @@ -15,28 +15,22 @@ struct ModeSelectionView: View { @ObservedObject public var cancelNotifier: GenericObservableObject @ObservedObject public var saveNotifier: GenericObservableObject - var service: SensorPairingProtocol + var pairingService: SensorPairingProtocol var modeSelectSection : some View { Section(header: Text(LocalizedString("Connection options", comment: "Text describing options for connecting to sensor or transmitter"))) { - #if canImport(CoreNFC) - NavigationLink(destination: Libre2DirectSetup(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, service: service)) { - - SettingsItem(title: LocalizedString("Libre 2 Direct", comment: "Libre 2 connection option")) - .actionButtonStyle(.primary) - .padding([.top, .bottom], 8) - - } - - #endif - - NavigationLink(destination: BluetoothSelection(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier)) { - SettingsItem(title: LocalizedString("Bluetooth Transmitters", comment: "Bluetooth Transmitter connection option")) - .actionButtonStyle(.primary) - .padding([.top, .bottom], 8) - } + NavigationLink(destination: Libre2DirectSetup(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, pairingService: pairingService)) { + SettingsItem(title: LocalizedString("Libre 2 Direct", comment: "Libre 2 connection option")) + .actionButtonStyle(.primary) + .padding([.top, .bottom], 8) + } + NavigationLink(destination: BluetoothSelection(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier)) { + SettingsItem(title: LocalizedString("Bluetooth Transmitters", comment: "Bluetooth Transmitter connection option")) + .actionButtonStyle(.primary) + .padding([.top, .bottom], 8) + } } } @@ -77,6 +71,6 @@ struct ModeSelectionView: View { struct ModeSelectionView_Previews: PreviewProvider { static var previews: some View { - ModeSelectionView(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), service: SensorPairingService()) + ModeSelectionView(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), pairingService: SensorPairingService()) } } diff --git a/LibreTransmitterUI/Views/Utilities/ViewExtensions.swift b/LibreTransmitterUI/Views/Utilities/ViewExtensions.swift index edfdf81..493fc11 100644 --- a/LibreTransmitterUI/Views/Utilities/ViewExtensions.swift +++ b/LibreTransmitterUI/Views/Utilities/ViewExtensions.swift @@ -9,7 +9,6 @@ import SwiftUI import LocalAuthentication -#if canImport(UIKit) extension View { func hideKeyboardPreIos16() { if #unavailable(iOS 16.0) { @@ -17,7 +16,7 @@ extension View { } } } -#endif + struct LeadingImage: View { var image: UIImage From d9a132617b80be285e9d154d64aa6e1c09c4584a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 24 Jun 2023 08:01:15 -0500 Subject: [PATCH 07/19] fix overflow error --- LibreSensor/SensorContents/Measurement.swift | 1 - LibreTransmitter/NotificationHelper.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/LibreSensor/SensorContents/Measurement.swift b/LibreSensor/SensorContents/Measurement.swift index 55677da..c9a4d81 100644 --- a/LibreSensor/SensorContents/Measurement.swift +++ b/LibreSensor/SensorContents/Measurement.swift @@ -23,7 +23,6 @@ extension MeasurementProtocol { // add extraslope and extraoffset as indicated by user in the ui round(glucoseValueFromRaw(calibrationInfo: calibrationInfo)) * calibrationInfo.extraSlope + calibrationInfo.extraOffset } - } public enum MeasurementError: Int, CaseIterable, Codable { diff --git a/LibreTransmitter/NotificationHelper.swift b/LibreTransmitter/NotificationHelper.swift index 8060014..71389e5 100644 --- a/LibreTransmitter/NotificationHelper.swift +++ b/LibreTransmitter/NotificationHelper.swift @@ -431,7 +431,7 @@ public extension NotificationHelper { body.append("Glucose: \(glucoseDesc)") if let oldValue { - let diff = Double(glucose.glucose - oldValue.glucose) + let diff = glucose.glucoseDouble - oldValue.glucoseDouble if diff >= 0 { body.append("+") } From 0b5e91847bd43d4f3926f57aa6b66b9bc8d64dd8 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 24 Jun 2023 11:54:00 -0500 Subject: [PATCH 08/19] Return mock devices for bluetooth scan for demo --- Bluetooth/BluetoothSearch.swift | 69 ++++---- Bluetooth/CBPeripheralExtensions.swift | 53 +------ Bluetooth/GenericThrottler.swift | 4 +- Bluetooth/Transmitter/BubbleTransmitter.swift | 4 +- .../Transmitter/Libre2DirectTransmitter.swift | 2 +- .../LibreTransmitterProxyProtocol.swift | 6 +- .../Transmitter/MiaomiaoTransmitter.swift | 2 +- .../FakeSensorPairingData.swift | 4 +- LibreDemoPlugin/LibreDemoCGMManager.swift | 4 + LibreDemoPlugin/MockBluetoothSearcher.swift | 64 ++++++++ LibreTransmitter.xcodeproj/project.pbxproj | 18 ++- .../LibreTransmitterManagerV3.swift | 5 + .../LibreTransmitterSetupViewController.swift | 2 +- .../LibreTransmitterManager+UI.swift | 3 +- .../Views/Settings/SettingsView.swift | 9 +- LibreTransmitterUI/Views/Setup/AuthView.swift | 5 +- .../Views/Setup/BluetoothSelection.swift | 150 +++++------------- .../Views/Setup/ModeSelectionView.swift | 5 +- 18 files changed, 190 insertions(+), 219 deletions(-) rename {LibreSensor/SensorPairing => LibreDemoPlugin}/FakeSensorPairingData.swift (97%) create mode 100644 LibreDemoPlugin/MockBluetoothSearcher.swift diff --git a/Bluetooth/BluetoothSearch.swift b/Bluetooth/BluetoothSearch.swift index 15de1eb..8fd4e46 100644 --- a/Bluetooth/BluetoothSearch.swift +++ b/Bluetooth/BluetoothSearch.swift @@ -8,21 +8,20 @@ import CoreBluetooth import Foundation -import LibreTransmitter import OSLog import UIKit import Combine -struct RSSIInfo { - let bledeviceID: String - let signalStrength: Int +public struct RSSIInfo { + public let bledeviceID: String + public let signalStrength: Int - var totalBars: Int { + public var totalBars: Int { 3 } - var signalBars: Int { + public var signalBars: Int { if signalStrength < -80 { return 1 // near } @@ -36,30 +35,35 @@ struct RSSIInfo { } -final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate { +public protocol BluetoothSearcher { + func disconnectManually() + func scanForCompatibleDevices() + func stopTimer() + + var passThroughMetaData: PassthroughSubject<(PeripheralProtocol, [String: Any]), Never> { get } + var throttledRSSI: GenericThrottler { get } +} + + +public final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, BluetoothSearcher { var centralManager: CBCentralManager! fileprivate lazy var logger = Logger(forType: Self.self) - // fileprivate let deviceNames = SupportedDevices.allNames - // fileprivate let serviceUUIDs:[CBUUID]? = [CBUUID(string: "6E400001-B5A3-F393-E0A9-E50E24DCCA9E")] - private var discoveredDevices = [CBPeripheral]() - public let passThrough = PassthroughSubject() - public let passThroughMetaData = PassthroughSubject<(CBPeripheral, [String: Any]), Never>() + public let passThroughMetaData = PassthroughSubject<(PeripheralProtocol, [String: Any]), Never>() public let throttledRSSI = GenericThrottler(identificator: \RSSIInfo.bledeviceID, interval: 5) private var rescanTimerBag = Set() public func addDiscoveredDevice(_ device: CBPeripheral, with metadata: [String: Any], rssi: Int) { - passThrough.send(device) passThroughMetaData.send((device, metadata)) throttledRSSI.incoming.send(RSSIInfo(bledeviceID: device.identifier.uuidString, signalStrength: rssi)) } - override init() { + public override init() { super.init() // calling readrssi on a peripheral is only supported on connected peripherals // here we want the AllowDuplicatesKey to be true so that we get a continous feed of new rssi values for @@ -106,7 +110,7 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph self.scanForCompatibleDevices() } - func scanForCompatibleDevices() { + public func scanForCompatibleDevices() { if centralManager.state == .poweredOn && !centralManager.isScanning { logger.debug("Before scan for transmitter while central manager state \(String(describing: self.centralManager.state.rawValue))") @@ -120,19 +124,16 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph } } - func disconnectManually() { + public func disconnectManually() { logger.debug("did disconnect manually") - // NotificationManager.scheduleDebugNotification(message: "Timer fired in Background", wait: 3) - // _ = Timer(timeInterval: 150, repeats: false, block: {timer in NotificationManager.scheduleDebugNotification(message: "Timer fired in Background", wait: 0.5)}) - - if centralManager.isScanning { - centralManager.stopScan() - } + if centralManager.isScanning { + centralManager.stopScan() + } } // MARK: - CBCentralManagerDelegate - func centralManagerDidUpdateState(_ central: CBCentralManager) { + public func centralManagerDidUpdateState(_ central: CBCentralManager) { logger.debug("Central Manager did update state to \(String(describing: central.state.rawValue))") switch central.state { case .poweredOff, .resetting, .unauthorized, .unknown, .unsupported: @@ -140,7 +141,7 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph case .poweredOn: // we don't want this to start scanning right away, but rather wait until the view has appeared // this means that the view is responsible for calling scanForCompatibleDevices it self - scanForCompatibleDevices() // power was switched on, while app is running -> reconnect. + //scanForCompatibleDevices() // power was switched on, while app is running -> reconnect. break @unknown default: @@ -148,7 +149,7 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph } } - func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) { + public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) { guard let name = peripheral.name?.lowercased() else { logger.debug("could not find name for device \(peripheral.identifier.uuidString)") return @@ -176,22 +177,22 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph } } - func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { + public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { // self.lastConnectedIdentifier = peripheral.identifier.uuidString } - func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { + public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { logger.error("did fail to connect") } - func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { + public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { logger.debug("did didDisconnectPeripheral") } // MARK: - CBPeripheralDelegate - func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { + public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { logger.debug("Did discover services") if let error { logger.error("Did discover services error: \(error.localizedDescription)") @@ -206,7 +207,7 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph } } - func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { + public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { logger.debug("Did discover characteristics for service \(String(describing: peripheral.name))") if let error { @@ -230,22 +231,22 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph } } - func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) { + public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) { // throttledRSSI.incoming.send(RSSIInfo(bledeviceID: peripheral.identifier.uuidString, signalStrength: RSSI.intValue)) // peripheral.readRSSI() //we keep contuing to update the rssi (only works if peripheral is already connected.... } - func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { + public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { logger.debug("Did update notification state for characteristic: \(String(describing: characteristic.debugDescription))") } - func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { + public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { logger.debug("Did update value for characteristic: \(String(describing: characteristic.debugDescription))") } - func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { + public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { logger.debug("Did Write value \(String(characteristic.value.debugDescription)) for characteristic \(String(characteristic.debugDescription))") } diff --git a/Bluetooth/CBPeripheralExtensions.swift b/Bluetooth/CBPeripheralExtensions.swift index 0ae7ae9..805f33c 100644 --- a/Bluetooth/CBPeripheralExtensions.swift +++ b/Bluetooth/CBPeripheralExtensions.swift @@ -21,43 +21,8 @@ public enum Either { case Right(B) } -public typealias SomePeripheral = Either -extension SomePeripheral: PeripheralProtocol, Identifiable, Hashable, Equatable { - public static func == (lhs: Either, rhs: Either) -> Bool { - lhs.asStringIdentifier == rhs.asStringIdentifier - } - - public var id: String { - actualPeripheral.asStringIdentifier - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(actualPeripheral.asStringIdentifier) - } - - private var actualPeripheral: PeripheralProtocol { - switch self { - case let .Left(real): - return real - case let .Right(mocked): - return mocked - } - } - public var name: String? { - actualPeripheral.name - } - - public var name2: String { - actualPeripheral.name2 - } - - public var asStringIdentifier: String { - actualPeripheral.asStringIdentifier - } -} - -extension CBPeripheral: PeripheralProtocol, Identifiable { +extension CBPeripheral: PeripheralProtocol { public var name2: String { self.name ?? "" } @@ -66,19 +31,3 @@ extension CBPeripheral: PeripheralProtocol, Identifiable { self.identifier.uuidString } } - -public class MockedPeripheral: PeripheralProtocol, Identifiable { - public var name: String? - - public var name2: String { - name ?? "unknown-device" - } - - public var asStringIdentifier: String { - name2 - } - - public init(name: String) { - self.name = name - } -} diff --git a/Bluetooth/GenericThrottler.swift b/Bluetooth/GenericThrottler.swift index 7cbc4c5..0582dd7 100644 --- a/Bluetooth/GenericThrottler.swift +++ b/Bluetooth/GenericThrottler.swift @@ -9,7 +9,7 @@ import Foundation import Combine -class GenericThrottler { +public class GenericThrottler { public var throttledPublisher: AnyPublisher { throttledSubject.eraseToAnyPublisher() @@ -88,7 +88,7 @@ class GenericThrottler { .store(in: &bag) } - init(identificator: KeyPath, interval: TimeInterval) { + public init(identificator: KeyPath, interval: TimeInterval) { self.identificator = identificator self.interval = interval diff --git a/Bluetooth/Transmitter/BubbleTransmitter.swift b/Bluetooth/Transmitter/BubbleTransmitter.swift index 1607512..0856c98 100644 --- a/Bluetooth/Transmitter/BubbleTransmitter.swift +++ b/Bluetooth/Transmitter/BubbleTransmitter.swift @@ -58,7 +58,7 @@ class BubbleTransmitter: MiaoMiaoTransmitter { UIImage(named: "bubble", in: Bundle.current, compatibleWith: nil) } - override static func canSupportPeripheral(_ peripheral: CBPeripheral) -> Bool { + override static func canSupportPeripheral(_ peripheral: PeripheralProtocol) -> Bool { peripheral.name?.lowercased().starts(with: "bubble") ?? false } @@ -93,7 +93,7 @@ class BubbleTransmitter: MiaoMiaoTransmitter { private static func getDeviceDetailsFromAdvertisementInternal(advertisementData: [String: Any]?) -> (String?, String?, String?) { - guard let data = advertisementData?["kCBAdvDataManufacturerData"] as? Data else { + guard let data = advertisementData?[CBAdvertisementDataManufacturerDataKey] as? Data else { return (nil, nil, nil) } var mac = "" diff --git a/Bluetooth/Transmitter/Libre2DirectTransmitter.swift b/Bluetooth/Transmitter/Libre2DirectTransmitter.swift index 674c3a4..f34eb44 100644 --- a/Bluetooth/Transmitter/Libre2DirectTransmitter.swift +++ b/Bluetooth/Transmitter/Libre2DirectTransmitter.swift @@ -45,7 +45,7 @@ class Libre2DirectTransmitter: LibreTransmitterProxyProtocol { private var sensorData: SensorData? private var metadata: LibreTransmitterMetadata? - class func canSupportPeripheral(_ peripheral: CBPeripheral) -> Bool { + class func canSupportPeripheral(_ peripheral: PeripheralProtocol) -> Bool { peripheral.name?.lowercased().starts(with: "abbott") ?? false } diff --git a/Bluetooth/Transmitter/LibreTransmitterProxyProtocol.swift b/Bluetooth/Transmitter/LibreTransmitterProxyProtocol.swift index fd9c26e..4bcf343 100644 --- a/Bluetooth/Transmitter/LibreTransmitterProxyProtocol.swift +++ b/Bluetooth/Transmitter/LibreTransmitterProxyProtocol.swift @@ -15,7 +15,7 @@ public protocol LibreTransmitterProxyProtocol: AnyObject { static var manufacturerer: String { get } static var requiresPhoneNFC: Bool { get } static var requiresSetup: Bool { get } - static func canSupportPeripheral(_ peripheral: CBPeripheral) -> Bool + static func canSupportPeripheral(_ peripheral: PeripheralProtocol) -> Bool static var writeCharacteristic: UUIDContainer? { get set } static var notifyCharacteristic: UUIDContainer? { get set } @@ -36,7 +36,7 @@ public protocol LibreTransmitterProxyProtocol: AnyObject { } extension LibreTransmitterProxyProtocol { - func canSupportPeripheral(_ peripheral: CBPeripheral) -> Bool { + func canSupportPeripheral(_ peripheral: PeripheralProtocol) -> Bool { Self.canSupportPeripheral(peripheral) } public var staticType: LibreTransmitterProxyProtocol.Type { @@ -78,7 +78,7 @@ public enum LibreTransmitters { getSupportedPlugins(peripheral)?.isEmpty == false } - public static func getSupportedPlugins(_ peripheral: CBPeripheral) -> [LibreTransmitterProxyProtocol.Type]? { + public static func getSupportedPlugins(_ peripheral: PeripheralProtocol) -> [LibreTransmitterProxyProtocol.Type]? { all.enumerated().compactMap { $0.element.canSupportPeripheral(peripheral) ? $0.element : nil } diff --git a/Bluetooth/Transmitter/MiaomiaoTransmitter.swift b/Bluetooth/Transmitter/MiaomiaoTransmitter.swift index 472e9cd..e78d25e 100644 --- a/Bluetooth/Transmitter/MiaomiaoTransmitter.swift +++ b/Bluetooth/Transmitter/MiaomiaoTransmitter.swift @@ -215,7 +215,7 @@ class MiaoMiaoTransmitter: LibreTransmitterProxyProtocol { private var sensorData: SensorData? private var metadata: LibreTransmitterMetadata? - class func canSupportPeripheral(_ peripheral: CBPeripheral) -> Bool { + class func canSupportPeripheral(_ peripheral: PeripheralProtocol) -> Bool { peripheral.name?.lowercased().starts(with: "miaomiao") ?? false } diff --git a/LibreSensor/SensorPairing/FakeSensorPairingData.swift b/LibreDemoPlugin/FakeSensorPairingData.swift similarity index 97% rename from LibreSensor/SensorPairing/FakeSensorPairingData.swift rename to LibreDemoPlugin/FakeSensorPairingData.swift index aa9d992..d71d733 100644 --- a/LibreSensor/SensorPairing/FakeSensorPairingData.swift +++ b/LibreDemoPlugin/FakeSensorPairingData.swift @@ -7,7 +7,7 @@ // import Foundation - +import LibreTransmitter // https://github.com/NightscoutFoundation/xDrip/blob/579d365a94cb1fa2ad28b692efb05036928a5dd3/wear/src/main/java/com/eveningoutpost/dexdrip/NFCReaderX.java#L95 public struct FakeSensorPairingData { @@ -21,7 +21,7 @@ public struct FakeSensorPairingData { public init() { } - public func fakeSensorPairingInfo() -> SensorPairingInfo{ + public func fakeSensorPairingInfo() -> SensorPairingInfo { SensorPairingInfo(uuid: Data(de_new_patch_uid), patchInfo: Data(de_new_patch_info), fram: Data(de_new_packet), streamingEnabled: true, sensorName: sensorName) } } diff --git a/LibreDemoPlugin/LibreDemoCGMManager.swift b/LibreDemoPlugin/LibreDemoCGMManager.swift index 6032637..33e375a 100644 --- a/LibreDemoPlugin/LibreDemoCGMManager.swift +++ b/LibreDemoPlugin/LibreDemoCGMManager.swift @@ -20,4 +20,8 @@ class LibreDemoCGMManager: LibreTransmitterManagerV3 { public override var pairingService: SensorPairingProtocol { return MockSensorPairingService() } + + public override var bluetoothSearcher: BluetoothSearcher { + return MockBluetoothSearcher() + } } diff --git a/LibreDemoPlugin/MockBluetoothSearcher.swift b/LibreDemoPlugin/MockBluetoothSearcher.swift new file mode 100644 index 0000000..f9dd31f --- /dev/null +++ b/LibreDemoPlugin/MockBluetoothSearcher.swift @@ -0,0 +1,64 @@ +// +// MockBluetoothSearcher.swift +// LibreDemoPlugin +// +// Created by Pete Schwamb on 6/24/23. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import LibreTransmitter +import os.log +import Combine +import CoreBluetooth + +struct MockBluetoothSearcher: BluetoothSearcher { + fileprivate lazy var logger = Logger(forType: Self.self) + + public let throttledRSSI = GenericThrottler(identificator: \RSSIInfo.bledeviceID, interval: 5) + public let passThroughMetaData = PassthroughSubject<(PeripheralProtocol, [String: Any]), Never>() + + func disconnectManually() { + print("Mock searcher disconnecting") + } + + func scanForCompatibleDevices() { + print("Mock searcher scanning") + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + for device in mockData { + passThroughMetaData.send((device, [:])) + } + } + } + + func stopTimer() { + print("Mock searcher stop timer") + } + + var mockData: [PeripheralProtocol] { + [ + MockedPeripheral(name: "device1"), + MockedPeripheral(name: "device2"), + MockedPeripheral(name: "device3"), + MockedPeripheral(name: "device4") + ] + } + +} + +public class MockedPeripheral: PeripheralProtocol, Identifiable { + public var name: String? + + public var name2: String { + name ?? "unknown-device" + } + + public var asStringIdentifier: String { + name2 + } + + public init(name: String) { + self.name = name + } +} + diff --git a/LibreTransmitter.xcodeproj/project.pbxproj b/LibreTransmitter.xcodeproj/project.pbxproj index 1db242c..7d9098a 100644 --- a/LibreTransmitter.xcodeproj/project.pbxproj +++ b/LibreTransmitter.xcodeproj/project.pbxproj @@ -66,7 +66,6 @@ 27850D6A25672DFB0020D109 /* icons8-down-arrow-50.png in Resources */ = {isa = PBXBuildFile; fileRef = 27850D4725672DFB0020D109 /* icons8-down-arrow-50.png */; }; 27850D6C25672DFB0020D109 /* SnoozeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27850D4A25672DFB0020D109 /* SnoozeView.swift */; }; 27850D6D25672DFB0020D109 /* BluetoothSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27850D4B25672DFB0020D109 /* BluetoothSelection.swift */; }; - 27850D8B25672ECA0020D109 /* BluetoothSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27850BC8256725E70020D109 /* BluetoothSearch.swift */; }; 27850D9125672F020020D109 /* CBPeripheralExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27850BC7256725E70020D109 /* CBPeripheralExtensions.swift */; }; 27850D9225672F020020D109 /* LibreTransmitterMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27850BC6256725E70020D109 /* LibreTransmitterMetadata.swift */; }; 27850D9825672F130020D109 /* BubbleTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27850BCC256725E80020D109 /* BubbleTransmitter.swift */; }; @@ -99,7 +98,6 @@ 27DD8F2A269106A100649010 /* ViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8F29269106A100649010 /* ViewExtensions.swift */; }; 27ED67B82698EF38003E5DAB /* AlarmStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27ED67B72698EF38003E5DAB /* AlarmStatus.swift */; }; 27ED67BA26990D6B003E5DAB /* GenericObservableObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27ED67B926990D6B003E5DAB /* GenericObservableObject.swift */; }; - 27F1593026CAF77200EBA666 /* GenericThrottler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1592E26CAF6BE00EBA666 /* GenericThrottler.swift */; }; 27F93B2A2816B7FF00EE39A7 /* LibreTransmitterManager+Libre2EU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F93B292816B7FF00EE39A7 /* LibreTransmitterManager+Libre2EU.swift */; }; 27F93B2C2816B93900EE39A7 /* LibreTransmitterManager+Transmitters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F93B2B2816B93900EE39A7 /* LibreTransmitterManager+Transmitters.swift */; }; 27FED6CC26C275A8001BD5E4 /* ic_bubble_mini_3-2.png in Resources */ = {isa = PBXBuildFile; fileRef = 27FED6CB26C275A8001BD5E4 /* ic_bubble_mini_3-2.png */; }; @@ -128,7 +126,10 @@ C1BDBB022A45064F00A787D1 /* LibreDemoCGMManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */; }; C1BDBB032A45158A00A787D1 /* SensorPairingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C74426DF636900E31BD9 /* SensorPairingService.swift */; }; C1BDBB042A45159100A787D1 /* SensorPairing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C74626DF63C800E31BD9 /* SensorPairing.swift */; }; - C1BDBB052A45159400A787D1 /* FakeSensorPairingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2709A6F229E5E0D3004355A3 /* FakeSensorPairingData.swift */; }; + C1C318CD2A47468D00C6F29F /* BluetoothSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27850BC8256725E70020D109 /* BluetoothSearch.swift */; }; + C1C318CE2A4746A800C6F29F /* GenericThrottler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1592E26CAF6BE00EBA666 /* GenericThrottler.swift */; }; + C1C318D02A4748A100C6F29F /* MockBluetoothSearcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C318CF2A4748A100C6F29F /* MockBluetoothSearcher.swift */; }; + C1C318D12A4756CD00C6F29F /* FakeSensorPairingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2709A6F229E5E0D3004355A3 /* FakeSensorPairingData.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -334,6 +335,7 @@ C1BDBAFC2A43985000A787D1 /* LibreDemoPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreDemoPlugin.swift; sourceTree = ""; }; C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreDemoCGMManager.swift; sourceTree = ""; }; C1BDBAFE2A44FFD800A787D1 /* MockSensorPairingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSensorPairingService.swift; sourceTree = ""; }; + C1C318CF2A4748A100C6F29F /* MockBluetoothSearcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBluetoothSearcher.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -393,7 +395,6 @@ children = ( 2746C74426DF636900E31BD9 /* SensorPairingService.swift */, 2746C74626DF63C800E31BD9 /* SensorPairing.swift */, - 2709A6F229E5E0D3004355A3 /* FakeSensorPairingData.swift */, ); path = SensorPairing; sourceTree = ""; @@ -692,6 +693,8 @@ C1BDBAFC2A43985000A787D1 /* LibreDemoPlugin.swift */, C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */, C1BDBAFE2A44FFD800A787D1 /* MockSensorPairingService.swift */, + 2709A6F229E5E0D3004355A3 /* FakeSensorPairingData.swift */, + C1C318CF2A4748A100C6F29F /* MockBluetoothSearcher.swift */, ); path = LibreDemoPlugin; sourceTree = ""; @@ -957,12 +960,14 @@ 2746C73C26D91FC900E31BD9 /* Libre2DirectTransmitter.swift in Sources */, 27F93B2C2816B93900EE39A7 /* LibreTransmitterManager+Transmitters.swift in Sources */, 27850CFB25672C0C0020D109 /* GlucoseSchedules.swift in Sources */, + C1C318CD2A47468D00C6F29F /* BluetoothSearch.swift in Sources */, 27850D9125672F020020D109 /* CBPeripheralExtensions.swift in Sources */, 27850DA625672F1E0020D109 /* MiaomiaoTransmitter.swift in Sources */, B40BF26823ABD55200A43CEE /* OSLog.swift in Sources */, 43A8EC9D210E68CE00A81379 /* LibreTransmitterManagerV3.swift in Sources */, 27850DB225672F3F0020D109 /* LimitedQueue.swift in Sources */, 274557C82979EA38003B027B /* LoggerExtension.swift in Sources */, + C1C318CE2A4746A800C6F29F /* GenericThrottler.swift in Sources */, 27850D9825672F130020D109 /* BubbleTransmitter.swift in Sources */, 27850DCD25672F780020D109 /* SensorState.swift in Sources */, 27B2150526E2C4EB000C322D /* Sensor.swift in Sources */, @@ -975,7 +980,6 @@ 27850CED25672C0C0020D109 /* DateExtensions.swift in Sources */, 27850DBE25672F530020D109 /* LibreError.swift in Sources */, 27DD8F24268FA13F00649010 /* HashableClass.swift in Sources */, - C1BDBB052A45159400A787D1 /* FakeSensorPairingData.swift in Sources */, 27850D9F25672F190020D109 /* LibreTransmitterProxyProtocol.swift in Sources */, 27850DCC25672F770020D109 /* Measurement.swift in Sources */, C1BDBB032A45158A00A787D1 /* SensorPairingService.swift in Sources */, @@ -1018,7 +1022,6 @@ 275EC9AB265EDC280043210E /* GlucoseSettingsView.swift in Sources */, 274E71D3297ED77300FCFECD /* AuthView.swift in Sources */, 27850CEA25672C0C0020D109 /* TimeIntervalExtensions.swift in Sources */, - 27850D8B25672ECA0020D109 /* BluetoothSearch.swift in Sources */, 27850DAC25672F3E0020D109 /* LimitedQueue.swift in Sources */, 275EC998265AF64E0043210E /* StatusMessage.swift in Sources */, 27850D6C25672DFB0020D109 /* SnoozeView.swift in Sources */, @@ -1034,7 +1037,6 @@ 27A5912726E0E32C00A7EE36 /* ModeSelectionView.swift in Sources */, 2746C73F26DCF83700E31BD9 /* Features.swift in Sources */, 27850CEC25672C0C0020D109 /* DoubleExtensions.swift in Sources */, - 27F1593026CAF77200EBA666 /* GenericThrottler.swift in Sources */, 277773AC2639EFB800431547 /* ErrorTextFieldStyle.swift in Sources */, 277773B62639F51300431547 /* CustomDataPickerView.swift in Sources */, 43A8EC91210E676500A81379 /* LibreTransmitterManager+UI.swift in Sources */, @@ -1063,6 +1065,8 @@ C1BDBAED2A4397E200A787D1 /* OSLog.swift in Sources */, C1BDBAFF2A44FFD800A787D1 /* MockSensorPairingService.swift in Sources */, C1BDBB002A45051B00A787D1 /* LibreDemoPlugin.swift in Sources */, + C1C318D12A4756CD00C6F29F /* FakeSensorPairingData.swift in Sources */, + C1C318D02A4748A100C6F29F /* MockBluetoothSearcher.swift in Sources */, C1BDBAEE2A4397E200A787D1 /* LibreTransmitterPlugin.swift in Sources */, C1BDBB022A45064F00A787D1 /* LibreDemoCGMManager.swift in Sources */, ); diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index f6b5a57..d43ee3e 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -277,6 +277,11 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { open var pairingService: SensorPairingProtocol { return SensorPairingService() } + + open var bluetoothSearcher: BluetoothSearcher { + return BluetoothSearchManager() + } + } // MARK: - Convenience functions diff --git a/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift b/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift index a952523..a0318bc 100644 --- a/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift +++ b/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift @@ -29,7 +29,7 @@ class LibreTransmitterSetupViewController: UINavigationController, CGMManagerOnb let cancelNotifier = GenericObservableObject() let saveNotifier = GenericObservableObject() - let myView = ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, pairingService: cgmManager.pairingService) + let myView = ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, pairingService: cgmManager.pairingService, bluetoothSearcher: cgmManager.bluetoothSearcher) .environmentObject(displayGlucosePreference) super.init(rootViewController: UIHostingController(rootView: myView)) diff --git a/LibreTransmitterUI/LibreTransmitterManager+UI.swift b/LibreTransmitterUI/LibreTransmitterManager+UI.swift index 3574754..1654311 100644 --- a/LibreTransmitterUI/LibreTransmitterManager+UI.swift +++ b/LibreTransmitterUI/LibreTransmitterManager+UI.swift @@ -49,7 +49,8 @@ extension LibreTransmitterManagerV3: CGMManagerUI { sensorInfoObervable: self.sensorInfoObservable, glucoseInfoObservable: self.glucoseInfoObservable, alarmStatus: self.alarmStatus, - pairingService: self.pairingService + pairingService: self.pairingService, + bluetoothSearcher: self.bluetoothSearcher ) let nav = CGMManagerSettingsNavigationViewController(rootViewController: settings) diff --git a/LibreTransmitterUI/Views/Settings/SettingsView.swift b/LibreTransmitterUI/Views/Settings/SettingsView.swift index 336c252..b6834cb 100644 --- a/LibreTransmitterUI/Views/Settings/SettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/SettingsView.swift @@ -76,6 +76,7 @@ struct SettingsView: View { // @Environment(\.presentationMode) var presentationMode var pairingService: SensorPairingProtocol + var bluetoothSearcher: BluetoothSearcher static func asHostedViewController( displayGlucosePreference: DisplayGlucosePreference, @@ -87,7 +88,8 @@ struct SettingsView: View { sensorInfoObervable: LibreTransmitter.SensorInfo, glucoseInfoObservable: LibreTransmitter.GlucoseInfo, alarmStatus: LibreTransmitter.AlarmStatus, - pairingService: SensorPairingProtocol + pairingService: SensorPairingProtocol, + bluetoothSearcher: BluetoothSearcher ) -> DismissibleHostingController { let view = self.init( @@ -99,7 +101,8 @@ struct SettingsView: View { notifyReset: notifyReset, notifyReconnect: notifyReconnect, alarmStatus: alarmStatus, - pairingService: pairingService + pairingService: pairingService, + bluetoothSearcher: bluetoothSearcher ).environmentObject(displayGlucosePreference) return DismissibleHostingController(rootView: view) } @@ -225,7 +228,7 @@ struct SettingsView: View { var sensorChangeSection: some View { Section { - NavigationLink(destination: AuthView(completeNotifier: notifyComplete, notifyReset: notifyReset, notifyReconnect: notifyReconnect, pairingService: pairingService)) { + NavigationLink(destination: AuthView(completeNotifier: notifyComplete, notifyReset: notifyReset, notifyReconnect: notifyReconnect, pairingService: pairingService, bluetoothSearcher: bluetoothSearcher)) { /*Button("Change Sensor") { }.foregroundColor(.blue)*/ SettingsItem(title: "Change Sensor").foregroundColor(.blue) diff --git a/LibreTransmitterUI/Views/Setup/AuthView.swift b/LibreTransmitterUI/Views/Setup/AuthView.swift index a8638db..498a2b0 100644 --- a/LibreTransmitterUI/Views/Setup/AuthView.swift +++ b/LibreTransmitterUI/Views/Setup/AuthView.swift @@ -25,6 +25,7 @@ struct AuthView: View { @State private var hasSetupListeners = false var pairingService: SensorPairingProtocol + var bluetoothSearcher: BluetoothSearcher var exclamation: Image { Image(systemName: "exclamationmark.triangle.fill") @@ -41,7 +42,7 @@ struct AuthView: View { Text(LocalizedString("Authenticated", comment: "Text confirming user is authenticated in AuthView")) .transition(AnyTransition.opacity.combined(with: .move(edge: .bottom))) - NavigationLink(destination: ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, pairingService: pairingService), isActive: $isNavigationActive) { + NavigationLink(destination: ModeSelectionView(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, pairingService: pairingService, bluetoothSearcher: bluetoothSearcher), isActive: $isNavigationActive) { Button(action: { self.notifyReset.notify() self.isNavigationActive = true @@ -159,6 +160,6 @@ struct AuthView: View { struct AuthView_Previews: PreviewProvider { static var previews: some View { - AuthView(completeNotifier: GenericObservableObject(), notifyReset: GenericObservableObject(), notifyReconnect: GenericObservableObject(), pairingService: SensorPairingService()) + AuthView(completeNotifier: GenericObservableObject(), notifyReset: GenericObservableObject(), notifyReconnect: GenericObservableObject(), pairingService: SensorPairingService(), bluetoothSearcher: BluetoothSearchManager()) } } diff --git a/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift b/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift index 4243f85..c21f8cf 100644 --- a/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift +++ b/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift @@ -54,7 +54,7 @@ private struct ListFooter: View { } private struct DeviceItem: View { - var device: SomePeripheral + var device: PeripheralProtocol @Binding var rssi: RSSIInfo? var details1: String var details2: String? @@ -67,25 +67,19 @@ private struct DeviceItem: View { @ObservedObject var selection: SelectionState = .shared - func getDeviceImage(_ device: SomePeripheral) -> Image { + func getDeviceImage(_ device: PeripheralProtocol) -> Image { var image: UIImage! - switch device { - case let .Left(realDevice): - image = LibreTransmitters.getSupportedPlugins(realDevice)?.first?.smallImage - - case .Right: - image = LibreTransmitters.all.randomElement()?.smallImage - } + image = LibreTransmitters.getSupportedPlugins(device)?.first?.smallImage return image == nil ? Image(systemName: "exclamationmark.triangle") : Image(uiImage: image) } - func getRowBackground(device: SomePeripheral) -> Color { + func getRowBackground(device: PeripheralProtocol) -> Color { selection.selectedStringIdentifier == device.asStringIdentifier ? Defaults.selectedRowBackground : Defaults.rowBackground } - init(device: SomePeripheral, requiresSetup: Bool, requiresPhoneNFC: Bool, details: String, rssi: Binding) { + init(device: PeripheralProtocol, requiresSetup: Bool, requiresPhoneNFC: Bool, details: String, rssi: Binding) { self.device = device self._rssi = rssi self.requiresPhoneNFC = requiresPhoneNFC @@ -195,19 +189,16 @@ struct BluetoothSelection: View { selection.selectedStringIdentifier } - private var searcher: BluetoothSearchManager! + var searcher: BluetoothSearcher // Should contain all discovered and compatible devices // This list is expected to contain 10 or 20 items at the most - @State var allDevices = [SomePeripheral]() + @State var allDevices = [PeripheralProtocol]() @State var deviceDetails = [String: String]() @State var deviceRequiresPhoneNFC = [String: Bool]() @State var deviceRequiresSetup = [String: Bool]() @State var rssi = [String: RSSIInfo]() - var nullPubliser: Empty! - var debugMode = false - var cancelButton: some View { Button("Cancel") { print("cancel button pressed") @@ -235,28 +226,11 @@ struct BluetoothSelection: View { #endif } - init(debugMode: Bool = false, cancelNotifier: GenericObservableObject, saveNotifier: GenericObservableObject) { - self.debugMode = debugMode + init(cancelNotifier: GenericObservableObject, saveNotifier: GenericObservableObject, searcher: BluetoothSearcher) { self.cancelNotifier = cancelNotifier self.saveNotifier = saveNotifier - - if self.debugMode { - allDevices = Self.getMockData() - nullPubliser = Empty() - - } else { - self.searcher = BluetoothSearchManager() - } - LibreTransmitter.NotificationHelper.requestNotificationPermissionsIfNeeded() - - } - - public mutating func stopScan(_ removeSearcher: Bool = false) { - self.searcher?.disconnectManually() - if removeSearcher { - self.searcher = nil - } + self.searcher = searcher } var header: some View { @@ -273,24 +247,17 @@ struct BluetoothSelection: View { var list : some View { List { Section(header: header) { - ForEach(allDevices) { device in - if debugMode { - let randomRSSI = RSSIInfo(bledeviceID: device.asStringIdentifier, signalStrength: -90 + (1...70).randomElement()!) - let requiresPhoneNFC = Bool.random() - DeviceItem(device: device, requiresSetup: false, requiresPhoneNFC: requiresPhoneNFC, details: "mockdatamockdata mockdata mockdata\nmockdata2 nmockdata2", rssi: .constant(randomRSSI)) - } else { - let requiresPhoneNFC = deviceRequiresPhoneNFC[device.asStringIdentifier, default: false] + ForEach(allDevices, id: \.name) { device in + let requiresPhoneNFC = deviceRequiresPhoneNFC[device.asStringIdentifier, default: false] - let requiresSetup = deviceRequiresSetup[device.asStringIdentifier, default: false] - let rssigetter = Binding(get: { - rssi[device.asStringIdentifier] - }, set: { _ in - // not ever needed - }) - - DeviceItem(device: device, requiresSetup: requiresSetup, requiresPhoneNFC: requiresPhoneNFC, details: deviceDetails[device.asStringIdentifier]!, rssi: rssigetter) - } + let requiresSetup = deviceRequiresSetup[device.asStringIdentifier, default: false] + let rssigetter = Binding(get: { + rssi[device.asStringIdentifier] + }, set: { _ in + // not ever needed + }) + DeviceItem(device: device, requiresSetup: requiresSetup, requiresPhoneNFC: requiresPhoneNFC, details: deviceDetails[device.asStringIdentifier]!, rssi: rssigetter) } } Section { @@ -298,21 +265,13 @@ struct BluetoothSelection: View { } } .onAppear { - // devices = Self.getMockData() - if debugMode { - allDevices = Self.getMockData() - } else { - print(" asking searcher to search!") - self.searcher?.scanForCompatibleDevices() - } + print(" asking searcher to search!") + self.searcher.scanForCompatibleDevices() } .onDisappear { - if !self.debugMode { - print(" asking searcher to stop searching!") - self.searcher?.stopTimer() - self.searcher?.disconnectManually() - - } + print(" asking searcher to stop searching!") + self.searcher.stopTimer() + self.searcher.disconnectManually() } .navigationBarBackButtonHidden(true) .navigationBarItems(leading: cancelButton, trailing: saveButton) @@ -326,54 +285,33 @@ struct BluetoothSelection: View { } var body: some View { - if debugMode { - list - .onReceive(nullPubliser) { _ in - print("nullpublisher received element!?") - // allDevices.append(SomePeripheral.Left(device)) - } - } else { - list - .onReceive(searcher.passThroughMetaData) { newDevice, advertisement in - print("received searcher passthrough") + list.onReceive(searcher.passThroughMetaData) { newDevice, advertisement in + print("received searcher passthrough") - let alreadyAdded = allDevices.contains { existingDevice -> Bool in - existingDevice.asStringIdentifier == newDevice.asStringIdentifier - } - if !alreadyAdded { - if let pluginForDevice = LibreTransmitters.getSupportedPlugins(newDevice)?.first { - - deviceRequiresPhoneNFC[newDevice.asStringIdentifier] = pluginForDevice.requiresPhoneNFC - deviceRequiresSetup[newDevice.asStringIdentifier] = pluginForDevice.requiresSetup - - if let parsedAdvertisement = pluginForDevice.getDeviceDetailsFromAdvertisement(advertisementData: advertisement) { + let alreadyAdded = allDevices.contains { existingDevice -> Bool in + existingDevice.asStringIdentifier == newDevice.asStringIdentifier + } + if !alreadyAdded { + if let pluginForDevice = LibreTransmitters.getSupportedPlugins(newDevice)?.first { - deviceDetails[newDevice.asStringIdentifier] = parsedAdvertisement - } else { - deviceDetails[newDevice.asStringIdentifier] = "" - } + deviceRequiresPhoneNFC[newDevice.asStringIdentifier] = pluginForDevice.requiresPhoneNFC + deviceRequiresSetup[newDevice.asStringIdentifier] = pluginForDevice.requiresSetup - } else { - deviceDetails[newDevice.asStringIdentifier] = newDevice.asStringIdentifier - } + if let parsedAdvertisement = pluginForDevice.getDeviceDetailsFromAdvertisement(advertisementData: advertisement) { - allDevices.append(SomePeripheral.Left(newDevice)) + deviceDetails[newDevice.asStringIdentifier] = parsedAdvertisement + } else { + deviceDetails[newDevice.asStringIdentifier] = "" } - } - .onReceive(searcher.throttledRSSI.throttledPublisher, perform: receiveRSSI) - } - } -} + } else { + deviceDetails[newDevice.asStringIdentifier] = newDevice.asStringIdentifier + } -extension BluetoothSelection { - static func getMockData() -> [SomePeripheral] { - [ - SomePeripheral.Right(MockedPeripheral(name: "device1")), - SomePeripheral.Right(MockedPeripheral(name: "device2")), - SomePeripheral.Right(MockedPeripheral(name: "device3")), - SomePeripheral.Right(MockedPeripheral(name: "device4")) - ] + allDevices.append(newDevice) + } + } + .onReceive(searcher.throttledRSSI.throttledPublisher, perform: receiveRSSI) } } @@ -382,6 +320,6 @@ struct BluetoothSelection_Previews: PreviewProvider { let testData = SelectionState.shared testData.selectedStringIdentifier = "device4" - return BluetoothSelection(debugMode: true, cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject()) + return BluetoothSelection(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), searcher: BluetoothSearchManager()) } } diff --git a/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift b/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift index 299103f..6d027c1 100644 --- a/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift +++ b/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift @@ -16,6 +16,7 @@ struct ModeSelectionView: View { @ObservedObject public var saveNotifier: GenericObservableObject var pairingService: SensorPairingProtocol + var bluetoothSearcher: BluetoothSearcher var modeSelectSection : some View { Section(header: Text(LocalizedString("Connection options", comment: "Text describing options for connecting to sensor or transmitter"))) { @@ -26,7 +27,7 @@ struct ModeSelectionView: View { .padding([.top, .bottom], 8) } - NavigationLink(destination: BluetoothSelection(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier)) { + NavigationLink(destination: BluetoothSelection(cancelNotifier: cancelNotifier, saveNotifier: saveNotifier, searcher: bluetoothSearcher)) { SettingsItem(title: LocalizedString("Bluetooth Transmitters", comment: "Bluetooth Transmitter connection option")) .actionButtonStyle(.primary) .padding([.top, .bottom], 8) @@ -71,6 +72,6 @@ struct ModeSelectionView: View { struct ModeSelectionView_Previews: PreviewProvider { static var previews: some View { - ModeSelectionView(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), pairingService: SensorPairingService()) + ModeSelectionView(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), pairingService: SensorPairingService(), bluetoothSearcher: BluetoothSearchManager()) } } From 1674afee086cdb497ffb2ae3623f109a4f861b0b Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 24 Jun 2023 15:03:20 -0500 Subject: [PATCH 09/19] Use mocks in view previews --- LibreTransmitter.xcodeproj/project.pbxproj | 49 +++++++------------ .../LibreTransmitterManagerV3.swift | 2 - .../Mocks}/FakeSensorPairingData.swift | 1 - .../Mocks}/MockBluetoothSearcher.swift | 20 ++++---- .../Mocks}/MockSensorPairingService.swift | 8 +-- .../LibreTransmitterManager+UI.swift | 22 ++++++--- .../Views/Settings/SettingsView.swift | 36 ++++++-------- LibreTransmitterUI/Views/Setup/AuthView.swift | 2 +- .../Views/Setup/BluetoothSelection.swift | 41 ++++++++-------- .../Views/Setup/Libre2DirectSetup.swift | 2 +- .../Views/Setup/ModeSelectionView.swift | 2 +- 11 files changed, 87 insertions(+), 98 deletions(-) rename {LibreDemoPlugin => LibreTransmitter/Mocks}/FakeSensorPairingData.swift (99%) rename {LibreDemoPlugin => LibreTransmitter/Mocks}/MockBluetoothSearcher.swift (75%) rename {LibreDemoPlugin => LibreTransmitter/Mocks}/MockSensorPairingService.swift (88%) diff --git a/LibreTransmitter.xcodeproj/project.pbxproj b/LibreTransmitter.xcodeproj/project.pbxproj index 7d9098a..7bf19f4 100644 --- a/LibreTransmitter.xcodeproj/project.pbxproj +++ b/LibreTransmitter.xcodeproj/project.pbxproj @@ -118,18 +118,18 @@ C1BDBAED2A4397E200A787D1 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26723ABD55200A43CEE /* OSLog.swift */; }; C1BDBAEE2A4397E200A787D1 /* LibreTransmitterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26523ABD4E700A43CEE /* LibreTransmitterPlugin.swift */; }; C1BDBAF02A4397E200A787D1 /* LibreTransmitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 432B0E881CDFC3C50045347B /* LibreTransmitter.framework */; }; - C1BDBAF12A4397E200A787D1 /* LibreTransmitterUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43A8EC82210E664300A81379 /* LibreTransmitterUI.framework */; }; C1BDBAF52A4397E200A787D1 /* LibreTransmitter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 432B0E881CDFC3C50045347B /* LibreTransmitter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C1BDBAF62A4397E200A787D1 /* LibreTransmitterUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43A8EC82210E664300A81379 /* LibreTransmitterUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C1BDBAFF2A44FFD800A787D1 /* MockSensorPairingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BDBAFE2A44FFD800A787D1 /* MockSensorPairingService.swift */; }; C1BDBB002A45051B00A787D1 /* LibreDemoPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BDBAFC2A43985000A787D1 /* LibreDemoPlugin.swift */; }; C1BDBB022A45064F00A787D1 /* LibreDemoCGMManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */; }; C1BDBB032A45158A00A787D1 /* SensorPairingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C74426DF636900E31BD9 /* SensorPairingService.swift */; }; C1BDBB042A45159100A787D1 /* SensorPairing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C74626DF63C800E31BD9 /* SensorPairing.swift */; }; C1C318CD2A47468D00C6F29F /* BluetoothSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27850BC8256725E70020D109 /* BluetoothSearch.swift */; }; C1C318CE2A4746A800C6F29F /* GenericThrottler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1592E26CAF6BE00EBA666 /* GenericThrottler.swift */; }; - C1C318D02A4748A100C6F29F /* MockBluetoothSearcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C318CF2A4748A100C6F29F /* MockBluetoothSearcher.swift */; }; - C1C318D12A4756CD00C6F29F /* FakeSensorPairingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2709A6F229E5E0D3004355A3 /* FakeSensorPairingData.swift */; }; + C1C318D22A475C0E00C6F29F /* LibreTransmitterUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43A8EC82210E664300A81379 /* LibreTransmitterUI.framework */; }; + C1C318D32A475C0E00C6F29F /* LibreTransmitterUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43A8EC82210E664300A81379 /* LibreTransmitterUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1C318D52A47638E00C6F29F /* FakeSensorPairingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2709A6F229E5E0D3004355A3 /* FakeSensorPairingData.swift */; }; + C1C318D62A47638E00C6F29F /* MockBluetoothSearcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C318CF2A4748A100C6F29F /* MockBluetoothSearcher.swift */; }; + C1C318D72A47638E00C6F29F /* MockSensorPairingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BDBAFE2A44FFD800A787D1 /* MockSensorPairingService.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -190,7 +190,7 @@ dstSubfolderSpec = 10; files = ( C1BDBAF52A4397E200A787D1 /* LibreTransmitter.framework in Embed Frameworks */, - C1BDBAF62A4397E200A787D1 /* LibreTransmitterUI.framework in Embed Frameworks */, + C1C318D32A475C0E00C6F29F /* LibreTransmitterUI.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -372,7 +372,7 @@ buildActionMask = 2147483647; files = ( C1BDBAF02A4397E200A787D1 /* LibreTransmitter.framework in Frameworks */, - C1BDBAF12A4397E200A787D1 /* LibreTransmitterUI.framework in Frameworks */, + C1C318D22A475C0E00C6F29F /* LibreTransmitterUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -614,6 +614,7 @@ 432B0E8A1CDFC3C50045347B /* LibreTransmitter */ = { isa = PBXGroup; children = ( + C1C318D42A47637100C6F29F /* Mocks */, 27ED67B62698EEEF003E5DAB /* Observables */, 27850D0B25672CA60020D109 /* ConcreteGlucoseDisplayable.swift */, 27850D0D25672CA60020D109 /* NotificationHelper.swift */, @@ -692,11 +693,18 @@ C1BDBAE22A4397AA00A787D1 /* LibreDemoPlugin.h */, C1BDBAFC2A43985000A787D1 /* LibreDemoPlugin.swift */, C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */, + ); + path = LibreDemoPlugin; + sourceTree = ""; + }; + C1C318D42A47637100C6F29F /* Mocks */ = { + isa = PBXGroup; + children = ( C1BDBAFE2A44FFD800A787D1 /* MockSensorPairingService.swift */, 2709A6F229E5E0D3004355A3 /* FakeSensorPairingData.swift */, C1C318CF2A4748A100C6F29F /* MockBluetoothSearcher.swift */, ); - path = LibreDemoPlugin; + path = Mocks; sourceTree = ""; }; /* End PBXGroup section */ @@ -786,7 +794,6 @@ C1BDBAEC2A4397E200A787D1 /* Sources */, C1BDBAEF2A4397E200A787D1 /* Frameworks */, C1BDBAF22A4397E200A787D1 /* Resources */, - C1BDBAF32A4397E200A787D1 /* Run script capture-build-details */, C1BDBAF42A4397E200A787D1 /* Embed Frameworks */, ); buildRules = ( @@ -928,24 +935,6 @@ shellPath = /bin/sh; shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\"${SRCROOT}/Scripts/capture-build-details.sh\"\n\n"; }; - C1BDBAF32A4397E200A787D1 /* Run script capture-build-details */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run script capture-build-details"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\"${SRCROOT}/Scripts/capture-build-details.sh\"\n\n"; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -957,6 +946,7 @@ 27850D1025672CA60020D109 /* LibreGlucose.swift in Sources */, 27850D9E25672F170020D109 /* UUIDContainer.swift in Sources */, 27850DCE25672F780020D109 /* SensorData.swift in Sources */, + C1C318D72A47638E00C6F29F /* MockSensorPairingService.swift in Sources */, 2746C73C26D91FC900E31BD9 /* Libre2DirectTransmitter.swift in Sources */, 27F93B2C2816B93900EE39A7 /* LibreTransmitterManager+Transmitters.swift in Sources */, 27850CFB25672C0C0020D109 /* GlucoseSchedules.swift in Sources */, @@ -980,10 +970,12 @@ 27850CED25672C0C0020D109 /* DateExtensions.swift in Sources */, 27850DBE25672F530020D109 /* LibreError.swift in Sources */, 27DD8F24268FA13F00649010 /* HashableClass.swift in Sources */, + C1C318D62A47638E00C6F29F /* MockBluetoothSearcher.swift in Sources */, 27850D9F25672F190020D109 /* LibreTransmitterProxyProtocol.swift in Sources */, 27850DCC25672F770020D109 /* Measurement.swift in Sources */, C1BDBB032A45158A00A787D1 /* SensorPairingService.swift in Sources */, 27850DCB25672F770020D109 /* SensorSerialNumber.swift in Sources */, + C1C318D52A47638E00C6F29F /* FakeSensorPairingData.swift in Sources */, 27850DA525672F1C0020D109 /* LibreTransmitterProxyManager.swift in Sources */, 27850CF725672C0C0020D109 /* UserDefaults+Bluetooth.swift in Sources */, 271A39F52975844B0005FEDA /* NotificationHelperOverride.swift in Sources */, @@ -1063,10 +1055,7 @@ buildActionMask = 2147483647; files = ( C1BDBAED2A4397E200A787D1 /* OSLog.swift in Sources */, - C1BDBAFF2A44FFD800A787D1 /* MockSensorPairingService.swift in Sources */, C1BDBB002A45051B00A787D1 /* LibreDemoPlugin.swift in Sources */, - C1C318D12A4756CD00C6F29F /* FakeSensorPairingData.swift in Sources */, - C1C318D02A4748A100C6F29F /* MockBluetoothSearcher.swift in Sources */, C1BDBAEE2A4397E200A787D1 /* LibreTransmitterPlugin.swift in Sources */, C1BDBB022A45064F00A787D1 /* LibreDemoCGMManager.swift in Sources */, ); diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index d43ee3e..6c0c428 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -119,8 +119,6 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { ].joined(separator: "\n") } - // public var miaomiaoService: MiaomiaoService - public func fetchNewDataIfNeeded(_ completion: @escaping (CGMReadingResult) -> Void) { logger.debug("fetchNewDataIfNeeded called but we don't continue") diff --git a/LibreDemoPlugin/FakeSensorPairingData.swift b/LibreTransmitter/Mocks/FakeSensorPairingData.swift similarity index 99% rename from LibreDemoPlugin/FakeSensorPairingData.swift rename to LibreTransmitter/Mocks/FakeSensorPairingData.swift index d71d733..14b1e58 100644 --- a/LibreDemoPlugin/FakeSensorPairingData.swift +++ b/LibreTransmitter/Mocks/FakeSensorPairingData.swift @@ -7,7 +7,6 @@ // import Foundation -import LibreTransmitter // https://github.com/NightscoutFoundation/xDrip/blob/579d365a94cb1fa2ad28b692efb05036928a5dd3/wear/src/main/java/com/eveningoutpost/dexdrip/NFCReaderX.java#L95 public struct FakeSensorPairingData { diff --git a/LibreDemoPlugin/MockBluetoothSearcher.swift b/LibreTransmitter/Mocks/MockBluetoothSearcher.swift similarity index 75% rename from LibreDemoPlugin/MockBluetoothSearcher.swift rename to LibreTransmitter/Mocks/MockBluetoothSearcher.swift index f9dd31f..1eea875 100644 --- a/LibreDemoPlugin/MockBluetoothSearcher.swift +++ b/LibreTransmitter/Mocks/MockBluetoothSearcher.swift @@ -7,22 +7,24 @@ // import Foundation -import LibreTransmitter import os.log import Combine import CoreBluetooth -struct MockBluetoothSearcher: BluetoothSearcher { +public struct MockBluetoothSearcher: BluetoothSearcher { fileprivate lazy var logger = Logger(forType: Self.self) public let throttledRSSI = GenericThrottler(identificator: \RSSIInfo.bledeviceID, interval: 5) public let passThroughMetaData = PassthroughSubject<(PeripheralProtocol, [String: Any]), Never>() - func disconnectManually() { + public init() { + } + + public func disconnectManually() { print("Mock searcher disconnecting") } - func scanForCompatibleDevices() { + public func scanForCompatibleDevices() { print("Mock searcher scanning") DispatchQueue.main.asyncAfter(deadline: .now() + 2) { for device in mockData { @@ -31,19 +33,17 @@ struct MockBluetoothSearcher: BluetoothSearcher { } } - func stopTimer() { + public func stopTimer() { print("Mock searcher stop timer") } var mockData: [PeripheralProtocol] { [ - MockedPeripheral(name: "device1"), - MockedPeripheral(name: "device2"), - MockedPeripheral(name: "device3"), - MockedPeripheral(name: "device4") + MockedPeripheral(name: "miaomiaoMockTransmitter"), + MockedPeripheral(name: "bubbleMockTransmitter"), + MockedPeripheral(name: "abbottMockSensor"), ] } - } public class MockedPeripheral: PeripheralProtocol, Identifiable { diff --git a/LibreDemoPlugin/MockSensorPairingService.swift b/LibreTransmitter/Mocks/MockSensorPairingService.swift similarity index 88% rename from LibreDemoPlugin/MockSensorPairingService.swift rename to LibreTransmitter/Mocks/MockSensorPairingService.swift index f407126..8b905fa 100644 --- a/LibreDemoPlugin/MockSensorPairingService.swift +++ b/LibreTransmitter/Mocks/MockSensorPairingService.swift @@ -7,11 +7,10 @@ // import Foundation -import LibreTransmitter import Combine import os.log -class MockSensorPairingService: SensorPairingProtocol { +public class MockSensorPairingService: SensorPairingProtocol { fileprivate lazy var logger = Logger(forType: Self.self) @@ -21,13 +20,16 @@ class MockSensorPairingService: SensorPairingProtocol { readingsSubject.eraseToAnyPublisher() } + public init() { + } + private func sendUpdate(_ info: SensorPairingInfo) { DispatchQueue.main.async { [weak self] in self?.readingsSubject.send(info) } } - func pairSensor() throws { + public func pairSensor() throws { let info = FakeSensorPairingData().fakeSensorPairingInfo() logger.debug("Sending fake sensor pairinginfo: \(info.description)") //delay a bit to simulate a real tag readout diff --git a/LibreTransmitterUI/LibreTransmitterManager+UI.swift b/LibreTransmitterUI/LibreTransmitterManager+UI.swift index 1654311..c6c55dc 100644 --- a/LibreTransmitterUI/LibreTransmitterManager+UI.swift +++ b/LibreTransmitterUI/LibreTransmitterManager+UI.swift @@ -41,19 +41,25 @@ extension LibreTransmitterManagerV3: CGMManagerUI { let wantToRestablishConnectionNotifier = GenericObservableObject() - let settings = SettingsView.asHostedViewController( - displayGlucosePreference: displayGlucosePreference, - notifyComplete: doneNotifier, notifyDelete: wantToTerminateNotifier, - notifyReset: wantToResetCGMManagerNotifier, notifyReconnect:wantToRestablishConnectionNotifier, - transmitterInfoObservable: self.transmitterInfoObservable, - sensorInfoObervable: self.sensorInfoObservable, - glucoseInfoObservable: self.glucoseInfoObservable, + let settingsView = SettingsView( + transmitterInfo: self.transmitterInfoObservable, + sensorInfo: self.sensorInfoObservable, + glucoseMeasurement: self.glucoseInfoObservable, + notifyComplete: doneNotifier, + notifyDelete: wantToTerminateNotifier, + notifyReset: wantToResetCGMManagerNotifier, + notifyReconnect:wantToRestablishConnectionNotifier, alarmStatus: self.alarmStatus, pairingService: self.pairingService, bluetoothSearcher: self.bluetoothSearcher ) - let nav = CGMManagerSettingsNavigationViewController(rootViewController: settings) + let hostedView = DismissibleHostingController( + rootView: settingsView + .environmentObject(displayGlucosePreference) + ) + + let nav = CGMManagerSettingsNavigationViewController(rootViewController: hostedView) nav.navigationItem.largeTitleDisplayMode = .always nav.navigationBar.prefersLargeTitles = true diff --git a/LibreTransmitterUI/Views/Settings/SettingsView.swift b/LibreTransmitterUI/Views/Settings/SettingsView.swift index b6834cb..3419df9 100644 --- a/LibreTransmitterUI/Views/Settings/SettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/SettingsView.swift @@ -78,36 +78,30 @@ struct SettingsView: View { var pairingService: SensorPairingProtocol var bluetoothSearcher: BluetoothSearcher - static func asHostedViewController( - displayGlucosePreference: DisplayGlucosePreference, + init( + transmitterInfo: LibreTransmitter.TransmitterInfo, + sensorInfo: LibreTransmitter.SensorInfo, + glucoseMeasurement: LibreTransmitter.GlucoseInfo, notifyComplete: GenericObservableObject, notifyDelete: GenericObservableObject, notifyReset: GenericObservableObject, notifyReconnect: GenericObservableObject, - transmitterInfoObservable: LibreTransmitter.TransmitterInfo, - sensorInfoObervable: LibreTransmitter.SensorInfo, - glucoseInfoObservable: LibreTransmitter.GlucoseInfo, alarmStatus: LibreTransmitter.AlarmStatus, pairingService: SensorPairingProtocol, - bluetoothSearcher: BluetoothSearcher - ) -> DismissibleHostingController + bluetoothSearcher: BluetoothSearcher) { - let view = self.init( - transmitterInfo: transmitterInfoObservable, - sensorInfo: sensorInfoObervable, - glucoseMeasurement: glucoseInfoObservable, - notifyComplete: notifyComplete, - notifyDelete: notifyDelete, - notifyReset: notifyReset, - notifyReconnect: notifyReconnect, - alarmStatus: alarmStatus, - pairingService: pairingService, - bluetoothSearcher: bluetoothSearcher - ).environmentObject(displayGlucosePreference) - return DismissibleHostingController(rootView: view) + self.transmitterInfo = transmitterInfo + self.sensorInfo = sensorInfo + self.glucoseMeasurement = glucoseMeasurement + self.notifyComplete = notifyComplete + self.notifyDelete = notifyDelete + self.notifyReset = notifyReset + self.notifyReconnect = notifyReconnect + self.alarmStatus = alarmStatus + self.pairingService = pairingService + self.bluetoothSearcher = bluetoothSearcher } - private var glucoseUnit: HKUnit { displayGlucosePreference.unit } diff --git a/LibreTransmitterUI/Views/Setup/AuthView.swift b/LibreTransmitterUI/Views/Setup/AuthView.swift index 498a2b0..f25d9c4 100644 --- a/LibreTransmitterUI/Views/Setup/AuthView.swift +++ b/LibreTransmitterUI/Views/Setup/AuthView.swift @@ -160,6 +160,6 @@ struct AuthView: View { struct AuthView_Previews: PreviewProvider { static var previews: some View { - AuthView(completeNotifier: GenericObservableObject(), notifyReset: GenericObservableObject(), notifyReconnect: GenericObservableObject(), pairingService: SensorPairingService(), bluetoothSearcher: BluetoothSearchManager()) + AuthView(completeNotifier: GenericObservableObject(), notifyReset: GenericObservableObject(), notifyReconnect: GenericObservableObject(), pairingService: MockSensorPairingService(), bluetoothSearcher: MockBluetoothSearcher()) } } diff --git a/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift b/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift index c21f8cf..9f99b78 100644 --- a/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift +++ b/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift @@ -244,7 +244,7 @@ struct BluetoothSelection: View { } } } - var list : some View { + var deviceList : some View { List { Section(header: header) { ForEach(allDevices, id: \.name) { device in @@ -285,33 +285,34 @@ struct BluetoothSelection: View { } var body: some View { - list.onReceive(searcher.passThroughMetaData) { newDevice, advertisement in - print("received searcher passthrough") + deviceList + .onReceive(searcher.passThroughMetaData) { newDevice, advertisement in + print("received searcher passthrough") - let alreadyAdded = allDevices.contains { existingDevice -> Bool in - existingDevice.asStringIdentifier == newDevice.asStringIdentifier - } - if !alreadyAdded { - if let pluginForDevice = LibreTransmitters.getSupportedPlugins(newDevice)?.first { + let alreadyAdded = allDevices.contains { existingDevice -> Bool in + existingDevice.asStringIdentifier == newDevice.asStringIdentifier + } + if !alreadyAdded { + if let pluginForDevice = LibreTransmitters.getSupportedPlugins(newDevice)?.first { - deviceRequiresPhoneNFC[newDevice.asStringIdentifier] = pluginForDevice.requiresPhoneNFC - deviceRequiresSetup[newDevice.asStringIdentifier] = pluginForDevice.requiresSetup + deviceRequiresPhoneNFC[newDevice.asStringIdentifier] = pluginForDevice.requiresPhoneNFC + deviceRequiresSetup[newDevice.asStringIdentifier] = pluginForDevice.requiresSetup - if let parsedAdvertisement = pluginForDevice.getDeviceDetailsFromAdvertisement(advertisementData: advertisement) { + if let parsedAdvertisement = pluginForDevice.getDeviceDetailsFromAdvertisement(advertisementData: advertisement) { + + deviceDetails[newDevice.asStringIdentifier] = parsedAdvertisement + } else { + deviceDetails[newDevice.asStringIdentifier] = "" + } - deviceDetails[newDevice.asStringIdentifier] = parsedAdvertisement } else { - deviceDetails[newDevice.asStringIdentifier] = "" + deviceDetails[newDevice.asStringIdentifier] = newDevice.asStringIdentifier } - } else { - deviceDetails[newDevice.asStringIdentifier] = newDevice.asStringIdentifier + allDevices.append(newDevice) } - - allDevices.append(newDevice) } - } - .onReceive(searcher.throttledRSSI.throttledPublisher, perform: receiveRSSI) + .onReceive(searcher.throttledRSSI.throttledPublisher, perform: receiveRSSI) } } @@ -320,6 +321,6 @@ struct BluetoothSelection_Previews: PreviewProvider { let testData = SelectionState.shared testData.selectedStringIdentifier = "device4" - return BluetoothSelection(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), searcher: BluetoothSearchManager()) + return BluetoothSelection(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), searcher: MockBluetoothSearcher()) } } diff --git a/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift b/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift index 0e7b7df..2b89741 100644 --- a/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift +++ b/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift @@ -136,6 +136,6 @@ struct Libre2DirectSetup: View { struct Libre2DirectSetup_Previews: PreviewProvider { static var previews: some View { - Libre2DirectSetup(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), pairingService: SensorPairingService()) + Libre2DirectSetup(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), pairingService: MockSensorPairingService()) } } diff --git a/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift b/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift index 6d027c1..ac2a266 100644 --- a/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift +++ b/LibreTransmitterUI/Views/Setup/ModeSelectionView.swift @@ -72,6 +72,6 @@ struct ModeSelectionView: View { struct ModeSelectionView_Previews: PreviewProvider { static var previews: some View { - ModeSelectionView(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), pairingService: SensorPairingService(), bluetoothSearcher: BluetoothSearchManager()) + ModeSelectionView(cancelNotifier: GenericObservableObject(), saveNotifier: GenericObservableObject(), pairingService: MockSensorPairingService(), bluetoothSearcher: MockBluetoothSearcher()) } } From c1ffc2edb7f379fbf5fc7634c6618d3267b23f75 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 24 Jun 2023 15:10:59 -0500 Subject: [PATCH 10/19] LibreTransmitterProxyManager was being instantiated in the constructor of LibreTransmitterManagerV3 via delegate setting, so lazy doesn't do anything here --- LibreTransmitter/LibreTransmitterManagerV3.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index 6c0c428..4d23ea1 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -249,8 +249,7 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { disconnect() } - // lazy because we don't want to scan immediately - public lazy var proxy: LibreTransmitterProxyManager? = LibreTransmitterProxyManager() + public var proxy: LibreTransmitterProxyManager? = LibreTransmitterProxyManager() /* These properties are mostly useful for swiftui From aebbe5b5ccc75a7032dac3034aab3aa20b80ea3b Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 24 Jun 2023 15:59:18 -0500 Subject: [PATCH 11/19] Report mock values for demo --- Common/HKUnit.swift | 1 - LibreDemoPlugin/HKUnit.swift | 18 ++++++++ LibreDemoPlugin/LibreDemoCGMManager.swift | 41 +++++++++++++++++++ LibreTransmitter.xcodeproj/project.pbxproj | 4 ++ .../LibreTransmitterManagerV3.swift | 2 +- 5 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 LibreDemoPlugin/HKUnit.swift diff --git a/Common/HKUnit.swift b/Common/HKUnit.swift index 6e717c3..719502d 100644 --- a/Common/HKUnit.swift +++ b/Common/HKUnit.swift @@ -8,7 +8,6 @@ import HealthKit -/* not needed for loop dev/jojo ? */ extension HKUnit { static let milligramsPerDeciliter: HKUnit = { HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci)) diff --git a/LibreDemoPlugin/HKUnit.swift b/LibreDemoPlugin/HKUnit.swift new file mode 100644 index 0000000..9d0432f --- /dev/null +++ b/LibreDemoPlugin/HKUnit.swift @@ -0,0 +1,18 @@ +// +// HKUnit.swift +// LibreDemoPlugin +// +// Created by Pete Schwamb on 6/24/23. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation + + +import HealthKit + +extension HKUnit { + static let milligramsPerDeciliter: HKUnit = { + HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci)) + }() +} diff --git a/LibreDemoPlugin/LibreDemoCGMManager.swift b/LibreDemoPlugin/LibreDemoCGMManager.swift index 33e375a..27b708f 100644 --- a/LibreDemoPlugin/LibreDemoCGMManager.swift +++ b/LibreDemoPlugin/LibreDemoCGMManager.swift @@ -11,8 +11,15 @@ import LibreTransmitter import LibreTransmitterUI import LoopKit import LoopKitUI +import HealthKit +import os.log class LibreDemoCGMManager: LibreTransmitterManagerV3 { + var timer: Timer? + + private let log = OSLog(category: "LibreDemoCGMManager") + + public override var managerIdentifier: String { "LibreDemoCGMManager" } @@ -24,4 +31,38 @@ class LibreDemoCGMManager: LibreTransmitterManagerV3 { public override var bluetoothSearcher: BluetoothSearcher { return MockBluetoothSearcher() } + + public required init() { + super.init() + + self.lastConnected = Date() + + timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(5*60), repeats: true) { _ in + self.reportMockSample() + } + + // Also trigger a sample immediately, for dev convenience. + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.reportMockSample() + } + } + + private func reportMockSample() { + let date = Date() + let value = 110.0 + sin(date.timeIntervalSinceReferenceDate.truncatingRemainder(dividingBy: 3600 * 5) / (3600*5) * Double.pi * 2) * 60 + let quantity = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: value) + let newSample = NewGlucoseSample( + date: date, + quantity: quantity, + condition: nil, + trend: nil, + trendRate: nil, + isDisplayOnly: false, + wasUserEntered: false, + syncIdentifier: "mock-libre + \(date)") + log.debug("Reporting mock value of %{public}@", String(describing: value)) + self.delegateQueue.async { + self.cgmManagerDelegate?.cgmManager(self, hasNew: CGMReadingResult.newData([newSample])) + } + } } diff --git a/LibreTransmitter.xcodeproj/project.pbxproj b/LibreTransmitter.xcodeproj/project.pbxproj index 7bf19f4..9a0fb06 100644 --- a/LibreTransmitter.xcodeproj/project.pbxproj +++ b/LibreTransmitter.xcodeproj/project.pbxproj @@ -130,6 +130,7 @@ C1C318D52A47638E00C6F29F /* FakeSensorPairingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2709A6F229E5E0D3004355A3 /* FakeSensorPairingData.swift */; }; C1C318D62A47638E00C6F29F /* MockBluetoothSearcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C318CF2A4748A100C6F29F /* MockBluetoothSearcher.swift */; }; C1C318D72A47638E00C6F29F /* MockSensorPairingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BDBAFE2A44FFD800A787D1 /* MockSensorPairingService.swift */; }; + C1C318D92A478E0000C6F29F /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C318D82A478E0000C6F29F /* HKUnit.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -336,6 +337,7 @@ C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreDemoCGMManager.swift; sourceTree = ""; }; C1BDBAFE2A44FFD800A787D1 /* MockSensorPairingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSensorPairingService.swift; sourceTree = ""; }; C1C318CF2A4748A100C6F29F /* MockBluetoothSearcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBluetoothSearcher.swift; sourceTree = ""; }; + C1C318D82A478E0000C6F29F /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -693,6 +695,7 @@ C1BDBAE22A4397AA00A787D1 /* LibreDemoPlugin.h */, C1BDBAFC2A43985000A787D1 /* LibreDemoPlugin.swift */, C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */, + C1C318D82A478E0000C6F29F /* HKUnit.swift */, ); path = LibreDemoPlugin; sourceTree = ""; @@ -1054,6 +1057,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C1C318D92A478E0000C6F29F /* HKUnit.swift in Sources */, C1BDBAED2A4397E200A787D1 /* OSLog.swift in Sources */, C1BDBB002A45051B00A787D1 /* LibreDemoPlugin.swift in Sources */, C1BDBAEE2A4397E200A787D1 /* LibreTransmitterPlugin.swift in Sources */, diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index 4d23ea1..b3db2ba 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -125,7 +125,7 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { completion(.noData) } - internal var lastConnected: Date? + public var lastConnected: Date? public internal(set) var alarmStatus = AlarmStatus() From 35808fd03bb3895778d19d8953ca6f6c5ba7836b Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 24 Jun 2023 16:26:42 -0500 Subject: [PATCH 12/19] Update titles, and mark demo cgm manager as testing device --- Info.plist | 2 +- LibreDemoPlugin/LibreDemoCGMManager.swift | 35 ++++++++++++++++++- .../LibreTransmitterManagerV3.swift | 4 +++ LibreTransmitterPlugin/Info.plist | 2 +- .../LibreTransmitterManager+UI.swift | 1 + .../Views/Settings/SettingsView.swift | 1 - 6 files changed, 41 insertions(+), 4 deletions(-) diff --git a/Info.plist b/Info.plist index 42ce65a..53401cf 100644 --- a/Info.plist +++ b/Info.plist @@ -23,7 +23,7 @@ NSPrincipalClass LibreDemoPlugin.LibreDemoPlugin com.loopkit.Loop.CGMManagerDisplayName - Libre Demo CGM + FreeStyle Libre Demo com.loopkit.Loop.CGMManagerIdentifier LibreDemoCGMManager diff --git a/LibreDemoPlugin/LibreDemoCGMManager.swift b/LibreDemoPlugin/LibreDemoCGMManager.swift index 27b708f..6c6da52 100644 --- a/LibreDemoPlugin/LibreDemoCGMManager.swift +++ b/LibreDemoPlugin/LibreDemoCGMManager.swift @@ -13,6 +13,7 @@ import LoopKit import LoopKitUI import HealthKit import os.log +import LoopTestingKit class LibreDemoCGMManager: LibreTransmitterManagerV3 { var timer: Timer? @@ -59,10 +60,42 @@ class LibreDemoCGMManager: LibreTransmitterManagerV3 { trendRate: nil, isDisplayOnly: false, wasUserEntered: false, - syncIdentifier: "mock-libre + \(date)") + syncIdentifier: "mock-libre + \(date)", + device: testingDevice + ) log.debug("Reporting mock value of %{public}@", String(describing: value)) self.delegateQueue.async { self.cgmManagerDelegate?.cgmManager(self, hasNew: CGMReadingResult.newData([newSample])) } } + + override var displayTitle: String { + return "Libre Demo" + } +} + +extension LibreDemoCGMManager: TestingCGMManager { + func injectGlucoseSamples(_ pastSamples: [LoopKit.NewGlucoseSample], futureSamples: [LoopKit.NewGlucoseSample]) { + // TODO: Support scenarios + } + + var testingDevice: HKDevice { + HKDevice( + name: "LibreDemoCGM", + manufacturer: "LoopKit", + model: nil, + hardwareVersion: nil, + firmwareVersion: nil, + softwareVersion: nil, + localIdentifier: nil, + udiDeviceIdentifier: nil + ) + } + + func acceptDefaultsAndSkipOnboarding() { + } + + func trigger(action: LoopTestingKit.DeviceAction) { + // TODO: Support scenario actions + } } diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index b3db2ba..5b7c520 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -279,6 +279,10 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { return BluetoothSearchManager() } + open var displayTitle: String { + return "FreeStyle Libre" + } + } // MARK: - Convenience functions diff --git a/LibreTransmitterPlugin/Info.plist b/LibreTransmitterPlugin/Info.plist index d0964c4..9f17846 100644 --- a/LibreTransmitterPlugin/Info.plist +++ b/LibreTransmitterPlugin/Info.plist @@ -23,7 +23,7 @@ NSPrincipalClass LibreTransmitterPlugin com.loopkit.Loop.CGMManagerDisplayName - Libre ThirdParty Transmitter + FreeStyle Libre com.loopkit.Loop.CGMManagerIdentifier LibreTransmitterManagerV3 diff --git a/LibreTransmitterUI/LibreTransmitterManager+UI.swift b/LibreTransmitterUI/LibreTransmitterManager+UI.swift index c6c55dc..40b6672 100644 --- a/LibreTransmitterUI/LibreTransmitterManager+UI.swift +++ b/LibreTransmitterUI/LibreTransmitterManager+UI.swift @@ -56,6 +56,7 @@ extension LibreTransmitterManagerV3: CGMManagerUI { let hostedView = DismissibleHostingController( rootView: settingsView + .navigationTitle(self.displayTitle) .environmentObject(displayGlucosePreference) ) diff --git a/LibreTransmitterUI/Views/Settings/SettingsView.swift b/LibreTransmitterUI/Views/Settings/SettingsView.swift index 3419df9..e516ed1 100644 --- a/LibreTransmitterUI/Views/Settings/SettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/SettingsView.swift @@ -142,7 +142,6 @@ struct SettingsView: View { doneButton } } - .navigationTitle("Libre Bluetooth") } var snoozeSection: some View { From 4d6135038801c550aff3fe374d72656c606fac3e Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 24 Jun 2023 16:28:12 -0500 Subject: [PATCH 13/19] Support for C5 Libre2 sensors --- Bluetooth/LibreTransmitterMetadata.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bluetooth/LibreTransmitterMetadata.swift b/Bluetooth/LibreTransmitterMetadata.swift index 70e6ed2..d1543d1 100644 --- a/Bluetooth/LibreTransmitterMetadata.swift +++ b/Bluetooth/LibreTransmitterMetadata.swift @@ -110,7 +110,7 @@ public enum SensorType: String, CustomStringConvertible { case 0xDF, 0xA2: self = .libre1 case 0xE5, 0xE6: self = .libreUS14day case 0x70: self = .libreProH - case 0x9D: self = .libre2 + case 0xC5, 0x9D: self = .libre2 case 0x76: self = patchInfo[3] == 0x02 ? .libre2US : patchInfo[3] == 0x04 ? .libre2CA : patchInfo[2] >> 4 == 7 ? .libreSense : .unknown default: if patchInfo.count == 24 { From d4ae5cef338895d03ade8fb43991f876da1fa344 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 25 Jun 2023 23:26:04 -0500 Subject: [PATCH 14/19] Defer instantiation of LibreTransmitterProxyManager until device selected --- LibreDemoPlugin/LibreDemoCGMManager.swift | 4 ++++ .../LibreTransmitterManagerV3.swift | 17 +++++++++++------ .../LibreTransmitterSetupViewController.swift | 3 +++ .../LibreTransmitterManager+UI.swift | 2 +- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/LibreDemoPlugin/LibreDemoCGMManager.swift b/LibreDemoPlugin/LibreDemoCGMManager.swift index 6c6da52..f912791 100644 --- a/LibreDemoPlugin/LibreDemoCGMManager.swift +++ b/LibreDemoPlugin/LibreDemoCGMManager.swift @@ -33,6 +33,10 @@ class LibreDemoCGMManager: LibreTransmitterManagerV3 { return MockBluetoothSearcher() } + public override func establishProxy() { + // do nothing + } + public required init() { super.init() diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index 5b7c520..37f62da 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -212,8 +212,14 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { logger.debug("LibreTransmitterManager will be created now") NotificationHelper.requestNotificationPermissionsIfNeeded() - - proxy?.delegate = self + + if isDeviceSelected { + establishProxy() + } + } + + var isDeviceSelected: Bool { + return UserDefaults.standard.preSelectedDevice != nil || UserDefaults.standard.preSelectedUid != nil } public func resetManager() { @@ -235,12 +241,11 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { lastDirectUpdate = nil } - public func reEstablishProxy() { - logger.debug("LibreTransmitterManager re-establish called") + open func establishProxy() { + logger.debug("LibreTransmitterManager establishProxy called") proxy = LibreTransmitterProxyManager() proxy?.delegate = self - } deinit { @@ -249,7 +254,7 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { disconnect() } - public var proxy: LibreTransmitterProxyManager? = LibreTransmitterProxyManager() + public var proxy: LibreTransmitterProxyManager? /* These properties are mostly useful for swiftui diff --git a/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift b/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift index a0318bc..19c79bb 100644 --- a/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift +++ b/LibreTransmitterUI/Controllers/LibreTransmitterSetupViewController.swift @@ -85,6 +85,9 @@ class LibreTransmitterSetupViewController: UINavigationController, CGMManagerOnb // stored both preSelectedDevice and selectedUID ! } + + cgmManager.establishProxy() + logger.debug("Setupcontroller Saving from setup") cgmManagerOnboardingDelegate?.cgmManagerOnboarding(didCreateCGMManager: cgmManager) cgmManagerOnboardingDelegate?.cgmManagerOnboarding(didOnboardCGMManager: cgmManager) diff --git a/LibreTransmitterUI/LibreTransmitterManager+UI.swift b/LibreTransmitterUI/LibreTransmitterManager+UI.swift index 40b6672..5abc071 100644 --- a/LibreTransmitterUI/LibreTransmitterManager+UI.swift +++ b/LibreTransmitterUI/LibreTransmitterManager+UI.swift @@ -72,7 +72,7 @@ extension LibreTransmitterManagerV3: CGMManagerUI { wantToRestablishConnectionNotifier.listenOnce { [weak self, weak nav] in self?.logger.debug("CGM wants to RestablishConnection") - self?.reEstablishProxy() + self?.establishProxy() nav?.notifyComplete() } From 617f412a2ae3045468180761ab172110da7c822e Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 26 Jun 2023 09:21:09 -0500 Subject: [PATCH 15/19] Update check for isDeviceSelected to include more states --- LibreTransmitter.xcodeproj/project.pbxproj | 4 ++++ .../LibreTransmitterManagerV3.swift | 2 +- .../Observables/SelectionState.swift | 19 +++++++++++++++++++ .../Views/Setup/BluetoothSelection.swift | 9 --------- 4 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 LibreTransmitter/Observables/SelectionState.swift diff --git a/LibreTransmitter.xcodeproj/project.pbxproj b/LibreTransmitter.xcodeproj/project.pbxproj index 9a0fb06..6c24315 100644 --- a/LibreTransmitter.xcodeproj/project.pbxproj +++ b/LibreTransmitter.xcodeproj/project.pbxproj @@ -115,6 +115,7 @@ B40BF26623ABD4E700A43CEE /* LibreTransmitterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26523ABD4E700A43CEE /* LibreTransmitterPlugin.swift */; }; B40BF26823ABD55200A43CEE /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26723ABD55200A43CEE /* OSLog.swift */; }; B40BF26923ABD55200A43CEE /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26723ABD55200A43CEE /* OSLog.swift */; }; + C1AF65982A49C902008D0690 /* SelectionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF65972A49C902008D0690 /* SelectionState.swift */; }; C1BDBAED2A4397E200A787D1 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26723ABD55200A43CEE /* OSLog.swift */; }; C1BDBAEE2A4397E200A787D1 /* LibreTransmitterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26523ABD4E700A43CEE /* LibreTransmitterPlugin.swift */; }; C1BDBAF02A4397E200A787D1 /* LibreTransmitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 432B0E881CDFC3C50045347B /* LibreTransmitter.framework */; }; @@ -330,6 +331,7 @@ B40BF26423ABD4E600A43CEE /* LibreTransmitterPlugin-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LibreTransmitterPlugin-Bridging-Header.h"; sourceTree = ""; }; B40BF26523ABD4E700A43CEE /* LibreTransmitterPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreTransmitterPlugin.swift; sourceTree = ""; }; B40BF26723ABD55200A43CEE /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; + C1AF65972A49C902008D0690 /* SelectionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionState.swift; sourceTree = ""; }; C1BDBAE22A4397AA00A787D1 /* LibreDemoPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LibreDemoPlugin.h; sourceTree = ""; }; C1BDBAFA2A4397E200A787D1 /* LibreDemoPlugin.loopplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LibreDemoPlugin.loopplugin; sourceTree = BUILT_PRODUCTS_DIR; }; C1BDBAFB2A4397E200A787D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = /Users/pete/dev/LoopWorkspace/LibreTransmitter/Info.plist; sourceTree = ""; }; @@ -580,6 +582,7 @@ 27DD8F25268FA75A00649010 /* SensorInfo.swift */, 27DD8F27268FA79800649010 /* GlucoseInfo.swift */, 27ED67B72698EF38003E5DAB /* AlarmStatus.swift */, + C1AF65972A49C902008D0690 /* SelectionState.swift */, ); path = Observables; sourceTree = ""; @@ -952,6 +955,7 @@ C1C318D72A47638E00C6F29F /* MockSensorPairingService.swift in Sources */, 2746C73C26D91FC900E31BD9 /* Libre2DirectTransmitter.swift in Sources */, 27F93B2C2816B93900EE39A7 /* LibreTransmitterManager+Transmitters.swift in Sources */, + C1AF65982A49C902008D0690 /* SelectionState.swift in Sources */, 27850CFB25672C0C0020D109 /* GlucoseSchedules.swift in Sources */, C1C318CD2A47468D00C6F29F /* BluetoothSearch.swift in Sources */, 27850D9125672F020020D109 /* CBPeripheralExtensions.swift in Sources */, diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index 37f62da..b930e8d 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -219,7 +219,7 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { } var isDeviceSelected: Bool { - return UserDefaults.standard.preSelectedDevice != nil || UserDefaults.standard.preSelectedUid != nil + return UserDefaults.standard.preSelectedDevice != nil || UserDefaults.standard.preSelectedUid != nil || UserDefaults.standard.preSelectedDevice != nil || SelectionState.shared.selectedUID != nil } public func resetManager() { diff --git a/LibreTransmitter/Observables/SelectionState.swift b/LibreTransmitter/Observables/SelectionState.swift new file mode 100644 index 0000000..e36713f --- /dev/null +++ b/LibreTransmitter/Observables/SelectionState.swift @@ -0,0 +1,19 @@ +// +// SelectionState.swift +// LibreTransmitter +// +// Created by Pete Schwamb on 6/26/23. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import SwiftUI + +// Decided to use shared instance instead of .environmentObject() +public class SelectionState: ObservableObject { + @Published public var selectedStringIdentifier: String? = "" + + @Published public var selectedUID: Data? + + public static var shared = SelectionState() +} diff --git a/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift b/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift index 9f99b78..e1a655e 100644 --- a/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift +++ b/LibreTransmitterUI/Views/Setup/BluetoothSelection.swift @@ -171,15 +171,6 @@ private struct DeviceItem: View { } } -// Decided to use shared instance instead of .environmentObject() -class SelectionState: ObservableObject { - @Published var selectedStringIdentifier: String? = "" - - @Published var selectedUID: Data? - - static var shared = SelectionState() -} - struct BluetoothSelection: View { @ObservedObject var selection: SelectionState = .shared @ObservedObject public var cancelNotifier: GenericObservableObject From 4d8d0351ac5eb3fd5ea5e887487637f2d2d1cfd5 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 27 Jun 2023 13:46:35 -0500 Subject: [PATCH 16/19] Fix typo --- LibreTransmitter/LibreTransmitterManagerV3.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index b930e8d..63b4dc9 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -219,7 +219,7 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { } var isDeviceSelected: Bool { - return UserDefaults.standard.preSelectedDevice != nil || UserDefaults.standard.preSelectedUid != nil || UserDefaults.standard.preSelectedDevice != nil || SelectionState.shared.selectedUID != nil + return UserDefaults.standard.preSelectedDevice != nil || UserDefaults.standard.preSelectedUid != nil || SelectionState.shared.selectedUID != nil } public func resetManager() { From ffb465536dcaaf6ae7b3ecc3ea518a89e2149638 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 27 Jun 2023 20:24:43 -0500 Subject: [PATCH 17/19] Add MockSensorData for demo cgm --- LibreDemoPlugin/{ => Extensions}/HKUnit.swift | 0 LibreDemoPlugin/Extensions/TimeInterval.swift | 64 +++++++++++++++++ LibreDemoPlugin/LibreDemoCGMManager.swift | 14 ++++ LibreSensor/SensorContents/SensorData.swift | 71 +++++++++++-------- LibreSensor/SensorContents/SensorState.swift | 2 +- LibreTransmitter.xcodeproj/project.pbxproj | 18 ++++- .../LibreTransmitterManagerV3.swift | 2 +- LibreTransmitter/Mocks/MockSensorData.swift | 32 +++++++++ 8 files changed, 169 insertions(+), 34 deletions(-) rename LibreDemoPlugin/{ => Extensions}/HKUnit.swift (100%) create mode 100644 LibreDemoPlugin/Extensions/TimeInterval.swift create mode 100644 LibreTransmitter/Mocks/MockSensorData.swift diff --git a/LibreDemoPlugin/HKUnit.swift b/LibreDemoPlugin/Extensions/HKUnit.swift similarity index 100% rename from LibreDemoPlugin/HKUnit.swift rename to LibreDemoPlugin/Extensions/HKUnit.swift diff --git a/LibreDemoPlugin/Extensions/TimeInterval.swift b/LibreDemoPlugin/Extensions/TimeInterval.swift new file mode 100644 index 0000000..2f27544 --- /dev/null +++ b/LibreDemoPlugin/Extensions/TimeInterval.swift @@ -0,0 +1,64 @@ +// +// TimeInterval.swift +// LibreDemoPlugin +// +// Created by Pete Schwamb on 6/27/23. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation + +extension TimeInterval { + static func hours(_ hours: Double) -> TimeInterval { + return self.init(hours: hours) + } + + static func minutes(_ minutes: Int) -> TimeInterval { + return self.init(minutes: Double(minutes)) + } + + static func minutes(_ minutes: Double) -> TimeInterval { + return self.init(minutes: minutes) + } + + static func seconds(_ seconds: Double) -> TimeInterval { + return self.init(seconds) + } + + static func milliseconds(_ milliseconds: Double) -> TimeInterval { + return self.init(milliseconds / 1000) + } + + init(minutes: Double) { + self.init(minutes * 60) + } + + init(hours: Double) { + self.init(minutes: hours * 60) + } + + init(days: Double) { + self.init(hours: days * 24) + } + + init(seconds: Double) { + self.init(seconds) + } + + init(milliseconds: Double) { + self.init(milliseconds / 1000) + } + + var milliseconds: Double { + return self * 1000 + } + + var minutes: Double { + return self / 60.0 + } + + var hours: Double { + return minutes / 60.0 + } + +} diff --git a/LibreDemoPlugin/LibreDemoCGMManager.swift b/LibreDemoPlugin/LibreDemoCGMManager.swift index f912791..8829f8b 100644 --- a/LibreDemoPlugin/LibreDemoCGMManager.swift +++ b/LibreDemoPlugin/LibreDemoCGMManager.swift @@ -37,11 +37,14 @@ class LibreDemoCGMManager: LibreTransmitterManagerV3 { // do nothing } + private var sensorStartDate = Date().addingTimeInterval(TimeInterval(days: -1)) + public required init() { super.init() self.lastConnected = Date() + timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(5*60), repeats: true) { _ in self.reportMockSample() } @@ -68,6 +71,17 @@ class LibreDemoCGMManager: LibreTransmitterManagerV3 { device: testingDevice ) log.debug("Reporting mock value of %{public}@", String(describing: value)) + + // must be inside this handler as setobservables "depend" on latestbackfill + let sensorData = MockSensorData( + minutesSinceStart: Int(Date().timeIntervalSince(sensorStartDate).minutes), + maxMinutesWearTime: Int(TimeInterval(days: 14).minutes), + state: .ready, + serialNumber: "12345", + footerCrc: 0xabcd, + date: Date()) + self.setObservables(sensorData: sensorData, bleData: nil, metaData: nil) + self.delegateQueue.async { self.cgmManagerDelegate?.cgmManager(self, hasNew: CGMReadingResult.newData([newSample])) } diff --git a/LibreSensor/SensorContents/SensorData.swift b/LibreSensor/SensorContents/SensorData.swift index 900719a..5ccd2de 100644 --- a/LibreSensor/SensorContents/SensorData.swift +++ b/LibreSensor/SensorContents/SensorData.swift @@ -8,9 +8,44 @@ import Foundation +public protocol SensorDataProtocol { + var minutesSinceStart: Int { get } + var maxMinutesWearTime: Int { get } + var state: SensorState { get } + var serialNumber: String { get } + var footerCrc: UInt16 { get } + var date: Date { get } +} + +public extension SensorDataProtocol { + var humanReadableSensorAge: String { + let days = TimeInterval(minutesSinceStart * 60).days + return String(format: "%.2f", days) + " day(s)" + } + + var humanReadableTimeLeft: String { + let days = TimeInterval(minutesLeft * 60).days + return String(format: "%.2f", days) + " day(s)" + } + + // the amount of minutes left before this sensor expires + var minutesLeft: Int { + maxMinutesWearTime - minutesSinceStart + } + + // once the sensor has ended we don't know the exact date anymore + var sensorEndTime: Date? { + if minutesLeft <= 0 { + return nil + } + + return self.date.addingTimeInterval(TimeInterval(minutes: Double(self.minutesLeft))) + } +} + /// Structure for data from Freestyle Libre sensor /// To be initialized with the bytes as read via nfc. Provides all derived data. -public struct SensorData: Codable { +public struct SensorData: Codable, SensorDataProtocol { /// Parameters for the temperature compensation algorithm // let temperatureAlgorithmParameterSet: TemperatureAlgorithmParameters? @@ -21,7 +56,7 @@ public struct SensorData: Codable { /// The uid of the sensor let uuid: Data /// The serial number of the sensor - var serialNumber: String { + public var serialNumber: String { guard let patchInfo, patchInfo.count >= 6, let family = SensorFamily(rawValue: Int(patchInfo[2] >> 4)) @@ -48,7 +83,7 @@ public struct SensorData: Codable { Array(bytes[footerRange]) } /// Date when data was read from sensor - let date: Date + public let date: Date /// Minutes (approx) since start of sensor public var minutesSinceStart: Int { Int(body[293]) << 8 + Int(body[292]) @@ -82,26 +117,12 @@ public struct SensorData: Codable { Crc.hasValidCrc16InFirstTwoBytes(footer) } /// Footer crc needed for checking integrity of SwiftLibreOOPWeb response - var footerCrc: UInt16 { + public var footerCrc: UInt16 { Crc.crc16(Array(footer.dropFirst(2)), seed: 0xffff) } - // the amount of minutes left before this sensor expires - public var minutesLeft: Int { - maxMinutesWearTime - minutesSinceStart - } - - // once the sensor has ended we don't know the exact date anymore - var sensorEndTime: Date? { - if minutesLeft <= 0 { - return nil - } - - return self.date.addingTimeInterval(TimeInterval(minutes: Double(self.minutesLeft))) - } - /// Sensor state (ready, failure, starting etc.) - var state: SensorState { + public var state: SensorState { SensorState(stateByte: header[4]) } @@ -230,18 +251,6 @@ public struct SensorData: Codable { // strictly only needed for decryption and calculating serial numbers properly public var patchInfo : Data? - fileprivate let aday = 86_400.0 // in seconds - - var humanReadableSensorAge: String { - let days = TimeInterval(minutesSinceStart * 60) / aday - return String(format: "%.2f", days) + " day(s)" - } - - var humanReadableTimeLeft: String { - let days = TimeInterval(minutesLeft * 60) / aday - return String(format: "%.2f", days) + " day(s)" - } - var toJson: String { "[" + self.bytes.map { String(format: "0x%02x", $0) }.joined(separator: ", ") + "]" } diff --git a/LibreSensor/SensorContents/SensorState.swift b/LibreSensor/SensorContents/SensorState.swift index 467c3b8..7ff36d5 100644 --- a/LibreSensor/SensorContents/SensorState.swift +++ b/LibreSensor/SensorContents/SensorState.swift @@ -17,7 +17,7 @@ import Foundation /// - expired: 0x05 sensor is expired /// - failure: 0x06 sensor has an error /// - unknown: any other state -enum SensorState { +public enum SensorState { case notYetStarted case starting case ready diff --git a/LibreTransmitter.xcodeproj/project.pbxproj b/LibreTransmitter.xcodeproj/project.pbxproj index 6c24315..ca5d74d 100644 --- a/LibreTransmitter.xcodeproj/project.pbxproj +++ b/LibreTransmitter.xcodeproj/project.pbxproj @@ -115,6 +115,8 @@ B40BF26623ABD4E700A43CEE /* LibreTransmitterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26523ABD4E700A43CEE /* LibreTransmitterPlugin.swift */; }; B40BF26823ABD55200A43CEE /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26723ABD55200A43CEE /* OSLog.swift */; }; B40BF26923ABD55200A43CEE /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26723ABD55200A43CEE /* OSLog.swift */; }; + C1AF1D122A4BC18400F46A26 /* MockSensorData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF1D112A4BC18400F46A26 /* MockSensorData.swift */; }; + C1AF1D152A4BC23D00F46A26 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF1D142A4BC23D00F46A26 /* TimeInterval.swift */; }; C1AF65982A49C902008D0690 /* SelectionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF65972A49C902008D0690 /* SelectionState.swift */; }; C1BDBAED2A4397E200A787D1 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26723ABD55200A43CEE /* OSLog.swift */; }; C1BDBAEE2A4397E200A787D1 /* LibreTransmitterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40BF26523ABD4E700A43CEE /* LibreTransmitterPlugin.swift */; }; @@ -331,6 +333,8 @@ B40BF26423ABD4E600A43CEE /* LibreTransmitterPlugin-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LibreTransmitterPlugin-Bridging-Header.h"; sourceTree = ""; }; B40BF26523ABD4E700A43CEE /* LibreTransmitterPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreTransmitterPlugin.swift; sourceTree = ""; }; B40BF26723ABD55200A43CEE /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; + C1AF1D112A4BC18400F46A26 /* MockSensorData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSensorData.swift; sourceTree = ""; }; + C1AF1D142A4BC23D00F46A26 /* TimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = ""; }; C1AF65972A49C902008D0690 /* SelectionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionState.swift; sourceTree = ""; }; C1BDBAE22A4397AA00A787D1 /* LibreDemoPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LibreDemoPlugin.h; sourceTree = ""; }; C1BDBAFA2A4397E200A787D1 /* LibreDemoPlugin.loopplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LibreDemoPlugin.loopplugin; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -691,14 +695,23 @@ path = LibreTransmitterPlugin; sourceTree = ""; }; + C1AF1D132A4BC22A00F46A26 /* Extensions */ = { + isa = PBXGroup; + children = ( + C1C318D82A478E0000C6F29F /* HKUnit.swift */, + C1AF1D142A4BC23D00F46A26 /* TimeInterval.swift */, + ); + path = Extensions; + sourceTree = ""; + }; C1BDBAE12A4397AA00A787D1 /* LibreDemoPlugin */ = { isa = PBXGroup; children = ( + C1AF1D132A4BC22A00F46A26 /* Extensions */, C1BDBAFB2A4397E200A787D1 /* Info.plist */, C1BDBAE22A4397AA00A787D1 /* LibreDemoPlugin.h */, C1BDBAFC2A43985000A787D1 /* LibreDemoPlugin.swift */, C1BDBAFD2A43992A00A787D1 /* LibreDemoCGMManager.swift */, - C1C318D82A478E0000C6F29F /* HKUnit.swift */, ); path = LibreDemoPlugin; sourceTree = ""; @@ -709,6 +722,7 @@ C1BDBAFE2A44FFD800A787D1 /* MockSensorPairingService.swift */, 2709A6F229E5E0D3004355A3 /* FakeSensorPairingData.swift */, C1C318CF2A4748A100C6F29F /* MockBluetoothSearcher.swift */, + C1AF1D112A4BC18400F46A26 /* MockSensorData.swift */, ); path = Mocks; sourceTree = ""; @@ -985,6 +999,7 @@ C1C318D52A47638E00C6F29F /* FakeSensorPairingData.swift in Sources */, 27850DA525672F1C0020D109 /* LibreTransmitterProxyManager.swift in Sources */, 27850CF725672C0C0020D109 /* UserDefaults+Bluetooth.swift in Sources */, + C1AF1D122A4BC18400F46A26 /* MockSensorData.swift in Sources */, 271A39F52975844B0005FEDA /* NotificationHelperOverride.swift in Sources */, 27850CE725672C0C0020D109 /* DataExtensions.swift in Sources */, 27850D1225672CA60020D109 /* NotificationHelper.swift in Sources */, @@ -1062,6 +1077,7 @@ buildActionMask = 2147483647; files = ( C1C318D92A478E0000C6F29F /* HKUnit.swift in Sources */, + C1AF1D152A4BC23D00F46A26 /* TimeInterval.swift in Sources */, C1BDBAED2A4397E200A787D1 /* OSLog.swift in Sources */, C1BDBB002A45051B00A787D1 /* LibreDemoPlugin.swift in Sources */, C1BDBAEE2A4397E200A787D1 /* LibreTransmitterPlugin.swift in Sources */, diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index 63b4dc9..dcd04cc 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -317,7 +317,7 @@ extension LibreTransmitterManagerV3 { } - func setObservables(sensorData: SensorData?, bleData: Libre2.LibreBLEResponse?, metaData: LibreTransmitterMetadata?) { + public func setObservables(sensorData: SensorDataProtocol?, bleData: Libre2.LibreBLEResponse?, metaData: LibreTransmitterMetadata?) { logger.debug("setObservables called") DispatchQueue.main.async { diff --git a/LibreTransmitter/Mocks/MockSensorData.swift b/LibreTransmitter/Mocks/MockSensorData.swift new file mode 100644 index 0000000..b907c8a --- /dev/null +++ b/LibreTransmitter/Mocks/MockSensorData.swift @@ -0,0 +1,32 @@ +// +// MockSensorData.swift +// LibreTransmitter +// +// Created by Pete Schwamb on 6/27/23. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation + +public struct MockSensorData: SensorDataProtocol { + public var minutesSinceStart: Int + + public var maxMinutesWearTime: Int + + public var state: SensorState + + public var serialNumber: String + + public var footerCrc: UInt16 + + public var date: Date + + public init(minutesSinceStart: Int, maxMinutesWearTime: Int, state: SensorState, serialNumber: String, footerCrc: UInt16, date: Date) { + self.minutesSinceStart = minutesSinceStart + self.maxMinutesWearTime = maxMinutesWearTime + self.state = state + self.serialNumber = serialNumber + self.footerCrc = footerCrc + self.date = date + } +} From 0713e039fa6ac364a82815c8dc21472c0c1f741b Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 27 Jun 2023 20:55:15 -0500 Subject: [PATCH 18/19] Set latest backfill in demo cgm --- LibreDemoPlugin/LibreDemoCGMManager.swift | 7 +++++-- LibreTransmitter/LibreGlucose.swift | 6 ++++++ LibreTransmitter/LibreTransmitterManagerV3.swift | 2 +- LibreTransmitterUI/Views/Settings/SettingsView.swift | 4 +++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/LibreDemoPlugin/LibreDemoCGMManager.swift b/LibreDemoPlugin/LibreDemoCGMManager.swift index 8829f8b..a89fdb6 100644 --- a/LibreDemoPlugin/LibreDemoCGMManager.swift +++ b/LibreDemoPlugin/LibreDemoCGMManager.swift @@ -74,12 +74,15 @@ class LibreDemoCGMManager: LibreTransmitterManagerV3 { // must be inside this handler as setobservables "depend" on latestbackfill let sensorData = MockSensorData( - minutesSinceStart: Int(Date().timeIntervalSince(sensorStartDate).minutes), + minutesSinceStart: Int(date.timeIntervalSince(sensorStartDate).minutes), maxMinutesWearTime: Int(TimeInterval(days: 14).minutes), state: .ready, serialNumber: "12345", footerCrc: 0xabcd, - date: Date()) + date: date) + + self.latestBackfill = LibreGlucose(unsmoothedGlucose: value, glucoseDouble: value, timestamp: date) + self.setObservables(sensorData: sensorData, bleData: nil, metaData: nil) self.delegateQueue.async { diff --git a/LibreTransmitter/LibreGlucose.swift b/LibreTransmitter/LibreGlucose.swift index 799a541..43ec73c 100644 --- a/LibreTransmitter/LibreGlucose.swift +++ b/LibreTransmitter/LibreGlucose.swift @@ -20,6 +20,12 @@ public struct LibreGlucose: Codable, Hashable { public var timestamp: Date + public init(unsmoothedGlucose: Double, glucoseDouble: Double, error: [MeasurementError] = [MeasurementError.OK], timestamp: Date) { + self.unsmoothedGlucose = unsmoothedGlucose + self.glucoseDouble = glucoseDouble + self.timestamp = timestamp + } + public static func timeDifference(oldGlucose: LibreGlucose, newGlucose: LibreGlucose) -> TimeInterval { newGlucose.startDate.timeIntervalSince(oldGlucose.startDate) } diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index dcd04cc..f67a349 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -131,7 +131,7 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { internal var latestPrediction: LibreGlucose? - internal var latestBackfill: LibreGlucose? { + public var latestBackfill: LibreGlucose? { willSet(newValue) { guard let newValue else { return diff --git a/LibreTransmitterUI/Views/Settings/SettingsView.swift b/LibreTransmitterUI/Views/Settings/SettingsView.swift index e516ed1..af14b29 100644 --- a/LibreTransmitterUI/Views/Settings/SettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/SettingsView.swift @@ -243,7 +243,9 @@ struct SettingsView: View { self.authenticate { success in print("got authentication response: \(success)") if success { - notifyDelete.notify() + DispatchQueue.main.async { + notifyDelete.notify() + } } } From 54a8d4a3cbb8a7290a9d7a67c7c8f7a23496e13f Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 30 Jun 2023 10:57:52 -0500 Subject: [PATCH 19/19] Re-enable pair button on cancel of NFC dialog --- LibreDemoPlugin/LibreDemoCGMManager.swift | 6 ++---- LibreSensor/SensorPairing/SensorPairing.swift | 5 ++--- LibreSensor/SensorPairing/SensorPairingService.swift | 6 ++++-- LibreTransmitter/LibreTransmitterManagerV3.swift | 7 +------ LibreTransmitter/Mocks/MockSensorPairingService.swift | 3 ++- LibreTransmitterUI/LibreTransmitterManager+UI.swift | 2 +- LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift | 8 +++++++- 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/LibreDemoPlugin/LibreDemoCGMManager.swift b/LibreDemoPlugin/LibreDemoCGMManager.swift index a89fdb6..98697fa 100644 --- a/LibreDemoPlugin/LibreDemoCGMManager.swift +++ b/LibreDemoPlugin/LibreDemoCGMManager.swift @@ -21,6 +21,8 @@ class LibreDemoCGMManager: LibreTransmitterManagerV3 { private let log = OSLog(category: "LibreDemoCGMManager") + override var localizedTitle: String { "Libre Demo" } + public override var managerIdentifier: String { "LibreDemoCGMManager" } @@ -89,10 +91,6 @@ class LibreDemoCGMManager: LibreTransmitterManagerV3 { self.cgmManagerDelegate?.cgmManager(self, hasNew: CGMReadingResult.newData([newSample])) } } - - override var displayTitle: String { - return "Libre Demo" - } } extension LibreDemoCGMManager: TestingCGMManager { diff --git a/LibreSensor/SensorPairing/SensorPairing.swift b/LibreSensor/SensorPairing/SensorPairing.swift index a0b99cc..a495065 100644 --- a/LibreSensor/SensorPairing/SensorPairing.swift +++ b/LibreSensor/SensorPairing/SensorPairing.swift @@ -72,9 +72,8 @@ public class SensorPairingInfo: ObservableObject, Codable { } -public protocol SensorPairingProtocol { +public protocol SensorPairingProtocol: AnyObject { + var onCancel: (() -> Void)? { get set } var publisher: AnyPublisher { get } - func pairSensor() throws - } diff --git a/LibreSensor/SensorPairing/SensorPairingService.swift b/LibreSensor/SensorPairing/SensorPairingService.swift index 2bd996e..fb7f73b 100644 --- a/LibreSensor/SensorPairing/SensorPairingService.swift +++ b/LibreSensor/SensorPairing/SensorPairingService.swift @@ -57,6 +57,8 @@ public class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, Sensor private let unlockCode: UInt32 = 42 // 42 + public var onCancel: (() -> Void)? + public func pairSensor() throws { if !Features.phoneNFCAvailable { throw PairingError.nfcNotSupported @@ -100,6 +102,8 @@ public class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, Sensor session.invalidate(errorMessage: "Connection failure: \(error.localizedDescription)") self.sendError(error) } + + self.onCancel?() } public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { @@ -198,8 +202,6 @@ public class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, Sensor self.sendUpdate(SensorPairingInfo(uuid: sensorUID, patchInfo: patchInfo, fram: Data(decryptedBytes), streamingEnabled: streamingEnabled)) session.invalidate() - - return } catch { print("problem decrypting") diff --git a/LibreTransmitter/LibreTransmitterManagerV3.swift b/LibreTransmitter/LibreTransmitterManagerV3.swift index f67a349..de72574 100644 --- a/LibreTransmitter/LibreTransmitterManagerV3.swift +++ b/LibreTransmitter/LibreTransmitterManagerV3.swift @@ -198,7 +198,7 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { [:] } - public let localizedTitle = LocalizedString("Libre Bluetooth", comment: "Title for the CGMManager option") + open var localizedTitle: String { "FreeStyle Libre" } public let appURL: URL? = nil // URL(string: "spikeapp://") @@ -283,11 +283,6 @@ open class LibreTransmitterManagerV3: CGMManager, LibreTransmitterDelegate { open var bluetoothSearcher: BluetoothSearcher { return BluetoothSearchManager() } - - open var displayTitle: String { - return "FreeStyle Libre" - } - } // MARK: - Convenience functions diff --git a/LibreTransmitter/Mocks/MockSensorPairingService.swift b/LibreTransmitter/Mocks/MockSensorPairingService.swift index 8b905fa..04178b1 100644 --- a/LibreTransmitter/Mocks/MockSensorPairingService.swift +++ b/LibreTransmitter/Mocks/MockSensorPairingService.swift @@ -11,11 +11,12 @@ import Combine import os.log public class MockSensorPairingService: SensorPairingProtocol { - fileprivate lazy var logger = Logger(forType: Self.self) private var readingsSubject = PassthroughSubject() + public var onCancel: (() -> Void)? + public var publisher: AnyPublisher { readingsSubject.eraseToAnyPublisher() } diff --git a/LibreTransmitterUI/LibreTransmitterManager+UI.swift b/LibreTransmitterUI/LibreTransmitterManager+UI.swift index 5abc071..7db319b 100644 --- a/LibreTransmitterUI/LibreTransmitterManager+UI.swift +++ b/LibreTransmitterUI/LibreTransmitterManager+UI.swift @@ -56,7 +56,7 @@ extension LibreTransmitterManagerV3: CGMManagerUI { let hostedView = DismissibleHostingController( rootView: settingsView - .navigationTitle(self.displayTitle) + .navigationTitle(self.localizedTitle) .environmentObject(displayGlucosePreference) ) diff --git a/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift b/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift index 2b89741..bfb8e45 100644 --- a/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift +++ b/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift @@ -24,10 +24,16 @@ struct Libre2DirectSetup: View { @ObservedObject public var cancelNotifier: GenericObservableObject @ObservedObject public var saveNotifier: GenericObservableObject - var pairingService: SensorPairingProtocol + let pairingService: SensorPairingProtocol func pairSensor() { + pairingService.onCancel = { + DispatchQueue.main.async { + isPairing = false + } + } + showPairingInfo = false isPairing = true