diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 1eac531b..a0b54dbb 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -228,6 +228,7 @@ F8252867243E50FE0067AF77 /* ConstantsLibre.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8252866243E50FE0067AF77 /* ConstantsLibre.swift */; }; F825286A2443AE190067AF77 /* DexcomG6BluetoothPeripheralViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F82528692443AE190067AF77 /* DexcomG6BluetoothPeripheralViewModel.swift */; }; F825286C2443BEDC0067AF77 /* CGMG6TransmitterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F825286B2443BEDC0067AF77 /* CGMG6TransmitterDelegate.swift */; }; + F8284230274ED56A0097E0C9 /* DexcomCalibrationParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = F828422F274ED56A0097E0C9 /* DexcomCalibrationParameters.swift */; }; F8297F4E238DCAD800D74D66 /* BluetoothPeripheralsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8297F4B238DCAD800D74D66 /* BluetoothPeripheralsViewController.swift */; }; F8297F4F238DCAD800D74D66 /* BluetoothPeripheralNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8297F4C238DCAD800D74D66 /* BluetoothPeripheralNavigationController.swift */; }; F8297F52238ECA3200D74D66 /* BluetoothPeripheralViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8297F51238ECA3200D74D66 /* BluetoothPeripheralViewController.swift */; }; @@ -422,6 +423,13 @@ F8CB59C02734976D00BA199E /* DexcomTransmitterTimeTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB59BF2734976D00BA199E /* DexcomTransmitterTimeTxMessage.swift */; }; F8CB59C22738206D00BA199E /* DexcomGlucoseDataTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB59C12738206D00BA199E /* DexcomGlucoseDataTxMessage.swift */; }; F8CB59C42739D1CD00BA199E /* DexcomBackfillTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB59C32739D1CD00BA199E /* DexcomBackfillTxMessage.swift */; }; + F8CB59C6273ECFE500BA199E /* DexcomG6GlucoseDataRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB59C5273ECFE500BA199E /* DexcomG6GlucoseDataRxMessage.swift */; }; + F8CB59C8273EF9F800BA199E /* DexcomAlgorithmState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB59C7273EF9F800BA199E /* DexcomAlgorithmState.swift */; }; + F8CB59CA27405A6800BA199E /* DexcomCalibrationTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB59C927405A6800BA199E /* DexcomCalibrationTxMessage.swift */; }; + F8CB59CC2744471100BA199E /* DexcomSessionStartResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB59CB2744471000BA199E /* DexcomSessionStartResponse.swift */; }; + F8CB59CE27444D6300BA199E /* DexcomSessionStopResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB59CD27444D6300BA199E /* DexcomSessionStopResponse.swift */; }; + F8CB59D12745D3FE00BA199E /* DexcomG6FireflyBluetoothPeripheralViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB59D02745D3FE00BA199E /* DexcomG6FireflyBluetoothPeripheralViewModel.swift */; }; + F8CB59D3274D94AF00BA199E /* DexcomSessionStartTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB59D2274D94AE00BA199E /* DexcomSessionStartTxMessage.swift */; }; F8D0587C24BCB570008C8734 /* SettingsViewHomeScreenSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D0587B24BCB570008C8734 /* SettingsViewHomeScreenSettingsViewModel.swift */; }; F8DF765323E34F4500063910 /* DexcomG5+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DF765223E34F4500063910 /* DexcomG5+CoreDataClass.swift */; }; F8DF765523E34FD500063910 /* DexcomG5+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DF765423E34FD500063910 /* DexcomG5+CoreDataProperties.swift */; }; @@ -461,7 +469,6 @@ F8F1670A2727317D001AA3D8 /* DexcomTransmitterTimeRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F167092727317C001AA3D8 /* DexcomTransmitterTimeRxMessage.swift */; }; F8F1670C27273774001AA3D8 /* GlucoseBackfillRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F1670B27273774001AA3D8 /* GlucoseBackfillRxMessage.swift */; }; F8F1670E27273EA7001AA3D8 /* GlucoseDataRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F1670D27273EA7001AA3D8 /* GlucoseDataRxMessage.swift */; }; - F8F1671127274080001AA3D8 /* DexcomCalibrationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F1671027274080001AA3D8 /* DexcomCalibrationState.swift */; }; F8F1671327274557001AA3D8 /* DexcomCalibrationRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F1671227274557001AA3D8 /* DexcomCalibrationRxMessage.swift */; }; F8F16715272745A2001AA3D8 /* DexcomCalibrationResponseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F16714272745A2001AA3D8 /* DexcomCalibrationResponseType.swift */; }; F8F1671727288B24001AA3D8 /* DexcomSessionStopRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F1671627288B24001AA3D8 /* DexcomSessionStopRxMessage.swift */; }; @@ -1026,6 +1033,7 @@ F8252866243E50FE0067AF77 /* ConstantsLibre.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstantsLibre.swift; sourceTree = ""; }; F82528692443AE190067AF77 /* DexcomG6BluetoothPeripheralViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomG6BluetoothPeripheralViewModel.swift; sourceTree = ""; }; F825286B2443BEDC0067AF77 /* CGMG6TransmitterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMG6TransmitterDelegate.swift; sourceTree = ""; }; + F828422F274ED56A0097E0C9 /* DexcomCalibrationParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationParameters.swift; sourceTree = ""; }; F8297F4B238DCAD800D74D66 /* BluetoothPeripheralsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothPeripheralsViewController.swift; sourceTree = ""; }; F8297F4C238DCAD800D74D66 /* BluetoothPeripheralNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothPeripheralNavigationController.swift; sourceTree = ""; }; F8297F51238ECA3200D74D66 /* BluetoothPeripheralViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothPeripheralViewController.swift; sourceTree = ""; }; @@ -1299,6 +1307,13 @@ F8CB59BF2734976D00BA199E /* DexcomTransmitterTimeTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomTransmitterTimeTxMessage.swift; sourceTree = ""; }; F8CB59C12738206D00BA199E /* DexcomGlucoseDataTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomGlucoseDataTxMessage.swift; sourceTree = ""; }; F8CB59C32739D1CD00BA199E /* DexcomBackfillTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomBackfillTxMessage.swift; sourceTree = ""; }; + F8CB59C5273ECFE500BA199E /* DexcomG6GlucoseDataRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomG6GlucoseDataRxMessage.swift; sourceTree = ""; }; + F8CB59C7273EF9F800BA199E /* DexcomAlgorithmState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomAlgorithmState.swift; sourceTree = ""; }; + F8CB59C927405A6800BA199E /* DexcomCalibrationTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationTxMessage.swift; sourceTree = ""; }; + F8CB59CB2744471000BA199E /* DexcomSessionStartResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSessionStartResponse.swift; sourceTree = ""; }; + F8CB59CD27444D6300BA199E /* DexcomSessionStopResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSessionStopResponse.swift; sourceTree = ""; }; + F8CB59D02745D3FE00BA199E /* DexcomG6FireflyBluetoothPeripheralViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomG6FireflyBluetoothPeripheralViewModel.swift; sourceTree = ""; }; + F8CB59D2274D94AE00BA199E /* DexcomSessionStartTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSessionStartTxMessage.swift; sourceTree = ""; }; F8D0587B24BCB570008C8734 /* SettingsViewHomeScreenSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewHomeScreenSettingsViewModel.swift; sourceTree = ""; }; F8DF765223E34F4500063910 /* DexcomG5+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DexcomG5+CoreDataClass.swift"; sourceTree = ""; }; F8DF765423E34FD500063910 /* DexcomG5+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DexcomG5+CoreDataProperties.swift"; sourceTree = ""; }; @@ -1344,7 +1359,6 @@ F8F167092727317C001AA3D8 /* DexcomTransmitterTimeRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomTransmitterTimeRxMessage.swift; sourceTree = ""; }; F8F1670B27273774001AA3D8 /* GlucoseBackfillRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBackfillRxMessage.swift; sourceTree = ""; }; F8F1670D27273EA7001AA3D8 /* GlucoseDataRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseDataRxMessage.swift; sourceTree = ""; }; - F8F1671027274080001AA3D8 /* DexcomCalibrationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationState.swift; sourceTree = ""; }; F8F1671227274557001AA3D8 /* DexcomCalibrationRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationRxMessage.swift; sourceTree = ""; }; F8F16714272745A2001AA3D8 /* DexcomCalibrationResponseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationResponseType.swift; sourceTree = ""; }; F8F1671627288B24001AA3D8 /* DexcomSessionStopRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSessionStopRxMessage.swift; sourceTree = ""; }; @@ -2446,6 +2460,14 @@ path = MiaoMiao; sourceTree = ""; }; + F8CB59CF2745D3D000BA199E /* DexcomG6Firefly */ = { + isa = PBXGroup; + children = ( + F8CB59D02745D3FE00BA199E /* DexcomG6FireflyBluetoothPeripheralViewModel.swift */, + ); + path = DexcomG6Firefly; + sourceTree = ""; + }; F8DF765923E350B100063910 /* Dexcom */ = { isa = PBXGroup; children = ( @@ -2475,6 +2497,7 @@ F8DF766A23ED9AF100063910 /* Dexcom */ = { isa = PBXGroup; children = ( + F8CB59CF2745D3D000BA199E /* DexcomG6Firefly */, F82528682443AE040067AF77 /* DexcomG6 */, F816E12F2439E2A0009EE65B /* DexcomG4 */, F8DF766B23ED9AF100063910 /* DexcomG5 */, @@ -2631,11 +2654,18 @@ F8F971C323A5915900C3F17D /* AuthRequestTxMessage.swift */, F8F971D123A5915900C3F17D /* BatteryStatusRxMessage.swift */, F8F971C523A5915900C3F17D /* BatteryStatusTxMessage.swift */, + F8CB59C7273EF9F800BA199E /* DexcomAlgorithmState.swift */, F8F1671A272B3E4F001AA3D8 /* DexcomBackfillStream.swift */, + F8CB59C32739D1CD00BA199E /* DexcomBackfillTxMessage.swift */, F8F16714272745A2001AA3D8 /* DexcomCalibrationResponseType.swift */, F8F1671227274557001AA3D8 /* DexcomCalibrationRxMessage.swift */, - F8F1671027274080001AA3D8 /* DexcomCalibrationState.swift */, + F8CB59C927405A6800BA199E /* DexcomCalibrationTxMessage.swift */, + F8CB59C5273ECFE500BA199E /* DexcomG6GlucoseDataRxMessage.swift */, + F8CB59C12738206D00BA199E /* DexcomGlucoseDataTxMessage.swift */, + F8CB59CB2744471000BA199E /* DexcomSessionStartResponse.swift */, F8F1671827288FC6001AA3D8 /* DexcomSessionStartRxMessage.swift */, + F8CB59D2274D94AE00BA199E /* DexcomSessionStartTxMessage.swift */, + F8CB59CD27444D6300BA199E /* DexcomSessionStopResponse.swift */, F8F1671627288B24001AA3D8 /* DexcomSessionStopRxMessage.swift */, F8F971C723A5915900C3F17D /* DexcomTransmitterOpCode.swift */, F8F167092727317C001AA3D8 /* DexcomTransmitterTimeRxMessage.swift */, @@ -2652,8 +2682,7 @@ F8F971C223A5915900C3F17D /* TransmitterMessage.swift */, F8F971CE23A5915900C3F17D /* TransmitterVersionRxMessage.swift */, F8F971C123A5915900C3F17D /* TransmitterVersionTxMessage.swift */, - F8CB59C12738206D00BA199E /* DexcomGlucoseDataTxMessage.swift */, - F8CB59C32739D1CD00BA199E /* DexcomBackfillTxMessage.swift */, + F828422F274ED56A0097E0C9 /* DexcomCalibrationParameters.swift */, ); path = Generic; sourceTree = ""; @@ -3396,6 +3425,7 @@ F816E0F12433C31B009EE65B /* Blucon+CoreDataClass.swift in Sources */, F8F9723323A5915900C3F17D /* M5StackBluetoothTransmitterDelegate.swift in Sources */, F8B3A81B227DEC92004BA588 /* SensorsAccessor.swift in Sources */, + F8CB59D12745D3FE00BA199E /* DexcomG6FireflyBluetoothPeripheralViewModel.swift in Sources */, F8F9721E23A5915900C3F17D /* LibreSensorState.swift in Sources */, F8B3A85D22821BB6004BA588 /* Int.swift in Sources */, F808D2CE2403292C0084B5DB /* Bubble+CoreDataProperties.swift in Sources */, @@ -3458,7 +3488,6 @@ F8F9723923A5928D00C3F17D /* M5StackBluetoothPeripheralViewModel.swift in Sources */, F8F9722B23A5915900C3F17D /* CGMTransmitterDelegate.swift in Sources */, F80859292364D61B00F3829D /* UserDefaults+charts.swift in Sources */, - F8F1671127274080001AA3D8 /* DexcomCalibrationState.swift in Sources */, F8B955B7258D5E2000C06016 /* ConstantsHealthKit.swift in Sources */, F8B3A7B2226A0878004BA588 /* TextsAlerts.swift in Sources */, F80D917024F85C7A006840B5 /* Libre2+BluetoothPeripheral.swift in Sources */, @@ -3492,6 +3521,7 @@ F8F1670E27273EA7001AA3D8 /* GlucoseDataRxMessage.swift in Sources */, F8B3A835227F08AC004BA588 /* PickerViewController.swift in Sources */, F821CF9522ADB0D7005C1E43 /* HealthKitManager.swift in Sources */, + F8CB59CE27444D6300BA199E /* DexcomSessionStopResponse.swift in Sources */, F8B3A81D227DEC92004BA588 /* CalibrationsAccessor.swift in Sources */, F821CF9D22AEF483005C1E43 /* BGReadingSpeaker.swift in Sources */, F8297F4E238DCAD800D74D66 /* BluetoothPeripheralsViewController.swift in Sources */, @@ -3580,6 +3610,7 @@ F8252867243E50FE0067AF77 /* ConstantsLibre.swift in Sources */, F8A1586D22EDB9BE007F5B5D /* ConstantsDexcomFollower.swift in Sources */, F8F9720923A5915900C3F17D /* AESCrypt.m in Sources */, + F8CB59D3274D94AF00BA199E /* DexcomSessionStartTxMessage.swift in Sources */, F80ED2ED236F68F90005C035 /* SettingsViewM5StackGeneralSettingsViewModel.swift in Sources */, F816E10A2437E7B8009EE65B /* CGMBlueReaderTransmitterDelegate.swift in Sources */, F8B3A850227F26F8004BA588 /* AlertTypesSettingsViewController.swift in Sources */, @@ -3602,6 +3633,7 @@ F8BECB12235CEA9B0060DAE1 /* TimeInterval.swift in Sources */, F8F1670C27273774001AA3D8 /* GlucoseBackfillRxMessage.swift in Sources */, F8F9720823A5915900C3F17D /* BatteryStatusTxMessage.swift in Sources */, + F8CB59C6273ECFE500BA199E /* DexcomG6GlucoseDataRxMessage.swift in Sources */, F81F9FFC2288C7530028C70F /* NewAlertSettingsViewController.swift in Sources */, F80D916524F5B3DE006840B5 /* Libre2+CoreDataClass.swift in Sources */, F898EDF2234A8A0500BFB79B /* UInt8.swift in Sources */, @@ -3620,6 +3652,7 @@ F8A2BC2E25DB0D6D001D1E78 /* BluetoothPeripheralManager+CGMDropletTransmitterDelegate.swift in Sources */, F8C5EBE722F38F0E00563B5F /* Trace.swift in Sources */, F8B48A9422B2A705009BCC01 /* TextsSpeakReading.swift in Sources */, + F8CB59CC2744471100BA199E /* DexcomSessionStartResponse.swift in Sources */, 47AB72F327105EF4005E7CAB /* SettingsViewHelpSettingModel.swift in Sources */, F821CF5F229BF43A005C1E43 /* ApplicationManager.swift in Sources */, F8B3A834227F08AC004BA588 /* PickerViewData.swift in Sources */, @@ -3676,6 +3709,7 @@ F816E0E42432A4FA009EE65B /* CGMBluconTransmitterDelegate.swift in Sources */, F8A389CF232AE2EA0010F405 /* M5StackSettingsViewController.swift in Sources */, F821CF5E229BF43A005C1E43 /* BgReading+NightScout.swift in Sources */, + F8CB59CA27405A6800BA199E /* DexcomCalibrationTxMessage.swift in Sources */, F8A2BC3025DB0D6D001D1E78 /* BluetoothPeripheralManager.swift in Sources */, F8A389EB233175A10010F405 /* SettingsViewM5StackSettingsViewModel.swift in Sources */, F816E1242439DB63009EE65B /* DexcomG4+BluetoothPeripheral.swift in Sources */, @@ -3704,6 +3738,7 @@ F85FF3D1252F9FF9004E6FF1 /* SnoozeParameters+CoreDataClass.swift in Sources */, F857A32A253E2D9E00951BB2 /* LibreAlgorithmThresholds.swift in Sources */, F8A1850C25643B16000EF8A0 /* Double+Smoothable.swift in Sources */, + F8CB59C8273EF9F800BA199E /* DexcomAlgorithmState.swift in Sources */, F890E07A247687AE008FB2EC /* URL.swift in Sources */, F8B3A856227F28DC004BA588 /* AlertTypeSettingsViewController.swift in Sources */, F8A1584F22ECB281007F5B5D /* SettingsViewInfoViewModel.swift in Sources */, @@ -3711,6 +3746,7 @@ F8F9720C23A5915900C3F17D /* SensorDataTxMessage.swift in Sources */, F8DF766223E390D100063910 /* BLEPeripheral+CoreDataProperties.swift in Sources */, F8A1585F22EDB81E007F5B5D /* ConstantsLog.swift in Sources */, + F8284230274ED56A0097E0C9 /* DexcomCalibrationParameters.swift in Sources */, F8A1586522EDB89D007F5B5D /* ConstantsDefaultAlertTypeSettings.swift in Sources */, F816E11624391A02009EE65B /* Droplet+BluetoothPeripheral.swift in Sources */, F8FDFEAD260DE1B90047597D /* ConstantsUI.swift in Sources */, diff --git a/xdrip/BluetoothPeripheral/CGM/Dexcom/G5/DexcomG5+BluetoothPeripheral.swift b/xdrip/BluetoothPeripheral/CGM/Dexcom/G5/DexcomG5+BluetoothPeripheral.swift index 8a9ecdaf..1ae46d02 100644 --- a/xdrip/BluetoothPeripheral/CGM/Dexcom/G5/DexcomG5+BluetoothPeripheral.swift +++ b/xdrip/BluetoothPeripheral/CGM/Dexcom/G5/DexcomG5+BluetoothPeripheral.swift @@ -4,6 +4,10 @@ extension DexcomG5: BluetoothPeripheral { func bluetoothPeripheralType() -> BluetoothPeripheralType { + if isDexcomG6 {return .DexcomG6Type} + + if isFirefly {return .DexcomG6FireflyType} + return .DexcomG5Type } diff --git a/xdrip/BluetoothPeripheral/Generic/BluetoothPeripheralType.swift b/xdrip/BluetoothPeripheral/Generic/BluetoothPeripheralType.swift index 9d2b1023..c86eb3bc 100644 --- a/xdrip/BluetoothPeripheral/Generic/BluetoothPeripheralType.swift +++ b/xdrip/BluetoothPeripheral/Generic/BluetoothPeripheralType.swift @@ -25,6 +25,9 @@ enum BluetoothPeripheralType: String, CaseIterable { /// DexcomG6 case DexcomG6Type = "Dexcom G6" + /// Dexcom G6 firefly + case DexcomG6FireflyType = "Dexcom G6 Firefly" + /// DexcomG5 case DexcomG5Type = "Dexcom G5" @@ -90,6 +93,9 @@ enum BluetoothPeripheralType: String, CaseIterable { case .DexcomG6Type: return DexcomG6BluetoothPeripheralViewModel() + case .DexcomG6FireflyType: + return DexcomG6FireflyBluetoothPeripheralViewModel() + case .Libre2Type: return Libre2BluetoothPeripheralViewModel() @@ -128,8 +134,26 @@ enum BluetoothPeripheralType: String, CaseIterable { case .DexcomG6Type: // DexcomG6 is a DexcomG5 with isDexcomG6 set to true + + // the cgm transmitter itself will use the transmitter id to determine that firefly logic needs to be used + // which means, a user could add a Dexcom G6 Firefly as a Dexcom G6 or even a Dexcom G5, the class CGMG5Transmitter will find out it's a firefly, based on transmitter id and handle it as a firefly + // It plays only a role in the RootViewController that a G6 Firefly is selected, because then also the user needs to add a code + let dexcomG6 = DexcomG5(address: address, name: name, alias: nil, nsManagedObjectContext: nsManagedObjectContext) dexcomG6.isDexcomG6 = true + + return dexcomG6 + + case .DexcomG6FireflyType: + + // DexcomG6 Firefly is a DexcomG5 with isFirefly set to true + + // the cgm transmitter itself will use the transmitter id to determine that firefly logic needs to be used + // which means, a user could add a Dexcom G6 Firefly as a Dexcom G6 or even a Dexcom G5, the class CGMG5Transmitter will find out it's a firefly, based on transmitter id and handle it as a firefly + // It plays only a role in the RootViewController that a G6 Firefly is selected, because then also the user needs to add a code + + let dexcomG6 = DexcomG5(address: address, name: name, alias: nil, nsManagedObjectContext: nsManagedObjectContext) + dexcomG6.isFirefly = true return dexcomG6 @@ -181,7 +205,7 @@ enum BluetoothPeripheralType: String, CaseIterable { case .M5StackType, .M5StickCType: return .M5Stack - case .DexcomG5Type, .BubbleType, .MiaoMiaoType, .BluconType, .GNSentryType, .BlueReaderType, .DropletType, .DexcomG4Type, .DexcomG6Type, .WatlaaType, .Libre2Type, .AtomType: + case .DexcomG5Type, .BubbleType, .MiaoMiaoType, .BluconType, .GNSentryType, .BlueReaderType, .DropletType, .DexcomG4Type, .DexcomG6Type, .WatlaaType, .Libre2Type, .AtomType, .DexcomG6FireflyType: return .CGM } @@ -196,7 +220,7 @@ enum BluetoothPeripheralType: String, CaseIterable { case .M5StackType, .M5StickCType, .WatlaaType, .BubbleType, .MiaoMiaoType, .GNSentryType, .BlueReaderType, .DropletType, .Libre2Type, .AtomType: return false - case .DexcomG5Type, .BluconType, .DexcomG4Type, .DexcomG6Type: + case .DexcomG5Type, .BluconType, .DexcomG4Type, .DexcomG6Type, .DexcomG6FireflyType: return true } @@ -209,7 +233,7 @@ enum BluetoothPeripheralType: String, CaseIterable { switch self { - case .DexcomG5Type, .DexcomG6Type: + case .DexcomG5Type, .DexcomG6Type, .DexcomG6FireflyType: // length for G5 and G6 is 6 if transmitterId.count != 6 { @@ -261,7 +285,7 @@ enum BluetoothPeripheralType: String, CaseIterable { switch self { - case .M5StackType, .M5StickCType, .WatlaaType, .DexcomG4Type, .DexcomG5Type, .DexcomG6Type, .BluconType, .BlueReaderType, .DropletType , .GNSentryType: + case .M5StackType, .M5StickCType, .WatlaaType, .DexcomG4Type, .DexcomG5Type, .DexcomG6Type, .BluconType, .BlueReaderType, .DropletType , .GNSentryType, .DexcomG6FireflyType: return false case .BubbleType, .MiaoMiaoType, .AtomType: @@ -280,7 +304,7 @@ enum BluetoothPeripheralType: String, CaseIterable { switch self { - case .M5StackType, .M5StickCType, .DexcomG4Type, .DexcomG5Type, .DexcomG6Type: + case .M5StackType, .M5StickCType, .DexcomG4Type, .DexcomG5Type, .DexcomG6Type, .DexcomG6FireflyType: return false case .BubbleType, .MiaoMiaoType, .WatlaaType, .BluconType, .BlueReaderType, .DropletType , .GNSentryType, .AtomType: diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G4/CGMG4xDripTransmitter.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/G4/CGMG4xDripTransmitter.swift index cec89b45..3d735012 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/G4/CGMG4xDripTransmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/G4/CGMG4xDripTransmitter.swift @@ -110,7 +110,7 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, CGMTransmitter { } - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: transmitterBatteryInfo, sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: transmitterBatteryInfo, sensorAge: nil) } @@ -149,7 +149,7 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, CGMTransmitter { cGMDexcomG4TransmitterDelegate?.received(batteryLevel: level, from: self) } - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: transmitterBatteryInfo, sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: transmitterBatteryInfo, sensorAge: nil) } } @@ -157,41 +157,10 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, CGMTransmitter { // MARK:- CGMTransmitter protocol functions - // this transmitter does not support Libre non fixed slopes - func setNonFixedSlopeEnabled(enabled: Bool) { - } - - /// this transmitter does not support oopWeb - func setWebOOPEnabled(enabled: Bool) { - } - - func setWebOOPSite(oopWebSite: String) {} - - func setWebOOPToken(oopWebToken: String) {} - func cgmTransmitterType() -> CGMTransmitterType { return .dexcomG4 } - func isNonFixedSlopeEnabled() -> Bool { - return false - } - - func isWebOOPEnabled() -> Bool { - return false - } - - func requestNewReading() { - // not supported for Dexcom G4 - } - - func maxSensorAgeInDays() -> Int? { - - // no max sensor age for Dexcom - return nil - - } - // MARK:- helper functions private func processxBridgeDataPacket(value:Data) -> (glucoseData:GlucoseData?, batteryLevel:Int?, transmitterID:String?) { diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5Transmitter.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5Transmitter.swift index 969cf27c..ce33a2f2 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5Transmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5Transmitter.swift @@ -3,7 +3,7 @@ import CoreBluetooth import os class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { - + // MARK: - public properties /// G5 or G6 transmitter firmware version - only used internally, if nil then it was never received @@ -62,7 +62,7 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { } } - // MARK: other + // MARK: - private properties /// the write and control Characteristic private var writeControlCharacteristic:CBCharacteristic? @@ -119,7 +119,26 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { /// to be used in firefly flow - backfillTxSent should be sent only once per connection setting private var backfillTxSent = false - // MARK: - functions + /// - if true then xDrip4iOS will not send anything to the transmitter, it will only listen + /// - sending should be done by other app (eg official Dexcom app) + /// - exception could be sending calibration request or start sensor request, because if user is calibrating or starting the sensor via xDrip4iOS then it would need to be send to the transmitter by xDrip4iOS + private let letDexcomAppDoTheTransmitWork = false + + /// - used to send calibration done by user via xDrip4iOS to Dexcom transmitter. For example, user may have given a calibration in the app, but it's not yet send to the transmitter. + /// - calibrationToSendToTransmitter.sentToTransmitter says if it's been sent to transmitter or not + private var calibrationToSendToTransmitter: Calibration? + + /// if the user starts the sensor via xDrip4iOS, then only after having receivec a confirmation from the transmitter, then sensorStartDate will be assigned to the actual sensor start date + private var sensorStartDate: Date? + + /// - used to send sensor start done by user via xDrip4iOS to Dexcom transmitter. For example, user may have started a sensor in the app, but it's not yet send to the transmitter. + /// - tuple consisitng of startDate and dexcomCalibrationParameters. If startDate is nil, then there's no start sensor waiting to be sent to the transmitter. + private var sensorStartToSendToTransmitter: (startDate: Date, dexcomCalibrationParameters: DexcomCalibrationParameters)? + + /// if true, then firefly flow will be used, even if the transmitter id < 8G + private let isFireflyNoMatterTransmitterId: Bool + + // MARK: - public functions /// - parameters: /// - address: if already connected before, then give here the address that was received during previous connect, if not give nil @@ -128,8 +147,11 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { /// - bluetoothTransmitterDelegate : a NluetoothTransmitterDelegate /// - cGMTransmitterDelegate : a CGMTransmitterDelegate /// - cGMG5TransmitterDelegate : a CGMG5TransmitterDelegate - /// - transmitterStartDate : transmitter start date, optinoal - init(address:String?, name: String?, transmitterID:String, bluetoothTransmitterDelegate: BluetoothTransmitterDelegate, cGMG5TransmitterDelegate: CGMG5TransmitterDelegate, cGMTransmitterDelegate:CGMTransmitterDelegate, transmitterStartDate: Date?, firmware: String?) { + /// - transmitterStartDate : transmitter start date, optional - actual transmitterStartDate is received from transmitter itself, and stored in coredata. The stored value iss given here as parameter in the initializer. Means at app start up, it's read from core data and added here as parameter + /// - sensorStartDate : should be sensorStartDate of active sensor. If a different sensor start date is received from the transmitter, then we know a new senosr was started + /// - calibrationToSendToTransmitter : used to send calibration done by user via xDrip4iOS to Dexcom transmitter. For example, user may have give a calibration in the app, but it's not yet send to the transmitter. This needs to be verified in CGMG5Transmitter, which is why it's given here as parameter - when initializing, assign last known calibration for the active sensor, even if it's already sent. + /// - isFireFly : if true then the transmitter will be treated as a firefly, no matter the transmitter id, no matter if it's a G5, G6 or real Firefly + init(address:String?, name: String?, transmitterID:String, bluetoothTransmitterDelegate: BluetoothTransmitterDelegate, cGMG5TransmitterDelegate: CGMG5TransmitterDelegate, cGMTransmitterDelegate:CGMTransmitterDelegate, transmitterStartDate: Date?, sensorStartDate: Date?, calibrationToSendToTransmitter: Calibration?, firmware: String?, isFireFly: Bool) { // assign addressname and name or expected devicename var newAddressAndName:BluetoothTransmitter.DeviceAddressAndName = BluetoothTransmitter.DeviceAddressAndName.notYetConnected(expectedName: "DEXCOM" + transmitterID[transmitterID.index(transmitterID.startIndex, offsetBy: 4).. Bool { + + if !letDexcomAppDoTheTransmitWork { + + return super.writeDataToPeripheral(data: data, characteristicToWriteTo: characteristicToWriteTo, type: type) + + } else { + + trace("in writeDataToPeripheral, letDexcomAppDoTheTransmitWork - data not written to characteristic", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + return true + + } + + } - /// to ask transmitter reset - func reset(requested:Bool) { - G5ResetRequested = requested - } - /// this transmitter does not support Libre non fixed slopes - func setNonFixedSlopeEnabled(enabled: Bool) { - } - - /// this transmitter does not support oopWeb - func setWebOOPEnabled(enabled: Bool) { - } + // MARK: - CGMTransmitter protocol functions func cgmTransmitterType() -> CGMTransmitterType { return .dexcomG5 } - - func isNonFixedSlopeEnabled() -> Bool { - return false - } - func isWebOOPEnabled() -> Bool { - return false - } - - func requestNewReading() { - // not supported for Dexcom G5 - } - - func maxSensorAgeInDays() -> Int? { + func startSensor(dexcomCalibrationParameters: DexcomCalibrationParameters, startDate: Date) { - // no max sensor age for Dexcom - return nil + // assign sensorStartToSendToTransmitter to nil, because a new sensor start command must be sent + sensorStartToSendToTransmitter = nil + + guard startDate != sensorStartDate else { + + trace("in startSensor, but startDate is equal to already known sensorStartDate, so it looks as if a startSensor is done for a sensor that is already started. No further processing", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + return + + } + + // assign sensorStartToSendToTransmitter + sensorStartToSendToTransmitter = (startDate, dexcomCalibrationParameters) } - - // MARK: helper functions + func calibrate(calibration: Calibration?) { + + // - if calibration is valid, then it assigns calibrationToSendToTransmitter to calibration, will be picked up when transmitter connects , and if it's within an hour + // - the function does not immediately send the calibration to the transmitter, this will happen only if connected, after successful authentication + + // set calibrationToSendToTransmitter to nil, because a new calibration needs to be sent + calibrationToSendToTransmitter = nil + + guard let calibration = calibration else { + + trace("in calibrate. assigned calibrationToSendToTransmitter to nil ", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + return + + } + + if calibrationIsValid(calibration: calibration) { + + calibrationToSendToTransmitter = calibration + + trace("in calibrate. New calibration stored. value = %{public}@, timestamp = %{public}@ ", log: log, category: ConstantsLog.categoryCGMG5, type: .info, calibration.bg.description, calibration.timeStamp.toString(timeStyle: .long, dateStyle: .none)) + + } + + } + + + // MARK: - private helper functions /// sends SensorTxMessage to transmitter private func getSensorData() { @@ -747,6 +845,44 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { } + /// sends calibrationTxMessage. Will use super.writeDataToPeripheral, meaning, even if letDexcomAppDoTheTransmitWork = true, then still it will send the data to the transmitter + private func sendCalibrationTxMessage(calibration: Calibration, transmitterStartDate: Date) { + + let calibrationTxMessage = DexcomCalibrationTxMessage(glucose: Int(calibration.bg), timeStamp: calibration.timeStamp, transmitterStartDate: transmitterStartDate) + + if let writeControlCharacteristic = writeControlCharacteristic { + + trace("sending calibrationTxMessage with timestamp %{public}@, value %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, calibration.timeStamp.toString(timeStyle: .long, dateStyle: .long), calibration.bg.description) + + _ = super.writeDataToPeripheral(data: calibrationTxMessage.data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse) + + } else { + + trace("in sendCalibrationTxMessage, writeControlCharacteristic is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + } + + } + + /// sends startDate and dexcomCalibrationParameters to transmitter. Will use super.writeDataToPeripheral, meaning, even if letDexcomAppDoTheTransmitWork = true, then still it will send the data to the transmitter + private func sendSessionStartTxMessage(sensorStartToSendToTransmitter: (startDate: Date, dexcomCalibrationParameters: DexcomCalibrationParameters), transmitterStartDate: Date) { + + let sessionStartTxMessage = DexcomSessionStartTxMessage(startDate: sensorStartToSendToTransmitter.startDate, transmitterStartDate: transmitterStartDate, dexcomCalibrationParameters: sensorStartToSendToTransmitter.dexcomCalibrationParameters) + + if let writeControlCharacteristic = writeControlCharacteristic { + + trace("sending sessionStartTxMessage with startDate %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, sensorStartToSendToTransmitter.startDate.toString(timeStyle: .long, dateStyle: .long)) + + _ = super.writeDataToPeripheral(data: sessionStartTxMessage.data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse) + + } else { + + trace("in sendSessionStartTxMessage, writeControlCharacteristic is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + } + + } + /// sends backfillTxMessage to transmitter private func sendBackfillTxMessage(startTime: Date, endTime: Date, transmitterStartDate: Date) { @@ -829,7 +965,7 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { cGMG5TransmitterDelegate?.received(transmitterBatteryInfo: TransmitterBatteryInfo.DexcomG5(voltageA: batteryStatusRxMessage.voltageA, voltageB: batteryStatusRxMessage.voltageB, resist: batteryStatusRxMessage.resist, runtime: batteryStatusRxMessage.runtime, temperature: batteryStatusRxMessage.temperature), cGMG5Transmitter: self) // cgmTransmitterDelegate , because rootviewcontroller also shows battery info in home screen - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.DexcomG5(voltageA: batteryStatusRxMessage.voltageA, voltageB: batteryStatusRxMessage.voltageB, resist: batteryStatusRxMessage.resist, runtime: batteryStatusRxMessage.runtime, temperature: batteryStatusRxMessage.temperature), sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.DexcomG5(voltageA: batteryStatusRxMessage.voltageA, voltageB: batteryStatusRxMessage.voltageB, resist: batteryStatusRxMessage.resist, runtime: batteryStatusRxMessage.runtime, temperature: batteryStatusRxMessage.temperature), sensorAge: nil) } else { @@ -859,7 +995,7 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { if let dexcomSessionStopRxMessage = DexcomSessionStopRxMessage(data: value) { - trace("in processSessionStopRxMessage, received dexcomSessionStopRxMessage, isOkay = %{public}@, received = %{public}@, sessionStartTime = %{public}@, sessionStopTime = %{public}@, status = %{public}@, transmitterTime = %{public}@,", log: log, category: ConstantsLog.categoryCGMG5, type: .info, dexcomSessionStopRxMessage.isOkay.description, dexcomSessionStopRxMessage.received.description, dexcomSessionStopRxMessage.sessionStartTime.description, dexcomSessionStopRxMessage.sessionStopTime.description, dexcomSessionStopRxMessage.status.description, dexcomSessionStopRxMessage.transmitterTime.description) + trace("in processSessionStopRxMessage, received dexcomSessionStopRxMessage, isOkay = %{public}@, received = %{public}@, sessionStartTime = %{public}@, sessionStopTime = %{public}@, status = %{public}@, transmitterTime = %{public}@,", log: log, category: ConstantsLog.categoryCGMG5, type: .info, dexcomSessionStopRxMessage.isOkay.description, dexcomSessionStopRxMessage.sessionStopResponse.description, dexcomSessionStopRxMessage.sessionStartTime.description, dexcomSessionStopRxMessage.sessionStopTime.description, dexcomSessionStopRxMessage.status.description, dexcomSessionStopRxMessage.transmitterTime.description) } else { trace("dexcomSessionStopRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) @@ -867,20 +1003,20 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { } - /// processSessionStartRxMessage + /// process sessionStartRxMessage private func processSessionStartRxMessage(value: Data) { if let dexcomSessionStartRxMessage = DexcomSessionStartRxMessage(data: value) { - trace("in processSessionStartRxMessage, received dexcomSessionStartRxMessage, info = %{public}@, requestedStartTime = %{public}@, sessionStartTime = %{public}@, status = %{public}@, transmitterTime = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, - dexcomSessionStartRxMessage.info.description, + trace("in processSessionStartRxMessage, received dexcomSessionStartRxMessage, sessionStartResponse = %{public}@, requestedStartTime = %{public}@, sessionStartTime = %{public}@, status = %{public}@, transmitterTime = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, + dexcomSessionStartRxMessage.sessionStartResponse.description, dexcomSessionStartRxMessage.requestedStartTime.description, dexcomSessionStartRxMessage.sessionStartTime.description, dexcomSessionStartRxMessage.status.description, dexcomSessionStartRxMessage.transmitterTime.description) } else { - trace("dexcomSessionStartRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + trace("in processSessionStartRxMessage, dexcomSessionStartRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) } } @@ -903,22 +1039,68 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { } } - - /// process glucoseRxMessage - private func processGlucoseDataRxMessage(value: Data) { + + /// - used by processGlucoseDataRxMessage and processGlucoseG6DataRxMessage + /// - verifies the algorithmStatus and if ok, creates lastGlucoseInSensorDataRxReading + /// - parameters; + /// - calculatedValue : the value in the reading + /// - algorithmStatus : algorithm status + /// - timeStamp : timestamp in the reading + private func processGlucoseG6DataRxMessageOrGlucoseDataRxMessage(calculatedValue: Double, algorithmStatus: DexcomAlgorithmState, timeStamp: Date) { - if let glucoseDataRxMessage = GlucoseDataRxMessage(data: value) { + switch algorithmStatus { - trace("in processGlucoseDataRxMessage, received glucoseDataRxMessage, value = %{public}@, algorithm state = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, glucoseDataRxMessage.calculatedValue.description, glucoseDataRxMessage.status.description) + case .okay, .needsCalibration: // create glucose data and assign to lastGlucoseInSensorDataRxReading - lastGlucoseInSensorDataRxReading = GlucoseData(timeStamp: Date(), glucoseLevelRaw: glucoseDataRxMessage.calculatedValue) + lastGlucoseInSensorDataRxReading = GlucoseData(timeStamp: timeStamp, glucoseLevelRaw: calculatedValue) + + break + + default: + + trace(" algorithm state is %{public}@, not creating last glucoseData", log: log, category: ConstantsLog.categoryCGMG5, type: .info, algorithmStatus.description) + + // for safety assign nil to lastGlucoseInSensorDataRxReading + lastGlucoseInSensorDataRxReading = nil + + } + + // don't send reading to delegate, will be done when transmitter disconnects, then we're sure we also received al necessary backfill data - // don't send reading to delegate, will be done when transmitter disconnects, then we're sure we also received al necessary backfill data + } + + + /// process glucoseRxMessage + private func processGlucoseDataRxMessage(value: Data) { + + if let glucoseDataRxMessage = GlucoseDataRxMessage(data: value) { + + trace("in processGlucoseDataRxMessage, received glucoseDataRxMessage, value = %{public}@, algorithm status = %{public}@, transmitter status = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, glucoseDataRxMessage.calculatedValue.description, glucoseDataRxMessage.algorithmStatus.description, glucoseDataRxMessage.transmitterStatus.description) + + processGlucoseG6DataRxMessageOrGlucoseDataRxMessage(calculatedValue: glucoseDataRxMessage.calculatedValue, algorithmStatus: glucoseDataRxMessage.algorithmStatus, timeStamp: Date()) } else { - trace("glucoseDataRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + trace("in processGlucoseDataRxMessage, glucoseDataRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + } + + } + + /// process glucoseG6RxMessage + private func processGlucoseG6DataRxMessage(value: Data) { + + if let transmitterStartDate = transmitterStartDate, let glucoseDataRxMessage = DexcomG6GlucoseDataRxMessage(data: value, transmitterStartDate: transmitterStartDate) { + + trace("in processGlucoseG6DataRxMessage, received glucoseDataRxMessage, value = %{public}@, timeStamp = %{public}@, algorithmState = %{public}@, transmitterStatus = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, glucoseDataRxMessage.calculatedValue.description, glucoseDataRxMessage.timeStamp.toString(timeStyle: .long, dateStyle: .none), glucoseDataRxMessage.algorithmStatus.description, + glucoseDataRxMessage.transmitterStatus.description) + + processGlucoseG6DataRxMessageOrGlucoseDataRxMessage(calculatedValue: glucoseDataRxMessage.calculatedValue, algorithmStatus: glucoseDataRxMessage.algorithmStatus, timeStamp: glucoseDataRxMessage.timeStamp) + + } else { + + trace("processGlucoseG6DataRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) } @@ -931,15 +1113,28 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { trace("in processTransmitterTimeRxMessage", log: log, category: ConstantsLog.categoryCGMG5, type: .info) - if let sensorStartDate = transmitterTimeRxMessage.sensorStartDate { + if let receivedSensorStartDate = transmitterTimeRxMessage.sensorStartDate { - trace(" sensorStartDate = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, sensorStartDate.toString(timeStyle: .long, dateStyle: .long)) + trace(" receivedSensorStartDate = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, receivedSensorStartDate.toString(timeStyle: .long, dateStyle: .long)) // send to delegate - cGMG5TransmitterDelegate?.received(sensorStartDate: sensorStartDate, cGMG5Transmitter: self) + cGMG5TransmitterDelegate?.received(sensorStartDate: receivedSensorStartDate, cGMG5Transmitter: self) + // set timeStampLastSensorStartTimeRead timeStampLastSensorStartTimeRead = Date() + // if current sensorStartDate is < from receivedSensorStartDate then it seems a new sensor + if sensorStartDate == nil || (sensorStartDate! < receivedSensorStartDate) { + + trace(" sensorStartDate == nil or sensorStartDate < receivedSensorStartDate, seems a new sensor is detected.", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + cgmTransmitterDelegate?.newSensorDetected(sensorStartDate: receivedSensorStartDate) + + } + + // assign sensorStartDate to receivedSensorStartDate + sensorStartDate = receivedSensorStartDate + } else { trace(" sensorStartDate is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .info) } @@ -1078,7 +1273,7 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { } // if firefly, then subscribe to backfillCharacteristic - if isFireFly(), let backfillCharacteristic = backfillCharacteristic { + if useFireflyFlow(), let backfillCharacteristic = backfillCharacteristic { trace(" calling setNotifyValue true for characteristic %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, CBUUID_Characteristic_UUID.CBUUID_Backfill.description) @@ -1097,9 +1292,9 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { private func fireflyMessageFlow() { // first of all check that the transmitter is really a firefly, if not stop processing - if !isFireFly() { return } + if !useFireflyFlow() { return } - // treat it as a firefly, ie using the calibration on the sensor + // check if firmware is known, if not ask it guard firmware != nil else { sendTransmitterVersionTxMessage() @@ -1109,29 +1304,47 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { } // check if battery status update needed, if not proceed with flow - // if yes, do no further processing (function batteryStatusRequested will also request battery Status if needed) + // if yes, request battery status (done in function batteryStatusRequested) if !batteryStatusRequested() { - // check if transmitterStartDate is known, and if we've recently requested the sensorStartTime + // check if transmitterStartDate is known, and if we've recently requested the sensorStartTime, if not then request transmitterStartDate, response should contain both transmitterStartDate and sensorStartDate if let transmitterStartDate = transmitterStartDate, Date() < Date(timeInterval: ConstantsDexcomG5.sensorStartTimeReadPeriod, since: timeStampLastSensorStartTimeRead) { + // if there's a sensor start command to send it + if let sensorStartToSendToTransmitter = sensorStartToSendToTransmitter { + + sendSessionStartTxMessage(sensorStartToSendToTransmitter: (startDate: sensorStartToSendToTransmitter.startDate, dexcomCalibrationParameters: sensorStartToSendToTransmitter.dexcomCalibrationParameters), transmitterStartDate: transmitterStartDate) + + } else + + // if there's a valid calibrationToSendToTransmitter + if let calibrationToSendToTransmitter = calibrationToSendToTransmitter, calibrationIsValid(calibration: calibrationToSendToTransmitter) { + + sendCalibrationTxMessage(calibration: calibrationToSendToTransmitter, transmitterStartDate: transmitterStartDate) + + } else + // if glucoseTx was not yet sent and minimumTimeBetweenTwoReadings smaller than now - timeStampOfLastG5Reading (for safety) + // then send glucoseTx message if !glucoseTxSent || Date() < Date(timeInterval: ConstantsDexcomG5.minimumTimeBetweenTwoReadings, since: timeStampOfLastG5Reading) { // ask latest glucose value sendGlucoseTxMessage() - trace(" did send glucoseTxMessage, setting glucoseTxSent to true", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + trace("in fireflyMessageFlow, did send glucoseTxMessage, setting glucoseTxSent to true", log: log, category: ConstantsLog.categoryCGMG5, type: .info) // next time don't ask again for glucoseTx, but ask for backfill if needed glucoseTxSent = true - // check if backfill needed, and request backfill if needed - // if not needed continue with flow - } else if Date().timeIntervalSince(timeStampOfLastG5Reading) > ConstantsDexcomG5.minPeriodOfLatestReadingsToStartBackFill && !backfillTxSent { + + } else + + // check if backfill needed, and request backfill if needed + // if not needed continue with flow + if Date().timeIntervalSince(timeStampOfLastG5Reading) > ConstantsDexcomG5.minPeriodOfLatestReadingsToStartBackFill && !backfillTxSent { // send backfillTxMessage - // start time = timeStampOfLastG5Reading, maximum maxBackfillPeriod + // start time = timeStampOfLastG5Reading - maximum maxBackfillPeriod // end time = now sendBackfillTxMessage(startTime: max(timeStampOfLastG5Reading, Date(timeIntervalSinceNow: -ConstantsDexcomG5.maxBackfillPeriod)), endTime: Date(), transmitterStartDate: transmitterStartDate) @@ -1166,7 +1379,7 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { /// - reset backFillStream, lastGlucoseInSensorDataRxReading, backfillTxSent, glucoseTxSent private func sendGlucoseDataToDelegate() { - guard isFireFly() else {return} + guard useFireflyFlow() else {return} // transmitterDate should be non nil guard let transmitterStartDate = transmitterStartDate else { @@ -1174,53 +1387,53 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { return } - // initialize glucoseDataArray, this array will contain the glucose values in the stream + trace("in sendGlucoseDataToDelegate", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + // initialize glucoseDataArray, in this array we will store the glucose values in the backfillStream and also othe lastGlucoseInSensorDataRxReading var glucoseDataArray = [GlucoseData]() - trace("in sendGlucoseDataToDelegate", log: log, category: ConstantsLog.categoryCGMG5, type: .info) - trace(" start processing backfillstream", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + // decode backfill stream + let backFills = backFillStream.decode() - // iterate through backfill's - for backFill in backFillStream.decode() { - - let backfillDate = transmitterStartDate + Double(backFill.dexTime) - - let diff = Date().timeIntervalSince1970 - backfillDate.timeIntervalSince1970 - - guard diff > 0, diff < TimeInterval.hours(6) else { continue } - - glucoseDataArray.insert(GlucoseData(timeStamp: backfillDate, glucoseLevelRaw: Double(backFill.glucose)), at: 0) - - // assign timeStampOfLastG5Reading to backfillDate if backfillDate is more recent - timeStampOfLastG5Reading = max(timeStampOfLastG5Reading, backfillDate) - - trace(" new backfill, value = %{public}@, date = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, backFill.glucose.description, backfillDate.toString(timeStyle: .long, dateStyle: .none)) + if backFills.count > 0 { + + trace(" start processing backfillstream", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + // iterate through backfill's + for backFill in backFills { + + let backfillDate = transmitterStartDate + Double(backFill.dexTime) + + let diff = Date().timeIntervalSince1970 - backfillDate.timeIntervalSince1970 + + // readings older dan maxBackfillPeriod are ignored + guard diff > 0, diff < TimeInterval.hours(ConstantsDexcomG5.maxBackfillPeriod) else { continue } + + glucoseDataArray.insert(GlucoseData(timeStamp: backfillDate, glucoseLevelRaw: Double(backFill.glucose)), at: 0) + + trace(" new backfill, value = %{public}@, date = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, backFill.glucose.description, backfillDate.toString(timeStyle: .long, dateStyle: .none)) + + } + } - // - add lastGlucoseInSensorDataRxReading, which should already have been received - // - this reading is already sent to the delegate (while receiving glucoseRx message), but it's sent again. The delegate needs to reprocess the whole range and fill up gaps in the graph, if needed + // - add lastGlucoseInSensorDataRxReading, which should already have been received in GlucoseDataRxMessage or DexcomG6GlucoseDataRxMessage if let lastGlucoseInSensorDataRxReading = lastGlucoseInSensorDataRxReading { + trace(" adding glucose data that was received in GlucoseDataRxMessage/DexcomG6GlucoseDataRxMessage, value = %{public}@, date = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, lastGlucoseInSensorDataRxReading.glucoseLevelRaw.description, lastGlucoseInSensorDataRxReading.timeStamp.toString(timeStyle: .long, dateStyle: .none)) + glucoseDataArray.insert(lastGlucoseInSensorDataRxReading, at: 0) - // assign timeStampOfLastG5Reading to max of current value and timestamp of lastGlucoseInSensorDataRxReading - timeStampOfLastG5Reading = max(timeStampOfLastG5Reading, lastGlucoseInSensorDataRxReading.timeStamp) - - } - - // if glucoseDataArray contains data (which should be the case) then assign timeStampOfLastG5Reading to the most recent reading - if let lastGluoseReading = glucoseDataArray.first { - - timeStampOfLastG5Reading = lastGluoseReading.timeStamp - } if glucoseDataArray.count > 0 { + // assign timeStampOfLastG5Reading to the the timestamp of the most recent reading, which should be the first reading + timeStampOfLastG5Reading = glucoseDataArray.first!.timeStamp + trace(" calling cgmTransmitterInfoReceived with %{public}@ values", log: log, category: ConstantsLog.categoryCGMG5, type: .info, glucoseDataArray.count.description) - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorAge: nil) } else { @@ -1238,7 +1451,49 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { backfillTxSent = false // reset glucoseTxSent to false - self.glucoseTxSent = false + glucoseTxSent = false + + } + + /// - check if stored calibration is valid or not, valid in the sense of "is it ok to send it to the transmitter" + /// + /// - returns: false if calibration.sentToTransmitter is true, false if calibrationToSendToTransmitter has a timestamp in the future, false if calibrationToSendToTransmitter has an invalid value ( < 40 or > 400). true in all other cases + private func calibrationIsValid(calibration: Calibration) -> Bool { + + trace("in calibrationIsValid", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + // if already sent to transmitter then invalid + if calibration.sentToTransmitter { + trace(" calibration.sentToTransmitter is true, means this calibration is already sent to the transmitter", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + return false + } + + // if calibrationToSendToTransmitter timestamp in future, then invalid + if Date().timeIntervalSince1970 - calibration.timeStamp.timeIntervalSince1970 < 0 { + trace(" calibration has timestamp in the future, not valid", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + return false + } + + // if calibrationToSendToTransmitter timestamp older than 1 hour then invalid + if Date().timeIntervalSince1970 - calibration.timeStamp.timeIntervalSince1970 > ConstantsDexcomG5.maxUnSentCalibrationAge { + trace(" calibration has timestamp older than %{public}@ hours, not valid", log: log, category: ConstantsLog.categoryCGMG5, type: .info, ConstantsDexcomG5.maxUnSentCalibrationAge.hours.description) + return false + } + + // value out of range + if calibration.bg < 40 || calibration.bg > 400 { + trace(" calibration invalid value, lower than 40 or higher than 400", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + return false + } + + return true + + } + + /// verifies if it's a firefly based on transmitterId, if >= 8G then considered to be a Firefly + private func useFireflyFlow() -> Bool { + + return transmitterId.uppercased().compare("8G") == .orderedDescending || isFireflyNoMatterTransmitterId } diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5TransmitterDelegate.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5TransmitterDelegate.swift index a3fd4a3a..d6f9f443 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5TransmitterDelegate.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5TransmitterDelegate.swift @@ -14,7 +14,7 @@ protocol CGMG5TransmitterDelegate: AnyObject { /// transmitter reset result func reset(for cGMG5Transmitter: CGMG5Transmitter, successful: Bool) - /// received sensor start time from transmitter + /// sensor start time as received from transmitter func received(sensorStartDate: Date, cGMG5Transmitter: CGMG5Transmitter) } diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G6/CGMG6Transmitter.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/G6/CGMG6Transmitter.swift index f03c4a03..3731b2d5 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/G6/CGMG6Transmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/G6/CGMG6Transmitter.swift @@ -4,13 +4,19 @@ class CGMG6Transmitter: CGMG5Transmitter { /// - parameters: /// - address: if already connected before, then give here the address that was received during previous connect, if not give nil + /// - name : if already connected before, then give here the name that was received during previous connect, if not give nil /// - transmitterID: expected transmitterID, 6 characters - /// - bluetoothTransmitterDelegate : a BluetoothTransmitterDelegate + /// - bluetoothTransmitterDelegate : a NluetoothTransmitterDelegate /// - cGMTransmitterDelegate : a CGMTransmitterDelegate - init(address:String?, name: String?, transmitterID:String, bluetoothTransmitterDelegate: BluetoothTransmitterDelegate, cGMG6TransmitterDelegate: CGMG6TransmitterDelegate, cGMTransmitterDelegate:CGMTransmitterDelegate, transmitterStartDate: Date?, firmware: String?) { + /// - cGMG5TransmitterDelegate : a CGMG5TransmitterDelegate + /// - transmitterStartDate : transmitter start date, optional - actual transmitterStartDate is received from transmitter itself, and stored in coredata. The stored value iss given here as parameter in the initializer. Means at app start up, it's read from core data and added here as parameter + /// - sensorStartDate : if the user starts the sensor via xDrip4iOS, then only after having receivec a confirmation from the transmitter, then sensorStartDate will be assigned to the actual sensor start date + /// - calibrationToSendToTransmitter : used to send calibration done by user via xDrip4iOS to Dexcom transmitter. For example, user may have give a calibration in the app, but it's not yet send to the transmitter. This needs to be verified in CGMG5Transmitter, which is why it's given here as parameter - when initializing, assign last known calibration for the active sensor, even if it's already sent. + /// - isFireFly : if true then the transmitter will be treated as a firefly, no matter the transmitter id, no matter if it's a G5, G6 or real Firefly + init(address:String?, name: String?, transmitterID:String, bluetoothTransmitterDelegate: BluetoothTransmitterDelegate, cGMG6TransmitterDelegate: CGMG6TransmitterDelegate, cGMTransmitterDelegate:CGMTransmitterDelegate, transmitterStartDate: Date?, sensorStartDate: Date?, calibrationToSendToTransmitter: Calibration?, firmware: String?, isFireFly: Bool) { // call super.init - super.init(address: address, name: name, transmitterID: transmitterID, bluetoothTransmitterDelegate: bluetoothTransmitterDelegate, cGMG5TransmitterDelegate: cGMG6TransmitterDelegate, cGMTransmitterDelegate: cGMTransmitterDelegate, transmitterStartDate: transmitterStartDate, firmware: firmware) + super.init(address: address, name: name, transmitterID: transmitterID, bluetoothTransmitterDelegate: bluetoothTransmitterDelegate, cGMG5TransmitterDelegate: cGMG6TransmitterDelegate, cGMTransmitterDelegate: cGMTransmitterDelegate, transmitterStartDate: transmitterStartDate, sensorStartDate: sensorStartDate, calibrationToSendToTransmitter: calibrationToSendToTransmitter, firmware: firmware, isFireFly: isFireFly) } diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomAlgorithmState.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomAlgorithmState.swift new file mode 100644 index 00000000..e90300c3 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomAlgorithmState.swift @@ -0,0 +1,69 @@ +// +// DexcomAlgorithmState.swift +// xdrip +// +// Created by Johan Degraeve on 12/11/2021. +// Copyright © 2021 Johan Degraeve. All rights reserved. +// + +import Foundation + +enum DexcomAlgorithmState: UInt8, CustomStringConvertible { + + case None = 0x00 + case SessionStopped = 0x01 + case SensorWarmup = 0x02 + case excessNoise = 0x03 + case FirstofTwoBGsNeeded = 0x04 + case SecondofTwoBGsNeeded = 0x05 + case okay = 0x06 + case needsCalibration = 0x07 + case CalibrationError1 = 0x08 + case CalibrationError2 = 0x09 + case CalibrationLinearityFitFailure = 0x0A + case SensorFailedDuetoCountsAberration = 0x0B + case SensorFailedDuetoResidualAberration = 0x0C + case OutOfCalibrationDueToOutlier = 0x0D + case OutlierCalibrationRequest = 0x0E + case SessionExpired = 0x0F + case SessionFailedDueToUnrecoverableError = 0x10 + case SessionFailedDueToTransmitterError = 0x11 + case TemporarySensorIssue = 0x12 + case SensorFailedDueToProgressiveSensorDecline = 0x13 + case SensorFailedDueToHighCountsAberration = 0x14 + case SensorFailedDueToLowCountsAberration = 0x15 + case SensorFailedDueToRestart = 0x16 + + public var description: String { + + switch self { + + case .None: return "None" + case .SessionStopped: return "SessionStopped" + case .SensorWarmup: return "SensorWarmup" + case .excessNoise: return "excessNoise" + case .FirstofTwoBGsNeeded: return "FirstofTwoBGsNeeded" + case .SecondofTwoBGsNeeded: return "SecondofTwoBGsNeeded" + case .okay: return "InCalibration" + case .needsCalibration: return "needsCalibration" + case .CalibrationError1: return "CalibrationError1" + case .CalibrationError2: return "CalibrationError2" + case .CalibrationLinearityFitFailure: return "CalibrationLinearityFitFailure" + case .SensorFailedDuetoCountsAberration: return "SensorFailedDuetoCountsAberration" + case .SensorFailedDuetoResidualAberration: return "SensorFailedDuetoResidualAberration" + case .OutOfCalibrationDueToOutlier: return "OutOfCalibrationDueToOutlier" + case .OutlierCalibrationRequest: return "OutlierCalibrationRequest" + case .SessionExpired: return "SessionExpired" + case .SessionFailedDueToUnrecoverableError: return "SessionFailedDueToUnrecoverableError" + case .SessionFailedDueToTransmitterError: return "SessionFailedDueToTransmitterError" + case .TemporarySensorIssue: return "TemporarySensorIssue" + case .SensorFailedDueToProgressiveSensorDecline: return "SensorFailedDueToProgressiveSensorDecline" + case .SensorFailedDueToHighCountsAberration: return "SensorFailedDueToHighCountsAberration" + case .SensorFailedDueToLowCountsAberration: return "SensorFailedDueToLowCountsAberration" + case .SensorFailedDueToRestart: return "SensorFailedDueToRestart" + + } + + } + +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomBackfillStream.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomBackfillStream.swift index 63aa9cad..70f4af10 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomBackfillStream.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomBackfillStream.swift @@ -74,7 +74,7 @@ final class DexcomBackfillStream { trend = data[idx] idx += 1 - if let state = DexcomCalibrationState(rawValue: type) { + if let state = DexcomAlgorithmState(rawValue: type) { switch state { case .okay, .needsCalibration: if dexTime != 0 { diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationParameters.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationParameters.swift new file mode 100644 index 00000000..00b7a183 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationParameters.swift @@ -0,0 +1,114 @@ +import Foundation + +struct DexcomCalibrationParameters { + + let parameter1: Int16 + + let parameter2: Int16 + + /// - sets parameter1 and parameter2 according to sensorCode + /// - if sensorCode == nil or "0000" then initialized with parameter1 and parameter2 equal to 0, meaning considered as a transmitter that doesn't need a sensorcode + init?(sensorCode: String?) { + + guard let sensorCode = sensorCode else { + + parameter1 = 0 + + parameter2 = 0 + + return + + } + + switch (sensorCode) { + + // special null code + case "0000": + parameter1 = 0 + parameter2 = 0 + break + + case "5915","9759": + parameter1 = 3100 + parameter2 = 3600 + break + + case "5917","9357": + parameter1 = 3000 + parameter2 = 3500 + break + + case "5931","9137": + parameter1 = 2900 + parameter2 = 3400 + break + + case "5937","7197": + parameter1 = 2800 + parameter2 = 3300 + break + + case "5951","9517": + parameter1 = 3100 + parameter2 = 3500 + break + + case "5955","9179": + parameter1 = 3000 + parameter2 = 3400 + break + + case "7171","7539": + parameter1 = 2700 + parameter2 = 3300 + break + + case "9117","7135": + parameter1 = 2700 + parameter2 = 3200 + break + + case "9159","5397": + parameter1 = 2600 + parameter2 = 3200 + break + + case "9311","5391": + parameter1 = 2600 + parameter2 = 3100 + break + + case "9371","5375": + parameter1 = 2500 + parameter2 = 3100 + break + + case "9515","5795": + parameter1 = 2500 + parameter2 = 3000 + break + + case "9551","5317": + parameter1 = 2400 + parameter2 = 3000 + break + + case "9577","5177": + parameter1 = 2400 + parameter2 = 2900 + break + + case "9713","5171": + parameter1 = 2300 + parameter2 = 2900 + break + + default: + return nil + + } + + } + +} + diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationState.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationState.swift deleted file mode 100644 index 059178f2..00000000 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationState.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// DexcomG6CalibrationState.swift -// xDrip -// -// Created by Ivan Skoryk on 02.11.2020. -// Copyright © 2020 Faifly. All rights reserved. -// -// update for xDrip4iOS : renamed to DexcomCalibrationState - -import Foundation - -enum DexcomCalibrationState: UInt8 { - case unknown = 0x00 - case stopped = 0x01 - case warmingUp = 0x02 - case excessNoise = 0x03 - case needsFirstCalibration = 0x04 - case needsSecondCalibration = 0x05 - case okay = 0x06 - case needsCalibration = 0x07 - case calibrationConfused = 0x08 - case calibrationConfused2 = 0x09 - case needsDifferentCalibration = 0x0a - case sensorFailed = 0x0b - case sensorFailed2 = 0x0c - case unusualCalibration = 0x0d - case insufficientCalibration = 0x0e - case ended = 0x0f - case sensorFailed3 = 0x10 - case transmitterProblem = 0x11 - case errors = 0x12 - case sensorFailed4 = 0x13 - case sensorFailed5 = 0x14 - case sensorFailed6 = 0x15 - case sensorFailedStart = 0x16 -} - -extension DexcomCalibrationState { - static let stoppedCollection: [DexcomCalibrationState] = [ - .stopped, .ended, .sensorFailed, .sensorFailed2, - .sensorFailed3, .sensorFailed4, .sensorFailed5, - .sensorFailed6, .sensorFailedStart - ] -} - -extension DexcomCalibrationState: CustomStringConvertible { - - public var description: String { - - switch self { - - case .unknown: - return "unknown" - case .stopped: - return "stopped" - case .warmingUp: - return "warmingUp" - case .excessNoise: - return "excessNoise" - case .needsFirstCalibration: - return "needsFirstCalibration" - case .needsSecondCalibration: - return "needsSecondCalibration" - case .okay: - return "okay" - case .needsCalibration: - return "needsCalibration" - case .calibrationConfused: - return "calibrationConfused" - case .calibrationConfused2: - return "calibrationConfused2" - case .needsDifferentCalibration: - return "needsDifferentCalibration" - case .sensorFailed: - return "sensorFailed" - case .sensorFailed2: - return "sensorFailed2" - case .unusualCalibration: - return "unusualCalibration" - case .insufficientCalibration: - return "insufficientCalibration" - case .ended: - return "ended" - case .sensorFailed3: - return "sensorFailed3" - case .transmitterProblem: - return "transmitterProblem" - case .errors: - return "errors" - case .sensorFailed4: - return "sensorFailed4" - case .sensorFailed5: - return "sensorFailed5" - case .sensorFailed6: - return "sensorFailed6" - case .sensorFailedStart: - return "sensorFailedStart" - - } - } -} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationTxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationTxMessage.swift new file mode 100644 index 00000000..a36e96f9 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationTxMessage.swift @@ -0,0 +1,36 @@ +// +// DexcomG6CalibrationTxMessage.swift +// xDrip +// +// Created by Dmitry on 29.12.2020. +// Copyright © 2020 Faifly. All rights reserved. +// + +import Foundation + +struct DexcomCalibrationTxMessage: TransmitterTxMessage { + + let data: Data + + init(glucose: Int, timeStamp: Date, transmitterStartDate: Date) { + + var array = [Int8]() + + let time = Int(timeStamp.timeIntervalSince1970 - transmitterStartDate.timeIntervalSince1970) + + array.append(Int8(DexcomTransmitterOpCode.calibrateGlucoseTx.rawValue)) + + withUnsafeBytes(of: glucose) { + array.append(contentsOf: Array($0.prefix(2 * MemoryLayout.size)).map { Int8(bitPattern: $0) }) + } + + withUnsafeBytes(of: time) { + array.append(contentsOf: Array($0.prefix(4 * MemoryLayout.size)).map { Int8(bitPattern: $0) }) + } + + let data = array.withUnsafeBufferPointer { Data(buffer: $0) } + + self.data = data.appendingCRC() + } + +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomG6GlucoseDataRxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomG6GlucoseDataRxMessage.swift new file mode 100644 index 00000000..99bde313 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomG6GlucoseDataRxMessage.swift @@ -0,0 +1,42 @@ +import Foundation + +struct DexcomG6GlucoseDataRxMessage { + + let transmitterStatus: UInt8 + + let sequenceNumber: UInt32 + + let timeStamp: Date + + let calculatedValue: Double + + let algorithmStatus: DexcomAlgorithmState + + init?(data: Data, transmitterStartDate: Date) { + + guard data.count >= 16 else { return nil } + + guard data.starts(with: .glucoseG6Rx) else { return nil } + + transmitterStatus = data[1] + + sequenceNumber = data[2..<6].toInt() + + timeStamp = transmitterStartDate + TimeInterval(data.subdata(in: 6..<10).to(Int32.self)) + + calculatedValue = Double(data[10]) + Double(data[11]) * 256.0 + Double(data[13]) * 256.0 * 256.0 + + if let receivedState = DexcomAlgorithmState(rawValue: data[14]) { + + algorithmStatus = receivedState + + } else { + + algorithmStatus = DexcomAlgorithmState.None + + + } + + } + +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartResponse.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartResponse.swift new file mode 100644 index 00000000..d3744000 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartResponse.swift @@ -0,0 +1,39 @@ +import Foundation + +enum DexcomSessionStartResponse: UInt8 { + + + case manualCalibrationSessionStarted = 0x01 + + case staleStartComand = 0x02 + + case error = 0x03 + + case transmitterEndOfLife = 0x04 + + case autoCalibrationSessionInProgress = 0x05 + + public var description: String { + + switch self { + + case .manualCalibrationSessionStarted: + return "manualCalibrationSessionStarted" + + case .staleStartComand: + return "staleStartComand" + + case .error: + return "error" + + case .transmitterEndOfLife: + return "transmitterEndOfLife" + + case .autoCalibrationSessionInProgress: + return "autoCalibrationSessionInProgress" + + } + + } + +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartRxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartRxMessage.swift index 674a0664..0c322fd4 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartRxMessage.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartRxMessage.swift @@ -12,21 +12,31 @@ import Foundation struct DexcomSessionStartRxMessage { let status: UInt8 - let info: UInt8 + + let sessionStartResponse: DexcomSessionStartResponse + let requestedStartTime: Double + let sessionStartTime: Double + let transmitterTime: Double init?(data: Data) { guard data.count >= 15 else { return nil } - guard data.starts(with: .sessionStartTx) else {return nil} + guard data.starts(with: .sessionStartRx) else {return nil} status = data[1] - info = data[2] + + guard let sessionStartResponseReceived = DexcomSessionStartResponse(rawValue: data[2]) else {return nil} + + sessionStartResponse = sessionStartResponseReceived + requestedStartTime = Double(Data(data[3..<7]).to(UInt32.self)) + sessionStartTime = Double(Data(data[7..<11]).to(UInt32.self)) + transmitterTime = Double(Data(data[11..<15]).to(UInt32.self)) } diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartTxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartTxMessage.swift new file mode 100644 index 00000000..d5f31f21 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartTxMessage.swift @@ -0,0 +1,47 @@ +// +// DexcomG6SessionStartTxMessage.swift +// xDrip +// +// Created by Dmitry on 08.01.2021. +// Copyright © 2021 Faifly. All rights reserved. +// + +import Foundation + +struct DexcomSessionStartTxMessage: TransmitterTxMessage { + + let data: Data + + init(startDate: Date, transmitterStartDate: Date, dexcomCalibrationParameters: DexcomCalibrationParameters) { + + var array = [Int8]() + + array.append(Int8(DexcomTransmitterOpCode.sessionStartTx.rawValue)) + + withUnsafeBytes(of: Int(startDate.timeIntervalSince1970 - transmitterStartDate.timeIntervalSince1970)) { + array.append(contentsOf: Array($0.prefix(4 * MemoryLayout.size)).map { Int8(bitPattern: $0) }) + } + + withUnsafeBytes(of: Int(startDate.timeIntervalSince1970)) { + array.append(contentsOf: Array($0.prefix(4 * MemoryLayout.size)).map { Int8(bitPattern: $0) }) + } + + if dexcomCalibrationParameters.parameter1 != 0 { + + withUnsafeBytes(of: dexcomCalibrationParameters.parameter1) { + array.append(contentsOf: Array($0.prefix(2 * MemoryLayout.size)).map { Int8(bitPattern: $0) }) + } + + withUnsafeBytes(of: dexcomCalibrationParameters.parameter2) { + array.append(contentsOf: Array($0.prefix(2 * MemoryLayout.size)).map { Int8(bitPattern: $0) }) + } + + } + + let data = array.withUnsafeBufferPointer { Data(buffer: $0) } + + self.data = data.appendingCRC() + + } + +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStopResponse.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStopResponse.swift new file mode 100644 index 00000000..b56de09d --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStopResponse.swift @@ -0,0 +1,30 @@ +import Foundation + +enum DexcomSessionStopResponse: UInt8 { + + // 0x01 = Stop Session Successful | 0x02 = No Session In Progress | 0x03 Stale Stop Command + + case stopSessionSuccessful = 0x01 + + case noSessionInProgress = 0x02 + + case staleStopCommand = 0x03 + + public var description: String { + + switch self { + + case .stopSessionSuccessful: + return "stopSessionSuccessful" + + case .noSessionInProgress: + return "noSessionInProgress" + + case .staleStopCommand: + return "staleStopCommand" + + } + + } + +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStopRxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStopRxMessage.swift index c6ef5326..4248af25 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStopRxMessage.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStopRxMessage.swift @@ -12,9 +12,13 @@ import Foundation struct DexcomSessionStopRxMessage { let status: UInt8 - let received: UInt8 + + let sessionStopResponse: DexcomSessionStopResponse + let sessionStopTime: Double + let sessionStartTime: Double + let transmitterTime: Double init?(data: Data) { @@ -24,9 +28,15 @@ struct DexcomSessionStopRxMessage { guard data.starts(with: .sessionStopRx) else {return nil} status = data[1] - received = data[2] + + guard let sessionStoptResponseReceived = DexcomSessionStopResponse(rawValue: data[2]) else {return nil} + + sessionStopResponse = sessionStoptResponseReceived + sessionStopTime = Double(Data(data[3..<7]).to(UInt32.self)) + sessionStartTime = Double(Data(data[7..<11]).to(UInt32.self)) + transmitterTime = Double(Data(data[11..<15]).to(UInt32.self)) } @@ -34,4 +44,5 @@ struct DexcomSessionStopRxMessage { var isOkay: Bool { return status == 0 } + } diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/GlucoseDataRxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/GlucoseDataRxMessage.swift index ee39456a..fe915a00 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/GlucoseDataRxMessage.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/GlucoseDataRxMessage.swift @@ -2,11 +2,11 @@ import Foundation struct GlucoseDataRxMessage { - let status: UInt8 + let transmitterStatus: UInt8 let calculatedValue: Double - let state: DexcomCalibrationState + let algorithmStatus: DexcomAlgorithmState init?(data: Data) { @@ -14,17 +14,17 @@ struct GlucoseDataRxMessage { guard data.starts(with: .glucoseRx) else { return nil } - status = data[1] + transmitterStatus = data[1] calculatedValue = Double(Data(data[10..<12]).to(UInt16.self) & 0xfff) - if let receivedState = DexcomCalibrationState(rawValue: data[12]) { + if let receivedState = DexcomAlgorithmState(rawValue: data[12]) { - state = receivedState + algorithmStatus = receivedState } else { - state = DexcomCalibrationState.unknown + algorithmStatus = DexcomAlgorithmState.None } diff --git a/xdrip/BluetoothTransmitter/CGM/Generic/CGMTransmitter.swift b/xdrip/BluetoothTransmitter/CGM/Generic/CGMTransmitter.swift index bc2e4d53..efcd8046 100644 --- a/xdrip/BluetoothTransmitter/CGM/Generic/CGMTransmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Generic/CGMTransmitter.swift @@ -10,7 +10,8 @@ protocol CGMTransmitter:AnyObject { /// --- for transmitters who support non fixed (all Libre transmitters) this should be implemented func setNonFixedSlopeEnabled(enabled:Bool) - /// is the CGMTransmitter nonFixed enabled or not + /// - is the CGMTransmitter nonFixed enabled or not + /// - default implementation returns false func isNonFixedSlopeEnabled() -> Bool /// to set webOOPEnabled - called when user changes the setting @@ -19,7 +20,8 @@ protocol CGMTransmitter:AnyObject { /// --- for transmitters who support webOOP (Bubble, MiaoMiao, ..) this should be implemented func setWebOOPEnabled(enabled:Bool) - /// is the CGMTransmitter web oop enabled or not + /// - is the CGMTransmitter web oop enabled or not + /// - default implementation returns false func isWebOOPEnabled() -> Bool /// get cgmTransmitterType @@ -28,9 +30,25 @@ protocol CGMTransmitter:AnyObject { /// only applicable for Libre transmitters. To request a new reading. func requestNewReading() - /// maximum sensor age in minutes, nil if no maximum + /// - maximum sensor age in days, nil if no maximum + /// - default implementation returns nil func maxSensorAgeInDays() -> Int? + /// - to send a start sensor command to the transmitter + /// - only useful for Dexcom - firefly type of transmitters, other transmitter types will have an empty implementation + /// - parameters: + /// - sensorCode : only to be filled in if code known, only applicable for Dexcom firefly + /// - startDate : sensor start timeStamp + func startSensor(sensorCode: String?, startDate: Date) + + /// - to send a stop sensor command to the transmitter + /// - only useful for Dexcom type of transmitters, other transmitter types will have an empty implementation + func stopSensor(stopDate: Date) + + /// - to send a calibration toe the transmitter + /// - only useful for Dexcom type of transmitters, other transmitter types will have an empty implementation + func calibrate(calibration: Calibration) + } /// cgm transmitter types @@ -42,9 +60,13 @@ enum CGMTransmitterType:String, CaseIterable { /// dexcom G5 case dexcomG5 = "Dexcom G5" - /// dexcom G6 + /// - dexcom G6 - for non Firefly - although it can also be used for firefly + /// - only difference with firefly, is that sensorCode will not be asked (which is needed for firefly), and user will be asked to set a sensor start time (in case of firefly it's always the actual time that is used as start time) case dexcomG6 = "Dexcom G6" + /// dexcom G6 firefly + case dexcomG6Firefly = "Dexcom G6 Firefly" + /// miaomiao case miaomiao = "MiaoMiao" @@ -77,7 +99,7 @@ enum CGMTransmitterType:String, CaseIterable { switch self { - case .dexcomG4, .dexcomG5, .dexcomG6 : + case .dexcomG4, .dexcomG5, .dexcomG6, .dexcomG6Firefly : return .Dexcom case .miaomiao, .Bubble, .GNSentry, .Droplet1, .blueReader, .watlaa, .Blucon, .Libre2, .Atom: @@ -91,7 +113,7 @@ enum CGMTransmitterType:String, CaseIterable { /// /// example MiaoMiao can detect new sensor, implementation should return true, Dexcom transmitter's can't /// - /// if true, then transmitterType must also be able to give the sensor age, ie sensorTimeInMinutes + /// if true, then transmitterType must also be able to give the sensor age, ie sensorAge func canDetectNewSensor() -> Bool { switch self { @@ -102,6 +124,10 @@ enum CGMTransmitterType:String, CaseIterable { case .dexcomG5, .dexcomG6: return false + case .dexcomG6Firefly: + // for firefly, we receive the sensorStart time from the transmitter, this will be used to determine if a new sensor is received + return true + case .miaomiao, .Bubble: return true @@ -139,6 +165,9 @@ enum CGMTransmitterType:String, CaseIterable { case .dexcomG4, .dexcomG5, .dexcomG6, .GNSentry, .Droplet1, .blueReader, .watlaa: return true + case .dexcomG6Firefly: + return true + case .miaomiao, .Bubble, .Blucon, .Libre2, .Atom: return true @@ -153,7 +182,7 @@ enum CGMTransmitterType:String, CaseIterable { case .dexcomG4: return ConstantsDefaultAlertLevels.defaultBatteryAlertLevelDexcomG4 - case .dexcomG5, .dexcomG6: + case .dexcomG5, .dexcomG6, .dexcomG6Firefly: return ConstantsDefaultAlertLevels.defaultBatteryAlertLevelDexcomG5 case .miaomiao: @@ -196,7 +225,7 @@ enum CGMTransmitterType:String, CaseIterable { case .dexcomG4: return "" - case .dexcomG5, .dexcomG6: + case .dexcomG5, .dexcomG6, .dexcomG6Firefly: return "voltA" case .miaomiao, .Bubble, .Droplet1: @@ -223,4 +252,66 @@ enum CGMTransmitterType:String, CaseIterable { } } + /// - if user starts, sensor, does it require a code? + /// - only true for Dexcom G6 firefly type of transmitters + func needsSensorStartCode() -> Bool { + + switch self { + + case .dexcomG6Firefly: + return true + + default: + return false + } + + } + + /// - if user starts, sensor, does it require to give the start time? + /// - only false for Dexcom G6 type of transmitters - all other true + func needsSensorStartTime() -> Bool { + + switch self { + + case .dexcomG6Firefly: + return false + + default: + return true + + } + + } + +} + +extension CGMTransmitter { + + // empty implementation for transmitter types that don't need this + func setNonFixedSlopeEnabled(enabled: Bool) {} + + // default implementation, false + func isNonFixedSlopeEnabled() -> Bool {return false} + + // empty implementation for transmitter types that don't need this + func setWebOOPEnabled(enabled:Bool) {} + + // default implementation, false + func isWebOOPEnabled() -> Bool {return false} + + // empty implementation for transmitter types that don't need this + func requestNewReading() {} + + // default implementation, nil + func maxSensorAgeInDays() -> Int? {return nil} + + // default implementation, does nothing + func startSensor(sensorCode: String?, startDate: Date) {} + + // default implementation, does nothing + func stopSensor(stopDate: Date) {} + + // default implementation, does nothing + func calibrate(calibration: Calibration) {} + } diff --git a/xdrip/BluetoothTransmitter/CGM/Generic/CGMTransmitterDelegate.swift b/xdrip/BluetoothTransmitter/CGM/Generic/CGMTransmitterDelegate.swift index 269bf21f..91bae55e 100644 --- a/xdrip/BluetoothTransmitter/CGM/Generic/CGMTransmitterDelegate.swift +++ b/xdrip/BluetoothTransmitter/CGM/Generic/CGMTransmitterDelegate.swift @@ -5,7 +5,9 @@ import CoreBluetooth protocol CGMTransmitterDelegate:AnyObject { /// only for transmitters that can detect new sensor - func newSensorDetected() + /// - parameters: + /// - detected sensor start time, optional, default nil + func newSensorDetected(sensorStartDate: Date?) /// only for transmitters that can detect missing sensor func sensorNotDetected() @@ -14,8 +16,8 @@ protocol CGMTransmitterDelegate:AnyObject { /// - parameters: /// - glucoseData : array of RawGlucoseData, can be empty array, first entry is the youngest /// - transmitterBatteryInfo : needed for battery level alarm - /// - sensorTimeInMinutes : sensor age in minutes, only if transmitter can give that info, eg MiaoMiao, otherwise nil - func cgmTransmitterInfoReceived(glucoseData:inout [GlucoseData], transmitterBatteryInfo:TransmitterBatteryInfo?, sensorTimeInMinutes:Int?) + /// - sensorAge : only if transmitter can give that info, eg MiaoMiao, otherwise nil + func cgmTransmitterInfoReceived(glucoseData:inout [GlucoseData], transmitterBatteryInfo:TransmitterBatteryInfo?, sensorAge: TimeInterval?) /// to pass some text error message, delegate can decide to show to user, log, ... func errorOccurred(xDripError: XdripError) diff --git a/xdrip/BluetoothTransmitter/CGM/Libre/Atom/CGMAtomTransmitter.swift b/xdrip/BluetoothTransmitter/CGM/Libre/Atom/CGMAtomTransmitter.swift index fc93382f..719ab925 100644 --- a/xdrip/BluetoothTransmitter/CGM/Libre/Atom/CGMAtomTransmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Libre/Atom/CGMAtomTransmitter.swift @@ -262,7 +262,7 @@ class CGMAtomTransmitter:BluetoothTransmitter, CGMTransmitter { let transmitterBatteryPercentage = Int(value[4]) // send transmitterBatteryInfo to cgmTransmitterDelegate - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: transmitterBatteryPercentage), sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: transmitterBatteryPercentage), sensorAge: nil) // send transmitter battery percentage to cGMAtomTransmitterDelegate cGMAtomTransmitterDelegate?.received(batteryLevel: transmitterBatteryPercentage, from: self) @@ -354,8 +354,9 @@ class CGMAtomTransmitter:BluetoothTransmitter, CGMTransmitter { self.sensorSerialNumber = libreSensorSerialNumber.serialNumber // call delegate, to inform that a new sensor is detected - cgmTransmitterDelegate?.newSensorDetected() - + // assign sensorStartDate, for this type of transmitter the sensorAge is passed in another call to cgmTransmitterDelegate + cgmTransmitterDelegate?.newSensorDetected(sensorStartDate: nil) + } } else { diff --git a/xdrip/BluetoothTransmitter/CGM/Libre/Blucon/CGMBluconTransmitter.swift b/xdrip/BluetoothTransmitter/CGM/Libre/Blucon/CGMBluconTransmitter.swift index d2df8bca..1aa0dcf3 100644 --- a/xdrip/BluetoothTransmitter/CGM/Libre/Blucon/CGMBluconTransmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Libre/Blucon/CGMBluconTransmitter.swift @@ -312,7 +312,7 @@ class CGMBluconTransmitter: BluetoothTransmitter { sendCommandToBlucon(opcode: BluconTransmitterOpCode.getPatchInfoRequest) // by default set battery level to 100 - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: 100), sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: 100), sensorAge: nil) cGMBluconTransmitterDelegate?.received(batteryLevel: 100, from: self) @@ -345,8 +345,9 @@ class CGMBluconTransmitter: BluetoothTransmitter { sensorSerialNumber = newSerialNumber // inform cGMTransmitterDelegate about new sensor detected - cgmTransmitterDelegate?.newSensorDetected() - + // assign sensorStartDate, for this type of transmitter the sensorAge is passed in another call to cgmTransmitterDelegate + cgmTransmitterDelegate?.newSensorDetected(sensorStartDate: nil) + cGMBluconTransmitterDelegate?.received(serialNumber: sensorSerialNumber, from: self) @@ -372,7 +373,7 @@ class CGMBluconTransmitter: BluetoothTransmitter { } // inform cGMTransmitterDelegate about sensorSerialNumber and sensorState - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorAge: nil) return @@ -407,7 +408,7 @@ class CGMBluconTransmitter: BluetoothTransmitter { if valueAsString.startsWith(unknownCommand2BatteryLowIndicator) { // this is considered as battery level 5% - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: 5), sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: 5), sensorAge: nil) cGMBluconTransmitterDelegate?.received(batteryLevel: 5, from: self) @@ -493,7 +494,7 @@ class CGMBluconTransmitter: BluetoothTransmitter { let glucoseData = GlucoseData(timeStamp: Date(), glucoseLevelRaw: glucoseValue) var glucoseDataArray = [glucoseData] - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorAge: nil) sendCommandToBlucon(opcode: .sleep) @@ -502,14 +503,14 @@ class CGMBluconTransmitter: BluetoothTransmitter { case .bluconBatteryLowIndication1: // this is considered as battery level 3% - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: 3), sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: 3), sensorAge: nil) cGMBluconTransmitterDelegate?.received(batteryLevel: 3, from: self) case .bluconBatteryLowIndication2: // this is considered as battery level 2% - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: 2), sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: 2), sensorAge: nil) cGMBluconTransmitterDelegate?.received(batteryLevel: 2, from: self) @@ -535,14 +536,6 @@ extension CGMBluconTransmitter: CGMTransmitter { nonFixedSlopeEnabled = enabled } - /// this transmitter does not support oopWeb - func setWebOOPEnabled(enabled: Bool) { - } - - func setWebOOPSite(oopWebSite: String) {} - - func setWebOOPToken(oopWebToken: String) {} - func cgmTransmitterType() -> CGMTransmitterType { return .Blucon } @@ -551,16 +544,5 @@ extension CGMBluconTransmitter: CGMTransmitter { return nonFixedSlopeEnabled } - func isWebOOPEnabled() -> Bool { - return false - } - - func maxSensorAgeInDays() -> Int? { - - // not supported for blucon - return nil - - } - } diff --git a/xdrip/BluetoothTransmitter/CGM/Libre/BlueReader/CGMBlueReaderTransmitter.swift b/xdrip/BluetoothTransmitter/CGM/Libre/BlueReader/CGMBlueReaderTransmitter.swift index 6f8bc7c0..b9e2452c 100644 --- a/xdrip/BluetoothTransmitter/CGM/Libre/BlueReader/CGMBlueReaderTransmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Libre/BlueReader/CGMBlueReaderTransmitter.swift @@ -114,7 +114,7 @@ class CGMBlueReaderTransmitter:BluetoothTransmitter, CGMTransmitter { // send to delegate var glucoseDataArray = [GlucoseData(timeStamp: Date(), glucoseLevelRaw: rawDataAsDouble)] - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: transMitterBatteryInfo, sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: transMitterBatteryInfo, sensorAge: nil) } else { trace(" value is nil, no further processing", log: log, category: ConstantsLog.categoryCGMBlueReader, type: .error) @@ -128,9 +128,6 @@ class CGMBlueReaderTransmitter:BluetoothTransmitter, CGMTransmitter { nonFixedSlopeEnabled = enabled } - /// this transmitter does not support oopWeb - func setWebOOPEnabled(enabled: Bool) {} - func cgmTransmitterType() -> CGMTransmitterType { return .blueReader } @@ -139,19 +136,4 @@ class CGMBlueReaderTransmitter:BluetoothTransmitter, CGMTransmitter { return nonFixedSlopeEnabled } - func isWebOOPEnabled() -> Bool { - return false - } - - func requestNewReading() { - // not supported for blucon - } - - func maxSensorAgeInDays() -> Int? { - - // not supported for bluereader - return nil - - } - } diff --git a/xdrip/BluetoothTransmitter/CGM/Libre/Bubble/CGMBubbleTransmitter.swift b/xdrip/BluetoothTransmitter/CGM/Libre/Bubble/CGMBubbleTransmitter.swift index c4b3a9d9..8820923a 100644 --- a/xdrip/BluetoothTransmitter/CGM/Libre/Bubble/CGMBubbleTransmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Libre/Bubble/CGMBubbleTransmitter.swift @@ -158,7 +158,7 @@ class CGMBubbleTransmitter:BluetoothTransmitter, CGMTransmitter { cGMBubbleTransmitterDelegate?.received(batteryLevel: batteryPercentage, from: self) // send batteryPercentage to delegate - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorAge: nil) // store received firmware local self.firmware = firmware @@ -248,8 +248,9 @@ class CGMBubbleTransmitter:BluetoothTransmitter, CGMTransmitter { trace(" new sensor detected : %{public}@", log: log, category: ConstantsLog.categoryCGMBubble, type: .info, libreSensorSerialNumber.serialNumber) // inform cgmTransmitterDelegate about new sensor detected - cgmTransmitterDelegate?.newSensorDetected() - + // assign sensorStartDate, for this type of transmitter the sensorAge is passed in another call to cgmTransmitterDelegate + cgmTransmitterDelegate?.newSensorDetected(sensorStartDate: nil) + // inform cGMBubbleTransmitterDelegate about new sensor detected cGMBubbleTransmitterDelegate?.received(serialNumber: libreSensorSerialNumber.serialNumber, from: self) diff --git a/xdrip/BluetoothTransmitter/CGM/Libre/Droplet/CGMDroplet1Transmitter.swift b/xdrip/BluetoothTransmitter/CGM/Libre/Droplet/CGMDroplet1Transmitter.swift index 74fa6adc..cf26ecf6 100644 --- a/xdrip/BluetoothTransmitter/CGM/Libre/Droplet/CGMDroplet1Transmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Libre/Droplet/CGMDroplet1Transmitter.swift @@ -108,14 +108,14 @@ class CGMDroplet1Transmitter:BluetoothTransmitter, CGMTransmitter { } // fourth field is sensor time in minutes, stop if convert to Int fails - guard let sensorTimeInMinutes = Int(String(valueAsString[valueAsString.index(after: indexesOfSplitter[2]).. CGMTransmitterType { return .Droplet1 } - func isWebOOPEnabled() -> Bool { - return false - } - func isNonFixedSlopeEnabled() -> Bool { return nonFixedSlopeEnabled } - func requestNewReading() { - // not supported for droplet - } - - func maxSensorAgeInDays() -> Int? { - - // no supported for droplet - return nil - - } - } diff --git a/xdrip/BluetoothTransmitter/CGM/Libre/GNSEntry/CGMGNSEntryTransmitter.swift b/xdrip/BluetoothTransmitter/CGM/Libre/GNSEntry/CGMGNSEntryTransmitter.swift index 0fa13f6a..cd5b0012 100644 --- a/xdrip/BluetoothTransmitter/CGM/Libre/GNSEntry/CGMGNSEntryTransmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Libre/GNSEntry/CGMGNSEntryTransmitter.swift @@ -229,7 +229,7 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, CGMTransmitter { i = i + 1 } - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &readings, transmitterBatteryInfo: nil, sensorTimeInMinutes: Int(sensorElapsedTimeInMinutes)) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &readings, transmitterBatteryInfo: nil, sensorAge: TimeInterval(minutes: Double(sensorElapsedTimeInMinutes))) } } @@ -239,9 +239,6 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, CGMTransmitter { // MARK: CGMTransmitter protocol functions - func setWebOOPEnabled(enabled: Bool) { - } - func setNonFixedSlopeEnabled(enabled: Bool) { nonFixedSlopeEnabled = enabled } @@ -250,25 +247,10 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, CGMTransmitter { return .GNSentry } - func isWebOOPEnabled() -> Bool { - return false - } - func isNonFixedSlopeEnabled() -> Bool { return nonFixedSlopeEnabled } - func requestNewReading() { - // not supported for GNSEntry - } - - func maxSensorAgeInDays() -> Int? { - - // not supported for gnsentry - return nil - - } - // MARK: CBCentralManager overriden functions override func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { diff --git a/xdrip/BluetoothTransmitter/CGM/Libre/Libre2/CGMLibre2Transmitter.swift b/xdrip/BluetoothTransmitter/CGM/Libre/Libre2/CGMLibre2Transmitter.swift index 812c78c9..41a5eaf6 100644 --- a/xdrip/BluetoothTransmitter/CGM/Libre/Libre2/CGMLibre2Transmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Libre/Libre2/CGMLibre2Transmitter.swift @@ -293,10 +293,10 @@ class CGMLibre2Transmitter:BluetoothTransmitter, CGMTransmitter { // if oop web not enabled, then don't pass libre1DerivedAlgorithmParameters var parsedBLEData = Libre2BLEUtilities.parseBLEData(Data(try Libre2BLEUtilities.decryptBLE(sensorUID: sensorUID, data: rxBuffer)), libre1DerivedAlgorithmParameters: isWebOOPEnabled() ? UserDefaults.standard.libre1DerivedAlgorithmParameters : nil) - // send glucoseData and sensorTimeInMinutes to cgmTransmitterDelegate - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &parsedBLEData.bleGlucose, transmitterBatteryInfo: nil, sensorTimeInMinutes: Int(parsedBLEData.sensorTimeInMinutes)) + // send glucoseData and sensorAge to cgmTransmitterDelegate + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &parsedBLEData.bleGlucose, transmitterBatteryInfo: nil, sensorAge: TimeInterval(minutes: Double(parsedBLEData.sensorTimeInMinutes))) - // send sensorTimeInMinutes also to cGMLibre2TransmitterDelegate + // send sensorAge also to cGMLibre2TransmitterDelegate cGMLibre2TransmitterDelegate?.received(sensorTimeInMinutes: Int(parsedBLEData.sensorTimeInMinutes), from: self) } catch { @@ -345,10 +345,6 @@ class CGMLibre2Transmitter:BluetoothTransmitter, CGMTransmitter { return nonFixedSlopeEnabled } - func requestNewReading() { - // not supported for Libre 2 - } - func maxSensorAgeInDays() -> Int? { return libreSensorType?.maxSensorAgeInDays() @@ -416,8 +412,9 @@ extension CGMLibre2Transmitter: LibreNFCDelegate { self.sensorSerialNumber = receivedSensorSerialNumberAsString - cgmTransmitterDelegate?.newSensorDetected() - + // assign sensorStartDate, for this type of transmitter the sensorAge is passed in another call to cgmTransmitterDelegate + cgmTransmitterDelegate?.newSensorDetected(sensorStartDate: nil) + cGMLibre2TransmitterDelegate?.received(serialNumber: receivedSensorSerialNumberAsString, from: self) } diff --git a/xdrip/BluetoothTransmitter/CGM/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift b/xdrip/BluetoothTransmitter/CGM/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift index b2fadc8e..32814f8a 100644 --- a/xdrip/BluetoothTransmitter/CGM/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift @@ -212,7 +212,7 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, CGMTransmitter { cGMMiaoMiaoTransmitterDelegate?.received(batteryLevel: batteryPercentage, from: self) // send batteryPercentage to delegate - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorAge: nil) // get sensor serialNumber and if changed inform delegate if let libreSensorSerialNumber = LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 5..<13)), with: LibreSensorType.type(patchInfo: patchInfo)) { @@ -225,8 +225,9 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, CGMTransmitter { trace(" new sensor detected : %{public}@", log: log, category: ConstantsLog.categoryCGMMiaoMiao, type: .info, libreSensorSerialNumber.serialNumber) // inform delegate about new sensor detected - cgmTransmitterDelegate?.newSensorDetected() - + // assign sensorStartDate, for this type of transmitter the sensorAge is passed in another call to cgmTransmitterDelegate + cgmTransmitterDelegate?.newSensorDetected(sensorStartDate: nil) + cGMMiaoMiaoTransmitterDelegate?.received(serialNumber: libreSensorSerialNumber.serialNumber, from: self) } @@ -251,8 +252,9 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, CGMTransmitter { case .newSensor: trace("in peripheral didUpdateValueFor, new sensor detected", log: log, category: ConstantsLog.categoryCGMMiaoMiao, type: .info) - cgmTransmitterDelegate?.newSensorDetected() - + // assign sensorStartDate, for this type of transmitter the sensorAge is passed in another call to cgmTransmitterDelegate + cgmTransmitterDelegate?.newSensorDetected(sensorStartDate: nil) + // send 0xD3 and 0x01 to confirm sensor change as defined in MiaoMiao protocol documentation // after that send start reading command, each with delay of 500 milliseconds DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .milliseconds(500)) { diff --git a/xdrip/BluetoothTransmitter/CGM/Libre/Utilities/LibreDataParser.swift b/xdrip/BluetoothTransmitter/CGM/Libre/Utilities/LibreDataParser.swift index 344c140d..69c5d22b 100644 --- a/xdrip/BluetoothTransmitter/CGM/Libre/Utilities/LibreDataParser.swift +++ b/xdrip/BluetoothTransmitter/CGM/Libre/Utilities/LibreDataParser.swift @@ -344,18 +344,24 @@ class LibreDataParser { cgmTransmitterDelegate?.errorOccurred(xDripError: xDripError) } - + + // variable to be used in last call to cgmTransmitterDelegate + var sensorTimeInResult: TimeInterval? + // if sensor time < 60, return an empty glucose data array // should probably not happen because we only get here if status = .ready or .expired ? if let sensorTimeInMinutes = result.sensorTimeInMinutes { + // assign sensorTimeInResult, will be used later + sensorTimeInResult = TimeInterval(minutes: Double(sensorTimeInMinutes)) + guard sensorTimeInMinutes >= 60 else { trace("in handleGlucoseData, sensorTimeInMinutes < 60 minutes, no further processing", log: log, category: ConstantsLog.categoryLibreDataParser, type: .info) var emptyArray = [GlucoseData]() - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorTimeInMinutes: result.sensorTimeInMinutes) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorAge: TimeInterval(minutes: Double(sensorTimeInMinutes))) // call completion handler to make sure the sensor state is handled, set state to .starting, because result.sensorState has value .ready here which is not correct completionHandler(.starting, result.xDripError) @@ -367,8 +373,9 @@ class LibreDataParser { } var result = result + // call delegate with result - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: nil, sensorTimeInMinutes: result.sensorTimeInMinutes) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: nil, sensorAge: sensorTimeInResult) completionHandler(result.sensorState, result.xDripError) diff --git a/xdrip/BluetoothTransmitter/watlaa/WatlaaBluetoothTransmitter.swift b/xdrip/BluetoothTransmitter/watlaa/WatlaaBluetoothTransmitter.swift index fb030134..8caf4932 100644 --- a/xdrip/BluetoothTransmitter/watlaa/WatlaaBluetoothTransmitter.swift +++ b/xdrip/BluetoothTransmitter/watlaa/WatlaaBluetoothTransmitter.swift @@ -303,7 +303,8 @@ final class WatlaaBluetoothTransmitter: BluetoothTransmitter { trace(" new sensor detected : %{public}@", log: log, category: ConstantsLog.categoryWatlaa, type: .info, libreSensorSerialNumber.serialNumber) // inform delegate about new sensor detected - cgmTransmitterDelegate?.newSensorDetected() + // assign sensorStartDate, for this type of transmitter the sensorAge is passed in another call to cgmTransmitterDelegate + cgmTransmitterDelegate?.newSensorDetected(sensorStartDate: nil) watlaaBluetoothTransmitterDelegate?.received(serialNumber: libreSensorSerialNumber.serialNumber, from: self) @@ -315,7 +316,7 @@ final class WatlaaBluetoothTransmitter: BluetoothTransmitter { watlaaBluetoothTransmitterDelegate?.received(transmitterBatteryLevel: batteryPercentage, watlaaBluetoothTransmitter: self) // send batteryPercentage to delegate - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorTimeInMinutes: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorAge: nil) libreDataParser.libreDataProcessor(libreSensorSerialNumber: LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 5..<13)), with: nil)?.serialNumber, patchInfo: nil, webOOPEnabled: webOOPEnabled, libreData: (rxBuffer.subdata(in: miaoMiaoHeaderLength..<(344 + miaoMiaoHeaderLength))), cgmTransmitterDelegate: cgmTransmitterDelegate, dataIsDecryptedToLibre1Format: false, testTimeStamp: nil, completionHandler: { (sensorState: LibreSensorState?, xDripError: XdripError?) in @@ -350,8 +351,10 @@ final class WatlaaBluetoothTransmitter: BluetoothTransmitter { // not sure if watlaa will ever send this, and if so if it will handle the response correctly // this is copied from MiaoMiao trace("in peripheral didUpdateValueFor, new sensor detected", log: log, category: ConstantsLog.categoryWatlaa, type: .info) - cgmTransmitterDelegate?.newSensorDetected() - + + // assign sensorStartDate, for this type of transmitter the sensorAge is passed in another call to cgmTransmitterDelegate + cgmTransmitterDelegate?.newSensorDetected(sensorStartDate: nil) + // send 0xD3 and 0x01 to confirm sensor change as defined in MiaoMiao protocol documentation // after that send start reading command, each with delay of 500 milliseconds DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .milliseconds(500)) { diff --git a/xdrip/BluetoothTransmitter/watlaa/WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift b/xdrip/BluetoothTransmitter/watlaa/WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift index cb89e665..6a8fef97 100644 --- a/xdrip/BluetoothTransmitter/watlaa/WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift +++ b/xdrip/BluetoothTransmitter/watlaa/WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift @@ -36,11 +36,4 @@ extension WatlaaBluetoothTransmitter: CGMTransmitter { _ = sendStartReadingCommand() } - func maxSensorAgeInDays() -> Int? { - - // no max sensor age for Watlaa - return nil - - } - } diff --git a/xdrip/Constants/ConstantsDexcomG5.swift b/xdrip/Constants/ConstantsDexcomG5.swift index 3fced00e..a820d75d 100644 --- a/xdrip/Constants/ConstantsDexcomG5.swift +++ b/xdrip/Constants/ConstantsDexcomG5.swift @@ -20,4 +20,7 @@ enum ConstantsDexcomG5 { /// if there's a new connect within this period, but latest reading was less than this interval ago, then no need to request new reading static let minimumTimeBetweenTwoReadings = TimeInterval(minutes: 2.0) + /// specifically for firefly. If calibration was created more than this period ago, but not yet sent to the transmitter, then it will not be sent anymore + static let maxUnSentCalibrationAge = TimeInterval(hours: 1) + } diff --git a/xdrip/Core Data/Extensions/Calibration+CoreDataProperties.swift b/xdrip/Core Data/Extensions/Calibration+CoreDataProperties.swift index c9f6185d..dd559115 100644 --- a/xdrip/Core Data/Extensions/Calibration+CoreDataProperties.swift +++ b/xdrip/Core Data/Extensions/Calibration+CoreDataProperties.swift @@ -24,6 +24,9 @@ extension Calibration { @NSManaged public var deviceName: String? @NSManaged public var bgreadings: NSSet @NSManaged public var sensor: Sensor + + // only used for firefly transmitters, for now + @NSManaged public var sentToTransmitter: Bool } diff --git a/xdrip/Core Data/accessors/CalibrationsAccessor.swift b/xdrip/Core Data/accessors/CalibrationsAccessor.swift index 41c7e0a6..ec3f96b8 100644 --- a/xdrip/Core Data/accessors/CalibrationsAccessor.swift +++ b/xdrip/Core Data/accessors/CalibrationsAccessor.swift @@ -33,11 +33,15 @@ class CalibrationsAccessor { /// get last calibration (ie youngest) for currently active sensor and with sensorconfidence and slopeconfidence != 0 /// - parameters: - /// - withActivesensor : should be currently active sensor + /// - withActivesensor : should be currently active sensor - if nil then returnvalue is also nil /// - returns: /// - the last calibration, can be nil - func lastCalibrationForActiveSensor(withActivesensor sensor:Sensor) -> Calibration? { + func lastCalibrationForActiveSensor(withActivesensor sensor:Sensor?) -> Calibration? { + + guard let sensor = sensor else {return nil} + return getFirstOrLastCalibration(withActivesensor: sensor, first: false) + } /// Returns last calibrations, possibly zero diff --git a/xdrip/Core Data/classes/Calibration+CoreDataClass.swift b/xdrip/Core Data/classes/Calibration+CoreDataClass.swift index e4d32180..c02b9803 100644 --- a/xdrip/Core Data/classes/Calibration+CoreDataClass.swift +++ b/xdrip/Core Data/classes/Calibration+CoreDataClass.swift @@ -43,6 +43,8 @@ public class Calibration: NSManagedObject { possibleBad = false id = UniqueId.createEventId() + sentToTransmitter = false + } var sensorAgeAtTimeOfEstimation:Double { @@ -75,6 +77,7 @@ public class Calibration: NSManagedObject { r += "\n" + indentation + "slope = " + slope.description r += "\n" + indentation + "slopeConfidence = " + slopeConfidence.description r += "\n" + indentation + "timestamp = " + timeStamp.description + "\n" + r += "\n" + indentation + "sentToTransmitter = " + sentToTransmitter.description + "\n" return r } } diff --git a/xdrip/Core Data/classes/DexcomG5+CoreDataProperties.swift b/xdrip/Core Data/classes/DexcomG5+CoreDataProperties.swift index c1613888..21628580 100644 --- a/xdrip/Core Data/classes/DexcomG5+CoreDataProperties.swift +++ b/xdrip/Core Data/classes/DexcomG5+CoreDataProperties.swift @@ -28,8 +28,12 @@ extension DexcomG5 { @NSManaged public var isDexcomG6: Bool + @NSManaged public var isFirefly: Bool + @NSManaged public var transmitterStartDate: Date? + /// - contains sensor start date, received from transmitter + /// - if the user starts the sensor via xDrip4iOS, then only after having receivec a confirmation from the transmitter, then sensorStartDate will be assigned to the actual sensor start date @NSManaged public var sensorStartDate: Date? } diff --git a/xdrip/Core Data/xdrip.xcdatamodeld/xdrip v16.xcdatamodel/contents b/xdrip/Core Data/xdrip.xcdatamodeld/xdrip v16.xcdatamodel/contents index 89d62c9e..7ce2fc1c 100644 --- a/xdrip/Core Data/xdrip.xcdatamodeld/xdrip v16.xcdatamodel/contents +++ b/xdrip/Core Data/xdrip.xcdatamodeld/xdrip v16.xcdatamodel/contents @@ -87,6 +87,7 @@ + @@ -103,6 +104,7 @@ + @@ -164,9 +166,9 @@ - + - + diff --git a/xdrip/Extensions/TimeInterval.swift b/xdrip/Extensions/TimeInterval.swift index 42c71ad8..01cb4c0c 100644 --- a/xdrip/Extensions/TimeInterval.swift +++ b/xdrip/Extensions/TimeInterval.swift @@ -37,4 +37,9 @@ extension TimeInterval { var hours: Double { return minutes / 60.0 } + + var days: Double { + return hours / 24.0 + } + } diff --git a/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager.swift b/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager.swift index b563c8f4..b6b5015f 100644 --- a/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager.swift +++ b/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager.swift @@ -61,6 +61,12 @@ class BluetoothPeripheralManager: NSObject { /// reference to BLEPeripheralAccessor private var bLEPeripheralAccessor: BLEPeripheralAccessor + /// reference to SensorsAccessor + private var sensorsAccessor: SensorsAccessor + + /// reference to CalibrationsAccessor + private var calibrationsAccessor: CalibrationsAccessor + /// to solve problem that sometemes UserDefaults key value changes is triggered twice for just one change private let keyValueObserverTimeKeeper:KeyValueObserverTimeKeeper = KeyValueObserverTimeKeeper() @@ -77,6 +83,8 @@ class BluetoothPeripheralManager: NSObject { // initialize properties self.coreDataManager = coreDataManager self.bgReadingsAccessor = BgReadingsAccessor(coreDataManager: coreDataManager) + self.sensorsAccessor = SensorsAccessor(coreDataManager: coreDataManager) + self.calibrationsAccessor = CalibrationsAccessor(coreDataManager: coreDataManager) self.cgmTransmitterDelegate = cgmTransmitterDelegate self.cgmTransmitterInfoChanged = cgmTransmitterInfoChanged self.bLEPeripheralAccessor = BLEPeripheralAccessor(coreDataManager: coreDataManager) @@ -149,7 +157,7 @@ class BluetoothPeripheralManager: NSObject { } - case .DexcomG5Type, .DexcomG6Type: + case .DexcomG5Type, .DexcomG6Type, .DexcomG6FireflyType: // both DexcomG5Type and DexcomG6Type are stored in blePeripheral as dexcomG5 if let dexcomG5orG6 = blePeripheral.dexcomG5 { @@ -163,13 +171,16 @@ class BluetoothPeripheralManager: NSObject { // create an instance of CGMG5Transmitter (or CGMG6Transmitter), CGMG5Transmitter (or CGMG6Transmitter) will automatically try to connect to the dexcom with the address that is stored in dexcom // add it to the array of bluetoothTransmitters - if !dexcomG5orG6.isDexcomG6 { + if !dexcomG5orG6.isDexcomG6 && !dexcomG5orG6.isFirefly { - bluetoothTransmitters.insert(CGMG5Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG5TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: dexcomG5orG6.transmitterStartDate, firmware: dexcomG5orG6.firmwareVersion), at: index) + bluetoothTransmitters.insert(CGMG5Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG5TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: dexcomG5orG6.transmitterStartDate, sensorStartDate: dexcomG5orG6.sensorStartDate, calibrationToSendToTransmitter: calibrationsAccessor.lastCalibrationForActiveSensor(withActivesensor: sensorsAccessor.fetchActiveSensor()), firmware: dexcomG5orG6.firmwareVersion, isFireFly: dexcomG5orG6.isFirefly), at: index) } else { - bluetoothTransmitters.insert(CGMG6Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG6TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: dexcomG5orG6.transmitterStartDate, firmware: dexcomG5orG6.firmwareVersion), at: index) + // CGMG6 transmitter is created for G6 or Firefly + // in the end for firefly we could as wel create it as a G5 + + bluetoothTransmitters.insert(CGMG6Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG6TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: dexcomG5orG6.transmitterStartDate, sensorStartDate: dexcomG5orG6.sensorStartDate, calibrationToSendToTransmitter: calibrationsAccessor.lastCalibrationForActiveSensor(withActivesensor: sensorsAccessor.fetchActiveSensor()), firmware: dexcomG5orG6.firmwareVersion, isFireFly: dexcomG5orG6.isFirefly), at: index) } @@ -182,7 +193,7 @@ class BluetoothPeripheralManager: NSObject { } else { - // bluetoothTransmitters array (which shoul dhave the same number of elements as bluetoothPeripherals) needs to have an empty row for the transmitter + // bluetoothTransmitters array (which should have the same number of elements as bluetoothPeripherals) needs to have an empty row for the transmitter bluetoothTransmitters.insert(nil, at: index) } @@ -513,7 +524,7 @@ class BluetoothPeripheralManager: NSObject { // no need to send reading to watlaa in master mode break - case .DexcomG5Type, .BubbleType, .MiaoMiaoType, .BluconType, .GNSentryType, .BlueReaderType, .DropletType, .DexcomG4Type, .DexcomG6Type, .Libre2Type, .AtomType: + case .DexcomG5Type, .BubbleType, .MiaoMiaoType, .BluconType, .GNSentryType, .BlueReaderType, .DropletType, .DexcomG4Type, .DexcomG6Type, .Libre2Type, .AtomType, .DexcomG6FireflyType: // cgm's don't receive reading, they send it break @@ -580,27 +591,29 @@ class BluetoothPeripheralManager: NSObject { } - case .DexcomG5Type, .DexcomG6Type: + case .DexcomG5Type, .DexcomG6Type, .DexcomG6FireflyType: if let dexcomG5orG6 = bluetoothPeripheral as? DexcomG5 { if let transmitterId = dexcomG5orG6.blePeripheral.transmitterId, let cgmTransmitterDelegate = cgmTransmitterDelegate { - if !dexcomG5orG6.isDexcomG6 { + if !dexcomG5orG6.isDexcomG6 && !dexcomG5orG6.isFirefly { - newTransmitter = CGMG5Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG5TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: dexcomG5orG6.transmitterStartDate, firmware: dexcomG5orG6.firmwareVersion) + newTransmitter = CGMG5Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG5TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: dexcomG5orG6.transmitterStartDate, sensorStartDate: dexcomG5orG6.sensorStartDate, calibrationToSendToTransmitter: calibrationsAccessor.lastCalibrationForActiveSensor(withActivesensor: sensorsAccessor.fetchActiveSensor()), firmware: dexcomG5orG6.firmwareVersion, isFireFly: dexcomG5orG6.isFirefly) } else { - newTransmitter = CGMG6Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG6TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: dexcomG5orG6.transmitterStartDate, firmware: dexcomG5orG6.firmwareVersion) - + // CGMG6 transmitter is created for G6 or Firefly + // in the end for firefly we could as wel create it as a G5 + + newTransmitter = CGMG6Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG6TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: dexcomG5orG6.transmitterStartDate, sensorStartDate: dexcomG5orG6.sensorStartDate, calibrationToSendToTransmitter: calibrationsAccessor.lastCalibrationForActiveSensor(withActivesensor: sensorsAccessor.fetchActiveSensor()), firmware: dexcomG5orG6.firmwareVersion, isFireFly: dexcomG5orG6.isFirefly) } } else { - trace("in getBluetoothTransmitter, case DexcomG5Type or DexcomG6Type but transmitterId is nil or cgmTransmitterDelegate is nil, looks like a coding error ", log: log, category: ConstantsLog.categoryBluetoothPeripheralManager, type: .error) + trace("in getBluetoothTransmitter, case DexcomG5Type or DexcomG6Type or DexcomG6FireflyType but transmitterId is nil or cgmTransmitterDelegate is nil, looks like a coding error", log: log, category: ConstantsLog.categoryBluetoothPeripheralManager, type: .error) } } @@ -780,7 +793,7 @@ class BluetoothPeripheralManager: NSObject { return .DexcomG5Type } - case .DexcomG6Type: + case .DexcomG6Type, .DexcomG6FireflyType: if bluetoothTransmitter is CGMG6Transmitter { return .DexcomG6Type } @@ -860,7 +873,7 @@ class BluetoothPeripheralManager: NSObject { fatalError("in createNewTransmitter, type DexcomG5Type, transmitterId is nil or cgmTransmitterDelegate is nil") } - return CGMG5Transmitter(address: nil, name: nil, transmitterID: transmitterId, bluetoothTransmitterDelegate: bluetoothTransmitterDelegate ?? self, cGMG5TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: nil, firmware: nil) + return CGMG5Transmitter(address: nil, name: nil, transmitterID: transmitterId, bluetoothTransmitterDelegate: bluetoothTransmitterDelegate ?? self, cGMG5TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: nil, sensorStartDate: nil, calibrationToSendToTransmitter: nil, firmware: nil, isFireFly: false) case .DexcomG6Type: @@ -868,7 +881,15 @@ class BluetoothPeripheralManager: NSObject { fatalError("in createNewTransmitter, type DexcomG6Type, transmitterId is nil or cgmTransmitterDelegate is nil") } - return CGMG6Transmitter(address: nil, name: nil, transmitterID: transmitterId, bluetoothTransmitterDelegate: bluetoothTransmitterDelegate ?? self, cGMG6TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: nil, firmware: nil) + return CGMG6Transmitter(address: nil, name: nil, transmitterID: transmitterId, bluetoothTransmitterDelegate: bluetoothTransmitterDelegate ?? self, cGMG6TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: nil, sensorStartDate: nil, calibrationToSendToTransmitter: nil, firmware: nil, isFireFly: false) + + case .DexcomG6FireflyType: + + guard let transmitterId = transmitterId, let cgmTransmitterDelegate = cgmTransmitterDelegate else { + fatalError("in createNewTransmitter, type DexcomG6Type, transmitterId is nil or cgmTransmitterDelegate is nil") + } + + return CGMG6Transmitter(address: nil, name: nil, transmitterID: transmitterId, bluetoothTransmitterDelegate: bluetoothTransmitterDelegate ?? self, cGMG6TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: nil, sensorStartDate: nil, calibrationToSendToTransmitter: nil, firmware: nil, isFireFly: true) case .BubbleType: @@ -1173,7 +1194,7 @@ class BluetoothPeripheralManager: NSObject { bluetoothPeripheral.blePeripheral.parameterUpdateNeededAtNextConnect = true } - case .WatlaaType, .DexcomG5Type, .BubbleType, .MiaoMiaoType, .BluconType, .GNSentryType, .BlueReaderType, .DropletType, .DexcomG4Type, .DexcomG6Type, .Libre2Type, .AtomType: + case .WatlaaType, .DexcomG5Type, .BubbleType, .MiaoMiaoType, .BluconType, .GNSentryType, .BlueReaderType, .DropletType, .DexcomG4Type, .DexcomG6Type, .Libre2Type, .AtomType, .DexcomG6FireflyType: // nothing to check break diff --git a/xdrip/Storyboards/en.lproj/HomeView.strings b/xdrip/Storyboards/en.lproj/HomeView.strings index fa2fb705..6347a7aa 100644 --- a/xdrip/Storyboards/en.lproj/HomeView.strings +++ b/xdrip/Storyboards/en.lproj/HomeView.strings @@ -37,3 +37,4 @@ "failed" = "Failed"; "calibrationNotNecessary" = "When using the Libre algoritm, it is not necessary to calibrate the sensor."; "dexcomBatteryTooLow" = "The Transmitter battery is too low!"; +"enterSensorCode" = "Enter Sensor Code"; diff --git a/xdrip/Texts/TextsHomeView.swift b/xdrip/Texts/TextsHomeView.swift index 8dfa5963..fae7940a 100644 --- a/xdrip/Texts/TextsHomeView.swift +++ b/xdrip/Texts/TextsHomeView.swift @@ -160,4 +160,8 @@ enum Texts_HomeView { return NSLocalizedString("dexcomBatteryTooLow", tableName: filename, bundle: Bundle.main, value: "The Transmitter battery is too low!", comment: "Error message in case Dexcom G5 (and G6?) battery is too low. This is deteced by wrong G5 values 2096896") }() + static let enterSensorCode: String = { + return NSLocalizedString("enterSensorCode", tableName: filename, bundle: Bundle.main, value: "Enter Sensor Code", comment: "When user needs to enter sensor code, to start firefly sensor") + }() + } diff --git a/xdrip/Utilities/Trace.swift b/xdrip/Utilities/Trace.swift index e617631e..4bd548ca 100644 --- a/xdrip/Utilities/Trace.swift +++ b/xdrip/Utilities/Trace.swift @@ -363,8 +363,8 @@ class Trace { } - case .DexcomG5Type: - if let dexcomG5 = blePeripheral.dexcomG5, !dexcomG5.isDexcomG6 { + case .DexcomG5Type, .DexcomG6Type, .DexcomG6FireflyType: + if let dexcomG5 = blePeripheral.dexcomG5 { traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue) @@ -374,17 +374,6 @@ class Trace { } - case .DexcomG6Type: - if let dexcomG6 = blePeripheral.dexcomG5, dexcomG6.isDexcomG6 { - - traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue) - - // if needed additional specific info can be added - traceInfo.appendStringAndNewLine(" voltageA : " + dexcomG6.voltageA.description) - traceInfo.appendStringAndNewLine(" voltageB : " + dexcomG6.voltageB.description) - - } - case .BluconType: if let blucon = blePeripheral.blucon { diff --git a/xdrip/View Controllers/BluetoothPeripheralsNavigationController/BluetoothPeripheralsViewController/BluetoothPeripheralViewController/Models/CGM/Dexcom/DexcomG5/DexcomG5BluetoothPeripheralViewModel.swift b/xdrip/View Controllers/BluetoothPeripheralsNavigationController/BluetoothPeripheralsViewController/BluetoothPeripheralViewController/Models/CGM/Dexcom/DexcomG5/DexcomG5BluetoothPeripheralViewModel.swift index a57d2f73..cbae15ff 100644 --- a/xdrip/View Controllers/BluetoothPeripheralsNavigationController/BluetoothPeripheralsViewController/BluetoothPeripheralViewController/Models/CGM/Dexcom/DexcomG5/DexcomG5BluetoothPeripheralViewModel.swift +++ b/xdrip/View Controllers/BluetoothPeripheralsNavigationController/BluetoothPeripheralsViewController/BluetoothPeripheralViewController/Models/CGM/Dexcom/DexcomG5/DexcomG5BluetoothPeripheralViewModel.swift @@ -19,7 +19,7 @@ class DexcomG5BluetoothPeripheralViewModel { case firmWareVersion = 2 } - + private enum ResetSettings:Int, CaseIterable { /// should reset be done yes or no diff --git a/xdrip/View Controllers/BluetoothPeripheralsNavigationController/BluetoothPeripheralsViewController/BluetoothPeripheralViewController/Models/CGM/Dexcom/DexcomG6Firefly/DexcomG6FireflyBluetoothPeripheralViewModel.swift b/xdrip/View Controllers/BluetoothPeripheralsNavigationController/BluetoothPeripheralsViewController/BluetoothPeripheralViewController/Models/CGM/Dexcom/DexcomG6Firefly/DexcomG6FireflyBluetoothPeripheralViewModel.swift new file mode 100644 index 00000000..5839b00d --- /dev/null +++ b/xdrip/View Controllers/BluetoothPeripheralsNavigationController/BluetoothPeripheralsViewController/BluetoothPeripheralViewController/Models/CGM/Dexcom/DexcomG6Firefly/DexcomG6FireflyBluetoothPeripheralViewModel.swift @@ -0,0 +1,13 @@ +import Foundation +import UIKit +import CoreBluetooth + +class DexcomG6FireflyBluetoothPeripheralViewModel: DexcomG5BluetoothPeripheralViewModel { + + // MARK: - overriden functions + + override public func dexcomScreenTitle() -> String { + return BluetoothPeripheralType.DexcomG6FireflyType.rawValue + } + +} diff --git a/xdrip/View Controllers/Root View Controller/RootViewController.swift b/xdrip/View Controllers/Root View Controller/RootViewController.swift index 1072066d..d1203670 100644 --- a/xdrip/View Controllers/Root View Controller/RootViewController.swift +++ b/xdrip/View Controllers/Root View Controller/RootViewController.swift @@ -788,7 +788,7 @@ final class RootViewController: UIViewController { trace("in cgmTransmitterInfoChanged, webOOPEnabled value changed to %{public}@, will stop the sensor", log: self.log, category: ConstantsLog.categoryRootView, type: .info, cgmTransmitter.isWebOOPEnabled().description) - self.stopSensor() + self.stopSensor(cGMTransmitter: cgmTransmitter) } @@ -797,7 +797,7 @@ final class RootViewController: UIViewController { trace("in cgmTransmitterInfoChanged, nonFixedSlopeEnabled value changed to %{public}@, will stop the sensor", log: self.log, category: ConstantsLog.categoryRootView, type: .info, cgmTransmitter.isNonFixedSlopeEnabled().description) - self.stopSensor() + self.stopSensor(cGMTransmitter: cgmTransmitter) } @@ -813,7 +813,7 @@ final class RootViewController: UIViewController { trace("in cgmTransmitterInfoChanged, sensorType value changed to %{public}@, will stop the sensor", log: self.log, category: ConstantsLog.categoryRootView, type: .info, cgmTransmitter.cgmTransmitterType().sensorType().rawValue) - self.stopSensor() + self.stopSensor(cGMTransmitter: cgmTransmitter) } @@ -862,8 +862,8 @@ final class RootViewController: UIViewController { /// process new glucose data received from transmitter. /// - parameters: /// - glucoseData : array with new readings - /// - sensorTimeInMinutes : should be present only if it's the first reading(s) being processed for a specific sensor and is needed if it's a transmitterType that returns true to the function canDetectNewSensor - private func processNewGlucoseData(glucoseData: inout [GlucoseData], sensorTimeInMinutes: Int?) { + /// - sensorAge : should be present only if it's the first reading(s) being processed for a specific sensor and is needed if it's a transmitterType that returns true to the function canDetectNewSensor + private func processNewGlucoseData(glucoseData: inout [GlucoseData], sensorAge: TimeInterval?) { // unwrap calibrationsAccessor and coreDataManager and cgmTransmitter guard let calibrationsAccessor = calibrationsAccessor, let coreDataManager = coreDataManager, let cgmTransmitter = bluetoothPeripheralManager?.getCGMTransmitter() else { @@ -876,17 +876,10 @@ final class RootViewController: UIViewController { if activeSensor == nil { - if let sensorTimeInMinutes = sensorTimeInMinutes, cgmTransmitter.cgmTransmitterType().canDetectNewSensor() { + if let sensorAge = sensorAge, cgmTransmitter.cgmTransmitterType().canDetectNewSensor() { + + self.startSensor(cGMTransmitter: cgmTransmitter, sensorStarDate: Date(timeIntervalSinceNow: -sensorAge), sensorCode: nil, coreDataManager: coreDataManager) - activeSensor = Sensor(startDate: Date(timeInterval: -Double(sensorTimeInMinutes * 60), since: Date()),nsManagedObjectContext: coreDataManager.mainManagedObjectContext) - if let activeSensor = activeSensor { - trace("created sensor with id : %{public}@ and startdate %{public}@", log: log, category: ConstantsLog.categoryRootView, type: .info, activeSensor.id, activeSensor.startDate.description) - } else { - trace("creation active sensor failed", log: log, category: ConstantsLog.categoryRootView, type: .info) - } - - // save the newly created Sensor permenantly in coredata - coreDataManager.saveChanges() } } @@ -1179,8 +1172,10 @@ final class RootViewController: UIViewController { changeButtonsStatusTo(enabled: UserDefaults.standard.isMaster) + guard let cgmTransmitter = self.bluetoothPeripheralManager?.getCGMTransmitter() else {break} + // no sensor needed in follower mode, stop it - stopSensor() + stopSensor(cGMTransmitter: cgmTransmitter) case UserDefaults.Key.showReadingInNotification: if !UserDefaults.standard.showReadingInNotification { @@ -1439,7 +1434,6 @@ final class RootViewController: UIViewController { // assign deviceName, needed in the closure when creating alert. As closures can create strong references (to bluetoothTransmitter in this case), I'm fetching the deviceName here let deviceName = bluetoothTransmitter.deviceName - // let alert = UIAlertController(title: "test title", message: "test message", keyboardType: .numberPad, text: nil, placeHolder: "...", actionTitle: nil, cancelTitle: nil, actionHandler: {_ in }, cancelHandler: nil) let alert = UIAlertController(title: Texts_Calibrations.enterCalibrationValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: nil, placeHolder: "...", actionTitle: nil, cancelTitle: nil, actionHandler: { (text:String) in @@ -1464,7 +1458,13 @@ final class RootViewController: UIViewController { trace("calibration : initial calibration, creating two calibrations", log: self.log, category: ConstantsLog.categoryRootView, type: .info) // calling initialCalibration will create two calibrations, they are returned also but we don't need them - _ = calibrator.initialCalibration(firstCalibrationBgValue: valueAsDoubleConvertedToMgDl, firstCalibrationTimeStamp: Date(timeInterval: -(5*60), since: Date()), secondCalibrationBgValue: valueAsDoubleConvertedToMgDl, sensor: activeSensor, lastBgReadingsWithCalculatedValue0AndForSensor: &latestReadings, deviceName: deviceName, nsManagedObjectContext: coreDataManager.mainManagedObjectContext) + let (_, calibration) = calibrator.initialCalibration(firstCalibrationBgValue: valueAsDoubleConvertedToMgDl, firstCalibrationTimeStamp: Date(timeInterval: -(5*60), since: Date()), secondCalibrationBgValue: valueAsDoubleConvertedToMgDl, sensor: activeSensor, lastBgReadingsWithCalculatedValue0AndForSensor: &latestReadings, deviceName: deviceName, nsManagedObjectContext: coreDataManager.mainManagedObjectContext) + + // send calibration to transmitter (only used for Dexcom, if firefly flow is used) + if let calibration = calibration { + cgmTransmitter.calibrate(calibration: calibration) + } + } else { @@ -1473,8 +1473,11 @@ final class RootViewController: UIViewController { trace("calibration : creating calibrations", log: self.log, category: ConstantsLog.categoryRootView, type: .info) - // calling createNewCalibration will create a new calibration, it is returned but we don't need it - _ = calibrator.createNewCalibration(bgValue: valueAsDoubleConvertedToMgDl, lastBgReading: latestReadings[0], sensor: activeSensor, lastCalibrationsForActiveSensorInLastXDays: &latestCalibrations, firstCalibration: firstCalibrationForActiveSensor, deviceName: deviceName, nsManagedObjectContext: coreDataManager.mainManagedObjectContext) + // create new calibration + let calibration = calibrator.createNewCalibration(bgValue: valueAsDoubleConvertedToMgDl, lastBgReading: latestReadings[0], sensor: activeSensor, lastCalibrationsForActiveSensorInLastXDays: &latestCalibrations, firstCalibration: firstCalibrationForActiveSensor, deviceName: deviceName, nsManagedObjectContext: coreDataManager.mainManagedObjectContext) + + // send calibration to transmitter (only used for Dexcom, if firefly flow is used) + cgmTransmitter.calibrate(calibration: calibration) } @@ -1524,20 +1527,13 @@ final class RootViewController: UIViewController { switch cgmTransmitterType { - case .dexcomG4, .dexcomG5 : + case .dexcomG4, .dexcomG5, .dexcomG6 : calibrator = DexcomCalibrator() - case .dexcomG6: + case .dexcomG6Firefly: - if (cgmTransmitter as! CGMG6Transmitter).isFireFly() { - - calibrator = NoCalibrator() - - } else { - - calibrator = DexcomCalibrator() - } + calibrator = NoCalibrator() case .miaomiao, .GNSentry, .Blucon, .Bubble, .Droplet1, .blueReader, .watlaa, .Libre2, .Atom: @@ -1846,6 +1842,10 @@ final class RootViewController: UIViewController { /// when user clicks transmitter button, this will create and present the actionsheet, contents depend on type of transmitter and sensor status private func createAndPresentSensorButtonActionSheet() { + // unwrap coredatamanager + guard let coreDataManager = coreDataManager else {return} + + // initialize list of actions var listOfActions = [UIAlertAction]() @@ -1858,18 +1858,35 @@ final class RootViewController: UIViewController { // next action is to start or stop the sensor, can also be omitted depending on type of device - also not applicable for follower mode if let cgmTransmitter = self.bluetoothPeripheralManager?.getCGMTransmitter() { if cgmTransmitter.cgmTransmitterType().allowManualSensorStart() && UserDefaults.standard.isMaster { - // user needs to start and stop the sensor manually + + // user can (or needs to) start and stop the sensor var startStopAction: UIAlertAction if activeSensor != nil { startStopAction = UIAlertAction(title: Texts_HomeView.stopSensorActionTitle, style: .default) { (UIAlertAction) in trace("in createAndPresentSensorButtonActionSheet, user clicked stop sensor, will stop the sensor", log: self.log, category: ConstantsLog.categoryRootView, type: .info) - self.stopSensor() + self.stopSensor(cGMTransmitter: cgmTransmitter) } } else { startStopAction = UIAlertAction(title: Texts_HomeView.startSensorActionTitle, style: .default) { (UIAlertAction) in - self.startSensorAskUserForStarttime() + + // either sensor needs a sensor start time, or a sensor code .. or none + if cgmTransmitter.cgmTransmitterType().needsSensorStartTime() { + + self.startSensorAskUserForStarttime(cGMTransmitter: cgmTransmitter) + + } else if cgmTransmitter.cgmTransmitterType().needsSensorStartCode() { + + self.startSensorAskUserForSensorCode(cGMTransmitter: cgmTransmitter) + + } else { + + self.startSensor(cGMTransmitter: cgmTransmitter, sensorStarDate: Date(), sensorCode: nil, coreDataManager: coreDataManager) + + } + + } } @@ -1941,36 +1958,17 @@ final class RootViewController: UIViewController { } - /// stops the active sensor and sets sensorSerialNumber in UserDefaults to nil - private func stopSensor() { - - if let activeSensor = activeSensor, let coreDataManager = coreDataManager { - activeSensor.endDate = Date() - coreDataManager.saveChanges() - } - // save the changes - coreDataManager?.saveChanges() - - activeSensor = nil - - // now that the activeSensor object has been destroyed, update (hide) the sensor countdown graphic - updateSensorCountdown() - - } - /// start a new sensor, ask user for starttime - private func startSensorAskUserForStarttime() { + /// - parameters: + /// - cGMTransmitter is required because startSensor command will be sent also to the transmitter + private func startSensorAskUserForStarttime(cGMTransmitter: CGMTransmitter) { // craete datePickerViewData let datePickerViewData = DatePickerViewData(withMainTitle: Texts_HomeView.startSensorActionTitle, withSubTitle: nil, datePickerMode: .dateAndTime, date: Date(), minimumDate: nil, maximumDate: Date(), okButtonText: Texts_Common.Ok, cancelButtonText: Texts_Common.Cancel, onOkClick: {(date) in - if let coreDataManager = self.coreDataManager { + if let coreDataManager = self.coreDataManager, let cgmTransmitter = self.bluetoothPeripheralManager?.getCGMTransmitter() { - // set sensorStartTime - let sensorStartTime = date - self.activeSensor = Sensor(startDate: sensorStartTime, nsManagedObjectContext: coreDataManager.mainManagedObjectContext) - - // save the newly created Sensor permenantly in coredata - coreDataManager.saveChanges() + // start sensor with date chosen by user, sensorCode nil + self.startSensor(cGMTransmitter: cgmTransmitter, sensorStarDate: date, sensorCode: nil, coreDataManager: coreDataManager) } }, onCancelClick: nil) @@ -1996,6 +1994,27 @@ final class RootViewController: UIViewController { } + /// start a new sensor, ask user for sensor code + /// - parameters: + /// - cGMTransmitter is required because startSensor command will be sent also to the transmitter + private func startSensorAskUserForSensorCode(cGMTransmitter: CGMTransmitter) { + + let alert = UIAlertController(title: Texts_HomeView.enterSensorCode, message: nil, keyboardType:.numberPad, text: nil, placeHolder: "0000", actionTitle: nil, cancelTitle: nil, actionHandler: { + (text:String) in + + if let coreDataManager = self.coreDataManager, let cgmTransmitter = self.bluetoothPeripheralManager?.getCGMTransmitter() { + + // start sensor with date chosen by user, sensorCode nil + self.startSensor(cGMTransmitter: cgmTransmitter, sensorStarDate: Date(), sensorCode: text, coreDataManager: coreDataManager) + + } + + }, cancelHandler: nil) + + self.present(alert, animated: true, completion: nil) + + } + private func valueLabelLongPressed(_ sender: UILongPressGestureRecognizer) { if sender.state == .began { @@ -2646,6 +2665,52 @@ final class RootViewController: UIViewController { } } + /// - creates a new sensor and assigns it to activeSensor + /// - sends startSensor command to transmitter (ony useful for Firefly) + /// - saves to coredata + private func startSensor(cGMTransmitter: CGMTransmitter, sensorStarDate: Date, sensorCode: String?, coreDataManager: CoreDataManager) { + + // create active sensor + let newSensor = Sensor(startDate: sensorStarDate, nsManagedObjectContext: coreDataManager.mainManagedObjectContext) + + // save the newly created Sensor permenantly in coredata + coreDataManager.saveChanges() + + // send to transmitter + cGMTransmitter.startSensor(sensorCode: sensorCode, startDate: sensorStarDate) + + // assign activeSensor to newSensor + activeSensor = newSensor + + } + + private func stopSensor(cGMTransmitter: CGMTransmitter) { + + // create stopDate + let stopDate = Date() + + // send stop sensor command to transmitter, don't check if there's an activeSensor in coredata or not, never know that there's a desync between coredata and transmitter + cGMTransmitter.stopSensor(stopDate: stopDate) + + // no need to further continue if activeSensor = nil, and at the same time, unwrap coredataManager + guard let activeSensor = activeSensor, let coreDataManager = coreDataManager else { + return + } + + // set endDate of activeSensor to stopDate + activeSensor.endDate = stopDate + + // save changes to coreData + coreDataManager.saveChanges() + + // asign nil to activeSensor + self.activeSensor = nil + + // now that the activeSensor object has been destroyed, update (hide) the sensor countdown graphic + updateSensorCountdown() + + } + } @@ -2653,10 +2718,23 @@ final class RootViewController: UIViewController { /// conform to CGMTransmitterDelegate extension RootViewController: CGMTransmitterDelegate { - - func newSensorDetected() { + + func newSensorDetected(sensorStartDate: Date?) { trace("new sensor detected", log: log, category: ConstantsLog.categoryRootView, type: .info) - stopSensor() + + // unwrap cgmTransmitter + guard let cgmTransmitter = self.bluetoothPeripheralManager?.getCGMTransmitter() else {return} + + stopSensor(cGMTransmitter: cgmTransmitter) + + // if sensorStartDate is given, then unwrap coreDataManager and startSensor + if let sensorStartDate = sensorStartDate, let coreDataManager = coreDataManager { + + // use sensorCode nil, in the end there will be no start sensor command sent to the transmitter because we just received the sensorStartTime from the transmitter, so it's already started + startSensor(cGMTransmitter: cgmTransmitter, sensorStarDate: sensorStartDate, sensorCode: nil, coreDataManager: coreDataManager) + + } + } func sensorNotDetected() { @@ -2666,10 +2744,10 @@ extension RootViewController: CGMTransmitterDelegate { } - func cgmTransmitterInfoReceived(glucoseData: inout [GlucoseData], transmitterBatteryInfo: TransmitterBatteryInfo?, sensorTimeInMinutes: Int?) { + func cgmTransmitterInfoReceived(glucoseData: inout [GlucoseData], transmitterBatteryInfo: TransmitterBatteryInfo?, sensorAge: TimeInterval?) { trace("transmitterBatteryInfo %{public}@", log: log, category: ConstantsLog.categoryRootView, type: .debug, transmitterBatteryInfo?.description ?? "not received") - trace("sensor time in minutes %{public}@", log: log, category: ConstantsLog.categoryRootView, type: .debug, sensorTimeInMinutes?.description ?? "not received") + trace("sensor time in days %{public}@", log: log, category: ConstantsLog.categoryRootView, type: .debug, sensorAge?.days.round(toDecimalPlaces: 1).description ?? "not received") trace("glucoseData size = %{public}@", log: log, category: ConstantsLog.categoryRootView, type: .info, glucoseData.count.description) // if received transmitterBatteryInfo not nil, then store it @@ -2685,7 +2763,7 @@ extension RootViewController: CGMTransmitterDelegate { } // process new readings - processNewGlucoseData(glucoseData: &glucoseData, sensorTimeInMinutes: sensorTimeInMinutes) + processNewGlucoseData(glucoseData: &glucoseData, sensorAge: sensorAge) }