temp commit
This commit is contained in:
parent
c4001d0bd2
commit
79ec1fabd1
|
@ -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 = "<group>"; };
|
||||
F82528692443AE190067AF77 /* DexcomG6BluetoothPeripheralViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomG6BluetoothPeripheralViewModel.swift; sourceTree = "<group>"; };
|
||||
F825286B2443BEDC0067AF77 /* CGMG6TransmitterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMG6TransmitterDelegate.swift; sourceTree = "<group>"; };
|
||||
F828422F274ED56A0097E0C9 /* DexcomCalibrationParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationParameters.swift; sourceTree = "<group>"; };
|
||||
F8297F4B238DCAD800D74D66 /* BluetoothPeripheralsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothPeripheralsViewController.swift; sourceTree = "<group>"; };
|
||||
F8297F4C238DCAD800D74D66 /* BluetoothPeripheralNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothPeripheralNavigationController.swift; sourceTree = "<group>"; };
|
||||
F8297F51238ECA3200D74D66 /* BluetoothPeripheralViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothPeripheralViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -1299,6 +1307,13 @@
|
|||
F8CB59BF2734976D00BA199E /* DexcomTransmitterTimeTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomTransmitterTimeTxMessage.swift; sourceTree = "<group>"; };
|
||||
F8CB59C12738206D00BA199E /* DexcomGlucoseDataTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomGlucoseDataTxMessage.swift; sourceTree = "<group>"; };
|
||||
F8CB59C32739D1CD00BA199E /* DexcomBackfillTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomBackfillTxMessage.swift; sourceTree = "<group>"; };
|
||||
F8CB59C5273ECFE500BA199E /* DexcomG6GlucoseDataRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomG6GlucoseDataRxMessage.swift; sourceTree = "<group>"; };
|
||||
F8CB59C7273EF9F800BA199E /* DexcomAlgorithmState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomAlgorithmState.swift; sourceTree = "<group>"; };
|
||||
F8CB59C927405A6800BA199E /* DexcomCalibrationTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationTxMessage.swift; sourceTree = "<group>"; };
|
||||
F8CB59CB2744471000BA199E /* DexcomSessionStartResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSessionStartResponse.swift; sourceTree = "<group>"; };
|
||||
F8CB59CD27444D6300BA199E /* DexcomSessionStopResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSessionStopResponse.swift; sourceTree = "<group>"; };
|
||||
F8CB59D02745D3FE00BA199E /* DexcomG6FireflyBluetoothPeripheralViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomG6FireflyBluetoothPeripheralViewModel.swift; sourceTree = "<group>"; };
|
||||
F8CB59D2274D94AE00BA199E /* DexcomSessionStartTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSessionStartTxMessage.swift; sourceTree = "<group>"; };
|
||||
F8D0587B24BCB570008C8734 /* SettingsViewHomeScreenSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewHomeScreenSettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
F8DF765223E34F4500063910 /* DexcomG5+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DexcomG5+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
F8DF765423E34FD500063910 /* DexcomG5+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DexcomG5+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1344,7 +1359,6 @@
|
|||
F8F167092727317C001AA3D8 /* DexcomTransmitterTimeRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomTransmitterTimeRxMessage.swift; sourceTree = "<group>"; };
|
||||
F8F1670B27273774001AA3D8 /* GlucoseBackfillRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBackfillRxMessage.swift; sourceTree = "<group>"; };
|
||||
F8F1670D27273EA7001AA3D8 /* GlucoseDataRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseDataRxMessage.swift; sourceTree = "<group>"; };
|
||||
F8F1671027274080001AA3D8 /* DexcomCalibrationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationState.swift; sourceTree = "<group>"; };
|
||||
F8F1671227274557001AA3D8 /* DexcomCalibrationRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationRxMessage.swift; sourceTree = "<group>"; };
|
||||
F8F16714272745A2001AA3D8 /* DexcomCalibrationResponseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationResponseType.swift; sourceTree = "<group>"; };
|
||||
F8F1671627288B24001AA3D8 /* DexcomSessionStopRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSessionStopRxMessage.swift; sourceTree = "<group>"; };
|
||||
|
@ -2446,6 +2460,14 @@
|
|||
path = MiaoMiao;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F8CB59CF2745D3D000BA199E /* DexcomG6Firefly */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F8CB59D02745D3FE00BA199E /* DexcomG6FireflyBluetoothPeripheralViewModel.swift */,
|
||||
);
|
||||
path = DexcomG6Firefly;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -4,6 +4,10 @@ extension DexcomG5: BluetoothPeripheral {
|
|||
|
||||
func bluetoothPeripheralType() -> BluetoothPeripheralType {
|
||||
|
||||
if isDexcomG6 {return .DexcomG6Type}
|
||||
|
||||
if isFirefly {return .DexcomG6FireflyType}
|
||||
|
||||
return .DexcomG5Type
|
||||
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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?) {
|
||||
|
|
|
@ -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)..<transmitterID.endIndex])
|
||||
|
@ -145,6 +167,15 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
|
||||
// initialize firmware
|
||||
self.firmware = firmware
|
||||
|
||||
// assign calibrationToSendToTransmitter
|
||||
self.calibrationToSendToTransmitter = calibrationToSendToTransmitter
|
||||
|
||||
// assign sensorStartDate
|
||||
self.sensorStartDate = sensorStartDate
|
||||
|
||||
// assign isFireflyNoMatterTransmitterId
|
||||
self.isFireflyNoMatterTransmitterId = isFireFly
|
||||
|
||||
// initialize - CBUUID_Receive_Authentication.rawValue and CBUUID_Write_Control.rawValue will probably not be used in the superclass
|
||||
super.init(addressAndName: newAddressAndName, CBUUID_Advertisement: CBUUID_Advertisement_G5, servicesCBUUIDs: [CBUUID(string: CBUUID_Service_G5)], CBUUID_ReceiveCharacteristic: CBUUID_Characteristic_UUID.CBUUID_Receive_Authentication.rawValue, CBUUID_WriteCharacteristic: CBUUID_Characteristic_UUID.CBUUID_Write_Control.rawValue, bluetoothTransmitterDelegate: bluetoothTransmitterDelegate)
|
||||
|
@ -170,12 +201,10 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
let testdata = GlucoseData(timeStamp: Date(), glucoseLevelRaw: testAmount)
|
||||
debuglogging("timestamp testdata = " + testdata.timeStamp.description + ", with amount = " + testAmount.description)
|
||||
var testdataasarray = [testdata]
|
||||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &testdataasarray, transmitterBatteryInfo: nil, sensorTimeInMinutes: nil)
|
||||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &testdataasarray, transmitterBatteryInfo: nil, sensorAge: nil)
|
||||
testAmount = testAmount + 1
|
||||
}
|
||||
|
||||
// MARK: public functions
|
||||
|
||||
/// scale the rawValue, dependent on transmitter version G5 , G6 --
|
||||
/// for G6, there's two possible scaling factors, depending on the firmware version. For G5 there's only one, firmware version independent
|
||||
/// - parameters:
|
||||
|
@ -190,13 +219,11 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
|
||||
}
|
||||
|
||||
/// verifies if it's a firefly based on transmitterId, if >= 8G then considered to be a Firefly
|
||||
public func isFireFly() -> Bool {
|
||||
|
||||
return transmitterId.uppercased().compare("8G") == .orderedDescending
|
||||
|
||||
/// to ask transmitter reset
|
||||
func reset(requested:Bool) {
|
||||
G5ResetRequested = requested
|
||||
}
|
||||
|
||||
|
||||
// MARK: - deinit
|
||||
|
||||
deinit {
|
||||
|
@ -279,7 +306,7 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
|
||||
// is it a firefly ?
|
||||
// if no treat it as a G5, with own calibration
|
||||
if !isFireFly() {
|
||||
if !useFireflyFlow() {
|
||||
|
||||
// send SensorTxMessage to transmitter
|
||||
getSensorData()
|
||||
|
@ -341,11 +368,15 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
|
||||
//check type of message and process according to type
|
||||
if let firstByte = value.first {
|
||||
|
||||
if let opCode = DexcomTransmitterOpCode(rawValue: firstByte) {
|
||||
|
||||
trace(" opcode = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, opCode.description)
|
||||
|
||||
switch opCode {
|
||||
|
||||
case .authChallengeRx:
|
||||
|
||||
if let authChallengeRxMessage = AuthChallengeRxMessage(data: value) {
|
||||
|
||||
// if not paired, then send message to delegate
|
||||
|
@ -363,7 +394,9 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
|
||||
// subscribe to writeControlCharacteristic
|
||||
if let writeControlCharacteristic = writeControlCharacteristic {
|
||||
|
||||
setNotifyValue(true, for: writeControlCharacteristic)
|
||||
|
||||
} else {
|
||||
trace(" writeControlCharacteristic is nil, can not set notifyValue", log: log, category: ConstantsLog.categoryCGMG5, type: .error)
|
||||
}
|
||||
|
@ -386,6 +419,12 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
}
|
||||
|
||||
case .authRequestRx:
|
||||
|
||||
if letDexcomAppDoTheTransmitWork {
|
||||
trace(" authRequestRx, letDexcomAppDoTheTransmitWork = true, no further processing", log: log, category: ConstantsLog.categoryCGMG5, type: .info)
|
||||
return
|
||||
}
|
||||
|
||||
if let authRequestRxMessage = AuthRequestRxMessage(data: value), let receiveAuthenticationCharacteristic = receiveAuthenticationCharacteristic {
|
||||
|
||||
guard let challengeHash = CGMG5Transmitter.computeHash(transmitterId, of: authRequestRxMessage.challenge) else {
|
||||
|
@ -405,6 +444,11 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
|
||||
case .sensorDataRx:
|
||||
|
||||
if letDexcomAppDoTheTransmitWork {
|
||||
trace(" sensorDataRx, letDexcomAppDoTheTransmitWork = true, no further processing", log: log, category: ConstantsLog.categoryCGMG5, type: .info)
|
||||
return
|
||||
}
|
||||
|
||||
// if this is the first sensorDataRx after a successful pairing, then inform delegate that pairing is finished
|
||||
if waitingPairingConfirmation {
|
||||
waitingPairingConfirmation = false
|
||||
|
@ -461,7 +505,7 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
|
||||
var glucoseDataArray = [glucoseData]
|
||||
|
||||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorTimeInMinutes: nil)
|
||||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorAge: nil)
|
||||
|
||||
}
|
||||
|
||||
|
@ -481,14 +525,14 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
processBatteryStatusRxMessage(value: value)
|
||||
|
||||
// if firefly continue with the firefly message flow
|
||||
if isFireFly() { fireflyMessageFlow() }
|
||||
if useFireflyFlow() { fireflyMessageFlow() }
|
||||
|
||||
case .transmitterVersionRx:
|
||||
|
||||
processTransmitterVersionRxMessage(value: value)
|
||||
|
||||
// if firefly continue with the firefly message flow
|
||||
if isFireFly() { fireflyMessageFlow() }
|
||||
if useFireflyFlow() { fireflyMessageFlow() }
|
||||
|
||||
case .keepAliveRx:
|
||||
|
||||
|
@ -497,6 +541,11 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
|
||||
case .pairRequestRx:
|
||||
|
||||
if letDexcomAppDoTheTransmitWork {
|
||||
trace(" pairRequestRx, letDexcomAppDoTheTransmitWork = true, no further processing", log: log, category: ConstantsLog.categoryCGMG5, type: .info)
|
||||
return
|
||||
}
|
||||
|
||||
// don't know if the user accepted the pairing request or not, we can only know by trying to subscribe to writeControlCharacteristic - if the device is paired, we'll receive a sensorDataRx message, if not paired, then a disconnect will happen
|
||||
|
||||
// set status to waitingForPairingConfirmation
|
||||
|
@ -514,21 +563,29 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
processTransmitterTimeRxMessage(value: value)
|
||||
|
||||
// if firefly continue with the firefly message flow
|
||||
if isFireFly() { fireflyMessageFlow() }
|
||||
if useFireflyFlow() { fireflyMessageFlow() }
|
||||
|
||||
case .glucoseBackfillRx:
|
||||
|
||||
processGlucoseBackfillRxMessage(value: value)
|
||||
|
||||
// if firefly continue with the firefly message flow
|
||||
if isFireFly() { fireflyMessageFlow() }
|
||||
if useFireflyFlow() { fireflyMessageFlow() }
|
||||
|
||||
case .glucoseRx:
|
||||
|
||||
processGlucoseDataRxMessage(value: value)
|
||||
|
||||
// if firefly continue with the firefly message flow
|
||||
if isFireFly() { fireflyMessageFlow() }
|
||||
if useFireflyFlow() { fireflyMessageFlow() }
|
||||
|
||||
case .glucoseG6Rx:
|
||||
// received when relying on official Dexcom app, ie in mode letDexcomAppDoTheTransmitWork = true
|
||||
|
||||
processGlucoseG6DataRxMessage(value: value)
|
||||
|
||||
// if firefly continue with the firefly message flow
|
||||
if useFireflyFlow() { fireflyMessageFlow() }
|
||||
|
||||
case .calibrateGlucoseRx:
|
||||
|
||||
|
@ -537,6 +594,10 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
case .sessionStopRx:
|
||||
|
||||
processSessionStopRxMessage(value: value)
|
||||
|
||||
case .sessionStartRx:
|
||||
|
||||
processSessionStartRxMessage(value: value)
|
||||
|
||||
default:
|
||||
trace(" unknown opcode received ", log: log, category: ConstantsLog.categoryCGMG5, type: .error)
|
||||
|
@ -558,6 +619,11 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
|
||||
override func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
|
||||
|
||||
if letDexcomAppDoTheTransmitWork {
|
||||
trace("didDiscover, letDexcomAppDoTheTransmitWork = true, no further processing", log: log, category: ConstantsLog.categoryCGMG5, type: .info)
|
||||
return
|
||||
}
|
||||
|
||||
if Date() < Date(timeInterval: ConstantsDexcomG5.minimumTimeBetweenTwoReadings, since: timeStampOfLastG5Reading) {
|
||||
// will probably never come here because reconnect doesn't happen with scanning, hence diddiscover will never be called except the very first time that an app tries to connect to a G5
|
||||
trace("diddiscover peripheral, but last reading was less than %{public}@ minutes ago, will ignore", log: log, category: ConstantsLog.categoryCGMG5, type: .info, ConstantsDexcomG5.minimumTimeBetweenTwoReadings.minutes.description)
|
||||
|
@ -576,7 +642,9 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
trace("connected to peripheral with name %{public}@, but last reading was less than %{public}@ minute ago", log: log, category: ConstantsLog.categoryCGMG5, type: .info, (deviceName != nil ? deviceName! : "unknown"), ConstantsDexcomG5.minimumTimeBetweenTwoReadings.minutes.description)
|
||||
// don't disconnect here, keep the connection open, the transmitter will disconnect in a few seconds, assumption is that this will increase battery life
|
||||
} else {
|
||||
|
||||
super.centralManager(central, didConnect: peripheral)
|
||||
|
||||
}
|
||||
|
||||
// to be sure waitingPairingConfirmation is reset to false
|
||||
|
@ -631,46 +699,76 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: CGMTransmitter protocol functions
|
||||
/// overrides writeDataToPeripheral and checks if letDexcomAppDoTheTransmitWork is true and if so, data is not written to characteristic
|
||||
override func writeDataToPeripheral(data: Data, characteristicToWriteTo: CBCharacteristic, type: CBCharacteristicWriteType) -> 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
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Int8>.size)).map { Int8(bitPattern: $0) })
|
||||
}
|
||||
|
||||
withUnsafeBytes(of: time) {
|
||||
array.append(contentsOf: Array($0.prefix(4 * MemoryLayout<Int8>.size)).map { Int8(bitPattern: $0) })
|
||||
}
|
||||
|
||||
let data = array.withUnsafeBufferPointer { Data(buffer: $0) }
|
||||
|
||||
self.data = data.appendingCRC()
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
|
||||
}
|
||||
|
|
|
@ -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<Int8>.size)).map { Int8(bitPattern: $0) })
|
||||
}
|
||||
|
||||
withUnsafeBytes(of: Int(startDate.timeIntervalSince1970)) {
|
||||
array.append(contentsOf: Array($0.prefix(4 * MemoryLayout<Int8>.size)).map { Int8(bitPattern: $0) })
|
||||
}
|
||||
|
||||
if dexcomCalibrationParameters.parameter1 != 0 {
|
||||
|
||||
withUnsafeBytes(of: dexcomCalibrationParameters.parameter1) {
|
||||
array.append(contentsOf: Array($0.prefix(2 * MemoryLayout<Int8>.size)).map { Int8(bitPattern: $0) })
|
||||
}
|
||||
|
||||
withUnsafeBytes(of: dexcomCalibrationParameters.parameter2) {
|
||||
array.append(contentsOf: Array($0.prefix(2 * MemoryLayout<Int8>.size)).map { Int8(bitPattern: $0) })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let data = array.withUnsafeBufferPointer { Data(buffer: $0) }
|
||||
|
||||
self.data = data.appendingCRC()
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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])..<valueAsString.endIndex])) else {
|
||||
trace(" failed to convert sensorTimeInMinutes field to Int", log: log, category: ConstantsLog.categoryCGMDroplet1, type: .error)
|
||||
guard let sensorAgeInMinutes = Int(String(valueAsString[valueAsString.index(after: indexesOfSplitter[2])..<valueAsString.endIndex])) else {
|
||||
trace(" failed to convert sensorAge field to Int", log: log, category: ConstantsLog.categoryCGMDroplet1, type: .error)
|
||||
return
|
||||
}
|
||||
|
||||
// send glucoseDataArray, transmitterBatteryInfo and sensorTimeInMinutes to cgmTransmitterDelegate
|
||||
// send glucoseDataArray, transmitterBatteryInfo and sensorAge to cgmTransmitterDelegate
|
||||
var glucoseDataArray = [GlucoseData(timeStamp: Date(), glucoseLevelRaw: rawValueAsDouble)]
|
||||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorTimeInMinutes: sensorTimeInMinutes * 10)
|
||||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorAge: TimeInterval(minutes: Double(sensorAgeInMinutes * 10) ))
|
||||
|
||||
// send transmitterBatteryInfo to delegate
|
||||
cGMDropletTransmitterDelegate?.received(batteryLevel: batteryPercentage, from: self)
|
||||
|
@ -132,31 +132,12 @@ class CGMDroplet1Transmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
nonFixedSlopeEnabled = enabled
|
||||
}
|
||||
|
||||
/// this transmitter does not support oopWeb
|
||||
func setWebOOPEnabled(enabled: Bool) {
|
||||
}
|
||||
|
||||
func cgmTransmitterType() -> 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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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?) {
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -36,11 +36,4 @@ extension WatlaaBluetoothTransmitter: CGMTransmitter {
|
|||
_ = sendStartReadingCommand()
|
||||
}
|
||||
|
||||
func maxSensorAgeInDays() -> Int? {
|
||||
|
||||
// no max sensor age for Watlaa
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
|
||||
}
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
<attribute name="rawTimeStamp" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="rawValue" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="sensorConfidence" attributeType="Double" usesScalarValueType="YES"/>
|
||||
<attribute name="sentToTransmitter" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="slope" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="slopeConfidence" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="timeStamp" attributeType="Date" usesScalarValueType="NO"/>
|
||||
|
@ -103,6 +104,7 @@
|
|||
<attribute name="batteryTemperature" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="isDexcomG6" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isFirefly" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="lastResetTimeStamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="sensorStartDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="transmitterStartDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
|
@ -164,9 +166,9 @@
|
|||
<element name="Blucon" positionX="-657" positionY="189" width="128" height="58"/>
|
||||
<element name="BlueReader" positionX="-639" positionY="207" width="128" height="58"/>
|
||||
<element name="Bubble" positionX="-657" positionY="189" width="128" height="88"/>
|
||||
<element name="Calibration" positionX="-859.21484375" positionY="46.21484375" width="128" height="285"/>
|
||||
<element name="Calibration" positionX="-859.21484375" positionY="46.21484375" width="128" height="284"/>
|
||||
<element name="DexcomG4" positionX="-621" positionY="225" width="128" height="58"/>
|
||||
<element name="DexcomG5" positionX="-648" positionY="198" width="128" height="209"/>
|
||||
<element name="DexcomG5" positionX="-648" positionY="198" width="128" height="224"/>
|
||||
<element name="Droplet" positionX="-630" positionY="216" width="128" height="58"/>
|
||||
<element name="GNSEntry" positionX="-648" positionY="198" width="128" height="103"/>
|
||||
<element name="Libre2" positionX="-648" positionY="198" width="128" height="58"/>
|
||||
|
|
|
@ -37,4 +37,9 @@ extension TimeInterval {
|
|||
var hours: Double {
|
||||
return minutes / 60.0
|
||||
}
|
||||
|
||||
var days: Double {
|
||||
return hours / 24.0
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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")
|
||||
}()
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ class DexcomG5BluetoothPeripheralViewModel {
|
|||
case firmWareVersion = 2
|
||||
|
||||
}
|
||||
|
||||
|
||||
private enum ResetSettings:Int, CaseIterable {
|
||||
|
||||
/// should reset be done yes or no
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue