From c4001d0bd20d33d765746892086adc194ed63eaf Mon Sep 17 00:00:00 2001 From: Johan Degraeve Date: Tue, 26 Oct 2021 22:52:06 +0200 Subject: [PATCH] Firefly support - connects to firefly and reads readings - backfilling 6 hours - sensor start and calibration must be done on the official Dexcom app, afterwards the official Dexcom app (or any other app) should not have access to bluetooth --- xdrip.xcodeproj/project.pbxproj | 106 +- .../Generic/BluetoothPeripheralType.swift | 7 - .../CGM/Dexcom/G5/CGMG5Transmitter.swift | 907 ++++++++++++++---- .../Dexcom/G5/CGMG5TransmitterDelegate.swift | 9 +- .../G5Messages/DexcomTransmitterOpCode.swift | 97 -- .../CGM/Dexcom/G6/CGMG6Transmitter.swift | 23 +- .../G6Firefly/CGMG6FireflyTransmitter.swift | 131 +++ .../CGMG6FireflyTransmitterDelegate.swift | 14 + .../{G5/G5Messages => Generic}/AESCrypt.h | 0 .../{G5/G5Messages => Generic}/AESCrypt.m | 0 .../AuthChallengeRxMessage.swift | 0 .../AuthChallengeTxMessage.swift | 0 .../AuthRequestRxMessage.swift | 0 .../AuthRequestTxMessage.swift | 0 .../BatteryStatusRxMessage.swift | 0 .../BatteryStatusTxMessage.swift | 0 .../DexcomAuthChallengeRxMessage.swift | 28 + .../Dexcom/Generic/DexcomBackfillStream.swift | 100 ++ .../Generic/DexcomBackfillTxMessage.swift | 46 + .../DexcomCalibrationResponseType.swift | 58 ++ .../Generic/DexcomCalibrationRxMessage.swift | 30 + .../Generic/DexcomCalibrationState.swift | 101 ++ .../Generic/DexcomGlucoseDataTxMessage.swift | 13 + .../Generic/DexcomSessionStartRxMessage.swift | 33 + .../Generic/DexcomSessionStopRxMessage.swift | 37 + .../Generic/DexcomTransmitterOpCode.swift | 194 ++++ .../DexcomTransmitterTimeRxMessage.swift | 31 + .../DexcomTransmitterTimeTxMessage.swift | 11 + .../FirmwareVersionTxMessage.swift | 0 .../Generic/GlucoseBackfillRxMessage.swift | 34 + .../Dexcom/Generic/GlucoseDataRxMessage.swift | 35 + .../KeepAliveTxMessage.swift | 0 .../G5Messages => Generic}/NSData+CRC.swift | 0 .../PairRequestTxMessage.swift | 0 .../G5Messages => Generic}/ResetMessage.swift | 0 .../SensorDataRxMessage.swift | 0 .../SensorDataTxMessage.swift | 0 .../TransmitterMessage.swift | 1 - .../TransmitterVersionRxMessage.swift | 12 +- .../TransmitterVersionTxMessage.swift | 0 .../CGM/Generic/CGMTransmitterDelegate.swift | 2 +- .../CGM/Generic/TransmitterBatteryInfo.swift | 1 - .../Generic/BluetoothTransmitter.swift | 16 +- xdrip/Constants/ConstantsDexcomG5.swift | 19 +- .../BLEPeripheral+CoreDataProperties.swift | 3 +- .../classes/DexcomG5+CoreDataProperties.swift | 4 + .../xdrip v16.xcdatamodel/contents | 8 +- ...Manager+BluetoothTransmitterDelegate.swift | 4 +- ...eralManager+CGMG5TransmitterDelegate.swift | 20 + .../BluetoothPeripheralManager.swift | 13 +- .../ar.lproj/SettingsViews.strings | 1 - .../de.lproj/SettingsViews.strings | 3 - .../en.lproj/BluetoothPeripheralView.strings | 2 + .../en.lproj/SettingsViews.strings | 1 - .../es.lproj/SettingsViews.strings | 1 - .../fi.lproj/SettingsViews.strings | 1 - .../fr.lproj/SettingsViews.strings | 1 - .../it.lproj/SettingsViews.strings | 3 - .../nl.lproj/SettingsViews.strings | 3 +- .../pl-PL.lproj/SettingsViews.strings | 3 - .../pt.lproj/SettingsViews.strings | 1 - .../ru.lproj/SettingsViews.strings | 3 - .../sl.lproj/SettingsViews.strings | 3 - .../sv.lproj/SettingsViews.strings | 1 - .../zh.lproj/SettingsViews.strings | 3 - .../Texts/TextsBluetoothPeripheralView.swift | 8 + xdrip/Texts/TextsSettingsView.swift | 4 - xdrip/Utilities/Trace.swift | 2 +- ...DexcomG5BluetoothPeripheralViewModel.swift | 46 +- .../RootViewController.swift | 42 +- xdrip/xdrip-Bridging-Header.h | 2 +- 71 files changed, 1892 insertions(+), 390 deletions(-) delete mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/DexcomTransmitterOpCode.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/G6Firefly/CGMG6FireflyTransmitter.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/G6Firefly/CGMG6FireflyTransmitterDelegate.swift rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/AESCrypt.h (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/AESCrypt.m (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/AuthChallengeRxMessage.swift (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/AuthChallengeTxMessage.swift (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/AuthRequestRxMessage.swift (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/AuthRequestTxMessage.swift (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/BatteryStatusRxMessage.swift (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/BatteryStatusTxMessage.swift (100%) create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomAuthChallengeRxMessage.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomBackfillStream.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomBackfillTxMessage.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationResponseType.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationRxMessage.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationState.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomGlucoseDataTxMessage.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartRxMessage.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStopRxMessage.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomTransmitterOpCode.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomTransmitterTimeRxMessage.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomTransmitterTimeTxMessage.swift rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/FirmwareVersionTxMessage.swift (100%) create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/GlucoseBackfillRxMessage.swift create mode 100644 xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/GlucoseDataRxMessage.swift rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/KeepAliveTxMessage.swift (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/NSData+CRC.swift (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/PairRequestTxMessage.swift (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/ResetMessage.swift (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/SensorDataRxMessage.swift (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/SensorDataTxMessage.swift (100%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/TransmitterMessage.swift (99%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/TransmitterVersionRxMessage.swift (65%) rename xdrip/BluetoothTransmitter/CGM/Dexcom/{G5/G5Messages => Generic}/TransmitterVersionTxMessage.swift (100%) diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index e5dde1702..1eac531b1 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -419,6 +419,9 @@ F8C97854242AA70D00A09483 /* MiaoMiao+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C97852242AA70C00A09483 /* MiaoMiao+CoreDataProperties.swift */; }; F8C97856242AA86B00A09483 /* CGMMiaoMiaoTransmitterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C97855242AA86B00A09483 /* CGMMiaoMiaoTransmitterDelegate.swift */; }; F8C97859242AAE7B00A09483 /* MiaoMiao+BluetoothPeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C97858242AAE7A00A09483 /* MiaoMiao+BluetoothPeripheral.swift */; }; + 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 */; }; 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 */; }; @@ -455,6 +458,15 @@ F8EEDD5422FF685400D2D610 /* NSMutableURLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD5322FF685400D2D610 /* NSMutableURLRequest.swift */; }; F8EEDD552300136F00D2D610 /* DexcomShareTestResult.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8EEDD572300136F00D2D610 /* DexcomShareTestResult.strings */; }; F8EEDD6423020FAD00D2D610 /* NoCalibrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD6323020FAD00D2D610 /* NoCalibrator.swift */; }; + 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 */; }; + F8F1671927288FC6001AA3D8 /* DexcomSessionStartRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F1671827288FC6001AA3D8 /* DexcomSessionStartRxMessage.swift */; }; + F8F1671B272B3E4F001AA3D8 /* DexcomBackfillStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F1671A272B3E4F001AA3D8 /* DexcomBackfillStream.swift */; }; F8F7B8E6259A6EBF00C47B04 /* LibreSmoothing.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F7B8E5259A6EBF00C47B04 /* LibreSmoothing.swift */; }; F8F7B8EB259A7B1C00C47B04 /* SavitzkyGolaySmoothableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F7B8EA259A7B1C00C47B04 /* SavitzkyGolaySmoothableArray.swift */; }; F8F971B623A5914D00C3F17D /* M5Stack+BluetoothPeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971B123A5914C00C3F17D /* M5Stack+BluetoothPeripheral.swift */; }; @@ -1284,6 +1296,9 @@ F8C97852242AA70C00A09483 /* MiaoMiao+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MiaoMiao+CoreDataProperties.swift"; sourceTree = ""; }; F8C97855242AA86B00A09483 /* CGMMiaoMiaoTransmitterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMMiaoMiaoTransmitterDelegate.swift; sourceTree = ""; }; F8C97858242AAE7A00A09483 /* MiaoMiao+BluetoothPeripheral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MiaoMiao+BluetoothPeripheral.swift"; sourceTree = ""; }; + F8CB59BF2734976D00BA199E /* DexcomTransmitterTimeTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomTransmitterTimeTxMessage.swift; sourceTree = ""; }; + F8CB59C12738206D00BA199E /* DexcomGlucoseDataTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomGlucoseDataTxMessage.swift; sourceTree = ""; }; + F8CB59C32739D1CD00BA199E /* DexcomBackfillTxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomBackfillTxMessage.swift; sourceTree = ""; }; F8D0587B24BCB570008C8734 /* SettingsViewHomeScreenSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewHomeScreenSettingsViewModel.swift; sourceTree = ""; }; F8DF765223E34F4500063910 /* DexcomG5+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DexcomG5+CoreDataClass.swift"; sourceTree = ""; }; F8DF765423E34FD500063910 /* DexcomG5+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DexcomG5+CoreDataProperties.swift"; sourceTree = ""; }; @@ -1326,6 +1341,15 @@ F8EEDD612300139800D2D610 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/DexcomShareTestResult.strings; sourceTree = ""; }; F8EEDD622300139A00D2D610 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/DexcomShareTestResult.strings; sourceTree = ""; }; F8EEDD6323020FAD00D2D610 /* NoCalibrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCalibrator.swift; sourceTree = ""; }; + F8F167092727317C001AA3D8 /* DexcomTransmitterTimeRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomTransmitterTimeRxMessage.swift; sourceTree = ""; }; + F8F1670B27273774001AA3D8 /* GlucoseBackfillRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBackfillRxMessage.swift; sourceTree = ""; }; + F8F1670D27273EA7001AA3D8 /* GlucoseDataRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseDataRxMessage.swift; sourceTree = ""; }; + F8F1671027274080001AA3D8 /* DexcomCalibrationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationState.swift; sourceTree = ""; }; + F8F1671227274557001AA3D8 /* DexcomCalibrationRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationRxMessage.swift; sourceTree = ""; }; + F8F16714272745A2001AA3D8 /* DexcomCalibrationResponseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrationResponseType.swift; sourceTree = ""; }; + F8F1671627288B24001AA3D8 /* DexcomSessionStopRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSessionStopRxMessage.swift; sourceTree = ""; }; + F8F1671827288FC6001AA3D8 /* DexcomSessionStartRxMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomSessionStartRxMessage.swift; sourceTree = ""; }; + F8F1671A272B3E4F001AA3D8 /* DexcomBackfillStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomBackfillStream.swift; sourceTree = ""; }; F8F7B8E5259A6EBF00C47B04 /* LibreSmoothing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreSmoothing.swift; sourceTree = ""; }; F8F7B8EA259A7B1C00C47B04 /* SavitzkyGolaySmoothableArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavitzkyGolaySmoothableArray.swift; sourceTree = ""; }; F8F971B123A5914C00C3F17D /* M5Stack+BluetoothPeripheral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "M5Stack+BluetoothPeripheral.swift"; sourceTree = ""; }; @@ -2527,7 +2551,6 @@ F8FDFEA6260DE1A70047597D /* CustomColoredDisclosureIndicator */, F8AF11F624B1E6D700AE5BA2 /* Errors */, F85FF38F25288860004E6FF1 /* HouseKeeping */, - F8F7B8EE259A84A300C47B04 /* KalmanFilter */, F81F9FF722861E6D0028C70F /* KeyValueObserverTimeKeeper.swift */, F821CF69229FC22D005C1E43 /* Network */, F821CF8022A5C814005C1E43 /* RepeatingTimer.swift */, @@ -2597,19 +2620,50 @@ path = Calibration; sourceTree = ""; }; - F8F7B8E9259A7A9400C47B04 /* SavitzkyGolayFilter */ = { + F8F1670F27274067001AA3D8 /* Generic */ = { isa = PBXGroup; children = ( - F8F7B8EA259A7B1C00C47B04 /* SavitzkyGolaySmoothableArray.swift */, + F8F971CA23A5915900C3F17D /* AESCrypt.h */, + F8F971C623A5915900C3F17D /* AESCrypt.m */, + F8F971D223A5915900C3F17D /* AuthChallengeRxMessage.swift */, + F8F971C423A5915900C3F17D /* AuthChallengeTxMessage.swift */, + F8F971CC23A5915900C3F17D /* AuthRequestRxMessage.swift */, + F8F971C323A5915900C3F17D /* AuthRequestTxMessage.swift */, + F8F971D123A5915900C3F17D /* BatteryStatusRxMessage.swift */, + F8F971C523A5915900C3F17D /* BatteryStatusTxMessage.swift */, + F8F1671A272B3E4F001AA3D8 /* DexcomBackfillStream.swift */, + F8F16714272745A2001AA3D8 /* DexcomCalibrationResponseType.swift */, + F8F1671227274557001AA3D8 /* DexcomCalibrationRxMessage.swift */, + F8F1671027274080001AA3D8 /* DexcomCalibrationState.swift */, + F8F1671827288FC6001AA3D8 /* DexcomSessionStartRxMessage.swift */, + F8F1671627288B24001AA3D8 /* DexcomSessionStopRxMessage.swift */, + F8F971C723A5915900C3F17D /* DexcomTransmitterOpCode.swift */, + F8F167092727317C001AA3D8 /* DexcomTransmitterTimeRxMessage.swift */, + F8CB59BF2734976D00BA199E /* DexcomTransmitterTimeTxMessage.swift */, + F8F971D023A5915900C3F17D /* FirmwareVersionTxMessage.swift */, + F8F1670B27273774001AA3D8 /* GlucoseBackfillRxMessage.swift */, + F8F1670D27273EA7001AA3D8 /* GlucoseDataRxMessage.swift */, + F8F971CF23A5915900C3F17D /* KeepAliveTxMessage.swift */, + F8F971CD23A5915900C3F17D /* NSData+CRC.swift */, + F8F971D323A5915900C3F17D /* PairRequestTxMessage.swift */, + F8F971CB23A5915900C3F17D /* ResetMessage.swift */, + F8F971C823A5915900C3F17D /* SensorDataRxMessage.swift */, + F8F971C923A5915900C3F17D /* SensorDataTxMessage.swift */, + F8F971C223A5915900C3F17D /* TransmitterMessage.swift */, + F8F971CE23A5915900C3F17D /* TransmitterVersionRxMessage.swift */, + F8F971C123A5915900C3F17D /* TransmitterVersionTxMessage.swift */, + F8CB59C12738206D00BA199E /* DexcomGlucoseDataTxMessage.swift */, + F8CB59C32739D1CD00BA199E /* DexcomBackfillTxMessage.swift */, ); - path = SavitzkyGolayFilter; + path = Generic; sourceTree = ""; }; - F8F7B8EE259A84A300C47B04 /* KalmanFilter */ = { + F8F7B8E9259A7A9400C47B04 /* SavitzkyGolayFilter */ = { isa = PBXGroup; children = ( + F8F7B8EA259A7B1C00C47B04 /* SavitzkyGolaySmoothableArray.swift */, ); - path = KalmanFilter; + path = SavitzkyGolayFilter; sourceTree = ""; }; F8F971AD23A5914C00C3F17D /* BluetoothPeripheral */ = { @@ -2691,6 +2745,7 @@ F8F971BB23A5915900C3F17D /* Dexcom */ = { isa = PBXGroup; children = ( + F8F1670F27274067001AA3D8 /* Generic */, F8F971BC23A5915900C3F17D /* G6 */, F8F971BE23A5915900C3F17D /* G5 */, F8F971D423A5915900C3F17D /* G4 */, @@ -2713,37 +2768,10 @@ children = ( F897E24A23FC86CF0075E0E8 /* CGMG5TransmitterDelegate.swift */, F8F971BF23A5915900C3F17D /* CGMG5Transmitter.swift */, - F8F971C023A5915900C3F17D /* G5Messages */, ); path = G5; sourceTree = ""; }; - F8F971C023A5915900C3F17D /* G5Messages */ = { - isa = PBXGroup; - children = ( - F8F971C123A5915900C3F17D /* TransmitterVersionTxMessage.swift */, - F8F971C223A5915900C3F17D /* TransmitterMessage.swift */, - F8F971C323A5915900C3F17D /* AuthRequestTxMessage.swift */, - F8F971C423A5915900C3F17D /* AuthChallengeTxMessage.swift */, - F8F971C523A5915900C3F17D /* BatteryStatusTxMessage.swift */, - F8F971C623A5915900C3F17D /* AESCrypt.m */, - F8F971C723A5915900C3F17D /* DexcomTransmitterOpCode.swift */, - F8F971C823A5915900C3F17D /* SensorDataRxMessage.swift */, - F8F971C923A5915900C3F17D /* SensorDataTxMessage.swift */, - F8F971CA23A5915900C3F17D /* AESCrypt.h */, - F8F971CB23A5915900C3F17D /* ResetMessage.swift */, - F8F971CC23A5915900C3F17D /* AuthRequestRxMessage.swift */, - F8F971CD23A5915900C3F17D /* NSData+CRC.swift */, - F8F971CE23A5915900C3F17D /* TransmitterVersionRxMessage.swift */, - F8F971CF23A5915900C3F17D /* KeepAliveTxMessage.swift */, - F8F971D023A5915900C3F17D /* FirmwareVersionTxMessage.swift */, - F8F971D123A5915900C3F17D /* BatteryStatusRxMessage.swift */, - F8F971D223A5915900C3F17D /* AuthChallengeRxMessage.swift */, - F8F971D323A5915900C3F17D /* PairRequestTxMessage.swift */, - ); - path = G5Messages; - sourceTree = ""; - }; F8F971D423A5915900C3F17D /* G4 */ = { isa = PBXGroup; children = ( @@ -3349,6 +3377,7 @@ F8A5EEC2257D18DC0085E660 /* LibreNFCDelegate.swift in Sources */, F8F9720523A5915900C3F17D /* TransmitterMessage.swift in Sources */, F816E1282439DE55009EE65B /* CGMDexomG4TransmitterDelegate.swift in Sources */, + F8F1670A2727317D001AA3D8 /* DexcomTransmitterTimeRxMessage.swift in Sources */, F8EA6C8221B723BC0082976B /* Date.swift in Sources */, F8E3A2A523D78FBD00E5E98A /* SettingsViewAppleWatchSettingsViewModel.swift in Sources */, F81FA006228E09D40028C70F /* TextsCalibration.swift in Sources */, @@ -3411,9 +3440,11 @@ F80D915C24F06A40006840B5 /* PreLibre2.swift in Sources */, 470F021326DD515300C5D626 /* SettingsViewSensorCountdownSettingsViewModel.swift in Sources */, F8F9722223A5915900C3F17D /* CRC.swift in Sources */, + F8CB59C02734976D00BA199E /* DexcomTransmitterTimeTxMessage.swift in Sources */, F821CF6F229FC280005C1E43 /* Endpoint+NightScout.swift in Sources */, F8F9722A23A5915900C3F17D /* TransmitterBatteryInfo.swift in Sources */, F821CF5D229BF43A005C1E43 /* NSDateFormatter.swift in Sources */, + F8F16715272745A2001AA3D8 /* DexcomCalibrationResponseType.swift in Sources */, F8F9721B23A5915900C3F17D /* CGMMiaoMiaoTransmitter.swift in Sources */, F8F9721A23A5915900C3F17D /* CGMBubbleTransmitter.swift in Sources */, 4752B400263570DA0081D551 /* ConstantsStatistics.swift in Sources */, @@ -3427,6 +3458,7 @@ 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 */, @@ -3457,6 +3489,7 @@ F8AF36152455C6F700B5977B /* ConstantsTrace.swift in Sources */, F8F971B723A5914D00C3F17D /* BluetoothPeripheralType.swift in Sources */, F80610C4222D4E4D00D8F236 /* ActionClosureable-extension.swift in Sources */, + F8F1670E27273EA7001AA3D8 /* GlucoseDataRxMessage.swift in Sources */, F8B3A835227F08AC004BA588 /* PickerViewController.swift in Sources */, F821CF9522ADB0D7005C1E43 /* HealthKitManager.swift in Sources */, F8B3A81D227DEC92004BA588 /* CalibrationsAccessor.swift in Sources */, @@ -3504,10 +3537,12 @@ F808D2CA240325E40084B5DB /* CGMBubbleTransmitterDelegate.swift in Sources */, F804870C2336D90200EBDDB7 /* M5Stack+CoreDataClass.swift in Sources */, F825286C2443BEDC0067AF77 /* CGMG6TransmitterDelegate.swift in Sources */, + F8CB59C22738206D00BA199E /* DexcomGlucoseDataTxMessage.swift in Sources */, F8C97854242AA70D00A09483 /* MiaoMiao+CoreDataProperties.swift in Sources */, F8F9720A23A5915900C3F17D /* DexcomTransmitterOpCode.swift in Sources */, F8AC425E21ADEBD60078C348 /* AppDelegate.swift in Sources */, F8A2BC0825DB09BE001D1E78 /* Atom+CoreDataProperties.swift in Sources */, + F8F1671927288FC6001AA3D8 /* DexcomSessionStartRxMessage.swift in Sources */, F804870D2336D90200EBDDB7 /* M5Stack+CoreDataProperties.swift in Sources */, F85FF3CD252F9FD7004E6FF1 /* SnoozeParameters+CoreDataProperties.swift in Sources */, F8F9722F23A5915900C3F17D /* M5StackPacket.swift in Sources */, @@ -3517,6 +3552,7 @@ F8297F4F238DCAD800D74D66 /* BluetoothPeripheralNavigationController.swift in Sources */, F8A1585322EDB602007F5B5D /* ConstantsBloodGlucose.swift in Sources */, F85FB769255DE14600D1C39E /* ConstantsLibreSmoothing.swift in Sources */, + F8F1671B272B3E4F001AA3D8 /* DexcomBackfillStream.swift in Sources */, F8DF766023E38FC100063910 /* BLEPeripheral+CoreDataClass.swift in Sources */, F80ED2EE236F68F90005C035 /* SettingsViewM5StackWiFiSettingsViewModel.swift in Sources */, F8FDD6CB2553385000625B49 /* Array.swift in Sources */, @@ -3539,6 +3575,7 @@ F8E3A2A923D906C200E5E98A /* WatchManager.swift in Sources */, F8B3A80A227A3D11004BA588 /* TextsAlertTypeSettings.swift in Sources */, F85FF39125288870004E6FF1 /* HouseKeeper.swift in Sources */, + F8CB59C42739D1CD00BA199E /* DexcomBackfillTxMessage.swift in Sources */, F8F9721823A5915900C3F17D /* CGMBlueReaderTransmitter.swift in Sources */, F8252867243E50FE0067AF77 /* ConstantsLibre.swift in Sources */, F8A1586D22EDB9BE007F5B5D /* ConstantsDexcomFollower.swift in Sources */, @@ -3563,11 +3600,13 @@ F821CF7B22A1D359005C1E43 /* NightScoutFollowerDelegate.swift in Sources */, F8F9721523A5915900C3F17D /* PairRequestTxMessage.swift in Sources */, F8BECB12235CEA9B0060DAE1 /* TimeInterval.swift in Sources */, + F8F1670C27273774001AA3D8 /* GlucoseBackfillRxMessage.swift in Sources */, F8F9720823A5915900C3F17D /* BatteryStatusTxMessage.swift in Sources */, F81F9FFC2288C7530028C70F /* NewAlertSettingsViewController.swift in Sources */, F80D916524F5B3DE006840B5 /* Libre2+CoreDataClass.swift in Sources */, F898EDF2234A8A0500BFB79B /* UInt8.swift in Sources */, F8DF766423E781C100063910 /* BLEPeripheralAccessor.swift in Sources */, + F8F1671727288B24001AA3D8 /* DexcomSessionStopRxMessage.swift in Sources */, F816E12124392D40009EE65B /* DropletBluetoothPeripheralViewModel.swift in Sources */, F8A5EEB8257CF2940085E660 /* TextsLibreNFC.swift in Sources */, F816E1082437E5B9009EE65B /* BlueReader+BluetoothPeripheral.swift in Sources */, @@ -3625,6 +3664,7 @@ F8F9720623A5915900C3F17D /* AuthRequestTxMessage.swift in Sources */, F8F9721123A5915900C3F17D /* KeepAliveTxMessage.swift in Sources */, F8B3A81E227DEC92004BA588 /* BgReadingsAccessor.swift in Sources */, + F8F1671327274557001AA3D8 /* DexcomCalibrationRxMessage.swift in Sources */, F8E51D63244B3386001C9E5A /* MiaoMiaoResponseType.swift in Sources */, F821CF6B229FC22D005C1E43 /* Endpoint.swift in Sources */, F821CF58229BF43A005C1E43 /* AlertManager.swift in Sources */, diff --git a/xdrip/BluetoothPeripheral/Generic/BluetoothPeripheralType.swift b/xdrip/BluetoothPeripheral/Generic/BluetoothPeripheralType.swift index c0c50e0ae..9d2b1023e 100644 --- a/xdrip/BluetoothPeripheral/Generic/BluetoothPeripheralType.swift +++ b/xdrip/BluetoothPeripheral/Generic/BluetoothPeripheralType.swift @@ -222,13 +222,6 @@ enum BluetoothPeripheralType: String, CaseIterable { return Texts_ErrorMessages.DexcomTransmitterIDInvalidCharacters } - // reject transmitters with id in range 8G or higher. These are Firefly's - // convert to upper - let transmitterIdUpper = transmitterId.uppercased() - if transmitterIdUpper.compare("8G") == .orderedDescending { - return Texts_SettingsView.transmitterId8OrHigherNotSupported - } - // validation successful return nil diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5Transmitter.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5Transmitter.swift index 478a9d99a..969cf27c6 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5Transmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5Transmitter.swift @@ -11,44 +11,53 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { /// created public because inheriting classes need it var firmware:String? + /// G5 or G6 age - only used internally, if nil then it was never received + /// + /// created public because inheriting classes need it + var transmitterStartDate: Date? + /// CGMG5TransmitterDelegate public weak var cGMG5TransmitterDelegate: CGMG5TransmitterDelegate? // MARK: UUID's - // advertisement + /// advertisement let CBUUID_Advertisement_G5 = "0000FEBC-0000-1000-8000-00805F9B34FB" - // service + /// service let CBUUID_Service_G5 = "F8083532-849E-531C-C594-30F1F86A4EA5" - // characteristic uuids (created them in an enum as there's a lot of them, it's easy to switch through the list) + /// characteristic uuids (created them in an enum as there's a lot of them, it's easy to switch through the list) private enum CBUUID_Characteristic_UUID:String, CustomStringConvertible { - // Read/Notify characteristic + /// Read/Notify characteristic case CBUUID_Communication = "F8083533-849E-531C-C594-30F1F86A4EA5" - // Write/Indicate - write characteristic + /// Write/Indicate - write characteristic case CBUUID_Write_Control = "F8083534-849E-531C-C594-30F1F86A4EA5" - // Read/Write/Indicate - Read Characteristic + /// Read/Write/Indicate - Read Characteristic case CBUUID_Receive_Authentication = "F8083535-849E-531C-C594-30F1F86A4EA5" - // Read/Write/Notify + /// Read/Write/Notify case CBUUID_Backfill = "F8083536-849E-531C-C594-30F1F86A4EA5" - /// for logging, returns a readable name for the characteristic + //// for logging, returns a readable name for the characteristic var description: String { switch self { case .CBUUID_Communication: return "Communication" + case .CBUUID_Write_Control: return "Write_Control" + case .CBUUID_Receive_Authentication: return "Receive_Authentication" + case .CBUUID_Backfill: return "Backfill" + } } } @@ -61,17 +70,17 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { /// the receive and authentication Characteristic private var receiveAuthenticationCharacteristic:CBCharacteristic? - /// the communication Characteristic + /// the communication Characteristic (not used) private var communicationCharacteristic:CBCharacteristic? /// the backfill Characteristic private var backfillCharacteristic:CBCharacteristic? - //timestamp of last reading - private var timeStampOfLastG5Reading:Date + /// - timestamp of last reading received during previous session + private var timeStampOfLastG5Reading = Date(timeIntervalSince1970: 0) - //timestamp of transmitterReset - private var timeStampTransmitterReset:Date + /// last GlucoseData read in sensorDataRx message + private var lastGlucoseInSensorDataRxReading: GlucoseData? /// transmitterId private let transmitterId:String @@ -83,20 +92,33 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryCGMG5) /// is G5 reset necessary or not - private var G5ResetRequested:Bool + private var G5ResetRequested = false /// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send var emptyArray: [GlucoseData] = [] - // for creating testreadings + /// for creating testreadings private var testAmount:Double = 150000.0 - /// true if pairing request was done, and waiting to see if pairing was done + //// true if pairing request was done, and waiting to see if pairing was done private var waitingPairingConfirmation = false - // to swap between request firmware or battery - var requestFirmware = true - + /// to swap between request firmware or battery + private var requestFirmware = true + + /// - backFillStream, used while receiving backfill data from transmitter + /// - will be processed when transmitter disconnects + private var backFillStream = DexcomBackfillStream() + + /// when was sensor start time read the last time. Initialize to 0, means at app start it will be read + private var timeStampLastSensorStartTimeRead = Date(timeIntervalSince1970: 0) + + /// to be used in firefly flow - glucoseTx must be sent before glucoseBackfillTx + private var glucoseTxSent = false + + /// to be used in firefly flow - backfillTxSent should be sent only once per connection setting + private var backfillTxSent = false + // MARK: - functions /// - parameters: @@ -106,7 +128,8 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { /// - bluetoothTransmitterDelegate : a NluetoothTransmitterDelegate /// - cGMTransmitterDelegate : a CGMTransmitterDelegate /// - cGMG5TransmitterDelegate : a CGMG5TransmitterDelegate - init(address:String?, name: String?, transmitterID:String, bluetoothTransmitterDelegate: BluetoothTransmitterDelegate, cGMG5TransmitterDelegate: CGMG5TransmitterDelegate, cGMTransmitterDelegate:CGMTransmitterDelegate) { + /// - transmitterStartDate : transmitter start date, optinoal + init(address:String?, name: String?, transmitterID:String, bluetoothTransmitterDelegate: BluetoothTransmitterDelegate, cGMG5TransmitterDelegate: CGMG5TransmitterDelegate, cGMTransmitterDelegate:CGMTransmitterDelegate, transmitterStartDate: Date?, firmware: String?) { // assign addressname and name or expected devicename var newAddressAndName:BluetoothTransmitter.DeviceAddressAndName = BluetoothTransmitter.DeviceAddressAndName.notYetConnected(expectedName: "DEXCOM" + transmitterID[transmitterID.index(transmitterID.startIndex, offsetBy: 4)..= 8G then considered to be a Firefly + public func isFireFly() -> Bool { + + return transmitterId.uppercased().compare("8G") == .orderedDescending + + } + // MARK: - deinit deinit { @@ -198,7 +225,10 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { bluetoothTransmitterDelegate?.pairingFailed() } - + + // disconnect seems best moment to send stored glucose data (backfill dand and received last glucose value) to delegate + sendGlucoseDataToDelegate() + // setting characteristics to nil, they will be reinitialized at next connect writeControlCharacteristic = nil receiveAuthenticationCharacteristic = nil @@ -211,7 +241,10 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { super.peripheral(peripheral, didUpdateNotificationStateFor: characteristic, error: error) - trace("in peripheralDidUpdateNotificationStateFor", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + // get characteristic description and trace + var characteristicDescription = characteristic.uuid.uuidString + if let characteristic = CBUUID_Characteristic_UUID(rawValue: characteristic.uuid.uuidString) { characteristicDescription = characteristic.description} + trace("in peripheralDidUpdateNotificationStateFor. characteristic = ", log: log, category: ConstantsLog.categoryCGMG5, type: .info, characteristicDescription) if let error = error { trace(" error: %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .error , error.localizedDescription) @@ -223,7 +256,18 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { switch characteristicValue { + case .CBUUID_Receive_Authentication: + + sendAuthRequestTxMessage() + + break + + case .CBUUID_Backfill: + + break + case .CBUUID_Write_Control: + if (G5ResetRequested) { // send ResetTxMessage sendG5Reset() @@ -232,16 +276,30 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { G5ResetRequested = false } else { - // send SensorTxMessage to transmitter - getSensorData() + + // is it a firefly ? + // if no treat it as a G5, with own calibration + if !isFireFly() { + + // send SensorTxMessage to transmitter + getSensorData() + + return + + } + + // it a transmitter with transmitterid >= 8G + // continue with the firefly message flow + fireflyMessageFlow() + } - case .CBUUID_Receive_Authentication: - //send AuthRequestTxMessage - sendAuthRequestTxMessage() + break + + case .CBUUID_Communication: - default: break + } } else { trace(" characteristicValue is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) @@ -264,112 +322,127 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { trace("error: %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .error , error.localizedDescription) } - if let value = characteristic.value { + switch characteristic_UUID { + + case .CBUUID_Backfill: - //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) { + trace(" storing received value on backFillStream", log: log, category: ConstantsLog.categoryCGMG5, type: .info, characteristic_UUID.description) + + // unwrap characteristic value + guard let value = characteristic.value else {return} + + // push value to backFillStream + backFillStream.push(value) + + + default: + + if let value = characteristic.value { + + //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 { - // if not paired, then send message to delegate - if !authChallengeRxMessage.paired { - - trace(" transmitter needs pairing, calling sendKeepAliveMessage", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + case .authChallengeRx: + if let authChallengeRxMessage = AuthChallengeRxMessage(data: value) { - // will send keep alive message - sendKeepAliveMessage() - - // delegate needs to be informed that pairing is needed - bluetoothTransmitterDelegate?.transmitterNeedsPairing(bluetoothTransmitter: self) + // if not paired, then send message to delegate + if !authChallengeRxMessage.paired { + + trace(" transmitter needs pairing, calling sendKeepAliveMessage", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + // will send keep alive message + sendKeepAliveMessage() + + // delegate needs to be informed that pairing is needed + bluetoothTransmitterDelegate?.transmitterNeedsPairing(bluetoothTransmitter: self) + + } else { + + // 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) + } + + // subscribe to backfillCharacteristic + if let backfillCharacteristic = backfillCharacteristic { + + setNotifyValue(true, for: backfillCharacteristic) + + } else { + + trace(" backfillCharacteristic is nil, can not set notifyValue", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + } + + } } else { + trace(" failed to create authChallengeRxMessage", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + } + + case .authRequestRx: + if let authRequestRxMessage = AuthRequestRxMessage(data: value), let receiveAuthenticationCharacteristic = receiveAuthenticationCharacteristic { - // 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) + guard let challengeHash = CGMG5Transmitter.computeHash(transmitterId, of: authRequestRxMessage.challenge) else { + trace(" failed to calculate challengeHash, no further processing", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + return } + let authChallengeTxMessage = AuthChallengeTxMessage(challengeHash: challengeHash) + + trace("sending authChallengeTxMessage with data %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, authChallengeTxMessage.data.hexEncodedString()) + + _ = writeDataToPeripheral(data: authChallengeTxMessage.data, characteristicToWriteTo: receiveAuthenticationCharacteristic, type: .withResponse) + + } else { + trace(" writeControlCharacteristic is nil or authRequestRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) } - } else { - trace(" failed to create authChallengeRxMessage", log: log, category: ConstantsLog.categoryCGMG5, type: .info) - } - - case .authRequestRx: - if let authRequestRxMessage = AuthRequestRxMessage(data: value), let receiveAuthenticationCharacteristic = receiveAuthenticationCharacteristic { + case .sensorDataRx: - guard let challengeHash = CGMG5Transmitter.computeHash(transmitterId, of: authRequestRxMessage.challenge) else { - trace(" failed to calculate challengeHash, no further processing", log: log, category: ConstantsLog.categoryCGMG5, type: .error) - return + // if this is the first sensorDataRx after a successful pairing, then inform delegate that pairing is finished + if waitingPairingConfirmation { + waitingPairingConfirmation = false + bluetoothTransmitterDelegate?.successfullyPaired() } - let authChallengeTxMessage = AuthChallengeTxMessage(challengeHash: challengeHash) - _ = writeDataToPeripheral(data: authChallengeTxMessage.data, characteristicToWriteTo: receiveAuthenticationCharacteristic, type: .withResponse) - - } else { - trace(" writeControlCharacteristic is nil or authRequestRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) - } - - case .sensorDataRx: - - // if this is the first sensorDataRx after a successful pairing, then inform delegate that pairing is finished - if waitingPairingConfirmation { - waitingPairingConfirmation = false - bluetoothTransmitterDelegate?.successfullyPaired() - } - - if let sensorDataRxMessage = SensorDataRxMessage(data: value) { - - // should we request firmware or battery level - if !requestFirmware { + if let sensorDataRxMessage = SensorDataRxMessage(data: value) { - // request battery level now, next time request firmware - requestFirmware = true - - // transmitterversion was already recceived, let's see if we need to get the batterystatus - if Date() > Date(timeInterval: ConstantsDexcomG5.batteryReadPeriodInHours * 60 * 60, since: UserDefaults.standard.timeStampOfLastBatteryReading != nil ? UserDefaults.standard.timeStampOfLastBatteryReading! : Date(timeIntervalSince1970: 0)) { - trace(" last battery reading was long time ago, requesting now", log: log, category: ConstantsLog.categoryCGMG5, type: .info) - if let writeControlCharacteristic = writeControlCharacteristic { - _ = writeDataToPeripheral(data: BatteryStatusTxMessage().data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse) + // should we request firmware or battery level + if !requestFirmware { + + // request battery level now, next time request firmware + requestFirmware = true + + // transmitterversion was already received, let's see if we need to get the batterystatus + // and if not, then disconnect + if !batteryStatusRequested() { - // UserDefaults.standard.timeStampOfLastBatteryReading value in userdefaults will be set implicitly because the cgmTransmitterDelegate is also storing the transmitterbatteryinfo, which updates the timeStampOfLastBatteryReading in the UserDefaults - + disconnect() - } else { + } + + } else { + + // request firmware now, next time request battery level + requestFirmware = false + + if firmware == nil { - trace(" writeControlCharacteristic is nil, can not send BatteryStatusTxMessage", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + sendTransmitterVersionTxMessage() } - } else { - disconnect() } - } else { - // request firmware now, next time request battery level - requestFirmware = false - - if firmware == nil { - - - } - } - - //if reset was done recently, less than 5 minutes ago, then ignore the reading - if Date() < Date(timeInterval: 5 * 60, since: timeStampTransmitterReset) { - trace(" last transmitter reset was less than 5 minutes ago, ignoring this reading", log: log, category: ConstantsLog.categoryCGMG5, type: .info) - //} else if sensorDataRxMessage.unfiltered == 0.0 { - // trace(" sensorDataRxMessage.unfiltered = 0.0, ignoring this reading", log: log, category: ConstantsLog.categoryCGMG5, type: .info) - } else { - if Date() < Date(timeInterval: 60, since: timeStampOfLastG5Reading) { + if Date() < Date(timeInterval: ConstantsDexcomG5.minimumTimeBetweenTwoReadings, since: timeStampOfLastG5Reading) { // should probably never come here because this check is already done at connection time - trace(" last reading was less than 1 minute ago, ignoring", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + trace(" last reading was less than %{public}@ minutes ago, ignoring", log: log, category: ConstantsLog.categoryCGMG5, type: .info, ConstantsDexcomG5.minimumTimeBetweenTwoReadings.minutes.description) } else { @@ -381,7 +454,7 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { cgmTransmitterDelegate?.errorOccurred(xDripError: DexcomError.receivedEnfilteredValue2096896) } else { - + timeStampOfLastG5Reading = Date() let glucoseData = GlucoseData(timeStamp: sensorDataRxMessage.timestamp, glucoseLevelRaw: scaleRawValue(firmwareVersion: firmware, rawValue: sensorDataRxMessage.unfiltered)) @@ -389,67 +462,105 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { var glucoseDataArray = [glucoseData] cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorTimeInMinutes: nil) - + } } + + } else { + trace(" sensorDataRxMessagee is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) } - } else { - trace(" sensorDataRxMessagee is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) - } - - case .resetRx: - - processResetRxMessage(value: value) - - case .batteryStatusRx: - - processBatteryStatusRxMessage(value: value) - - case .transmitterVersionRx: - - processTransmitterVersionRxMessage(value: value) - - case .keepAliveRx: - - // seems no processing is necessary, now the user should get a pairing requeset - break - - case .paireRequestRx: - - // 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 - waitingPairingConfirmation = true - - // setNotifyValue - if let writeControlCharacteristic = writeControlCharacteristic { - setNotifyValue(true, for: writeControlCharacteristic) - } else { - trace(" writeControlCharacteristic is nil, can not set notifyValue", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + case .resetRx: + + processResetRxMessage(value: value) + + case .batteryStatusRx: + + processBatteryStatusRxMessage(value: value) + + // if firefly continue with the firefly message flow + if isFireFly() { fireflyMessageFlow() } + + case .transmitterVersionRx: + + processTransmitterVersionRxMessage(value: value) + + // if firefly continue with the firefly message flow + if isFireFly() { fireflyMessageFlow() } + + case .keepAliveRx: + + // seems no processing is necessary, now the user should get a pairing requeset + break + + case .pairRequestRx: + + // 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 + waitingPairingConfirmation = true + + // setNotifyValue + if let writeControlCharacteristic = writeControlCharacteristic { + setNotifyValue(true, for: writeControlCharacteristic) + } else { + trace(" writeControlCharacteristic is nil, can not set notifyValue", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + } + + case .transmitterTimeRx: + + processTransmitterTimeRxMessage(value: value) + + // if firefly continue with the firefly message flow + if isFireFly() { fireflyMessageFlow() } + + case .glucoseBackfillRx: + + processGlucoseBackfillRxMessage(value: value) + + // if firefly continue with the firefly message flow + if isFireFly() { fireflyMessageFlow() } + + case .glucoseRx: + + processGlucoseDataRxMessage(value: value) + + // if firefly continue with the firefly message flow + if isFireFly() { fireflyMessageFlow() } + + case .calibrateGlucoseRx: + + processCalibrateGlucoseRxMessage(value: value) + + case .sessionStopRx: + + processSessionStopRxMessage(value: value) + + default: + trace(" unknown opcode received ", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + break } - - default: - trace(" unknown opcode received ", log: log, category: ConstantsLog.categoryCGMG5, type: .error) - break + } else { + trace(" value doesn't start with a known opcode = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .error, firstByte) } } else { - trace(" value doesn't start with a known opcode = %{public}d", log: log, category: ConstantsLog.categoryCGMG5, type: .error, firstByte) + trace(" characteristic.value is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) } - } else { - trace(" characteristic.value is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) } + + break + } } override func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { - if Date() < Date(timeInterval: 60, since: timeStampOfLastG5Reading) { - // will probably never come here because reconnect doesn't happen with scanning, hence diddiscover will never be called excep the very first time that an app tries to connect to a G5 - trace("diddiscover peripheral, but last reading was less than 1 minute ago, will ignore", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + 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) } else { super.centralManager(central, didDiscover: peripheral, advertisementData: advertisementData, rssi: RSSI) } @@ -461,8 +572,8 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { // not calling super.didconnect here // if last reading was less than a minute ago, then no need to continue, otherwise continue with process by calling super.centralManager(central, didConnect: peripheral) - if Date() < Date(timeInterval: 60, since: timeStampOfLastG5Reading) { - trace("connected, but last reading was less than 1 minute ago", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + if Date() < Date(timeInterval: ConstantsDexcomG5.minimumTimeBetweenTwoReadings, since: timeStampOfLastG5Reading) { + 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) @@ -485,6 +596,7 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { } if let characteristics = service.characteristics { + for characteristic in characteristics { if let characteristicValue = CBUUID_Characteristic_UUID(rawValue: characteristic.uuid.uuidString) { @@ -502,8 +614,11 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { communicationCharacteristic = characteristic case .CBUUID_Receive_Authentication: + receiveAuthenticationCharacteristic = characteristic - trace(" calling setNotifyValue true", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + trace(" calling setNotifyValue true for characteristic %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, CBUUID_Characteristic_UUID.CBUUID_Receive_Authentication.description) + peripheral.setNotifyValue(true, for: characteristic) } @@ -555,40 +670,140 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { } - // MARK:- helper functions + // MARK: helper functions /// sends SensorTxMessage to transmitter private func getSensorData() { - trace("sending getsensordata", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + trace("trying to send SensorDataTxMessage", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + if let writeControlCharacteristic = writeControlCharacteristic { + _ = writeDataToPeripheral(data: SensorDataTxMessage().data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse) + } else { - trace(" writeControlCharacteristic is nil, not getsensordata", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + trace(" writeControlCharacteristic is nil, not sending SensorDataTxMessage", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + } } + /// sends G5 reset to transmitter private func sendG5Reset() { - trace("in sendG5Reset", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + let resetTxMessage = ResetTxMessage() + if let writeControlCharacteristic = writeControlCharacteristic { - _ = writeDataToPeripheral(data: ResetTxMessage().data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse) + + trace("sending resetTxMessage with data %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, resetTxMessage.data.hexEncodedString()) + + _ = writeDataToPeripheral(data: resetTxMessage.data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse) + G5ResetRequested = false + } else { + trace(" writeControlCharacteristic is nil, not sending G5 reset", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + } } + /// sends transmitterTimeTxMessage to transmitter (also used to request sensor start time, because the response includes both transmitter and sensor start date) + private func sendTransmitterTimeTxMessage() { + + let transmitterTimeTxMessage = DexcomTransmitterTimeTxMessage() + + if let writeControlCharacteristic = writeControlCharacteristic { + + trace("sending transmitterTimeTxMessage with data %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, transmitterTimeTxMessage.data.hexEncodedString()) + + _ = writeDataToPeripheral(data: transmitterTimeTxMessage.data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse) + + } else { + + trace("writeControlCharacteristic is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + } + + } + + /// sends glucoseTxMessage to transmitter + private func sendGlucoseTxMessage() { + + let glucoseDataTxMessage = DexcomGlucoseDataTxMessage() + + if let writeControlCharacteristic = writeControlCharacteristic { + + trace("sending glucoseDataTxMessage with data %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, glucoseDataTxMessage.data.hexEncodedString()) + + _ = writeDataToPeripheral(data: glucoseDataTxMessage.data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse) + + } else { + + trace("writeControlCharacteristic is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + } + + } + + /// sends backfillTxMessage to transmitter + private func sendBackfillTxMessage(startTime: Date, endTime: Date, transmitterStartDate: Date) { + + let backfillTxMessage = DexcomBackfillTxMessage(startTime: startTime, endTime: endTime, transmitterStartDate: transmitterStartDate) + + if let writeControlCharacteristic = writeControlCharacteristic { + + trace("sending backfillTxMessage with startTime %{public}@, endTime %{public}@, data %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, startTime.toString(timeStyle: .long, dateStyle: .none), endTime.toString(timeStyle: .long, dateStyle: .none), backfillTxMessage.data.hexEncodedString()) + + _ = writeDataToPeripheral(data: backfillTxMessage.data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse) + + } else { + + trace("writeControlCharacteristic is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + } + + } + /// sends AuthRequestTxMessage to transmitter private func sendAuthRequestTxMessage() { + let authMessage = AuthRequestTxMessage() + if let receiveAuthenticationCharacteristic = receiveAuthenticationCharacteristic { + + trace("sending authMessage with data %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, authMessage.data.hexEncodedString()) + _ = writeDataToPeripheral(data: authMessage.data, characteristicToWriteTo: receiveAuthenticationCharacteristic, type: .withResponse) + } else { + trace("receiveAuthenticationCharacteristic is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + } } + private func sendTransmitterVersionTxMessage() { + + let transmitterVersionTxMessage = TransmitterVersionTxMessage() + + if let writeControlCharacteristic = writeControlCharacteristic { + + trace("sending transmitterVersionTxMessage with data %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, transmitterVersionTxMessage.data.hexEncodedString()) + + _ = writeDataToPeripheral(data: transmitterVersionTxMessage.data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse) + + } else { + + trace(" writeControlCharacteristic is nil, can not send TransmitterVersionTxMessage", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + } + + } + private func processResetRxMessage(value:Data) { + if let resetRxMessage = ResetRxMessage(data: value) { trace("in processResetRxMessage, considering reset successful = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, (resetRxMessage.status == 0).description) @@ -596,14 +811,20 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { cGMG5TransmitterDelegate?.reset(for: self, successful: resetRxMessage.status == 0 ) } else { + trace("resetRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + } + } + /// process batteryStatusRxMessage private func processBatteryStatusRxMessage(value:Data) { if let batteryStatusRxMessage = BatteryStatusRxMessage(data: value) { + trace("in processBatteryStatusRxMessage, voltageA = %{public}@, voltageB = %{public}@, resist = %{public}@, runtime = %{public}@, temperature = %{public}@, status = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, batteryStatusRxMessage.voltageA.description, batteryStatusRxMessage.voltageB.description, batteryStatusRxMessage.resist.description, batteryStatusRxMessage.runtime.description, batteryStatusRxMessage.temperature.description, batteryStatusRxMessage.status.description) + // cGMG5TransmitterDelegate for showing info on bluetoothviewcontroller and store in coredata cGMG5TransmitterDelegate?.received(transmitterBatteryInfo: TransmitterBatteryInfo.DexcomG5(voltageA: batteryStatusRxMessage.voltageA, voltageB: batteryStatusRxMessage.voltageB, resist: batteryStatusRxMessage.resist, runtime: batteryStatusRxMessage.runtime, temperature: batteryStatusRxMessage.temperature), cGMG5Transmitter: self) @@ -617,13 +838,140 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { } } + + /// process glucoseBackfillRxMessage + private func processGlucoseBackfillRxMessage(value:Data) { + + if let transmitterStartDate = transmitterStartDate, let glucoseBackFillRxMessage = GlucoseBackfillRxMessage(data: value, transmitterStartDate: transmitterStartDate) { + + trace("in processGlucoseBackfillRxMessage. backFillStartTimeStamp = %{public}@, backFillEndTimeStamp = %{public}@, backFillIdentifier = %{public}@, backFillStatus = %{public}@, transmitterStatus = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, glucoseBackFillRxMessage.backFillStartTimeStamp.toString(timeStyle: .short, dateStyle: .short), glucoseBackFillRxMessage.backFillEndTimeStamp.toString(timeStyle: .short, dateStyle: .short), glucoseBackFillRxMessage.backFillIdentifier.description, glucoseBackFillRxMessage.backFillStatus.description, glucoseBackFillRxMessage.transmitterStatus.description) + + } else { + + trace("backFillRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + } + + } + /// process sessionStopRxMessage + private func processSessionStopRxMessage(value: Data) { + + 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) + + } else { + trace("dexcomSessionStopRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + } + + } + + /// processSessionStartRxMessage + 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, + dexcomSessionStartRxMessage.requestedStartTime.description, + dexcomSessionStartRxMessage.sessionStartTime.description, + dexcomSessionStartRxMessage.status.description, + dexcomSessionStartRxMessage.transmitterTime.description) + + } else { + trace("dexcomSessionStartRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + } + + } + + /// process CalibrateGlucoseRxMessage + private func processCalibrateGlucoseRxMessage(value: Data) { + + if let dexcomCalibrationRxMessage = DexcomCalibrationRxMessage(data: value) { + + var type = "unknown" + + if let dexcomCalibrationResponseType = dexcomCalibrationRxMessage.type { + type = dexcomCalibrationResponseType.description + } + + trace("in processCalibrateGlucoseRxMessage, received dexcomCalibrationRxMessage, accepted = %{public}@, type = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, dexcomCalibrationRxMessage.accepted.description, type) + + } else { + trace("dexcomCalibrationRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + } + + } + + /// process glucoseRxMessage + private func processGlucoseDataRxMessage(value: Data) { + + if let glucoseDataRxMessage = GlucoseDataRxMessage(data: value) { + + trace("in processGlucoseDataRxMessage, received glucoseDataRxMessage, value = %{public}@, algorithm state = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, glucoseDataRxMessage.calculatedValue.description, glucoseDataRxMessage.status.description) + + // create glucose data and assign to lastGlucoseInSensorDataRxReading + lastGlucoseInSensorDataRxReading = GlucoseData(timeStamp: Date(), glucoseLevelRaw: glucoseDataRxMessage.calculatedValue) + + // don't send reading to delegate, will be done when transmitter disconnects, then we're sure we also received al necessary backfill data + + } else { + + trace("glucoseDataRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + } + + } + + /// process transmitterTimeRxMessage + private func processTransmitterTimeRxMessage(value:Data) { + + if let transmitterTimeRxMessage = DexcomTransmitterTimeRxMessage(data: value) { + + trace("in processTransmitterTimeRxMessage", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + if let sensorStartDate = transmitterTimeRxMessage.sensorStartDate { + + trace(" sensorStartDate = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, sensorStartDate.toString(timeStyle: .long, dateStyle: .long)) + + // send to delegate + cGMG5TransmitterDelegate?.received(sensorStartDate: sensorStartDate, cGMG5Transmitter: self) + + timeStampLastSensorStartTimeRead = Date() + + } else { + trace(" sensorStartDate is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + } + + // assign transmitterStartDate to local var + transmitterStartDate = transmitterTimeRxMessage.transmitterStartDate + + if let transmitterStartDate = transmitterStartDate { + + // send to delegate + cGMG5TransmitterDelegate?.received(transmitterStartDate: transmitterStartDate, cGMG5Transmitter: self) + + trace(" transmitterStartDate = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, transmitterStartDate.toString(timeStyle: .long, dateStyle: .long)) + + } else { + trace(" transmitterStartDate is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + } + + } else { + trace("in processTransmitterTimeRxMessage, transmitterTimeRxMessage is nil", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + } + + } + private func processTransmitterVersionRxMessage(value:Data) { if let transmitterVersionRxMessage = TransmitterVersionRxMessage(data: value) { // assign transmitterVersion - firmware = transmitterVersionRxMessage.firmwareVersion.hexEncodedString() + firmware = transmitterVersionRxMessage.firmwareVersionFormatted() + + trace("in processTransmitterVersionRxMessage, firmware = %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, firmware!) // send to delegate cGMG5TransmitterDelegate?.received(firmware: firmware!, cGMG5Transmitter: self) @@ -676,11 +1024,222 @@ class CGMG5Transmitter:BluetoothTransmitter, CGMTransmitter { if let receiveAuthenticationCharacteristic = receiveAuthenticationCharacteristic { // to make sure the Dexcom doesn't disconnect the next 60 seconds, this gives the user sufficient time to accept the pairing request, which will come next - _ = writeDataToPeripheral(data: KeepAliveTxMessage(time: UInt8(ConstantsDexcomG5.maxTimeToAcceptPairingInSeconds)).data, characteristicToWriteTo: receiveAuthenticationCharacteristic, type: .withResponse) + _ = writeDataToPeripheral(data: KeepAliveTxMessage(time: UInt8(ConstantsDexcomG5.maxTimeToAcceptPairing)).data, characteristicToWriteTo: receiveAuthenticationCharacteristic, type: .withResponse) } else { trace(" in sendKeepAliveMessage, receiveAuthenticationCharacteristic is nil, can not send KeepAliveTxMessage", log: log, category: ConstantsLog.categoryCGMG5, type: .error) } } + + /// - checks if battery status needs to be requested to tranmsitter (depends on when it was last updated), and if yes requests it (ie sends message BatteryStatusTxMessage) + /// - returns: + /// - true if batter status requested, otherwise false + private func batteryStatusRequested() -> Bool { + + if Date() > Date(timeInterval: ConstantsDexcomG5.batteryReadPeriod, since: UserDefaults.standard.timeStampOfLastBatteryReading != nil ? UserDefaults.standard.timeStampOfLastBatteryReading! : Date(timeIntervalSince1970: 0)) { + trace(" last battery reading was long time ago, requesting now", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + if let writeControlCharacteristic = writeControlCharacteristic { + _ = writeDataToPeripheral(data: BatteryStatusTxMessage().data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse) + + return true + + } else { + + trace(" writeControlCharacteristic is nil, can not send BatteryStatusTxMessage", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + return false + + } + + } else { + + return false + + } + + } + + /// - if firefly then calls setNotifyValue(true) for both writeControlCharacteristic and backfillCharacteristic + /// - if not firefly then only calls setNotifyValue(true) for both writeControlCharacteristic + private func subscribeToWriteControlAndBackFillCharacteristic() { + + // subscribe to writeControlCharacteristic + if let writeControlCharacteristic = writeControlCharacteristic { + + trace(" calling setNotifyValue true for characteristic %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, CBUUID_Characteristic_UUID.CBUUID_Write_Control.description) + + setNotifyValue(true, for: writeControlCharacteristic) + + } else { + + trace(" writeControlCharacteristic is nil, can not set notifyValue", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + } + + // if firefly, then subscribe to backfillCharacteristic + if isFireFly(), let backfillCharacteristic = backfillCharacteristic { + + trace(" calling setNotifyValue true for characteristic %{public}@", log: log, category: ConstantsLog.categoryCGMG5, type: .info, CBUUID_Characteristic_UUID.CBUUID_Backfill.description) + + setNotifyValue(true, for: backfillCharacteristic) + + } else { + + trace(" backfillCharacteristic is nil, can not set notifyValue", log: log, category: ConstantsLog.categoryCGMG5, type: .error) + + } + + } + + /// - verifies what is the next message to send to the firefly (is it battery request, firmware request, etc... + /// - and sends that message + private func fireflyMessageFlow() { + + // first of all check that the transmitter is really a firefly, if not stop processing + if !isFireFly() { return } + + // treat it as a firefly, ie using the calibration on the sensor + guard firmware != nil else { + + sendTransmitterVersionTxMessage() + + return + + } + + // 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 !batteryStatusRequested() { + + // check if transmitterStartDate is known, and if we've recently requested the sensorStartTime + if let transmitterStartDate = transmitterStartDate, Date() < Date(timeInterval: ConstantsDexcomG5.sensorStartTimeReadPeriod, since: timeStampLastSensorStartTimeRead) { + + // if glucoseTx was not yet sent and minimumTimeBetweenTwoReadings smaller than now - timeStampOfLastG5Reading (for safety) + 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) + + // 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 { + + // send backfillTxMessage + // start time = timeStampOfLastG5Reading, maximum maxBackfillPeriod + // end time = now + sendBackfillTxMessage(startTime: max(timeStampOfLastG5Reading, Date(timeIntervalSinceNow: -ConstantsDexcomG5.maxBackfillPeriod)), endTime: Date(), transmitterStartDate: transmitterStartDate) + + // set backfillTxSent to true, to avoid that again backfillTx is sent next time we come here + backfillTxSent = true + + } else { + + trace(" end of firefly flow", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + // disconnect + trace(" will disconnect now", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + disconnect() + + } + + } else { + + // request transmitterStartDate + sendTransmitterTimeTxMessage() + + } + + } + + } + + /// - sends contents of backFillStream and lastGlucoseInSensorDataRxReading + /// - also assigns timeStampOfLastG5Reading to timestamp of lastGlucoseInSensorDataRxReading + /// - only to be used for firefly + /// - reset backFillStream, lastGlucoseInSensorDataRxReading, backfillTxSent, glucoseTxSent + private func sendGlucoseDataToDelegate() { + + guard isFireFly() else {return} + + // transmitterDate should be non nil + guard let transmitterStartDate = transmitterStartDate else { + trace("in sendGlucoseDataToDelegate but transmitterStartDate is nil, no further processing", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + return + } + + // initialize glucoseDataArray, this array will contain the glucose values in the stream + var glucoseDataArray = [GlucoseData]() + + trace("in sendGlucoseDataToDelegate", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + trace(" start processing backfillstream", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + // 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)) + + } + + // - 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 + if let lastGlucoseInSensorDataRxReading = lastGlucoseInSensorDataRxReading { + + 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 { + + trace(" calling cgmTransmitterInfoReceived with %{public}@ values", log: log, category: ConstantsLog.categoryCGMG5, type: .info, glucoseDataArray.count.description) + + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorTimeInMinutes: nil) + + } else { + + trace(" glucoseDataArray has no values", log: log, category: ConstantsLog.categoryCGMG5, type: .info) + + } + + // reset both backFillStream + backFillStream = DexcomBackfillStream() + + // reset lastGlucoseInSensorDataRxReading + lastGlucoseInSensorDataRxReading = nil + + // reset backfillTxSent + backfillTxSent = false + + // reset glucoseTxSent to false + self.glucoseTxSent = false + + } + } diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5TransmitterDelegate.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5TransmitterDelegate.swift index 54f963fcc..a3fd4a3a0 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5TransmitterDelegate.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/CGMG5TransmitterDelegate.swift @@ -4,11 +4,18 @@ protocol CGMG5TransmitterDelegate: AnyObject { /// received firmware from CGMG5Transmitter func received(firmware: String, cGMG5Transmitter: CGMG5Transmitter) - + + /// received transmitterBatteryInfo func received(transmitterBatteryInfo: TransmitterBatteryInfo, cGMG5Transmitter: CGMG5Transmitter) + /// received transmitterStartDate + func received(transmitterStartDate: Date, cGMG5Transmitter: CGMG5Transmitter) + /// transmitter reset result func reset(for cGMG5Transmitter: CGMG5Transmitter, successful: Bool) + /// received sensor start time from transmitter + func received(sensorStartDate: Date, cGMG5Transmitter: CGMG5Transmitter) + } diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/DexcomTransmitterOpCode.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/DexcomTransmitterOpCode.swift deleted file mode 100644 index 65c6cbfe0..000000000 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/DexcomTransmitterOpCode.swift +++ /dev/null @@ -1,97 +0,0 @@ -// -// Opcode.swift -// xDripG5 -// -// Copyright © 2018 LoopKit Authors. All rights reserved. -// - -import Foundation - -enum DexcomTransmitterOpCode: UInt8 { - // Auth - case authRequestTx = 0x01 - - case authRequestRx = 0x03 - case authChallengeTx = 0x04 - case authChallengeRx = 0x05 - case keepAliveTx = 0x06 // auth; setAdvertisementParametersTx for control - case bondRequestTx = 0x07 - case paireRequestRx = 0x08 // comes in after having accepted the bluetooth pairing request - - // Control - case disconnectTx = 0x09 - - case setAdvertisementParametersRx = 0x1c - - case firmwareVersionTx = 0x20 - case firmwareVersionRx = 0x21 - case batteryStatusTx = 0x22 - case batteryStatusRx = 0x23 - case transmitterTimeTx = 0x24 - case transmitterTimeRx = 0x25 - case sessionStartTx = 0x26 - case sessionStartRx = 0x27 - case sessionStopTx = 0x28 - case sessionStopRx = 0x29 - case sensorDataTx = 0x2E - case sensorDataRx = 0x2F - - case glucoseTx = 0x30 - case glucoseRx = 0x31 - case calibrationDataTx = 0x32 - case calibrationDataRx = 0x33 - case calibrateGlucoseTx = 0x34 - case calibrateGlucoseRx = 0x35 - - case glucoseHistoryTx = 0x3e - - case resetTx = 0x42 - case resetRx = 0x43 - - case transmitterVersionTx = 0x4a - case transmitterVersionRx = 0x4b - - case glucoseG6Tx = 0x4e - case glucoseG6Rx = 0x4f - - case glucoseBackfillTx = 0x50 - case glucoseBackfillRx = 0x51 - - case keepAliveRx = 0xFF // found during testing -} - - -extension Data { - init(for opcode: DexcomTransmitterOpCode) { - self.init([opcode.rawValue]) - } - - func starts(with opcode: DexcomTransmitterOpCode) -> Bool { - guard count > 0 else { - return false - } - - return self[startIndex] == opcode.rawValue - } -} - -extension DexcomTransmitterOpCode: CustomStringConvertible { - public var description: String { - switch self { - case .authRequestRx: - return "authRequestRx" - case .authChallengeRx: - return "authChallengeRx" - case .sensorDataRx: - return "sensorDataRx" - case .resetRx: - return "resetRx" - case .batteryStatusRx: - return "batteryStatusRx" - case .transmitterVersionRx: - return "transmitterVersionRx" - default: - return self.rawValue.description - } - } -} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G6/CGMG6Transmitter.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/G6/CGMG6Transmitter.swift index 85c877ccd..f03c4a034 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/G6/CGMG6Transmitter.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/G6/CGMG6Transmitter.swift @@ -2,25 +2,34 @@ import Foundation class CGMG6Transmitter: CGMG5Transmitter { - /// scaling factor for G6 firmware version 1 - private let G6v1ScalingFactor = 34.0 - /// - parameters: /// - address: if already connected before, then give here the address that was received during previous connect, if not give nil /// - transmitterID: expected transmitterID, 6 characters /// - bluetoothTransmitterDelegate : a BluetoothTransmitterDelegate /// - cGMTransmitterDelegate : a CGMTransmitterDelegate - init(address:String?, name: String?, transmitterID:String, bluetoothTransmitterDelegate: BluetoothTransmitterDelegate, cGMG6TransmitterDelegate: CGMG6TransmitterDelegate, cGMTransmitterDelegate:CGMTransmitterDelegate) { + init(address:String?, name: String?, transmitterID:String, bluetoothTransmitterDelegate: BluetoothTransmitterDelegate, cGMG6TransmitterDelegate: CGMG6TransmitterDelegate, cGMTransmitterDelegate:CGMTransmitterDelegate, transmitterStartDate: Date?, firmware: String?) { // call super.init - super.init(address: address, name: name, transmitterID: transmitterID, bluetoothTransmitterDelegate: bluetoothTransmitterDelegate, cGMG5TransmitterDelegate: cGMG6TransmitterDelegate, cGMTransmitterDelegate: cGMTransmitterDelegate) + super.init(address: address, name: name, transmitterID: transmitterID, bluetoothTransmitterDelegate: bluetoothTransmitterDelegate, cGMG5TransmitterDelegate: cGMG6TransmitterDelegate, cGMTransmitterDelegate: cGMTransmitterDelegate, transmitterStartDate: transmitterStartDate, firmware: firmware) } override func scaleRawValue(firmwareVersion: String?, rawValue: Double) -> Double { - return rawValue * G6v1ScalingFactor; - + guard let firmwareVersion = firmwareVersion else { return rawValue } + + if firmwareVersion.starts(with: "1.") { + + return rawValue * 34.0 + + } else if firmwareVersion.starts(with: "2.") { + + return (rawValue - 1151500000.0) / 110.0 + + } + + return rawValue + } override func cgmTransmitterType() -> CGMTransmitterType { diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G6Firefly/CGMG6FireflyTransmitter.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/G6Firefly/CGMG6FireflyTransmitter.swift new file mode 100644 index 000000000..cfc52b322 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/G6Firefly/CGMG6FireflyTransmitter.swift @@ -0,0 +1,131 @@ +import Foundation +import CoreBluetooth +import OSLog + +class CGMG6FireflyTransmitter:BluetoothTransmitter, CGMTransmitter { + + // MARK: - public properties + + /// CGMG5TransmitterDelegate + public weak var cGMG6FireflyTransmitterDelegate: CGMG6FireflyTransmitterDelegate? + + // MARK: UUID's + + // advertisement + let CBUUID_Advertisement_G6 = "0000FEBC-0000-1000-8000-00805F9B34FB" + + // service + let CBUUID_Service_G6 = "F8083532-849E-531C-C594-30F1F86A4EA5" + + // characteristic uuids (created them in an enum as there's a lot of them, it's easy to switch through the list) + private enum CBUUID_Characteristic_UUID:String, CustomStringConvertible { + + // Read/Notify characteristic + case CBUUID_Communication = "F8083533-849E-531C-C594-30F1F86A4EA5" + + // Write/Indicate - write characteristic + case CBUUID_Write_Control = "F8083534-849E-531C-C594-30F1F86A4EA5" + + // Read/Write/Indicate - Read Characteristic + case CBUUID_Receive_Authentication = "F8083535-849E-531C-C594-30F1F86A4EA5" + + // Read/Write/Notify + case CBUUID_Backfill = "F8083536-849E-531C-C594-30F1F86A4EA5" + + /// for logging, returns a readable name for the characteristic + var description: String { + switch self { + + case .CBUUID_Communication: + return "Communication" + case .CBUUID_Write_Control: + return "Write_Control" + case .CBUUID_Receive_Authentication: + return "Receive_Authentication" + case .CBUUID_Backfill: + return "Backfill" + } + } + } + + // MARK: other + + //timestamp of last reading + private var timeStampOfLastG6FireflyReading: Date + + //timestamp of transmitterReset + private var timeStampTransmitterReset:Date + + /// transmitterId + private let transmitterId:String + + /// for trace + private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryCGMG6Firefly) + + /// is G6 reset necessary or not + private var G6FireflyResetRequested:Bool + + /// will be used to pass back bluetooth and cgm related events + private(set) weak var cgmTransmitterDelegate:CGMTransmitterDelegate? + + /// G6 firefly transmitter firmware version - only used internally, if nil then it was never received + /// + /// created public because inheriting classes need it + private var firmware:String? + + // MARK: - functions + + /// - 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 NluetoothTransmitterDelegate + /// - cGMTransmitterDelegate : a CGMTransmitterDelegate + /// - cGMG6FireflyTransmitterDelegate : a cGMG6FireflyTransmitterDelegate + init(address:String?, name: String?, transmitterID:String, bluetoothTransmitterDelegate: BluetoothTransmitterDelegate, cGMG6FireflyTransmitterDelegate: CGMG6FireflyTransmitterDelegate, cGMTransmitterDelegate:CGMTransmitterDelegate) { + + // assign addressname and name or expected devicename + var newAddressAndName:BluetoothTransmitter.DeviceAddressAndName = BluetoothTransmitter.DeviceAddressAndName.notYetConnected(expectedName: "DEXCOM" + transmitterID[transmitterID.index(transmitterID.startIndex, offsetBy: 4)..= 3 else { + return nil + } + + guard data.starts(with: .authChallengeRx) else { + return nil + } + + authenticated = data[1] + paired = data[2] != 2 + } +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomBackfillStream.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomBackfillStream.swift new file mode 100644 index 000000000..63aa9cad9 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomBackfillStream.swift @@ -0,0 +1,100 @@ +// +// DexcomBackfillStream.swift +// xDrip +// +// Created by Ivan Skoryk on 02.11.2020. +// Copyright © 2020 Faifly. All rights reserved. +// + +import Foundation + +final class DexcomBackfillStream { + struct BackFill { + let glucose: Int + let trend: Int + let dexTime: Int + } + + var data = Data() + private var lastSequence = 0 + private var idx = 4 + + + func push(_ packet: Data) { + guard !packet.isEmpty else { return } + + let thisSequence = packet[0] + + if thisSequence == lastSequence + 1 { + lastSequence += 1 + + for idx in 2 ..< packet.count { + guard data.count < 1000 else { + return + } + data.append(packet[idx]) + } + } + } + + func decode() -> [BackFill] { + var backfills: [BackFill] = [] + + while idx < data.count { + var dexTime: UInt32 + var glucose: UInt16 + var type: UInt8 + var trend: UInt8 + + guard idx + 4 <= data.count - 1 else { + idx = 0 + break + } + dexTime = Data(data[idx ..< idx + 4]).to(UInt32.self) + idx += 4 + + guard idx + 2 <= data.count - 1 else { + idx = 0 + break + } + glucose = Data(data[idx ..< idx + 2]).to(UInt16.self) + idx += 2 + + guard idx <= data.count - 1 else { + idx = 0 + break + } + type = data[idx] + idx += 1 + + guard idx <= data.count - 1 else { + idx = 0 + break + } + trend = data[idx] + idx += 1 + + if let state = DexcomCalibrationState(rawValue: type) { + switch state { + case .okay, .needsCalibration: + if dexTime != 0 { + backfills.append( + BackFill(glucose: Int(glucose), trend: Int(trend), dexTime: Int(dexTime)) + ) + } + default: + continue + } + } + + if idx <= data.count - 1 { + data = Data(data[idx ..< data.count]) + } else { + data = Data() + } + idx = 0 + } + + return backfills + } +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomBackfillTxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomBackfillTxMessage.swift new file mode 100644 index 000000000..cab866453 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomBackfillTxMessage.swift @@ -0,0 +1,46 @@ +// +// DexcomG6BackfillTxMessage.swift +// xDrip +// +// Created by Artem Kalmykov on 24.10.2020. +// Copyright © 2020 Faifly. All rights reserved. +// + +import Foundation + +struct DexcomBackfillTxMessage: TransmitterTxMessage { + + let data: Data + + init(startTime: Date, endTime: Date, transmitterStartDate: Date) { + + let earliestTimestamp = startTime.timeIntervalSince1970 + + let latestTimestamp = endTime.timeIntervalSince1970 + + let transmitterTimestamp = transmitterStartDate.timeIntervalSince1970 + + let startTimeAsInt = Int(earliestTimestamp - TimeInterval(minutes: 5) - transmitterTimestamp) + + let endTimeAsInt = Int(latestTimestamp + TimeInterval(minutes: 5) - transmitterTimestamp) + + var array = [Int8]() + + array.append(contentsOf: [Int8(DexcomTransmitterOpCode.glucoseBackfillTx.rawValue), 0x5, 0x2, 0x0]) + + withUnsafeBytes(of: startTimeAsInt) { + array.append(contentsOf: Array($0.prefix(4 * MemoryLayout.size)).map { Int8(bitPattern: $0) }) + } + + withUnsafeBytes(of: endTimeAsInt) { + array.append(contentsOf: Array($0.prefix(4 * MemoryLayout.size)).map { Int8(bitPattern: $0) }) + } + + array.append(contentsOf: [Int8](repeating: 0, count: 6)) + + let data = array.withUnsafeBufferPointer { Data(buffer: $0) } + + self.data = data.appendingCRC() + } + +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationResponseType.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationResponseType.swift new file mode 100644 index 000000000..db611ed36 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationResponseType.swift @@ -0,0 +1,58 @@ +// +// DexcomG6CalibrationResponseType.swift +// xDrip +// +// Created by Dmitry on 29.12.2020. +// Copyright © 2020 Faifly. All rights reserved. +// + +import Foundation + +enum DexcomCalibrationResponseType: UInt8 { + case okay = 0x00 + case codeOne = 0x01 + case secondCalibrationNeeded = 0x06 + case rejected = 0x08 + case sensorStopped = 0x0B + case duplicate = 0x0D + case notReady = 0x0E + case unableToDecode = 0xFF + + var description: String { + + switch self { + + case .okay: + return "okay" + + case .codeOne: + return "codeOne" + + case .secondCalibrationNeeded: + return "secondCalibrationNeeded" + + case .rejected: + return "rejected" + + case .sensorStopped: + return "sensorStopped" + + case .duplicate: + return "duplicate" + + case .notReady: + return "notReady" + + case .unableToDecode: + return "unableToDecode" + + } + + } +} + +extension DexcomCalibrationResponseType { + static let validCollection: [DexcomCalibrationResponseType] = [ + .okay, .secondCalibrationNeeded, .duplicate + ] +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationRxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationRxMessage.swift new file mode 100644 index 000000000..02201435d --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationRxMessage.swift @@ -0,0 +1,30 @@ +// +// DexcomG6CalibrationRxMessage.swift +// xDrip +// +// Created by Dmitry on 29.12.2020. +// Copyright © 2020 Faifly. All rights reserved. +// + +import Foundation + +struct DexcomCalibrationRxMessage { + + let type: DexcomCalibrationResponseType? + + init?(data: Data) { + + guard data.count == 5 else { return nil } + + guard data.starts(with: .calibrateGlucoseRx) else { return nil } + + type = DexcomCalibrationResponseType(rawValue: data[2]) + + } + + var accepted: Bool { + + return type == .okay || type == .secondCalibrationNeeded || type == .duplicate + + } +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationState.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationState.swift new file mode 100644 index 000000000..059178f2f --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomCalibrationState.swift @@ -0,0 +1,101 @@ +// +// DexcomG6CalibrationState.swift +// xDrip +// +// Created by Ivan Skoryk on 02.11.2020. +// Copyright © 2020 Faifly. All rights reserved. +// +// update for xDrip4iOS : renamed to DexcomCalibrationState + +import Foundation + +enum DexcomCalibrationState: UInt8 { + case unknown = 0x00 + case stopped = 0x01 + case warmingUp = 0x02 + case excessNoise = 0x03 + case needsFirstCalibration = 0x04 + case needsSecondCalibration = 0x05 + case okay = 0x06 + case needsCalibration = 0x07 + case calibrationConfused = 0x08 + case calibrationConfused2 = 0x09 + case needsDifferentCalibration = 0x0a + case sensorFailed = 0x0b + case sensorFailed2 = 0x0c + case unusualCalibration = 0x0d + case insufficientCalibration = 0x0e + case ended = 0x0f + case sensorFailed3 = 0x10 + case transmitterProblem = 0x11 + case errors = 0x12 + case sensorFailed4 = 0x13 + case sensorFailed5 = 0x14 + case sensorFailed6 = 0x15 + case sensorFailedStart = 0x16 +} + +extension DexcomCalibrationState { + static let stoppedCollection: [DexcomCalibrationState] = [ + .stopped, .ended, .sensorFailed, .sensorFailed2, + .sensorFailed3, .sensorFailed4, .sensorFailed5, + .sensorFailed6, .sensorFailedStart + ] +} + +extension DexcomCalibrationState: CustomStringConvertible { + + public var description: String { + + switch self { + + case .unknown: + return "unknown" + case .stopped: + return "stopped" + case .warmingUp: + return "warmingUp" + case .excessNoise: + return "excessNoise" + case .needsFirstCalibration: + return "needsFirstCalibration" + case .needsSecondCalibration: + return "needsSecondCalibration" + case .okay: + return "okay" + case .needsCalibration: + return "needsCalibration" + case .calibrationConfused: + return "calibrationConfused" + case .calibrationConfused2: + return "calibrationConfused2" + case .needsDifferentCalibration: + return "needsDifferentCalibration" + case .sensorFailed: + return "sensorFailed" + case .sensorFailed2: + return "sensorFailed2" + case .unusualCalibration: + return "unusualCalibration" + case .insufficientCalibration: + return "insufficientCalibration" + case .ended: + return "ended" + case .sensorFailed3: + return "sensorFailed3" + case .transmitterProblem: + return "transmitterProblem" + case .errors: + return "errors" + case .sensorFailed4: + return "sensorFailed4" + case .sensorFailed5: + return "sensorFailed5" + case .sensorFailed6: + return "sensorFailed6" + case .sensorFailedStart: + return "sensorFailedStart" + + } + } +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomGlucoseDataTxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomGlucoseDataTxMessage.swift new file mode 100644 index 000000000..18b8628d7 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomGlucoseDataTxMessage.swift @@ -0,0 +1,13 @@ +import Foundation + +struct DexcomGlucoseDataTxMessage: TransmitterTxMessage { + + let data: Data + + init() { + + data = Data([DexcomTransmitterOpCode.glucoseTx.rawValue]).appendingCRC() + + } + +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartRxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartRxMessage.swift new file mode 100644 index 000000000..674a06641 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStartRxMessage.swift @@ -0,0 +1,33 @@ +// +// DexcomG6SessionStartRxMessage.swift +// xDrip +// +// Created by Dmitry on 08.01.2021. +// Copyright © 2021 Faifly. All rights reserved. +// +// adapted by Johan Degraeve + +import Foundation + +struct DexcomSessionStartRxMessage { + + let status: UInt8 + let info: UInt8 + 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} + + status = data[1] + info = data[2] + requestedStartTime = Double(Data(data[3..<7]).to(UInt32.self)) + sessionStartTime = Double(Data(data[7..<11]).to(UInt32.self)) + transmitterTime = Double(Data(data[11..<15]).to(UInt32.self)) + + } +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStopRxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStopRxMessage.swift new file mode 100644 index 000000000..c6ef53261 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomSessionStopRxMessage.swift @@ -0,0 +1,37 @@ +// +// DexcomG6SessionStopRxMessage.swift +// xDrip +// +// Created by Dmitry on 22.03.2021. +// Copyright © 2021 Faifly. All rights reserved. +// +// adapted by Johan Degraeve + +import Foundation + +struct DexcomSessionStopRxMessage { + + let status: UInt8 + let received: UInt8 + let sessionStopTime: Double + let sessionStartTime: Double + let transmitterTime: Double + + init?(data: Data) { + + guard data.count >= 15 else { return nil } + + guard data.starts(with: .sessionStopRx) else {return nil} + + status = data[1] + received = data[2] + 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)) + + } + + var isOkay: Bool { + return status == 0 + } +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomTransmitterOpCode.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomTransmitterOpCode.swift new file mode 100644 index 000000000..cd411ea8c --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomTransmitterOpCode.swift @@ -0,0 +1,194 @@ +// +// Opcode.swift +// xDripG5 +// +// Copyright © 2018 LoopKit Authors. All rights reserved. +// + +import Foundation + +enum DexcomTransmitterOpCode: UInt8 { + // Auth + case authRequestTx = 0x01 + + case authRequestRx = 0x03 + case authChallengeTx = 0x04 + case authChallengeRx = 0x05 + case keepAliveTx = 0x06 // auth; setAdvertisementParametersTx for control + case bondRequestTx = 0x07 + case pairRequestRx = 0x08 // comes in after having accepted the bluetooth pairing request + + // Control + case disconnectTx = 0x09 + + case setAdvertisementParametersRx = 0x1c + + case firmwareVersionTx = 0x20 + case firmwareVersionRx = 0x21 + case batteryStatusTx = 0x22 + case batteryStatusRx = 0x23 + case transmitterTimeTx = 0x24 + case transmitterTimeRx = 0x25 + case sessionStartTx = 0x26 + case sessionStartRx = 0x27 + case sessionStopTx = 0x28 + case sessionStopRx = 0x29 + case sensorDataTx = 0x2E + case sensorDataRx = 0x2F + + case glucoseTx = 0x30 + case glucoseRx = 0x31 + case calibrationDataTx = 0x32 + case calibrationDataRx = 0x33 + case calibrateGlucoseTx = 0x34 + case calibrateGlucoseRx = 0x35 + + case glucoseHistoryTx = 0x3e + + case resetTx = 0x42 + case resetRx = 0x43 + + case transmitterVersionTx = 0x4a + case transmitterVersionRx = 0x4b + + case glucoseG6Tx = 0x4e + case glucoseG6Rx = 0x4f + + case glucoseBackfillTx = 0x50 + case glucoseBackfillRx = 0x51 + + case keepAliveRx = 0xFF // found during testing +} + + +extension Data { + init(for opcode: DexcomTransmitterOpCode) { + self.init([opcode.rawValue]) + } + + func starts(with opcode: DexcomTransmitterOpCode) -> Bool { + guard count > 0 else { + return false + } + + return self[startIndex] == opcode.rawValue + } +} + +extension DexcomTransmitterOpCode: CustomStringConvertible { + public var description: String { + switch self { + case .authRequestRx: + return "authRequestRx" + + case .authChallengeRx: + return "authChallengeRx" + + case .sensorDataRx: + return "sensorDataRx" + + case .resetRx: + return "resetRx" + + case .batteryStatusRx: + return "batteryStatusRx" + + case .transmitterVersionRx: + return "transmitterVersionRx" + + case .authRequestTx: + return "authRequestTx" + + case .authChallengeTx: + return "authChallengeTx" + + case .keepAliveTx: + return "keepAliveTx" + + case .bondRequestTx: + return "bondRequestTx" + + case .pairRequestRx: + return "pairRequestRx" + + case .disconnectTx: + return "disconnectTx" + + case .setAdvertisementParametersRx: + return "setAdvertisementParametersRx" + + case .firmwareVersionTx: + return "firmwareVersionTx" + + case .firmwareVersionRx: + return "firmwareVersionRx" + + case .batteryStatusTx: + return "batteryStatusTx" + + case .transmitterTimeTx: + return "transmitterTimeTx" + + case .transmitterTimeRx: + return "transmitterTimeRx" + + case .sessionStartTx: + return "sessionStartTx" + + case .sessionStartRx: + return "sessionStartRx" + + case .sessionStopTx: + return "sessionStopTx" + + case .sessionStopRx: + return "sessionStopRx" + + case .sensorDataTx: + return "sensorDataTx" + + case .glucoseTx: + return "glucoseTx" + + case .glucoseRx: + return "glucoseRx" + + case .calibrationDataTx: + return "calibrationDataTx" + + case .calibrationDataRx: + return "calibrationDataRx" + + case .calibrateGlucoseTx: + return "calibrateGlucoseTx" + + case .calibrateGlucoseRx: + return "calibrateGlucoseRx" + + case .glucoseHistoryTx: + return "glucoseHistoryTx" + + case .resetTx: + return "resetTx" + + case .transmitterVersionTx: + return "transmitterVersionTx" + + case .glucoseG6Tx: + return "glucoseG6Tx" + + case .glucoseG6Rx: + return "glucoseG6Rx" + + case .glucoseBackfillTx: + return "glucoseBackfillTx" + + case .glucoseBackfillRx: + return "glucoseBackfillRx" + + case .keepAliveRx: + return "keepAliveRx" + + } + } +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomTransmitterTimeRxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomTransmitterTimeRxMessage.swift new file mode 100644 index 000000000..a094e00c9 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomTransmitterTimeRxMessage.swift @@ -0,0 +1,31 @@ +import Foundation + +struct DexcomTransmitterTimeRxMessage { + + /// transmitter start time + public let transmitterStartDate: Date + + /// sensor start time + public let sensorStartDate: Date? + + init?(data: Data) { + + guard data.count >= 5 else { return nil } + + guard data.starts(with: .transmitterTimeRx) else {return nil} + + transmitterStartDate = Date() - TimeInterval(data.subdata(in: 2..<6).to(Int32.self)) + + if data.count >= 9 { + + sensorStartDate = transmitterStartDate + TimeInterval(data.subdata(in: 6..<10).to(Int32.self)) + + } else { + + sensorStartDate = nil + + } + + } + +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomTransmitterTimeTxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomTransmitterTimeTxMessage.swift new file mode 100644 index 000000000..da0da8f60 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/DexcomTransmitterTimeTxMessage.swift @@ -0,0 +1,11 @@ +import Foundation + +struct DexcomTransmitterTimeTxMessage { + typealias Response = DexcomTransmitterTimeRxMessage + + let opcode: DexcomTransmitterOpCode = .transmitterTimeTx + var data: Data { + return Data(for: .transmitterTimeTx).appendingCRC() + } +} + diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/FirmwareVersionTxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/FirmwareVersionTxMessage.swift similarity index 100% rename from xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/FirmwareVersionTxMessage.swift rename to xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/FirmwareVersionTxMessage.swift diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/GlucoseBackfillRxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/GlucoseBackfillRxMessage.swift new file mode 100644 index 000000000..1ba82c669 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/GlucoseBackfillRxMessage.swift @@ -0,0 +1,34 @@ +import Foundation + +struct GlucoseBackfillRxMessage { + + // variables defined according to documentation Dexcom-Doc + + let transmitterStatus: UInt8 + + let backFillStatus: UInt8 + + let backFillIdentifier: UInt8 + + let backFillStartTimeStamp: Date + + let backFillEndTimeStamp: Date + + init?(data: Data, transmitterStartDate: Date) { + + guard data.count >= 20 else { return nil } + + guard data.starts(with: .glucoseBackfillRx) else {return nil} + + transmitterStatus = data[1] + + backFillStatus = data[2] + + backFillIdentifier = data[3] + + backFillStartTimeStamp = transmitterStartDate + TimeInterval(data.subdata(in: 4..<8).to(Int32.self)) + + backFillEndTimeStamp = transmitterStartDate + TimeInterval(data.subdata(in: 8..<12).to(Int32.self)) + + } +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/GlucoseDataRxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/GlucoseDataRxMessage.swift new file mode 100644 index 000000000..ee39456a8 --- /dev/null +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/GlucoseDataRxMessage.swift @@ -0,0 +1,35 @@ +import Foundation + +struct GlucoseDataRxMessage { + + let status: UInt8 + + let calculatedValue: Double + + let state: DexcomCalibrationState + + init?(data: Data) { + + guard data.count >= 14 else { return nil } + + guard data.starts(with: .glucoseRx) else { return nil } + + status = data[1] + + calculatedValue = Double(Data(data[10..<12]).to(UInt16.self) & 0xfff) + + if let receivedState = DexcomCalibrationState(rawValue: data[12]) { + + state = receivedState + + } else { + + state = DexcomCalibrationState.unknown + + } + + + + } + +} diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/KeepAliveTxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/KeepAliveTxMessage.swift similarity index 100% rename from xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/KeepAliveTxMessage.swift rename to xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/KeepAliveTxMessage.swift diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/NSData+CRC.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/NSData+CRC.swift similarity index 100% rename from xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/NSData+CRC.swift rename to xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/NSData+CRC.swift diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/PairRequestTxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/PairRequestTxMessage.swift similarity index 100% rename from xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/PairRequestTxMessage.swift rename to xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/PairRequestTxMessage.swift diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/ResetMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/ResetMessage.swift similarity index 100% rename from xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/ResetMessage.swift rename to xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/ResetMessage.swift diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/SensorDataRxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/SensorDataRxMessage.swift similarity index 100% rename from xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/SensorDataRxMessage.swift rename to xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/SensorDataRxMessage.swift diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/SensorDataTxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/SensorDataTxMessage.swift similarity index 100% rename from xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/SensorDataTxMessage.swift rename to xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/SensorDataTxMessage.swift diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/TransmitterMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/TransmitterMessage.swift similarity index 99% rename from xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/TransmitterMessage.swift rename to xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/TransmitterMessage.swift index f6c9908f8..3d1e6e888 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/TransmitterMessage.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/TransmitterMessage.swift @@ -26,7 +26,6 @@ protocol RespondableMessage: TransmitterTxMessage { /// A data sequence received by the transmitter protocol TransmitterRxMessage { - init?(data: Data) } diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/TransmitterVersionRxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/TransmitterVersionRxMessage.swift similarity index 65% rename from xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/TransmitterVersionRxMessage.swift rename to xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/TransmitterVersionRxMessage.swift index 63ef25bec..3f1d7cbc6 100644 --- a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/TransmitterVersionRxMessage.swift +++ b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/TransmitterVersionRxMessage.swift @@ -11,8 +11,8 @@ import Foundation struct TransmitterVersionRxMessage: TransmitterRxMessage { let status: UInt8 - let firmwareVersion: Data - + let firmwareVersionAsData: Data + init?(data: Data) { guard data.count == 19 && data.isCRCValid else { return nil @@ -23,7 +23,13 @@ struct TransmitterVersionRxMessage: TransmitterRxMessage { } status = data[1] - firmwareVersion = data[2..<6] + firmwareVersionAsData = data[2..<6] + + } + + /// firmware version in readable format + func firmwareVersionFormatted() -> String { + return firmwareVersionAsData.map { "\(Int($0))" }.joined(separator: ".") } } diff --git a/xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/TransmitterVersionTxMessage.swift b/xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/TransmitterVersionTxMessage.swift similarity index 100% rename from xdrip/BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/TransmitterVersionTxMessage.swift rename to xdrip/BluetoothTransmitter/CGM/Dexcom/Generic/TransmitterVersionTxMessage.swift diff --git a/xdrip/BluetoothTransmitter/CGM/Generic/CGMTransmitterDelegate.swift b/xdrip/BluetoothTransmitter/CGM/Generic/CGMTransmitterDelegate.swift index 265c5d3e1..269bf21fa 100644 --- a/xdrip/BluetoothTransmitter/CGM/Generic/CGMTransmitterDelegate.swift +++ b/xdrip/BluetoothTransmitter/CGM/Generic/CGMTransmitterDelegate.swift @@ -4,7 +4,7 @@ import CoreBluetooth /// to be implemented for anyone who needs to receive information from a specific type of cgm transmitter protocol CGMTransmitterDelegate:AnyObject { - /// only for transmitters that can detect new sensor id + /// only for transmitters that can detect new sensor func newSensorDetected() /// only for transmitters that can detect missing sensor diff --git a/xdrip/BluetoothTransmitter/CGM/Generic/TransmitterBatteryInfo.swift b/xdrip/BluetoothTransmitter/CGM/Generic/TransmitterBatteryInfo.swift index 06583bbc6..425482f2c 100644 --- a/xdrip/BluetoothTransmitter/CGM/Generic/TransmitterBatteryInfo.swift +++ b/xdrip/BluetoothTransmitter/CGM/Generic/TransmitterBatteryInfo.swift @@ -108,7 +108,6 @@ enum TransmitterBatteryInfo: Equatable { temperature = Int(data.uint32(position: 1 + 16)) case 41:// if values are stored with 8 bytes per int - debuglogging("data = " + data.hexEncodedString()) voltageA = Int(data.uint64(position: 1)) voltageB = Int(data.uint64(position: 1 + 8)) resist = Int(data.uint64(position: 1 + 16)) diff --git a/xdrip/BluetoothTransmitter/Generic/BluetoothTransmitter.swift b/xdrip/BluetoothTransmitter/Generic/BluetoothTransmitter.swift index 02cce0e7d..8a7fa298a 100644 --- a/xdrip/BluetoothTransmitter/Generic/BluetoothTransmitter.swift +++ b/xdrip/BluetoothTransmitter/Generic/BluetoothTransmitter.swift @@ -245,13 +245,21 @@ class BluetoothTransmitter: NSObject, CBCentralManagerDelegate, CBPeripheralDele /// will write to characteristicToWriteTo /// - returns: true if writeValue was successfully called, doesn't necessarily mean data is successvully written to peripheral func writeDataToPeripheral(data:Data, characteristicToWriteTo:CBCharacteristic, type:CBCharacteristicWriteType) -> Bool { + if let peripheral = peripheral, getConnectionStatus() == CBPeripheralState.connected { + trace("in writeDataToPeripheral, for peripheral with name %{public}@, for characteristic %{public}@", log: log, category: ConstantsLog.categoryBlueToothTransmitter, type: .info, deviceName ?? "'unknown'", characteristicToWriteTo.uuid.description) + peripheral.writeValue(data, for: characteristicToWriteTo, type: type) + return true + } else { + trace("in writeDataToPeripheral, for peripheral with name %{public}@, failed because either peripheral or characteristicToWriteTo is nil or not connected", log: log, category: ConstantsLog.categoryBlueToothTransmitter, type: .error, deviceName ?? "'unknown'") + return false + } } @@ -482,9 +490,9 @@ class BluetoothTransmitter: NSObject, CBCentralManagerDelegate, CBPeripheralDele timeStampLastStatusUpdate = Date() if let error = error { - trace("didWriteValueFor characteristic %{public}@, characteristic description %{public}@, error = %{public}@", log: log, category: ConstantsLog.categoryBlueToothTransmitter, type: .error, String(describing: characteristic.uuid), String(characteristic.debugDescription), error.localizedDescription) + trace("in didWriteValueFor. Characteristic %{public}@, error = %{public}@", log: log, category: ConstantsLog.categoryBlueToothTransmitter, type: .error, String(describing: characteristic.uuid), error.localizedDescription) } else { - trace("didWriteValueFor characteristic %{public}@, characteristic description %{public}@", log: log, category: ConstantsLog.categoryBlueToothTransmitter, type: .info, String(describing: characteristic.uuid), String(characteristic.debugDescription)) + trace("in didWriteValueFor. Characteristic %{public}@", log: log, category: ConstantsLog.categoryBlueToothTransmitter, type: .info, String(describing: characteristic.uuid)) } } @@ -493,7 +501,7 @@ class BluetoothTransmitter: NSObject, CBCentralManagerDelegate, CBPeripheralDele timeStampLastStatusUpdate = Date() if let error = error { - trace("didUpdateNotificationStateFor for peripheral with name %{public}@, characteristic %{public}@, characteristic description %{public}@, error = %{public}@", log: log, category: ConstantsLog.categoryBlueToothTransmitter, type: .error, deviceName ?? "'unkonwn'", String(describing: characteristic.uuid), String(characteristic.debugDescription), error.localizedDescription) + trace("didUpdateNotificationStateFor for peripheral with name %{public}@, characteristic %{public}@, error = %{public}@", log: log, category: ConstantsLog.categoryBlueToothTransmitter, type: .error, deviceName ?? "'unkonwn'", String(describing: characteristic.uuid), error.localizedDescription) } } @@ -502,7 +510,7 @@ class BluetoothTransmitter: NSObject, CBCentralManagerDelegate, CBPeripheralDele // trace the received value if let value = characteristic.value { - trace("in peripheral didUpdateValueFor, data = %{public}@", log: log, category: ConstantsLog.categoryBlueToothTransmitter, type: .info, value.hexEncodedString()) + trace("in peripheralDidUpdateValueFor, data = %{public}@", log: log, category: ConstantsLog.categoryBlueToothTransmitter, type: .info, value.hexEncodedString()) } timeStampLastStatusUpdate = Date() diff --git a/xdrip/Constants/ConstantsDexcomG5.swift b/xdrip/Constants/ConstantsDexcomG5.swift index bb1e5dbaa..3fced00e2 100644 --- a/xdrip/Constants/ConstantsDexcomG5.swift +++ b/xdrip/Constants/ConstantsDexcomG5.swift @@ -1,8 +1,23 @@ +import Foundation /// dexcom G5 specific constants enum ConstantsDexcomG5 { + /// how often to read battery level - static let batteryReadPeriodInHours = 2.0 + static let batteryReadPeriod = TimeInterval(hours: 2.0) /// in case transmitter needs pairing, how long to keep connection up to give time to the user to accept the pairing request, inclusive opening the notification - static let maxTimeToAcceptPairingInSeconds = 60 + static let maxTimeToAcceptPairing = TimeInterval(minutes: 1.0) + + /// how often to read sensor start time (only for Firefly) + static let sensorStartTimeReadPeriod = TimeInterval(hours: 1.0) + + /// how far in history to go back for fetching readings + static let maxBackfillPeriod = TimeInterval(hours: 6.0) + + /// request back fill reading if time since latest readings is longer than this period + static let minPeriodOfLatestReadingsToStartBackFill = TimeInterval(minutes: 5.30) + + /// 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) + } diff --git a/xdrip/Core Data/classes/BLEPeripheral+CoreDataProperties.swift b/xdrip/Core Data/classes/BLEPeripheral+CoreDataProperties.swift index 5b0e18659..68da44c11 100644 --- a/xdrip/Core Data/classes/BLEPeripheral+CoreDataProperties.swift +++ b/xdrip/Core Data/classes/BLEPeripheral+CoreDataProperties.swift @@ -31,7 +31,8 @@ extension BLEPeripheral { /// should weboop be used or not - defined here to make it easier for coding, although not every type of bluetoothperipheral needs this @NSManaged public var webOOPEnabled: Bool - /// a BLEPeripheral should only have one of dexcomG5, watlaa, m5Stack, ... + /// - a BLEPeripheral should only have one of dexcomG5, watlaa, m5Stack, ... + /// - dexcomG5 is also used for dexcomG6 @NSManaged public var dexcomG5: DexcomG5? /// a BLEPeripheral should only have one of dexcomG5, watlaa, m5Stack, ... diff --git a/xdrip/Core Data/classes/DexcomG5+CoreDataProperties.swift b/xdrip/Core Data/classes/DexcomG5+CoreDataProperties.swift index 80cb61e40..c1613888f 100644 --- a/xdrip/Core Data/classes/DexcomG5+CoreDataProperties.swift +++ b/xdrip/Core Data/classes/DexcomG5+CoreDataProperties.swift @@ -28,4 +28,8 @@ extension DexcomG5 { @NSManaged public var isDexcomG6: Bool + @NSManaged public var transmitterStartDate: Date? + + @NSManaged public var sensorStartDate: Date? + } diff --git a/xdrip/Core Data/xdrip.xcdatamodeld/xdrip v16.xcdatamodel/contents b/xdrip/Core Data/xdrip.xcdatamodeld/xdrip v16.xcdatamodel/contents index 71dd90eb7..89d62c9ef 100644 --- a/xdrip/Core Data/xdrip.xcdatamodeld/xdrip v16.xcdatamodel/contents +++ b/xdrip/Core Data/xdrip.xcdatamodeld/xdrip v16.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -104,6 +104,8 @@ + + @@ -156,6 +158,7 @@ + @@ -163,7 +166,7 @@ - + @@ -172,6 +175,5 @@ - \ No newline at end of file diff --git a/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager+BluetoothTransmitterDelegate.swift b/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager+BluetoothTransmitterDelegate.swift index 6364a6eb0..d27b9e1dd 100644 --- a/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager+BluetoothTransmitterDelegate.swift +++ b/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager+BluetoothTransmitterDelegate.swift @@ -65,8 +65,8 @@ extension BluetoothPeripheralManager: BluetoothTransmitterDelegate { // add closure to ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground so that if user opens the app, the pairing request will be initiated. This can be done only if the app is opened within 60 seconds. // If the app is already in the foreground, then userNotificationCenter willPresent will be called, in this function the closure will be removed immediately, and the pairing request will be called. As a result, if the app is in the foreground, the user will not see (or hear) any notification, but the pairing will be initiated - // max timestamp when notification was fired - connection stays open for 1 minute, taking 1 second as d - let maxTimeUserCanOpenApp = Date(timeIntervalSinceNow: TimeInterval(Double(ConstantsDexcomG5.maxTimeToAcceptPairingInSeconds) - 1.0)) + // max timestamp when notification was fired - connection stays open for 1 minute, taking 1 second as margin + let maxTimeUserCanOpenApp = Date(timeIntervalSinceNow: TimeInterval(Double(ConstantsDexcomG5.maxTimeToAcceptPairing) - 1.0)) // we will not just count on it that the user will click the notification to open the app (assuming the app is in the background, if the app is in the foreground, then we come in another flow) // whenever app comes from-back to foreground, updateLabelsAndChart needs to be called diff --git a/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager+CGMG5TransmitterDelegate.swift b/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager+CGMG5TransmitterDelegate.swift index 7fcfb5fa3..b26394bde 100644 --- a/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager+CGMG5TransmitterDelegate.swift +++ b/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager+CGMG5TransmitterDelegate.swift @@ -48,6 +48,26 @@ extension BluetoothPeripheralManager: CGMG5TransmitterDelegate { } + func received(transmitterStartDate: Date, cGMG5Transmitter: CGMG5Transmitter) { + + guard let dexcomG5 = getDexcomG5(cGMG5Transmitter: cGMG5Transmitter) else {return} + + dexcomG5.transmitterStartDate = transmitterStartDate + + coreDataManager.saveChanges() + + } + + func received(sensorStartDate: Date, cGMG5Transmitter: CGMG5Transmitter) { + + guard let dexcomG5 = getDexcomG5(cGMG5Transmitter: cGMG5Transmitter) else {return} + + dexcomG5.sensorStartDate = sensorStartDate + + coreDataManager.saveChanges() + + } + private func getDexcomG5(cGMG5Transmitter: CGMG5Transmitter) -> DexcomG5? { guard let index = bluetoothTransmitters.firstIndex(of: cGMG5Transmitter), let dexcomG5 = bluetoothPeripherals[index] as? DexcomG5 else {return nil} diff --git a/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager.swift b/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager.swift index 947bb223b..b563c8f49 100644 --- a/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager.swift +++ b/xdrip/Managers/BluetoothPeripheral/BluetoothPeripheralManager.swift @@ -165,11 +165,11 @@ class BluetoothPeripheralManager: NSObject { // add it to the array of bluetoothTransmitters if !dexcomG5orG6.isDexcomG6 { - bluetoothTransmitters.insert(CGMG5Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG5TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate), at: index) + 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) } else { - bluetoothTransmitters.insert(CGMG6Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG6TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate), at: index) + 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) } @@ -588,11 +588,12 @@ class BluetoothPeripheralManager: NSObject { if !dexcomG5orG6.isDexcomG6 { - newTransmitter = CGMG5Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG5TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate) + newTransmitter = CGMG5Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG5TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: dexcomG5orG6.transmitterStartDate, firmware: dexcomG5orG6.firmwareVersion) } else { - newTransmitter = CGMG6Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG6TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate) + newTransmitter = CGMG6Transmitter(address: dexcomG5orG6.blePeripheral.address, name: dexcomG5orG6.blePeripheral.name, transmitterID: transmitterId, bluetoothTransmitterDelegate: self, cGMG6TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: dexcomG5orG6.transmitterStartDate, firmware: dexcomG5orG6.firmwareVersion) + } @@ -859,7 +860,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) + return CGMG5Transmitter(address: nil, name: nil, transmitterID: transmitterId, bluetoothTransmitterDelegate: bluetoothTransmitterDelegate ?? self, cGMG5TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: nil, firmware: nil) case .DexcomG6Type: @@ -867,7 +868,7 @@ 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) + return CGMG6Transmitter(address: nil, name: nil, transmitterID: transmitterId, bluetoothTransmitterDelegate: bluetoothTransmitterDelegate ?? self, cGMG6TransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, transmitterStartDate: nil, firmware: nil) case .BubbleType: diff --git a/xdrip/Storyboards/ar.lproj/SettingsViews.strings b/xdrip/Storyboards/ar.lproj/SettingsViews.strings index 9683e31ab..de94d8631 100644 --- a/xdrip/Storyboards/ar.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/ar.lproj/SettingsViews.strings @@ -31,7 +31,6 @@ "settingsviews_nonfixedtransmitter" = "تفعيل المعايرة متعددة النقاط ?"; "settingsviews_labelNonFixed" = "المعايرة متعددة النقاط"; "settingsviews_labelWebOOP" = "خوارزمية xDrip أو Libre"; -"transmitterId8OrHigherNotSupported" = "جهاز الإرسال برمز الهوية 8Gxxxx أو الأحدث غير مدعوم!"; "settingsviews_sectiontitlealerting" = "التنبيهات"; "settingsviews_row_alert_types" = "أنواع التنبيهات"; "settingsviews_row_alerts" = "التنبيهات"; diff --git a/xdrip/Storyboards/de.lproj/SettingsViews.strings b/xdrip/Storyboards/de.lproj/SettingsViews.strings index 24334322d..15db6ac9d 100644 --- a/xdrip/Storyboards/de.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/de.lproj/SettingsViews.strings @@ -203,9 +203,6 @@ /// speak settings, where user can enable or disable speak delta "settingsviews_speakDelta" = "Delta vorlesen?"; -/// User sets a transmitter id with id 8G or higher. This is not supported -"transmitterId8OrHigherNotSupported" = "Transmitter mit der ID 8Gxxxx oder neuer werden zur Zeit nicht unterstützt!"; - /// M5 stack setting, brightness "m5stack_settingsviews_brightness" = "Bildschirmhelligkeit"; diff --git a/xdrip/Storyboards/en.lproj/BluetoothPeripheralView.strings b/xdrip/Storyboards/en.lproj/BluetoothPeripheralView.strings index 8a48738ee..84635591d 100644 --- a/xdrip/Storyboards/en.lproj/BluetoothPeripheralView.strings +++ b/xdrip/Storyboards/en.lproj/BluetoothPeripheralView.strings @@ -29,3 +29,5 @@ "cannotActiveCGMInFollowerMode" = "You can not activate a CGM in Follower Mode"; "confirmDisconnectTitle" = "Confirm Disconnect"; "confirmDisconnectMessage" = "Click 'Disconnect' to confirm that you really want to disconnect from the transmitter."; +"transmittterStartDate" = "Transmitter Start"; +"sensorStartDate" = "Sensor Start"; diff --git a/xdrip/Storyboards/en.lproj/SettingsViews.strings b/xdrip/Storyboards/en.lproj/SettingsViews.strings index 8dfc534c3..6fa065fd4 100644 --- a/xdrip/Storyboards/en.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/en.lproj/SettingsViews.strings @@ -39,7 +39,6 @@ "settingsviews_nonfixedtransmitter" = "Enable Multi-point Calibration?"; "settingsviews_labelNonFixed" = "Multi-point Calibration"; "settingsviews_labelWebOOP" = "xDrip or Libre Algorithm"; -"transmitterId8OrHigherNotSupported" = "Transmitters with ID 8Gxxxx or newer are not currently supported!"; "settingsviews_sectiontitlealerting" = "Alarms"; "settingsviews_row_alert_types" = "Alarm Types"; "settingsviews_row_alerts" = "Alarms"; diff --git a/xdrip/Storyboards/es.lproj/SettingsViews.strings b/xdrip/Storyboards/es.lproj/SettingsViews.strings index 506b858f4..db11edcfb 100644 --- a/xdrip/Storyboards/es.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/es.lproj/SettingsViews.strings @@ -36,7 +36,6 @@ "settingsviews_nonfixedtransmitter" = "Habilitar Calibración Multi-punto?"; "settingsviews_labelNonFixed" = "Calibración Multi-punto"; "settingsviews_labelWebOOP" = "Algoritmo xDrip o Libre"; -"transmitterId8OrHigherNotSupported" = "Los transmisores con ID 8Gxxxx o más nuevos no son compatibles de momento!"; "settingsviews_sectiontitlealerting" = "Alarmas"; "settingsviews_row_alert_types" = "Tipos de Alarmas"; "settingsviews_row_alerts" = "Alarmas"; diff --git a/xdrip/Storyboards/fi.lproj/SettingsViews.strings b/xdrip/Storyboards/fi.lproj/SettingsViews.strings index d23e09b00..0958c1d1b 100644 --- a/xdrip/Storyboards/fi.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/fi.lproj/SettingsViews.strings @@ -22,7 +22,6 @@ "settingsviews_nonfixedtransmitter" = "Käytä monipistekalibrointia?"; "settingsviews_labelNonFixed" = "Monipistekalibrointi"; "settingsviews_labelWebOOP" = "xDrip vai Libre algorithmi"; -"transmitterId8OrHigherNotSupported" = "Lähettimet ID:llä 8Gxxxx tai uudemmat eivät ole tuettu!"; "settingsviews_sectiontitlealerting" = "Hälytys"; "settingsviews_row_alert_types" = "Hälytystyypit"; "settingsviews_row_alerts" = "Hälytykset"; diff --git a/xdrip/Storyboards/fr.lproj/SettingsViews.strings b/xdrip/Storyboards/fr.lproj/SettingsViews.strings index f5826d07d..16f851070 100644 --- a/xdrip/Storyboards/fr.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/fr.lproj/SettingsViews.strings @@ -27,7 +27,6 @@ "settingsviews_resettransmitter" = "Réinitialiser Transmetteur"; "settingsviews_webooptransmitter" = "Utiliser l'algorithme Libre?"; "settingsviews_labelWebOOP" = "Algorithme xDrip ou Libre"; -"transmitterId8OrHigherNotSupported" = "Transmetteur avec ID 8Gxxxx ou plus récent ne sont pas supportés !"; "settingsviews_sectiontitlealerting" = "Alarme"; "settingsviews_row_alert_types" = "Types d'alarme"; "settingsviews_row_alerts" = "Alarmes"; diff --git a/xdrip/Storyboards/it.lproj/SettingsViews.strings b/xdrip/Storyboards/it.lproj/SettingsViews.strings index 05ae1f0ca..633a6ecff 100644 --- a/xdrip/Storyboards/it.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/it.lproj/SettingsViews.strings @@ -273,9 +273,6 @@ /// "settingsviews_showcoloredobjectives" = "Show Colored Lines?"; -/// User sets a transmitter id with id 8G or higher. This is not supported -"transmitterId8OrHigherNotSupported" = "Transmitters with ID 8Gxxxx or newer are not currently supported!"; - ///////////////////////////////////////////////////////////////////////////////////////////// ///// Translation needed - remove this header after translation ///// ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/xdrip/Storyboards/nl.lproj/SettingsViews.strings b/xdrip/Storyboards/nl.lproj/SettingsViews.strings index 7abe982a1..1b2601883 100644 --- a/xdrip/Storyboards/nl.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/nl.lproj/SettingsViews.strings @@ -36,8 +36,7 @@ "settingsviews_nonfixedtransmitter" = "Schakel Meerpuntskalibratie In?"; "settingsviews_labelNonFixed" = "Meerpuntskalibratie"; "settingsviews_labelWebOOP" = "xDrip of Libre Algoritme"; -"transmitterId8OrHigherNotSupported" = "Transmitters met ID 8Gxxxx of nieuwer zijn momenteel niet ondersteund!"; -"settingsviews_sectiontitlealerting" = "Alarms"; +"settingsviews_sectiontitlealerting" = "Alarmen"; "settingsviews_row_alert_types" = "Alarm Types"; "settingsviews_row_alerts" = "Alarmen"; "settingsviews_sectiontitlehealthkit" = "Apple Gezondheid"; diff --git a/xdrip/Storyboards/pl-PL.lproj/SettingsViews.strings b/xdrip/Storyboards/pl-PL.lproj/SettingsViews.strings index 05ae1f0ca..633a6ecff 100644 --- a/xdrip/Storyboards/pl-PL.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/pl-PL.lproj/SettingsViews.strings @@ -273,9 +273,6 @@ /// "settingsviews_showcoloredobjectives" = "Show Colored Lines?"; -/// User sets a transmitter id with id 8G or higher. This is not supported -"transmitterId8OrHigherNotSupported" = "Transmitters with ID 8Gxxxx or newer are not currently supported!"; - ///////////////////////////////////////////////////////////////////////////////////////////// ///// Translation needed - remove this header after translation ///// ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/xdrip/Storyboards/pt.lproj/SettingsViews.strings b/xdrip/Storyboards/pt.lproj/SettingsViews.strings index a19f893c9..8684293b9 100644 --- a/xdrip/Storyboards/pt.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/pt.lproj/SettingsViews.strings @@ -38,7 +38,6 @@ "settingsviews_nonfixedtransmitter" = "Activar Calibração Multi-ponto?"; "settingsviews_labelNonFixed" = "Calibração Multi-ponto"; "settingsviews_labelWebOOP" = "xDrip ou Algoritmo do Libre"; -"transmitterId8OrHigherNotSupported" = "Transmissores com ID 8Gxxxx ou mais recentes, não são suportados!"; "settingsviews_sectiontitlealerting" = "Alarme"; "settingsviews_row_alert_types" = "Tipos de Alarmes"; "settingsviews_row_alerts" = "Alarmes"; diff --git a/xdrip/Storyboards/ru.lproj/SettingsViews.strings b/xdrip/Storyboards/ru.lproj/SettingsViews.strings index 05ae1f0ca..633a6ecff 100644 --- a/xdrip/Storyboards/ru.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/ru.lproj/SettingsViews.strings @@ -273,9 +273,6 @@ /// "settingsviews_showcoloredobjectives" = "Show Colored Lines?"; -/// User sets a transmitter id with id 8G or higher. This is not supported -"transmitterId8OrHigherNotSupported" = "Transmitters with ID 8Gxxxx or newer are not currently supported!"; - ///////////////////////////////////////////////////////////////////////////////////////////// ///// Translation needed - remove this header after translation ///// ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/xdrip/Storyboards/sl.lproj/SettingsViews.strings b/xdrip/Storyboards/sl.lproj/SettingsViews.strings index c900f8352..f49076b0b 100644 --- a/xdrip/Storyboards/sl.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/sl.lproj/SettingsViews.strings @@ -273,9 +273,6 @@ /// "settingsviews_showcoloredobjectives" = "Show Colored Lines?"; -/// User sets a transmitter id with id 8G or higher. This is not supported -"transmitterId8OrHigherNotSupported" = "Transmitters with ID 8Gxxxx or newer are not currently supported!"; - ///////////////////////////////////////////////////////////////////////////////////////////// ///// Translation needed - remove this header after translation ///// ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/xdrip/Storyboards/sv.lproj/SettingsViews.strings b/xdrip/Storyboards/sv.lproj/SettingsViews.strings index 96f010656..c84b92a21 100644 --- a/xdrip/Storyboards/sv.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/sv.lproj/SettingsViews.strings @@ -35,7 +35,6 @@ "settingsviews_nonfixedtransmitter" = "Använd flerpunktskalibrering?"; "settingsviews_labelNonFixed" = "Flerpunktskalibrering"; "settingsviews_labelWebOOP" = "xDrip- eller Libre-algoritm"; -"transmitterId8OrHigherNotSupported" = "Sändare med ID 8Gxxxx eller nyare stöds för tillfället inte"; "settingsviews_sectiontitlealerting" = "Larm"; "settingsviews_row_alert_types" = "Typ av larm"; "settingsviews_row_alerts" = "Larm"; diff --git a/xdrip/Storyboards/zh.lproj/SettingsViews.strings b/xdrip/Storyboards/zh.lproj/SettingsViews.strings index b34f0f498..5fa389d36 100644 --- a/xdrip/Storyboards/zh.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/zh.lproj/SettingsViews.strings @@ -204,9 +204,6 @@ /// "settingsviews_showcoloredobjectives" = "Show Colored Lines?"; -/// User sets a transmitter id with id 8G or higher. This is not supported -"transmitterId8OrHigherNotSupported" = "Transmitters with ID 8Gxxxx or newer are not currently supported!"; - ///////////////////////////////////////////////////////////////////////////////////////////// ///// Translation needed - remove this header after translation ///// ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/xdrip/Texts/TextsBluetoothPeripheralView.swift b/xdrip/Texts/TextsBluetoothPeripheralView.swift index 218b52a3b..e9988f707 100644 --- a/xdrip/Texts/TextsBluetoothPeripheralView.swift +++ b/xdrip/Texts/TextsBluetoothPeripheralView.swift @@ -100,6 +100,14 @@ class Texts_BluetoothPeripheralView { return NSLocalizedString("lastReset", tableName: filename, bundle: Bundle.main, value: "Last Reset:", comment: "cell text, shows when last reset was done, if known. Only for Dexcom") }() + static let transmittterStartDate: String = { + return NSLocalizedString("transmittterStartDate", tableName: filename, bundle: Bundle.main, value: "Transmitter Start", comment: "cell text, transmitter start time") + }() + + static let sensorStartDate: String = { + return NSLocalizedString("sensorStartDate", tableName: filename, bundle: Bundle.main, value: "Sensor Start", comment: "cell text, sensor start time") + }() + static let lastResetTimeStampNotKnown: String = { return NSLocalizedString("lastResetNotKnown", tableName: filename, bundle: Bundle.main, value: "Last Reset Timestamp is not known", comment: "cell text, shows when last reset was done, if known. Only for Dexcom") }() diff --git a/xdrip/Texts/TextsSettingsView.swift b/xdrip/Texts/TextsSettingsView.swift index fe832c30c..580e89d97 100644 --- a/xdrip/Texts/TextsSettingsView.swift +++ b/xdrip/Texts/TextsSettingsView.swift @@ -187,10 +187,6 @@ class Texts_SettingsView { return NSLocalizedString("settingsviews_labelNonFixed", tableName: filename, bundle: Bundle.main, value: "Multi-point Calibration", comment: "non fixed settings, title of the section") }() - static let transmitterId8OrHigherNotSupported: String = { - return NSLocalizedString("transmitterId8OrHigherNotSupported", tableName: filename, bundle: Bundle.main, value: "Transmitters with ID 8Gxxxx or newer are not currently supported!", comment: "User sets a transmitter id with id 8G or higher. This is not supported") - }() - // MARK: - Section Alerts static let sectionTitleAlerting: String = { diff --git a/xdrip/Utilities/Trace.swift b/xdrip/Utilities/Trace.swift index 976fb565a..e617631ec 100644 --- a/xdrip/Utilities/Trace.swift +++ b/xdrip/Utilities/Trace.swift @@ -178,7 +178,7 @@ func trace(_ message: StaticString, log:OSLog, category: String, type: OSLogType // nslog if enabled and if type = debug, then check also if debug logging is required if UserDefaults.standard.NSLogEnabled && (type != .debug || (type == .debug && UserDefaults.standard.addDebugLevelLogsInTraceFileAndNSLog)) { - NSLog("%@", ConstantsLog.tracePrefix + " " + timeStamp + " " + applicationVersion + " " + buildNumber + " " + category + " " + actualMessage) + NSLog("%@", ConstantsLog.tracePrefix + " " + timeStamp + " " + applicationVersion + " " + buildNumber + " " + category + " " + Date().toString(timeStyle: .medium, dateStyle: .none) + " " + actualMessage) } diff --git a/xdrip/View Controllers/BluetoothPeripheralsNavigationController/BluetoothPeripheralsViewController/BluetoothPeripheralViewController/Models/CGM/Dexcom/DexcomG5/DexcomG5BluetoothPeripheralViewModel.swift b/xdrip/View Controllers/BluetoothPeripheralsNavigationController/BluetoothPeripheralsViewController/BluetoothPeripheralViewController/Models/CGM/Dexcom/DexcomG5/DexcomG5BluetoothPeripheralViewModel.swift index 2528746e3..a57d2f73a 100644 --- a/xdrip/View Controllers/BluetoothPeripheralsNavigationController/BluetoothPeripheralsViewController/BluetoothPeripheralViewController/Models/CGM/Dexcom/DexcomG5/DexcomG5BluetoothPeripheralViewModel.swift +++ b/xdrip/View Controllers/BluetoothPeripheralsNavigationController/BluetoothPeripheralsViewController/BluetoothPeripheralViewController/Models/CGM/Dexcom/DexcomG5/DexcomG5BluetoothPeripheralViewModel.swift @@ -9,8 +9,14 @@ class DexcomG5BluetoothPeripheralViewModel { /// settings specific for Dexcom G5 private enum Settings:Int, CaseIterable { + /// sensor start time + case sensorStartDate = 0 + + /// transmitter start time + case transmitterStartDate = 1 + /// firmware version - case firmWareVersion = 0 + case firmWareVersion = 2 } @@ -194,6 +200,18 @@ extension DexcomG5BluetoothPeripheralViewModel: BluetoothPeripheralViewModel { guard let setting = Settings(rawValue: rawValue) else { fatalError("DexcomG5BluetoothPeripheralViewModel update, unexpected setting") } switch setting { + + case .sensorStartDate: + + cell.textLabel?.text = Texts_BluetoothPeripheralView.sensorStartDate + cell.detailTextLabel?.text = dexcomG5.sensorStartDate?.toString(timeStyle: .short, dateStyle: .short) + cell.accessoryType = .none + + case .transmitterStartDate: + + cell.textLabel?.text = Texts_BluetoothPeripheralView.transmittterStartDate + cell.detailTextLabel?.text = dexcomG5.transmitterStartDate?.toString(timeStyle: .short, dateStyle: .short) + cell.accessoryType = .none case .firmWareVersion: @@ -372,6 +390,32 @@ extension DexcomG5BluetoothPeripheralViewModel: CGMG5TransmitterDelegate { } + /// received transmitterStartDate + func received(transmitterStartDate: Date, cGMG5Transmitter: CGMG5Transmitter) { + + (bluetoothPeripheralManager as? CGMG5TransmitterDelegate)?.received(transmitterStartDate: transmitterStartDate, cGMG5Transmitter: cGMG5Transmitter) + + // transmitterStartDate should get updated in DexcomG5 object by bluetoothPeripheralManager, here's the trigger to update the table + + if let bluetoothPeripheralViewController = bluetoothPeripheralViewController { + reloadRow(row: Settings.firmWareVersion.rawValue, section: DexcomSection.commonDexcomSettings.rawValue + bluetoothPeripheralViewController.numberOfGeneralSections()) + } + + } + + /// received sensorStartDate + func received(sensorStartDate: Date, cGMG5Transmitter: CGMG5Transmitter) { + + (bluetoothPeripheralManager as? CGMG5TransmitterDelegate)?.received(sensorStartDate: sensorStartDate, cGMG5Transmitter: cGMG5Transmitter) + + // sensorStartDate should get updated in DexcomG5 object by bluetoothPeripheralManager, here's the trigger to update the table + + if let bluetoothPeripheralViewController = bluetoothPeripheralViewController { + reloadRow(row: Settings.sensorStartDate.rawValue, section: DexcomSection.commonDexcomSettings.rawValue + bluetoothPeripheralViewController.numberOfGeneralSections()) + } + + } + private func reloadRow(row: Int, section: Int) { tableView?.reloadRows(at: [IndexPath(row: row, section: section)], with: .none) diff --git a/xdrip/View Controllers/Root View Controller/RootViewController.swift b/xdrip/View Controllers/Root View Controller/RootViewController.swift index 8a5d847d2..1072066d2 100644 --- a/xdrip/View Controllers/Root View Controller/RootViewController.swift +++ b/xdrip/View Controllers/Root View Controller/RootViewController.swift @@ -1519,44 +1519,53 @@ final class RootViewController: UIViewController { let cgmTransmitterType = cgmTransmitter.cgmTransmitterType() + // initialize return value + var calibrator: Calibrator = NoCalibrator() + switch cgmTransmitterType { - case .dexcomG4, .dexcomG5, .dexcomG6: + case .dexcomG4, .dexcomG5 : - trace("in getCalibrator, calibrator = DexcomCalibrator", log: log, category: ConstantsLog.categoryRootView, type: .info) + calibrator = DexcomCalibrator() - return DexcomCalibrator() + case .dexcomG6: + + if (cgmTransmitter as! CGMG6Transmitter).isFireFly() { + + calibrator = NoCalibrator() + + } else { + + calibrator = DexcomCalibrator() + } case .miaomiao, .GNSentry, .Blucon, .Bubble, .Droplet1, .blueReader, .watlaa, .Libre2, .Atom: if cgmTransmitter.isWebOOPEnabled() { // received values are already calibrated - - trace("in getCalibrator, calibrator = NoCalibrator", log: log, category: ConstantsLog.categoryRootView, type: .info) - - return NoCalibrator() + calibrator = NoCalibrator() } else if cgmTransmitter.isNonFixedSlopeEnabled() { // no oop web, non-fixed slope - trace("in getCalibrator, calibrator = Libre1NonFixedSlopeCalibrator", log: log, category: ConstantsLog.categoryRootView, type: .info) - return Libre1NonFixedSlopeCalibrator() } else { // no oop web, fixed slope - trace("in getCalibrator, calibrator = Libre1Calibrator", log: log, category: ConstantsLog.categoryRootView, type: .info) - - return Libre1Calibrator() + calibrator = Libre1Calibrator() } } + trace("in getCalibrator, calibrator = %{public}@", log: log, category: ConstantsLog.categoryRootView, type: .info, calibrator.description()) + + return calibrator + } /// for debug purposes @@ -2661,13 +2670,20 @@ extension RootViewController: CGMTransmitterDelegate { 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("glucoseData size = %{public}@", log: log, category: ConstantsLog.categoryRootView, type: .debug, glucoseData.count.description) + trace("glucoseData size = %{public}@", log: log, category: ConstantsLog.categoryRootView, type: .info, glucoseData.count.description) // if received transmitterBatteryInfo not nil, then store it if let transmitterBatteryInfo = transmitterBatteryInfo { UserDefaults.standard.transmitterBatteryInfo = transmitterBatteryInfo } + // list readings + for (index, glucose) in glucoseData.enumerated() { + + trace("glucoseData %{public}@, value = %{public}@, timestamp = %{public}@", log: log, category: ConstantsLog.categoryRootView, type: .info, index.description, glucose.glucoseLevelRaw.description, glucose.timeStamp.toString(timeStyle: .long, dateStyle: .none)) + + } + // process new readings processNewGlucoseData(glucoseData: &glucoseData, sensorTimeInMinutes: sensorTimeInMinutes) diff --git a/xdrip/xdrip-Bridging-Header.h b/xdrip/xdrip-Bridging-Header.h index a6491dacd..57480b80f 100644 --- a/xdrip/xdrip-Bridging-Header.h +++ b/xdrip/xdrip-Bridging-Header.h @@ -1,3 +1,3 @@ -#import "BluetoothTransmitter/CGM/Dexcom/G5/G5Messages/AESCrypt.h" +#import "BluetoothTransmitter/CGM/Dexcom/Generic/AESCrypt.h" #import "Utilities/CustomColoredDisclosureIndicator/DTCustomColoredAccessory.h"