resolve merge conflict
This commit is contained in:
commit
2402129e26
|
@ -9,7 +9,7 @@ DEPENDENCIES:
|
|||
- SwiftCharts (~> 0.6.5)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
https://github.com/CocoaPods/Specs.git:
|
||||
- CryptoSwift
|
||||
- SwiftCharts
|
||||
|
||||
|
@ -29,4 +29,4 @@ SPEC CHECKSUMS:
|
|||
|
||||
PODFILE CHECKSUM: 53c167b7f8be72aa4e6d555f141e0b6d33700680
|
||||
|
||||
COCOAPODS: 1.8.4
|
||||
COCOAPODS: 1.8.0.beta.1
|
||||
|
|
|
@ -59,6 +59,10 @@
|
|||
F816E12C2439DFBA009EE65B /* DexcomG4+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F816E12B2439DFBA009EE65B /* DexcomG4+CoreDataProperties.swift */; };
|
||||
F816E12E2439E06E009EE65B /* BluetoothPeripheralManager+CGMDexcomG4TransmitterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F816E12D2439E06E009EE65B /* BluetoothPeripheralManager+CGMDexcomG4TransmitterDelegate.swift */; };
|
||||
F816E1312439E2DD009EE65B /* DexcomG4BluetoothPeripheralViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F816E1302439E2DD009EE65B /* DexcomG4BluetoothPeripheralViewModel.swift */; };
|
||||
F8177023248CF78300AA3600 /* LibreSensorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8177022248CF78300AA3600 /* LibreSensorType.swift */; };
|
||||
F8177025248ED4DE00AA3600 /* Libre1DerivedAlgorithmParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8177024248ED4DE00AA3600 /* Libre1DerivedAlgorithmParameters.swift */; };
|
||||
F8177027248ED57000AA3600 /* LibreRawGlucoseOOPA2Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8177026248ED57000AA3600 /* LibreRawGlucoseOOPA2Data.swift */; };
|
||||
F8177029248ED6E600AA3600 /* LibreRawGlucoseOOPData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8177028248ED6E600AA3600 /* LibreRawGlucoseOOPData.swift */; };
|
||||
F81D6D4822BD5F62005EFAE2 /* DexcomShareUploadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81D6D4722BD5F62005EFAE2 /* DexcomShareUploadManager.swift */; };
|
||||
F81D6D4E22BFC762005EFAE2 /* TextsDexcomShareTestResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81D6D4D22BFC762005EFAE2 /* TextsDexcomShareTestResult.swift */; };
|
||||
F81D6D5222C27F18005EFAE2 /* BgReading+DexcomShare.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81D6D5122C27F18005EFAE2 /* BgReading+DexcomShare.swift */; };
|
||||
|
@ -119,6 +123,11 @@
|
|||
F878FA7D2405B3CF00BC6DA6 /* BluetoothPeripheralManager+CGMBubbleTransmitterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F878FA7C2405B3CF00BC6DA6 /* BluetoothPeripheralManager+CGMBubbleTransmitterDelegate.swift */; };
|
||||
F889CB6F236D84AC00A81068 /* M5StackView.strings in Resources */ = {isa = PBXBuildFile; fileRef = F889CB71236D84AC00A81068 /* M5StackView.strings */; };
|
||||
F890E07A247687AE008FB2EC /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = F890E079247687AE008FB2EC /* URL.swift */; };
|
||||
F89467EE249EBBDD00F18424 /* LibreRawGlucoseWeb.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89467ED249EBBDD00F18424 /* LibreRawGlucoseWeb.swift */; };
|
||||
F89467F0249FFF5900F18424 /* LibreRawGlucoseOOPGlucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89467EF249FFF5900F18424 /* LibreRawGlucoseOOPGlucose.swift */; };
|
||||
F89467F2249FFF8700F18424 /* LibreRawGlucoseOOPA2List.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89467F1249FFF8700F18424 /* LibreRawGlucoseOOPA2List.swift */; };
|
||||
F89467F4249FFFA700F18424 /* LibreRawGlucoseOOPA2Cotent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89467F3249FFFA700F18424 /* LibreRawGlucoseOOPA2Cotent.swift */; };
|
||||
F89467F824A7C94C00F18424 /* LibreHistoricGlucoseA2.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89467F724A7C94C00F18424 /* LibreHistoricGlucoseA2.swift */; };
|
||||
F897AAF92200F2D200CDDD10 /* CBPeripheralState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897AAF82200F2D200CDDD10 /* CBPeripheralState.swift */; };
|
||||
F897AAFB2201018800CDDD10 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897AAFA2201018800CDDD10 /* String.swift */; };
|
||||
F897E24B23FC86CF0075E0E8 /* CGMG5TransmitterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897E24A23FC86CF0075E0E8 /* CGMG5TransmitterDelegate.swift */; };
|
||||
|
@ -293,10 +302,8 @@
|
|||
F8F9721A23A5915900C3F17D /* CGMBubbleTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971DE23A5915900C3F17D /* CGMBubbleTransmitter.swift */; };
|
||||
F8F9721B23A5915900C3F17D /* CGMMiaoMiaoTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E023A5915900C3F17D /* CGMMiaoMiaoTransmitter.swift */; };
|
||||
F8F9721C23A5915900C3F17D /* LibreOOPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E223A5915900C3F17D /* LibreOOPResponse.swift */; };
|
||||
F8F9721D23A5915900C3F17D /* LibreDerivedAlgorithmRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E323A5915900C3F17D /* LibreDerivedAlgorithmRunner.swift */; };
|
||||
F8F9721E23A5915900C3F17D /* LibreSensorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E423A5915900C3F17D /* LibreSensorState.swift */; };
|
||||
F8F9721F23A5915900C3F17D /* LibreOOPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E523A5915900C3F17D /* LibreOOPClient.swift */; };
|
||||
F8F9722023A5915900C3F17D /* LibreGlucoseSmoothing.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E623A5915900C3F17D /* LibreGlucoseSmoothing.swift */; };
|
||||
F8F9722123A5915900C3F17D /* LibreSensorSerialNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E723A5915900C3F17D /* LibreSensorSerialNumber.swift */; };
|
||||
F8F9722223A5915900C3F17D /* CRC.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E823A5915900C3F17D /* CRC.swift */; };
|
||||
F8F9722323A5915900C3F17D /* LibreDataParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E923A5915900C3F17D /* LibreDataParser.swift */; };
|
||||
|
@ -385,6 +392,10 @@
|
|||
F816E12D2439E06E009EE65B /* BluetoothPeripheralManager+CGMDexcomG4TransmitterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BluetoothPeripheralManager+CGMDexcomG4TransmitterDelegate.swift"; sourceTree = "<group>"; };
|
||||
F816E1302439E2DD009EE65B /* DexcomG4BluetoothPeripheralViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomG4BluetoothPeripheralViewModel.swift; sourceTree = "<group>"; };
|
||||
F817702A2491860F00AA3600 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/SettingsViews.strings; sourceTree = "<group>"; };
|
||||
F8177022248CF78300AA3600 /* LibreSensorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreSensorType.swift; sourceTree = "<group>"; };
|
||||
F8177024248ED4DE00AA3600 /* Libre1DerivedAlgorithmParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Libre1DerivedAlgorithmParameters.swift; sourceTree = "<group>"; };
|
||||
F8177026248ED57000AA3600 /* LibreRawGlucoseOOPA2Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreRawGlucoseOOPA2Data.swift; sourceTree = "<group>"; };
|
||||
F8177028248ED6E600AA3600 /* LibreRawGlucoseOOPData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreRawGlucoseOOPData.swift; sourceTree = "<group>"; };
|
||||
F81D6D4522B67F55005EFAE2 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/SpeakReading.strings; sourceTree = "<group>"; };
|
||||
F81D6D4722BD5F62005EFAE2 /* DexcomShareUploadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomShareUploadManager.swift; sourceTree = "<group>"; };
|
||||
F81D6D4D22BFC762005EFAE2 /* TextsDexcomShareTestResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextsDexcomShareTestResult.swift; sourceTree = "<group>"; };
|
||||
|
@ -473,6 +484,11 @@
|
|||
F889CB97236D89C800A81068 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
F889CB9D236D8EEC00A81068 /* xdrip v4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "xdrip v4.xcdatamodel"; sourceTree = "<group>"; };
|
||||
F890E079247687AE008FB2EC /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
|
||||
F89467ED249EBBDD00F18424 /* LibreRawGlucoseWeb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreRawGlucoseWeb.swift; sourceTree = "<group>"; };
|
||||
F89467EF249FFF5900F18424 /* LibreRawGlucoseOOPGlucose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreRawGlucoseOOPGlucose.swift; sourceTree = "<group>"; };
|
||||
F89467F1249FFF8700F18424 /* LibreRawGlucoseOOPA2List.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreRawGlucoseOOPA2List.swift; sourceTree = "<group>"; };
|
||||
F89467F3249FFFA700F18424 /* LibreRawGlucoseOOPA2Cotent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreRawGlucoseOOPA2Cotent.swift; sourceTree = "<group>"; };
|
||||
F89467F724A7C94C00F18424 /* LibreHistoricGlucoseA2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreHistoricGlucoseA2.swift; sourceTree = "<group>"; };
|
||||
F897AAF82200F2D200CDDD10 /* CBPeripheralState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBPeripheralState.swift; sourceTree = "<group>"; };
|
||||
F897AAFA2201018800CDDD10 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
|
||||
F897E24A23FC86CF0075E0E8 /* CGMG5TransmitterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMG5TransmitterDelegate.swift; sourceTree = "<group>"; };
|
||||
|
@ -727,10 +743,8 @@
|
|||
F8F971DE23A5915900C3F17D /* CGMBubbleTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMBubbleTransmitter.swift; sourceTree = "<group>"; };
|
||||
F8F971E023A5915900C3F17D /* CGMMiaoMiaoTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMMiaoMiaoTransmitter.swift; sourceTree = "<group>"; };
|
||||
F8F971E223A5915900C3F17D /* LibreOOPResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPResponse.swift; sourceTree = "<group>"; };
|
||||
F8F971E323A5915900C3F17D /* LibreDerivedAlgorithmRunner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreDerivedAlgorithmRunner.swift; sourceTree = "<group>"; };
|
||||
F8F971E423A5915900C3F17D /* LibreSensorState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreSensorState.swift; sourceTree = "<group>"; };
|
||||
F8F971E523A5915900C3F17D /* LibreOOPClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPClient.swift; sourceTree = "<group>"; };
|
||||
F8F971E623A5915900C3F17D /* LibreGlucoseSmoothing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreGlucoseSmoothing.swift; sourceTree = "<group>"; };
|
||||
F8F971E723A5915900C3F17D /* LibreSensorSerialNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreSensorSerialNumber.swift; sourceTree = "<group>"; };
|
||||
F8F971E823A5915900C3F17D /* CRC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC.swift; sourceTree = "<group>"; };
|
||||
F8F971E923A5915900C3F17D /* LibreDataParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreDataParser.swift; sourceTree = "<group>"; };
|
||||
|
@ -1847,16 +1861,23 @@
|
|||
F8F971E123A5915900C3F17D /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F8F971E223A5915900C3F17D /* LibreOOPResponse.swift */,
|
||||
F8F971E323A5915900C3F17D /* LibreDerivedAlgorithmRunner.swift */,
|
||||
F8F971E423A5915900C3F17D /* LibreSensorState.swift */,
|
||||
F8F971E523A5915900C3F17D /* LibreOOPClient.swift */,
|
||||
F8F971E623A5915900C3F17D /* LibreGlucoseSmoothing.swift */,
|
||||
F8F971E723A5915900C3F17D /* LibreSensorSerialNumber.swift */,
|
||||
F8F971E823A5915900C3F17D /* CRC.swift */,
|
||||
F8177024248ED4DE00AA3600 /* Libre1DerivedAlgorithmParameters.swift */,
|
||||
F8F971E923A5915900C3F17D /* LibreDataParser.swift */,
|
||||
F89467F724A7C94C00F18424 /* LibreHistoricGlucoseA2.swift */,
|
||||
F8F971EA23A5915900C3F17D /* LibreMeasurement.swift */,
|
||||
F8F971E523A5915900C3F17D /* LibreOOPClient.swift */,
|
||||
F8F971E223A5915900C3F17D /* LibreOOPResponse.swift */,
|
||||
F8F971EB23A5915900C3F17D /* LibreRawGlucoseData.swift */,
|
||||
F89467F3249FFFA700F18424 /* LibreRawGlucoseOOPA2Cotent.swift */,
|
||||
F8177026248ED57000AA3600 /* LibreRawGlucoseOOPA2Data.swift */,
|
||||
F89467F1249FFF8700F18424 /* LibreRawGlucoseOOPA2List.swift */,
|
||||
F8177028248ED6E600AA3600 /* LibreRawGlucoseOOPData.swift */,
|
||||
F89467EF249FFF5900F18424 /* LibreRawGlucoseOOPGlucose.swift */,
|
||||
F89467ED249EBBDD00F18424 /* LibreRawGlucoseWeb.swift */,
|
||||
F8F971E723A5915900C3F17D /* LibreSensorSerialNumber.swift */,
|
||||
F8F971E423A5915900C3F17D /* LibreSensorState.swift */,
|
||||
F8177022248CF78300AA3600 /* LibreSensorType.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2194,17 +2215,20 @@
|
|||
F8F9721623A5915900C3F17D /* CGMG4xDripTransmitter.swift in Sources */,
|
||||
F821CF8122A5C814005C1E43 /* RepeatingTimer.swift in Sources */,
|
||||
F8F9722223A5915900C3F17D /* CRC.swift in Sources */,
|
||||
F89467F2249FFF8700F18424 /* LibreRawGlucoseOOPA2List.swift in Sources */,
|
||||
F821CF6F229FC280005C1E43 /* Endpoint+NightScout.swift in Sources */,
|
||||
F8F9722A23A5915900C3F17D /* TransmitterBatteryInfo.swift in Sources */,
|
||||
F878FA7D2405B3CF00BC6DA6 /* BluetoothPeripheralManager+CGMBubbleTransmitterDelegate.swift in Sources */,
|
||||
F821CF5D229BF43A005C1E43 /* NSDateFormatter.swift in Sources */,
|
||||
F8F9721B23A5915900C3F17D /* CGMMiaoMiaoTransmitter.swift in Sources */,
|
||||
F8F9721A23A5915900C3F17D /* CGMBubbleTransmitter.swift in Sources */,
|
||||
F89467F824A7C94C00F18424 /* LibreHistoricGlucoseA2.swift in Sources */,
|
||||
F8AC42A121B31F170078C348 /* xdrip.xcdatamodeld in Sources */,
|
||||
F8EA6CA921BBE3010082976B /* UniqueId.swift in Sources */,
|
||||
F8A1585122EDB597007F5B5D /* ConstantsBGGraphBuilder.swift in Sources */,
|
||||
F81D6D4822BD5F62005EFAE2 /* DexcomShareUploadManager.swift in Sources */,
|
||||
F8C9784D2428052E00A09483 /* CGMSensorType.swift in Sources */,
|
||||
F89467EE249EBBDD00F18424 /* LibreRawGlucoseWeb.swift in Sources */,
|
||||
F8A1586B22EDB967007F5B5D /* ConstantsMaster.swift in Sources */,
|
||||
F8F9723923A5928D00C3F17D /* M5StackBluetoothPeripheralViewModel.swift in Sources */,
|
||||
F8F9722B23A5915900C3F17D /* CGMTransmitterDelegate.swift in Sources */,
|
||||
|
@ -2240,6 +2264,7 @@
|
|||
F8297F4E238DCAD800D74D66 /* BluetoothPeripheralsViewController.swift in Sources */,
|
||||
F8B3A848227F090E004BA588 /* SettingsViewHealthKitSettingsViewModel.swift in Sources */,
|
||||
F8025E5021EE746400ECF0C0 /* Calibrator.swift in Sources */,
|
||||
F89467F4249FFFA700F18424 /* LibreRawGlucoseOOPA2Cotent.swift in Sources */,
|
||||
F85DC2F421CFE3D400B9F74A /* Sensor+CoreDataClass.swift in Sources */,
|
||||
F8B3A844227F090E004BA588 /* SettingsViewAlertSettingsViewModel.swift in Sources */,
|
||||
F8A1586322EDB86E007F5B5D /* ConstantsSounds.swift in Sources */,
|
||||
|
@ -2265,6 +2290,7 @@
|
|||
F898EDF4234A8A3200BFB79B /* UInt16.swift in Sources */,
|
||||
F8BDD452221DEAB2006EAB84 /* TextsSettingsView.swift in Sources */,
|
||||
F8EEDD6423020FAD00D2D610 /* NoCalibrator.swift in Sources */,
|
||||
F8177023248CF78300AA3600 /* LibreSensorType.swift in Sources */,
|
||||
F8C97859242AAE7B00A09483 /* MiaoMiao+BluetoothPeripheral.swift in Sources */,
|
||||
F8EEDD5422FF685400D2D610 /* NSMutableURLRequest.swift in Sources */,
|
||||
F897AAFB2201018800CDDD10 /* String.swift in Sources */,
|
||||
|
@ -2302,6 +2328,7 @@
|
|||
F8E3A2A923D906C200E5E98A /* WatchManager.swift in Sources */,
|
||||
F8B3A80A227A3D11004BA588 /* TextsAlertTypeSettings.swift in Sources */,
|
||||
F8F9721823A5915900C3F17D /* CGMBlueReaderTransmitter.swift in Sources */,
|
||||
F8177029248ED6E600AA3600 /* LibreRawGlucoseOOPData.swift in Sources */,
|
||||
F8252867243E50FE0067AF77 /* ConstantsLibre.swift in Sources */,
|
||||
F8A1586D22EDB9BE007F5B5D /* ConstantsDexcomFollower.swift in Sources */,
|
||||
F8F9720923A5915900C3F17D /* AESCrypt.m in Sources */,
|
||||
|
@ -2314,7 +2341,6 @@
|
|||
F8E3C3AB21FE17B700907A04 /* StringProtocol.swift in Sources */,
|
||||
F8F9720723A5915900C3F17D /* AuthChallengeTxMessage.swift in Sources */,
|
||||
F8B3A78E22622954004BA588 /* AlertType+CoreDataClass.swift in Sources */,
|
||||
F8F9721D23A5915900C3F17D /* LibreDerivedAlgorithmRunner.swift in Sources */,
|
||||
F821CF5A229BF43A005C1E43 /* CoreDataManager.swift in Sources */,
|
||||
F8E51D6924549E2C001C9E5A /* SettingsViewTraceSettingsViewModel.swift in Sources */,
|
||||
F85DC2F321CFE3D400B9F74A /* Calibration+CoreDataClass.swift in Sources */,
|
||||
|
@ -2339,8 +2365,10 @@
|
|||
F8B48A9422B2A705009BCC01 /* TextsSpeakReading.swift in Sources */,
|
||||
F821CF5F229BF43A005C1E43 /* ApplicationManager.swift in Sources */,
|
||||
F8B3A834227F08AC004BA588 /* PickerViewData.swift in Sources */,
|
||||
F8177025248ED4DE00AA3600 /* Libre1DerivedAlgorithmParameters.swift in Sources */,
|
||||
F808592D23677D6A00F3829D /* ChartPoint.swift in Sources */,
|
||||
F8F9722823A5915900C3F17D /* BluconUtilities.swift in Sources */,
|
||||
F8177027248ED57000AA3600 /* LibreRawGlucoseOOPA2Data.swift in Sources */,
|
||||
F8F9724723A69A8000C3F17D /* BluetoothPeripheralCategory.swift in Sources */,
|
||||
F8B3A79522635A25004BA588 /* AlertType+CoreDataProperties.swift in Sources */,
|
||||
F8B3A84C227F090E004BA588 /* SettingsViewController.swift in Sources */,
|
||||
|
@ -2355,6 +2383,7 @@
|
|||
F897E25323FC871C0075E0E8 /* BluetoothPeripheralManager+WatlaaBluetoothTransmitterDelegate.swift in Sources */,
|
||||
F81D6D5222C27F18005EFAE2 /* BgReading+DexcomShare.swift in Sources */,
|
||||
F8F9723023A5915900C3F17D /* M5StackUtilities.swift in Sources */,
|
||||
F89467F0249FFF5900F18424 /* LibreRawGlucoseOOPGlucose.swift in Sources */,
|
||||
F8297F52238ECA3200D74D66 /* BluetoothPeripheralViewController.swift in Sources */,
|
||||
F816E1312439E2DD009EE65B /* DexcomG4BluetoothPeripheralViewModel.swift in Sources */,
|
||||
F821CF66229EE68B005C1E43 /* NightScoutFollowManager.swift in Sources */,
|
||||
|
@ -2387,7 +2416,6 @@
|
|||
F808D2D2240329E80084B5DB /* Bubble+BluetoothPeripheral.swift in Sources */,
|
||||
F8025C1321DA683400ECF0C0 /* Data.swift in Sources */,
|
||||
F825286A2443AE190067AF77 /* DexcomG6BluetoothPeripheralViewModel.swift in Sources */,
|
||||
F8F9722023A5915900C3F17D /* LibreGlucoseSmoothing.swift in Sources */,
|
||||
F80859272364355F00F3829D /* ConstantsGlucoseChart.swift in Sources */,
|
||||
F825286E2443C1000067AF77 /* BluetoothPeripheralManager+CGMG6TransmitterDelegate.swift in Sources */,
|
||||
F816E10324367389009EE65B /* GNSEntryBluetoothPeripheralViewModel.swift in Sources */,
|
||||
|
|
|
@ -243,7 +243,7 @@ enum BluetoothPeripheralType: String, CaseIterable {
|
|||
|
||||
}
|
||||
|
||||
/// is it web oop enabled or not
|
||||
/// is it web oop supported or not.
|
||||
func canWebOOP() -> Bool {
|
||||
|
||||
switch self {
|
||||
|
|
|
@ -10,21 +10,25 @@ public class GlucoseData {
|
|||
var glucoseLevelFiltered:Double
|
||||
|
||||
init(timeStamp:Date, glucoseLevelRaw:Double, glucoseLevelFiltered:Double) {
|
||||
|
||||
self.timeStamp = timeStamp
|
||||
|
||||
self.glucoseLevelRaw = glucoseLevelRaw
|
||||
|
||||
self.glucoseLevelFiltered = glucoseLevelFiltered
|
||||
|
||||
}
|
||||
|
||||
convenience init(timeStamp:Date, glucoseLevelRaw:Double) {
|
||||
|
||||
self.init(timeStamp: timeStamp, glucoseLevelRaw: glucoseLevelRaw, glucoseLevelFiltered: glucoseLevelRaw)
|
||||
|
||||
}
|
||||
|
||||
convenience init(timeStamp:Date) {
|
||||
self.init(timeStamp: timeStamp, glucoseLevelRaw: 0.0, glucoseLevelFiltered: 0.0)
|
||||
}
|
||||
|
||||
|
||||
var description: String {
|
||||
return "timeStamp = " + timeStamp.description(with: .current) + ", glucoseLevelRaw = " + glucoseLevelRaw.description
|
||||
|
||||
return "timeStamp = " + timeStamp.description(with: .current) + ", glucoseLevelRaw = " + glucoseLevelRaw.description + ", glucoseLevelFiltered = " + glucoseLevelFiltered.description
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -172,7 +172,8 @@ class CGMBluconTransmitter: BluetoothTransmitter {
|
|||
}
|
||||
|
||||
//get readings from buffer and send to cGMTransmitterDelegate
|
||||
var result = LibreDataParser.parse(libreData: rxBuffer, timeStampLastBgReading: timeStampLastBgReading)
|
||||
// TODO: use LibreDataParser.libreDataProcessor and make parseLibre1DataWithoutCalibration private to LibreDataParser
|
||||
var result = LibreDataParser.parseLibre1DataWithoutCalibration(libreData: rxBuffer, timeStampLastBgReading: timeStampLastBgReading)
|
||||
|
||||
//TODO: sort glucosedata before calling newReadingsReceived
|
||||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: nil, sensorTimeInMinutes: result.sensorTimeInMinutes)
|
||||
|
|
|
@ -43,9 +43,12 @@ class CGMBubbleTransmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
/// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send
|
||||
var emptyArray: [GlucoseData] = []
|
||||
|
||||
// current sensor serial number, if nil then it's not known yet
|
||||
/// current sensor serial number, if nil then it's not known yet
|
||||
private var sensorSerialNumber:String?
|
||||
|
||||
/// gives information about type of sensor (Libre1, Libre2, etc..)
|
||||
private var patchInfo: String?
|
||||
|
||||
/// oop website url to use in case oop web would be enabled
|
||||
private var oopWebSite: String
|
||||
|
||||
|
@ -129,6 +132,9 @@ class CGMBubbleTransmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
|
||||
if let value = characteristic.value {
|
||||
|
||||
// trace the contents, as we'll probably do much troubleshooting still
|
||||
trace("in didUpdateValueFor, received data as hexstring : %{public}@", log: log, category: ConstantsLog.categoryCGMBubble, type: .info, value.hexEncodedString())
|
||||
|
||||
//check if buffer needs to be reset
|
||||
if (Date() > startDate.addingTimeInterval(CGMBubbleTransmitter.maxWaitForpacketInSeconds - 1)) {
|
||||
trace("in peripheral didUpdateValueFor, more than %{public}d seconds since last update - or first update since app launch, resetting buffer", log: log, category: ConstantsLog.categoryCGMBubble, type: .info, CGMBubbleTransmitter.maxWaitForpacketInSeconds)
|
||||
|
@ -138,6 +144,7 @@ class CGMBubbleTransmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
if let firstByte = value.first {
|
||||
if let bubbleResponseState = BubbleResponseType(rawValue: firstByte) {
|
||||
switch bubbleResponseState {
|
||||
|
||||
case .dataInfo:
|
||||
|
||||
// get hardware, firmware and batteryPercentage
|
||||
|
@ -158,48 +165,81 @@ class CGMBubbleTransmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
|
||||
case .serialNumber:
|
||||
|
||||
guard value.count >= 10 else { return }
|
||||
rxBuffer.append(value.subdata(in: 2..<10))
|
||||
|
||||
case .dataPacket:
|
||||
|
||||
rxBuffer.append(value.suffix(from: 4))
|
||||
if rxBuffer.count >= 352 {
|
||||
if (Crc.LibreCrc(data: &rxBuffer, headerOffset: bubbleHeaderLength)) {
|
||||
|
||||
guard let libreSensorSerialNumber = LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 0..<8))) else {
|
||||
trace(" could not create libreSensorSerialNumber", log: self.log, category: ConstantsLog.categoryCGMBubble, type: .info)
|
||||
return
|
||||
}
|
||||
|
||||
// verify serial number and if changed inform delegate
|
||||
if libreSensorSerialNumber.serialNumber != sensorSerialNumber {
|
||||
|
||||
if let libreSensorSerialNumber = LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 0..<8))) {
|
||||
|
||||
|
||||
// verify serial number and if changed inform delegate
|
||||
if libreSensorSerialNumber.serialNumber != sensorSerialNumber {
|
||||
|
||||
sensorSerialNumber = libreSensorSerialNumber.serialNumber
|
||||
|
||||
trace(" new sensor detected : %{public}@", log: log, category: ConstantsLog.categoryCGMBubble, type: .info, libreSensorSerialNumber.serialNumber)
|
||||
|
||||
// inform delegate about new sensor detected
|
||||
cgmTransmitterDelegate?.newSensorDetected()
|
||||
|
||||
cGMBubbleTransmitterDelegate?.received(serialNumber: libreSensorSerialNumber.serialNumber, from: self)
|
||||
|
||||
// also reset timestamp last reading, to be sure that if new sensor is started, we get historic data
|
||||
timeStampLastBgReading = Date(timeIntervalSince1970: 0)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LibreDataParser.libreDataProcessor(sensorSerialNumber: sensorSerialNumber, webOOPEnabled: webOOPEnabled, oopWebSite: oopWebSite, oopWebToken: oopWebToken, libreData: (rxBuffer.subdata(in: bubbleHeaderLength..<(344 + bubbleHeaderLength))), cgmTransmitterDelegate: cgmTransmitterDelegate, transmitterBatteryInfo: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, timeStampLastBgReading: timeStampLastBgReading, completionHandler: {(timeStampLastBgReading:Date) in
|
||||
self.timeStampLastBgReading = timeStampLastBgReading
|
||||
|
||||
})
|
||||
sensorSerialNumber = libreSensorSerialNumber.serialNumber
|
||||
|
||||
//reset the buffer
|
||||
resetRxBuffer()
|
||||
trace(" new sensor detected : %{public}@", log: log, category: ConstantsLog.categoryCGMBubble, type: .info, libreSensorSerialNumber.serialNumber)
|
||||
|
||||
// inform delegate about new sensor detected
|
||||
cgmTransmitterDelegate?.newSensorDetected()
|
||||
|
||||
cGMBubbleTransmitterDelegate?.received(serialNumber: libreSensorSerialNumber.serialNumber, from: self)
|
||||
|
||||
// also reset timestamp last reading, to be sure that if new sensor is started, we get historic data
|
||||
timeStampLastBgReading = Date(timeIntervalSince1970: 0)
|
||||
|
||||
}
|
||||
|
||||
// function used two times in next statements
|
||||
let checkCrc = { guard Crc.LibreCrc(data: &self.rxBuffer, headerOffset: self.bubbleHeaderLength) else {
|
||||
trace(" Libre 1 sensor, CRC check failed", log: self.log, category: ConstantsLog.categoryCGMBubble, type: .info)
|
||||
return
|
||||
} }
|
||||
|
||||
// if patchInfo != nil, then this is a Bubble that sends patchInfo.
|
||||
// libre1 patchinfo first byte is "70" or "E5" - other case no CRC check required
|
||||
if let patchInfo = patchInfo, patchInfo.contains(find: "70") || patchInfo.contains(find: "E5") {
|
||||
checkCrc()
|
||||
} else if patchInfo == nil {
|
||||
// must be a Bubble that doesn't support patchInfo, so it must be a Libre1, CRC check required
|
||||
checkCrc()
|
||||
}
|
||||
|
||||
LibreDataParser.libreDataProcessor(libreSensorSerialNumber: libreSensorSerialNumber, patchInfo: patchInfo, webOOPEnabled: webOOPEnabled, oopWebSite: oopWebSite, oopWebToken: oopWebToken, libreData: (rxBuffer.subdata(in: bubbleHeaderLength..<(344 + bubbleHeaderLength))), cgmTransmitterDelegate: cgmTransmitterDelegate, timeStampLastBgReading: timeStampLastBgReading, completionHandler: { (timeStampLastBgReading: Date?, sensorState: LibreSensorState?) in
|
||||
|
||||
if let timeStampLastBgReading = timeStampLastBgReading {
|
||||
self.timeStampLastBgReading = timeStampLastBgReading
|
||||
}
|
||||
|
||||
if let sensorState = sensorState {
|
||||
self.cGMBubbleTransmitterDelegate?.received(sensorStatus: sensorState, from: self)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
//reset the buffer
|
||||
resetRxBuffer()
|
||||
}
|
||||
|
||||
case .noSensor:
|
||||
cgmTransmitterDelegate?.sensorNotDetected()
|
||||
|
||||
case .patchInfo:
|
||||
if value.count >= 10 {
|
||||
patchInfo = value.subdata(in: 5 ..< 11).hexEncodedString().uppercased()
|
||||
}
|
||||
|
||||
// send libreSensorType to delegate
|
||||
if let libreSensorType = LibreSensorType.type(patchInfo: patchInfo) {
|
||||
cGMBubbleTransmitterDelegate?.received(libreSensorType: libreSensorType, from: self)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -262,6 +302,7 @@ fileprivate enum BubbleResponseType: UInt8 {
|
|||
case dataInfo = 128 //0x80
|
||||
case noSensor = 191 //0xBF
|
||||
case serialNumber = 192 //0xC0
|
||||
case patchInfo = 193 //0xC1
|
||||
}
|
||||
|
||||
extension BubbleResponseType: CustomStringConvertible {
|
||||
|
@ -274,7 +315,9 @@ extension BubbleResponseType: CustomStringConvertible {
|
|||
case .dataInfo:
|
||||
return "Data info received"
|
||||
case .serialNumber:
|
||||
return "serial number received"
|
||||
return "Serial number received"
|
||||
case .patchInfo:
|
||||
return "Patch info received"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,14 @@ protocol CGMBubbleTransmitterDelegate: AnyObject {
|
|||
/// received sensor Serial Number
|
||||
func received(serialNumber: String, from cGMBubbleTransmitter: CGMBubbleTransmitter)
|
||||
|
||||
/// M5Stack is sending batteryLevel
|
||||
/// Bubble is sending batteryLevel
|
||||
func received(batteryLevel: Int, from cGMBubbleTransmitter: CGMBubbleTransmitter)
|
||||
|
||||
/// Bubble is sending type of transmitter
|
||||
func received(libreSensorType: LibreSensorType, from cGMBubbleTransmitter: CGMBubbleTransmitter)
|
||||
|
||||
/// bubble is sending sensorStatus
|
||||
func received(sensorStatus: LibreSensorState, from cGMBubbleTransmitter: CGMBubbleTransmitter)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -188,11 +188,13 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, CGMTransmitter {
|
|||
// send battery level to delegate
|
||||
cGMMiaoMiaoTransmitterDelegate?.received(batteryLevel: batteryPercentage, from: self)
|
||||
|
||||
LibreDataParser.libreDataProcessor(sensorSerialNumber: LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 5..<13)))?.serialNumber, webOOPEnabled: webOOPEnabled, oopWebSite: oopWebSite, oopWebToken: oopWebToken, libreData: (rxBuffer.subdata(in: miaoMiaoHeaderLength..<(344 + miaoMiaoHeaderLength))), cgmTransmitterDelegate: cgmTransmitterDelegate, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), firmware: firmware, hardware: hardware, hardwareSerialNumber: nil, bootloader: nil, timeStampLastBgReading: timeStampLastBgReading, completionHandler: {(timeStampLastBgReading:Date) in
|
||||
self.timeStampLastBgReading = timeStampLastBgReading
|
||||
LibreDataParser.libreDataProcessor(libreSensorSerialNumber: LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 5..<13))), patchInfo: nil, webOOPEnabled: webOOPEnabled, oopWebSite: oopWebSite, oopWebToken: oopWebToken, libreData: (rxBuffer.subdata(in: miaoMiaoHeaderLength..<(344 + miaoMiaoHeaderLength))), cgmTransmitterDelegate: cgmTransmitterDelegate, timeStampLastBgReading: timeStampLastBgReading, completionHandler: { (timeStampLastBgReading: Date?, sensorState: LibreSensorState?) in
|
||||
|
||||
if let timeStampLastBgReading = timeStampLastBgReading {
|
||||
self.timeStampLastBgReading = timeStampLastBgReading
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
//reset the buffer
|
||||
resetRxBuffer()
|
||||
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
// adapted by Johan Degraeve for xdrip ios
|
||||
import Foundation
|
||||
|
||||
public struct LibreDerivedAlgorithmParameters: Codable, CustomStringConvertible {
|
||||
|
||||
/// local algorithm use this
|
||||
public struct Libre1DerivedAlgorithmParameters: Codable, CustomStringConvertible {
|
||||
public var slope_slope: Double
|
||||
public var slope_offset: Double
|
||||
public var offset_slope: Double
|
||||
|
@ -22,7 +24,14 @@ public struct LibreDerivedAlgorithmParameters: Codable, CustomStringConvertible
|
|||
return "LibreDerivedAlgorithmParameters:: slopeslope: \(slope_slope), slopeoffset: \(slope_offset), offsetoffset: \(offset_offset), offsetSlope: \(offset_slope), extraSlope: \(extraSlope), extraOffset: \(extraOffset), isValidForFooterWithReverseCRCs: \(isValidForFooterWithReverseCRCs)"
|
||||
}
|
||||
|
||||
public init(slope_slope: Double, slope_offset:Double, offset_slope: Double, offset_offset: Double, isValidForFooterWithReverseCRCs: Int, extraSlope: Double, extraOffset: Double, sensorSerialNumber:String) {
|
||||
/// init will fail if slope_slope == 0 && slope_offset == 0 && offset_slope == 0 && offset_offset == 0
|
||||
public init?(slope_slope: Double, slope_offset:Double, offset_slope: Double, offset_offset: Double, isValidForFooterWithReverseCRCs: Int, extraSlope: Double, extraOffset: Double, sensorSerialNumber:String) {
|
||||
|
||||
guard slope_slope == 0 &&
|
||||
slope_offset == 0 &&
|
||||
offset_slope == 0 &&
|
||||
offset_offset == 0 else {return nil}
|
||||
|
||||
self.slope_slope = slope_slope
|
||||
self.slope_offset = slope_offset
|
||||
self.offset_slope = offset_slope
|
||||
|
@ -31,6 +40,7 @@ public struct LibreDerivedAlgorithmParameters: Codable, CustomStringConvertible
|
|||
self.extraSlope = extraSlope
|
||||
self.extraOffset = extraOffset
|
||||
self.serialNumber = sensorSerialNumber
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
import Foundation
|
||||
import os
|
||||
|
||||
/// for trace
|
||||
fileprivate let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryLibreDataParser)
|
||||
|
||||
class LibreDataParser {
|
||||
|
||||
/// parses libre block
|
||||
// MARK: - public functions
|
||||
|
||||
/// parses libre1 block, without oop web.
|
||||
/// - parameters:
|
||||
/// - libreData: the 344 bytes block from Libre
|
||||
/// - timeStampLastBgReading: this is of the timestamp of the latest reading we already received during previous session
|
||||
|
@ -10,22 +16,21 @@ class LibreDataParser {
|
|||
/// - array of GlucoseData, first is the most recent. Only returns recent readings, ie not the ones that are older than timeStampLastBgReading. 30 seconds are added here, meaning, new reading should be at least 30 seconds more recent than timeStampLastBgReading
|
||||
/// - sensorState: status of the sensor
|
||||
/// - sensorTimeInMinutes: age of sensor in minutes
|
||||
public static func parse(libreData: Data, timeStampLastBgReading:Date) -> (glucoseData:[GlucoseData], sensorState:LibreSensorState, sensorTimeInMinutes:Int) {
|
||||
public static func parseLibre1DataWithoutCalibration(libreData: Data, timeStampLastBgReading:Date) -> (glucoseData:[GlucoseData], sensorState:LibreSensorState, sensorTimeInMinutes:Int) {
|
||||
|
||||
var i:Int
|
||||
var glucoseData:GlucoseData
|
||||
var byte:Data
|
||||
var timeInMinutes:Double
|
||||
let ourTime:Date = Date()
|
||||
let indexTrend:Int = getByteAt(buffer: libreData, position: 26) & 0xFF
|
||||
let indexHistory:Int = getByteAt(buffer: libreData, position: 27) & 0xFF
|
||||
let sensorTimeInMinutes:Int = 256 * (getByteAt(buffer:libreData, position: 317) & 0xFF) + (getByteAt(buffer:libreData, position: 316) & 0xFF)
|
||||
let indexTrend:Int = libreData.getByteAt(position: 26) & 0xFF
|
||||
let indexHistory:Int = libreData.getByteAt(position: 27) & 0xFF
|
||||
let sensorTimeInMinutes:Int = 256 * (libreData.getByteAt(position: 317) & 0xFF) + (libreData.getByteAt(position: 316) & 0xFF)
|
||||
let sensorStartTimeInMilliseconds:Double = ourTime.toMillisecondsAsDouble() - (Double)(sensorTimeInMinutes * 60 * 1000)
|
||||
var returnValue:Array<GlucoseData> = []
|
||||
let sensorState = LibreSensorState(stateByte: libreData[4])
|
||||
|
||||
/////// loads trend values
|
||||
|
||||
// we will add the most recent readings, but then we'll only add the readings that are at least 5 minutes apart (giving 10 seconds spare)
|
||||
// we will add the most recent readings, but then we'll only add the readings that are at least 5 minutes apart (giving 10 seconds spare)
|
||||
// for that variable timeStampLastAddedGlucoseData is used. It's initially set to now + 5 minutes
|
||||
var timeStampLastAddedGlucoseData = Date().toMillisecondsAsDouble() + 5 * 60 * 1000
|
||||
|
||||
|
@ -81,81 +86,316 @@ class LibreDataParser {
|
|||
}
|
||||
|
||||
return (returnValue, sensorState, sensorTimeInMinutes)
|
||||
|
||||
}
|
||||
|
||||
/// Function which groups common functionality used for transmitters that support the 344 Libre block. It checks if webOOP is enabled, if yes tries to use the webOOP, response is processed and delegate is called. If webOOP is not enabled, then local parsing is done.
|
||||
/// Process Libre block for all types of Libre sensors, and for both with and without web oop (without only for Libre 1). It checks if webOOP is enabled, if yes tries to use the webOOP, response is processed and delegate is called. If webOOP not enabled, and if Libre1, then local processing is done, in that case glucose values are not calibrated
|
||||
/// - parameters:
|
||||
/// - sensorSerialNumber : if nil, then webOOP will not be used and local parsing will be done
|
||||
/// - libreSensorSerialNumber : if nil, then webOOP will not be used and local parsing will be done, but only for Libre 1
|
||||
/// - patchInfo : will be used by server to out the glucose data, corresponds to type of sensor. Nil if not known which is used for Bubble or MM older firmware versions and also Watlaa
|
||||
/// - libreData : the 344 bytes from Libre sensor
|
||||
/// - timeStampLastBgReading : timestamp of last reading, older readings will be ignored
|
||||
/// - webOOPEnabled : is webOOP enabled or not, if not enabled, local parsing is used
|
||||
/// - timeStampLastBgReading : timestamp of last reading, older readings will be ignored. This is only used to save some processing time. If not known, set it to Date(timeIntervalSince1970: 0)
|
||||
/// - webOOPEnabled : is webOOP enabled or not, if not enabled, local parsing is used. This can only be the case for Libre1
|
||||
/// - oopWebSite : the site url to use if oop web would be enabled
|
||||
/// - oopWebToken : the token to use if oop web would be enabled
|
||||
/// - cgmTransmitterDelegate : the cgmTransmitterDelegate
|
||||
/// - transmitterBatteryInfo : not mandatory, if nil then delegate will simply not receive it, possibly the delegate already received it before, and if not
|
||||
/// - firmware : not mandatory, if nil then delegate will simply not receive it, possibly the delegate already received it before, and if not or maybe it doesn't exist for the specific type of transmitter
|
||||
/// - hardware : not mandatory, if nil then delegate will simply not receive it, possibly the delegate already received it before, and if not or maybe it doesn't exist for the specific type of transmitter
|
||||
/// - hardwareSerialNumber : not mandatory, if nil then delegate will simply not receive it, possibly the delegate already received it before, and if not or maybe it doesn't exist for the specific type of transmitter
|
||||
/// - bootloader : not mandatory, if nil then delegate will simply not receive it, possibly the delegate already received it before, and if not or maybe it doesn't exist for the specific type of transmitter
|
||||
/// - completionHandler : will be called when glucose data is read with as parameter the timestamp of the last reading. Goal is that caller an set timeStampLastBgReading to the new value
|
||||
///
|
||||
/// parameter values that are not known, simply ignore them, if they are not known then they are probably not important, or they've already been passed to the delegate before.
|
||||
public static func libreDataProcessor(sensorSerialNumber: String?, webOOPEnabled: Bool, oopWebSite: String?, oopWebToken: String?, libreData: Data, cgmTransmitterDelegate : CGMTransmitterDelegate?, transmitterBatteryInfo:TransmitterBatteryInfo?, firmware: String?, hardware: String?, hardwareSerialNumber: String?, bootloader:String?, timeStampLastBgReading: Date, completionHandler:@escaping ((_ timeStampLastBgReading: Date) -> ())) {
|
||||
/// - cgmTransmitterDelegate : the cgmTransmitterDelegate, will be used to send the resultin glucose data and sensorTime (function cgmTransmitterInfoReceived)
|
||||
/// - completionHandler : will be called when glucose data is read with as parameter the timestamp of the last reading. Goal is that caller can set timeStampLastBgReading to the new value. Also the LibreSensorState if found.
|
||||
public static func libreDataProcessor(libreSensorSerialNumber: LibreSensorSerialNumber?, patchInfo: String?, webOOPEnabled: Bool, oopWebSite: String?, oopWebToken: String?, libreData: Data, cgmTransmitterDelegate : CGMTransmitterDelegate?, timeStampLastBgReading: Date, completionHandler:@escaping ((_ timeStampLastBgReading: Date?, _ sensorState: LibreSensorState?) -> ())) {
|
||||
|
||||
if let sensorSerialNumber = sensorSerialNumber, let oopWebSite = oopWebSite, let oopWebToken = oopWebToken, webOOPEnabled {
|
||||
LibreOOPClient.handleLibreData(libreData: libreData, timeStampLastBgReading: timeStampLastBgReading, serialNumber: sensorSerialNumber, oopWebSite: oopWebSite, oopWebToken: oopWebToken) {
|
||||
(result) in
|
||||
handleGlucoseData(result: result, cgmTransmitterDelegate: cgmTransmitterDelegate, transmitterBatteryInfo: transmitterBatteryInfo, firmware: firmware, hardware: hardware, hardwareSerialNumber: hardwareSerialNumber, bootloader: bootloader, sensorSerialNumber: sensorSerialNumber, completionHandler: completionHandler)
|
||||
// get libreSensorType, if this fails then it must be an unknown Libre sensor type in which case we don't proceed
|
||||
guard let libreSensorType = LibreSensorType.type(patchInfo: patchInfo) else {
|
||||
|
||||
// unwrap patchInfo, although it can't be nil here because LibreSensorType.type would have returned .libre1 otherwise
|
||||
if let patchInfo = patchInfo {
|
||||
|
||||
// as we failed to create libreSensorType, patchInfo should not be nil
|
||||
trace("in libreDataProcessor, failed to create libreSensorType, patchInfo = %{public}@", log: log, category: ConstantsLog.categoryLibreDataParser, type: .info, patchInfo)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// unwrap patchInfo, patchInfo must be non nill as we've already tested that
|
||||
guard let patchInfo = patchInfo else {return}
|
||||
|
||||
// let's see if we can and must use web oop
|
||||
if let libreSensorSerialNumber = libreSensorSerialNumber, let oopWebSite = oopWebSite, let oopWebToken = oopWebToken, webOOPEnabled {
|
||||
|
||||
switch libreSensorType {
|
||||
|
||||
case .libre1, .libreUS, .libreProH:// these types are all Libre 1
|
||||
|
||||
// get LibreDerivedAlgorithmParameters and parse using the libre1DerivedAlgorithmParameters
|
||||
LibreOOPClient.getLibre1DerivedAlgorithmParameters(bytes: libreData, libreSensorSerialNumber: libreSensorSerialNumber, oopWebSite: oopWebSite, oopWebToken: oopWebToken) { (libre1DerivedAlgorithmParameters) in
|
||||
|
||||
// parse the data using oop web algorithm
|
||||
let parsedResult = parseLibre1DataWithOOPWebCalibration(libreData: libreData, libre1DerivedAlgorithmParameters: libre1DerivedAlgorithmParameters, timeStampLastBgReading: timeStampLastBgReading)
|
||||
|
||||
handleGlucoseData(result: (parsedResult.libreRawGlucoseData.map { $0 as GlucoseData }, parsedResult.sensorTimeInMinutes, parsedResult.sensorState, nil), cgmTransmitterDelegate: cgmTransmitterDelegate, libreSensorSerialNumber: libreSensorSerialNumber, completionHandler: completionHandler)
|
||||
|
||||
}
|
||||
|
||||
case .libre1A2:
|
||||
|
||||
LibreOOPClient.getLibreRawGlucoseOOPOA2Data(libreData: libreData, oopWebSite: oopWebSite) { (libreRawGlucoseOOPA2Data) in
|
||||
|
||||
// TODO : check if debug tracing is enabled, to avoid to parse for description when it's not needed
|
||||
trace("in libreDataProcessor, received libreRawGlucoseOOPA2Data = %{public}@", log: log, category: ConstantsLog.categoryLibreDataParser, type: .debug, libreRawGlucoseOOPA2Data.description)
|
||||
|
||||
// convert libreRawGlucoseOOPA2Data to (libreRawGlucoseData:[LibreRawGlucoseData], sensorState:LibreSensorState, sensorTimeInMinutes:Int?)
|
||||
let parsedResult = libreRawGlucoseOOPA2Data.glucoseData(timeStampLastBgReading: timeStampLastBgReading)
|
||||
|
||||
handleGlucoseData(result: (parsedResult.libreRawGlucoseData.map { $0 as GlucoseData }, parsedResult.sensorTimeInMinutes, parsedResult.sensorState, nil), cgmTransmitterDelegate: cgmTransmitterDelegate, libreSensorSerialNumber: libreSensorSerialNumber, completionHandler: completionHandler)
|
||||
|
||||
}
|
||||
|
||||
case .libre2:
|
||||
|
||||
LibreOOPClient.getLibreRawGlucoseOOPData(libreData: libreData, libreSensorSerialNumber: libreSensorSerialNumber, patchInfo: patchInfo, oopWebSite: oopWebSite, oopWebToken: oopWebToken) { (libreRawGlucoseOOPData) in
|
||||
|
||||
// TODO : check if debug tracing is enabled, to avoid to parse for description when it's not needed
|
||||
trace("in libreDataProcessor, received libreRawGlucoseOOPData = %{public}@", log: log, category: ConstantsLog.categoryLibreDataParser, type: .debug, libreRawGlucoseOOPData.description)
|
||||
|
||||
// convert libreRawGlucoseOOPData to (libreRawGlucoseData:[LibreRawGlucoseData], sensorState:LibreSensorState, sensorTimeInMinutes:Int?)
|
||||
let parsedResult = libreRawGlucoseOOPData.glucoseData(timeStampLastBgReading: timeStampLastBgReading)
|
||||
|
||||
debuglogging("in libreDataProcessor, parsedResult.gluosedata size = " + parsedResult.libreRawGlucoseData.count.description)
|
||||
|
||||
handleGlucoseData(result: (parsedResult.libreRawGlucoseData.map { $0 as GlucoseData }, parsedResult.sensorTimeInMinutes, parsedResult.sensorState, nil), cgmTransmitterDelegate: cgmTransmitterDelegate, libreSensorSerialNumber: libreSensorSerialNumber, completionHandler: completionHandler)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else if !webOOPEnabled {
|
||||
// use local parser
|
||||
process(libreData: libreData, timeStampLastBgReading: timeStampLastBgReading, cgmTransmitterDelegate: cgmTransmitterDelegate, transmitterBatteryInfo: transmitterBatteryInfo, firmware: firmware, hardware: hardware, hardwareSerialNumber: hardwareSerialNumber, bootloader: bootloader, sensorSerialNumber: sensorSerialNumber, completionHandler: completionHandler)
|
||||
|
||||
//get readings from buffer using local Libre 1 parser
|
||||
let parsedLibre1Data = LibreDataParser.parseLibre1DataWithoutCalibration(libreData: libreData, timeStampLastBgReading: timeStampLastBgReading)
|
||||
|
||||
// handle the result
|
||||
handleGlucoseData(result: (parsedLibre1Data.glucoseData, parsedLibre1Data.sensorTimeInMinutes, parsedLibre1Data.sensorState, nil), cgmTransmitterDelegate: cgmTransmitterDelegate, libreSensorSerialNumber: libreSensorSerialNumber, completionHandler: completionHandler)
|
||||
|
||||
|
||||
} else {
|
||||
// it's not a libre 1 and oop web is enabled, so there's nothing we can do
|
||||
trace("in libreDataProcessor, can not continue - web oop is enabled, but there's missing info in the request", log: log, category: ConstantsLog.categoryLibreDataParser, type: .info)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fileprivate func getByteAt(buffer:Data, position:Int) -> Int {
|
||||
// TODO: move to extension data
|
||||
return Int(buffer[position])
|
||||
}
|
||||
|
||||
fileprivate func getGlucoseRaw(bytes:Data) -> Int {
|
||||
return ((256 * (getByteAt(buffer: bytes, position: 0) & 0xFF) + (getByteAt(buffer: bytes, position: 1) & 0xFF)) & 0x1FFF)
|
||||
return ((256 * (bytes.getByteAt(position: 0) & 0xFF) + (bytes.getByteAt(position: 1) & 0xFF)) & 0x1FFF)
|
||||
}
|
||||
|
||||
/// calls LibreDataParser.parse - calls handleGlucoseData
|
||||
fileprivate func process(libreData: Data, timeStampLastBgReading: Date, cgmTransmitterDelegate : CGMTransmitterDelegate?, transmitterBatteryInfo:TransmitterBatteryInfo?, firmware: String?, hardware: String?, hardwareSerialNumber: String?, bootloader:String?, sensorSerialNumber:String?, completionHandler:((_ timeStampLastBgReading: Date) -> ())) {
|
||||
fileprivate func trendMeasurements(bytes: Data, mostRecentReadingDate: Date, timeStampLastBgReading: Date, _ offset: Double = 0.0, slope: Double = 0.1, libre1DerivedAlgorithmParameters: Libre1DerivedAlgorithmParameters?) -> [LibreMeasurement] {
|
||||
|
||||
//get readings from buffer
|
||||
let result = LibreDataParser.parse(libreData: libreData, timeStampLastBgReading: timeStampLastBgReading)
|
||||
// let headerRange = 0..<24 // 24 bytes, i.e. 3 blocks a 8 bytes
|
||||
let bodyRange = 24..<320 // 296 bytes, i.e. 37 blocks a 8 bytes
|
||||
// let footerRange = 320..<344 // 24 bytes, i.e. 3 blocks a 8 bytes
|
||||
|
||||
// add errordescription nil, needed by handleGlucoseData
|
||||
let resultWithErrorDescription: ([GlucoseData], LibreSensorState, Int, String?) = (result.glucoseData, result.sensorState, result.sensorTimeInMinutes, nil)
|
||||
let body = Array(bytes[bodyRange])
|
||||
let nextTrendBlock = Int(body[2])
|
||||
|
||||
var measurements = [LibreMeasurement]()
|
||||
// Trend data is stored in body from byte 4 to byte 4+96=100 in units of 6 bytes. Index on data such that most recent block is first.
|
||||
for blockIndex in 0...15 {
|
||||
var index = 4 + (nextTrendBlock - 1 - blockIndex) * 6 // runs backwards
|
||||
if index < 4 {
|
||||
index = index + 96 // if end of ring buffer is reached shift to beginning of ring buffer
|
||||
}
|
||||
let range = index..<index+6
|
||||
let measurementBytes = Array(body[range])
|
||||
let measurementDate = mostRecentReadingDate.addingTimeInterval(Double(-60 * blockIndex))
|
||||
|
||||
if measurementDate > timeStampLastBgReading {
|
||||
let measurement = LibreMeasurement(bytes: measurementBytes, slope: slope, offset: offset, date: measurementDate, libre1DerivedAlgorithmParameters: libre1DerivedAlgorithmParameters)
|
||||
measurements.append(measurement)
|
||||
}
|
||||
|
||||
}
|
||||
return measurements
|
||||
}
|
||||
|
||||
fileprivate func historyMeasurements(bytes: Data, timeStampLastBgReading: Date, _ offset: Double = 0.0, slope: Double = 0.1, libre1DerivedAlgorithmParameters: Libre1DerivedAlgorithmParameters?) -> [LibreMeasurement] {
|
||||
// let headerRange = 0..<24 // 24 bytes, i.e. 3 blocks a 8 bytes
|
||||
let bodyRange = 24..<320 // 296 bytes, i.e. 37 blocks a 8 bytes
|
||||
// let footerRange = 320..<344 // 24 bytes, i.e. 3 blocks a 8 bytes
|
||||
|
||||
let body = Array(bytes[bodyRange])
|
||||
let nextHistoryBlock = Int(body[3])
|
||||
let minutesSinceStart = Int(body[293]) << 8 + Int(body[292])
|
||||
let sensorStartTimeInMilliseconds:Double = Date().toMillisecondsAsDouble() - (Double)(minutesSinceStart * 60 * 1000)
|
||||
|
||||
var measurements = [LibreMeasurement]()
|
||||
|
||||
// History data is stored in body from byte 100 to byte 100+192-1=291 in units of 6 bytes. Index on data such that most recent block is first.
|
||||
for blockIndex in 0..<32 {
|
||||
|
||||
let timeInMinutes = max(0,(Double)(abs(minutesSinceStart - 3)/15)*15 - (Double)(blockIndex*15))
|
||||
|
||||
var index = 100 + (nextHistoryBlock - 1 - blockIndex) * 6 // runs backwards
|
||||
if index < 100 {
|
||||
index = index + 192 // if end of ring buffer is reached shift to beginning of ring buffer
|
||||
}
|
||||
|
||||
let range = index..<index+6
|
||||
let measurementBytes = Array(body[range])
|
||||
|
||||
let measurementDate = Date(timeIntervalSince1970: sensorStartTimeInMilliseconds/1000 + timeInMinutes * 60)
|
||||
|
||||
if measurementDate > timeStampLastBgReading {
|
||||
|
||||
let measurement = LibreMeasurement(bytes: measurementBytes, slope: slope, offset: offset, minuteCounter: Int(timeInMinutes.rawValue), date: measurementDate, libre1DerivedAlgorithmParameters: libre1DerivedAlgorithmParameters)
|
||||
measurements.append(measurement)
|
||||
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return measurements
|
||||
|
||||
handleGlucoseData(result: resultWithErrorDescription, cgmTransmitterDelegate: cgmTransmitterDelegate, transmitterBatteryInfo: transmitterBatteryInfo, firmware: firmware, hardware: hardware, hardwareSerialNumber: hardwareSerialNumber, bootloader: bootloader, sensorSerialNumber: sensorSerialNumber, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
/// calls delegate with parameters from result
|
||||
/// - parameters:
|
||||
/// - result
|
||||
/// - glucoseData : array of GlucoseData
|
||||
/// - sensorTimeInMinutes: int
|
||||
/// - errorDescription: optional
|
||||
/// - sensorState: LibreSensorState
|
||||
/// - cgmTransmitterDelegate: instance of CGMTransmitterDelegate, which will be called with result
|
||||
/// - libreSensorSerialNumber
|
||||
/// - callback which takes a data as parameter, being timeStampLastBgReading
|
||||
///
|
||||
/// if result.errorDescription not nil, then delegate function error will be called
|
||||
fileprivate func handleGlucoseData(result: (glucoseData:[GlucoseData], sensorState: LibreSensorState, sensorTimeInMinutes:Int, errorDescription: String?), cgmTransmitterDelegate : CGMTransmitterDelegate?, transmitterBatteryInfo:TransmitterBatteryInfo?, firmware:String?, hardware:String?, hardwareSerialNumber:String?, bootloader:String?, sensorSerialNumber:String?, completionHandler:((_ timeStampLastBgReading: Date) -> ())) {
|
||||
fileprivate func handleGlucoseData(result: (glucoseData:[GlucoseData], sensorTimeInMinutes:Int?, sensorState: LibreSensorState?, errorDescription: String?), cgmTransmitterDelegate : CGMTransmitterDelegate?, libreSensorSerialNumber:LibreSensorSerialNumber?, completionHandler:((_ timeStampLastBgReading: Date?, _ sensorState: LibreSensorState?) -> ())) {
|
||||
|
||||
if let errorDescription = result.errorDescription {
|
||||
cgmTransmitterDelegate?.error(message: "Web OOP : " + errorDescription)
|
||||
} else {
|
||||
var result = result
|
||||
|
||||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: transmitterBatteryInfo, sensorTimeInMinutes: result.sensorTimeInMinutes)
|
||||
// if result.errorDescription not nil, then send it to the delegate
|
||||
guard result.errorDescription == nil else {
|
||||
|
||||
//set timeStampLastBgReading to timestamp of latest reading in the response so that next time we parse only the more recent readings
|
||||
if result.glucoseData.count > 0 {
|
||||
completionHandler(result.glucoseData[0].timeStamp)
|
||||
}
|
||||
// in case weboop is not used then result.errorDescription is always nil, that's how it's code here. So we can
|
||||
cgmTransmitterDelegate?.error(message: "Web OOP : " + result.errorDescription!)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// if sensor time < 60, return an empty glucose data array
|
||||
if let sensorTimeInMinutes = result.sensorTimeInMinutes {
|
||||
|
||||
guard sensorTimeInMinutes >= 60 else {
|
||||
|
||||
var emptyArray = [GlucoseData]()
|
||||
|
||||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorTimeInMinutes: result.sensorTimeInMinutes)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// call delegate with result
|
||||
var result = result
|
||||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: nil, sensorTimeInMinutes: result.sensorTimeInMinutes)
|
||||
|
||||
//set timeStampLastBgReading to timestamp of latest reading in the response so that next time we parse only the more recent readings
|
||||
completionHandler(result.glucoseData.count > 0 ? result.glucoseData[0].timeStamp : nil, result.sensorState)
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// to glucose data
|
||||
/// - Parameter measurements: array of LibreMeasurement
|
||||
/// - Returns: array of LibreRawGlucoseData
|
||||
fileprivate func trendToLibreGlucose(_ measurements: [LibreMeasurement]) -> [LibreRawGlucoseData] {
|
||||
|
||||
var origarr = [LibreRawGlucoseData]()
|
||||
for trend in measurements {
|
||||
let glucose = LibreRawGlucoseData.init(timeStamp: trend.date, glucoseLevelRaw: trend.temperatureAlgorithmGlucose)
|
||||
origarr.append(glucose)
|
||||
}
|
||||
return origarr
|
||||
}
|
||||
|
||||
fileprivate func historyToLibreGlucose(_ measurements: [LibreMeasurement]) -> [LibreRawGlucoseData] {
|
||||
|
||||
var origarr = [LibreRawGlucoseData]()
|
||||
|
||||
for history in measurements {
|
||||
let glucose = LibreRawGlucoseData(timeStamp: history.date, unsmoothedGlucose: history.temperatureAlgorithmGlucose)
|
||||
origarr.append(glucose)
|
||||
}
|
||||
|
||||
return origarr
|
||||
|
||||
}
|
||||
|
||||
/// parses libre 1 block with OOP WEB.
|
||||
/// - parameters:
|
||||
/// - libreData: the 344 bytes block from Libre
|
||||
/// - timeStampLastBgReading: this is of the timestamp of the latest reading we already received during previous session
|
||||
/// - returns:
|
||||
/// - array of libreRawGlucoseData, first is the most recent. Only returns recent readings, ie not the ones that are older than timeStampLastBgReading. 30 seconds are added here, meaning, new reading should be at least 30 seconds more recent than timeStampLastBgReading
|
||||
/// - sensorState: status of the sensor
|
||||
/// - sensorTimeInMinutes: age of sensor in minutes
|
||||
fileprivate func parseLibre1DataWithOOPWebCalibration(libreData: Data, libre1DerivedAlgorithmParameters: Libre1DerivedAlgorithmParameters, timeStampLastBgReading: Date) -> (libreRawGlucoseData:[LibreRawGlucoseData], sensorState:LibreSensorState, sensorTimeInMinutes:Int) {
|
||||
|
||||
// initialise returnvalue, array of LibreRawGlucoseData
|
||||
var finalResult:[LibreRawGlucoseData] = []
|
||||
|
||||
// calculate sensorState
|
||||
let sensorState = LibreSensorState(stateByte: libreData[4])
|
||||
|
||||
let sensorTimeInMinutes:Int = 256 * (Int)(libreData.uint8(position: 317) & 0xFF) + (Int)(libreData.uint8(position: 316) & 0xFF)
|
||||
|
||||
// iterates through glucoseData, compares timestamp, if still higher than timeStampLastBgReading (+ 30 seconds) then adds it to finalResult
|
||||
let processGlucoseData = { (glucoseData: [LibreRawGlucoseData], timeStampLastAddedGlucoseData: Date) in
|
||||
|
||||
var timeStampLastAddedGlucoseDataAsDouble = timeStampLastAddedGlucoseData.toMillisecondsAsDouble()
|
||||
|
||||
for glucose in glucoseData {
|
||||
|
||||
let timeStampOfNewGlucoseData = glucose.timeStamp
|
||||
if timeStampOfNewGlucoseData.toMillisecondsAsDouble() > (timeStampLastBgReading.toMillisecondsAsDouble() + 30000.0) {
|
||||
|
||||
// return only readings that are at least 5 minutes away from each other, except the first, same approach as in LibreDataParser.parse
|
||||
if timeStampOfNewGlucoseData.toMillisecondsAsDouble() < timeStampLastAddedGlucoseDataAsDouble - (5 * 60 * 1000 - 10000) {
|
||||
timeStampLastAddedGlucoseDataAsDouble = timeStampOfNewGlucoseData.toMillisecondsAsDouble()
|
||||
finalResult.append(glucose)
|
||||
}
|
||||
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// get last16 from trend data
|
||||
// latest reading will get date of now
|
||||
let last16 = trendMeasurements(bytes: libreData, mostRecentReadingDate: Date(), timeStampLastBgReading: timeStampLastBgReading, libre1DerivedAlgorithmParameters: libre1DerivedAlgorithmParameters)
|
||||
|
||||
// process last16, new readings should be smaller than now + 5 minutes
|
||||
processGlucoseData(trendToLibreGlucose(last16), Date(timeIntervalSinceNow: 5 * 60))
|
||||
|
||||
// get last32 from history data
|
||||
let last32 = historyMeasurements(bytes: libreData, timeStampLastBgReading: timeStampLastBgReading, libre1DerivedAlgorithmParameters: libre1DerivedAlgorithmParameters)
|
||||
|
||||
// process last 32 with date earlier than the earliest in last16
|
||||
var timeStampLastAddedGlucoseData = Date()
|
||||
if last16.count > 0, let last = last16.last {
|
||||
timeStampLastAddedGlucoseData = last.date
|
||||
}
|
||||
|
||||
processGlucoseData(historyToLibreGlucose(last32), timeStampLastAddedGlucoseData)
|
||||
|
||||
return (finalResult, sensorState, sensorTimeInMinutes)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
//
|
||||
// GlucoseSmoothing.swift
|
||||
// BubbleClientUI
|
||||
//
|
||||
// Created by Bjørn Inge Berg on 25/03/2019.
|
||||
// Copyright © 2019 Mark Wilson. All rights reserved.
|
||||
//
|
||||
// adapted by Johan Degraeve for xdrip ios
|
||||
import Foundation
|
||||
|
||||
class LibreGlucoseSmoothing {
|
||||
|
||||
public static func CalculateSmothedData5Points(origtrends: [LibreRawGlucoseData]) -> [LibreRawGlucoseData] {
|
||||
// In all places in the code, there should be exactly 16 points.
|
||||
// Since that might change, and I'm doing an average of 5, then in the case of less then 5 points,
|
||||
// I'll only copy the data as is (to make sure there are reasonable values when the function returns).
|
||||
|
||||
let trends = origtrends
|
||||
//this is an adoptation, doesn't follow the original directly
|
||||
if(trends.count < 5) {
|
||||
for i in 0 ..< trends.count {
|
||||
trends[i].glucoseLevelRaw = trends[i].unsmoothedGlucose
|
||||
}
|
||||
|
||||
return trends;
|
||||
}
|
||||
for i in 0 ..< trends.count - 4 {
|
||||
|
||||
trends[i].glucoseLevelRaw = (trends[i].unsmoothedGlucose + trends[i+1].glucoseLevelRaw + trends[i+2].unsmoothedGlucose + trends[i+3].glucoseLevelRaw + trends[i+4].unsmoothedGlucose) / 5
|
||||
|
||||
|
||||
}
|
||||
trends[trends.count - 4].glucoseLevelRaw = (trends[trends.count - 4].unsmoothedGlucose + trends[trends.count - 3].unsmoothedGlucose + trends[trends.count - 2].unsmoothedGlucose + trends[trends.count - 1].unsmoothedGlucose) / 4
|
||||
trends[trends.count - 3].glucoseLevelRaw = (trends[trends.count - 3].unsmoothedGlucose + trends[trends.count - 2].unsmoothedGlucose + trends[trends.count - 1].unsmoothedGlucose ) / 3
|
||||
|
||||
trends[trends.count - 2].glucoseLevelRaw = (trends[trends.count - 2].unsmoothedGlucose + trends[trends.count - 1].unsmoothedGlucose ) / 2
|
||||
|
||||
trends[trends.count - 1].glucoseLevelRaw = trends[trends.count - 2].glucoseLevelRaw
|
||||
|
||||
return trends
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import Foundation
|
||||
|
||||
class LibreHistoricGlucoseA2: NSObject, Codable {
|
||||
|
||||
/// if quality != 0, it means the value is error
|
||||
let quality : Int?
|
||||
|
||||
/// the value's sensor time
|
||||
let time: Int?
|
||||
|
||||
/// glucose value
|
||||
let bg : Double?
|
||||
|
||||
/// description
|
||||
override var description: String {
|
||||
|
||||
return "LibreHistoricGlucoseA2 = \nquality = " + (quality != nil ? quality!.description : "nil") + ", time = " + (time != nil ? time!.description : "nil") + ", bg = " + (bg != nil ? bg!.description : "nil")
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -11,82 +11,70 @@ import Foundation
|
|||
|
||||
/// Structure for one glucose measurement including value, date and raw data bytes
|
||||
struct LibreMeasurement {
|
||||
|
||||
/// The date for this measurement
|
||||
let date: Date
|
||||
|
||||
/// The minute counter for this measurement
|
||||
let counter: Int
|
||||
/// The bytes as read from the sensor. All data is derived from this \"raw data"
|
||||
let bytes: [UInt8]
|
||||
/// The bytes as String
|
||||
let byteString: String
|
||||
private let minuteCounter: Int
|
||||
|
||||
/// The raw glucose as read from the sensor
|
||||
let rawGlucose: Int
|
||||
|
||||
/// The raw temperature as read from the sensor
|
||||
let rawTemperature: Int
|
||||
|
||||
/// slope to calculate glucose from raw value in (mg/dl)/raw
|
||||
let slope: Double
|
||||
|
||||
/// glucose offset to be added in mg/dl
|
||||
let offset: Double
|
||||
|
||||
/// The glucose value in mg/dl
|
||||
let glucose: Double
|
||||
/// Initialize a new glucose measurement
|
||||
// let slope_slope: Double = 0.0
|
||||
// let slope_offset: Double = 0.0
|
||||
// let offset_slope: Double = 0.0
|
||||
// let offset_offset: Double = 0.0
|
||||
private let glucose: Double
|
||||
|
||||
let temperatureAlgorithmGlucose: Double
|
||||
// {"status":"complete","slope_slope":0.00001816666666666667,"slope_offset":-0.00016666666666666666,"offset_offset":-21.5,"offset_slope":0.007499999999999993,"uuid":"calibrationmetadata-e61686dd-1305-44f0-a675-df98aabce67f","isValidForFooterWithReverseCRCs":61141}
|
||||
|
||||
let oopSlope: Double
|
||||
let oopOffset: Double
|
||||
///
|
||||
let temperatureAlgorithmParameterSet: LibreDerivedAlgorithmParameters?
|
||||
private let oopSlope: Double
|
||||
|
||||
private let oopOffset: Double
|
||||
|
||||
private let temperatureAlgorithmParameterSet: Libre1DerivedAlgorithmParameters?
|
||||
|
||||
|
||||
///
|
||||
/// - parameter bytes: raw data bytes as read from the sensor
|
||||
/// - parameter slope: slope to calculate glucose from raw value in (mg/dl)/raw
|
||||
/// - parameter offset: glucose offset to be added in mg/dl
|
||||
/// - parameter date: date of the measurement
|
||||
///
|
||||
/// - returns: Measurement
|
||||
init(bytes: [UInt8], slope: Double = 0.1, offset: Double = 0.0, minuteCounter: Int = 0, date: Date, LibreDerivedAlgorithmParameterSet: LibreDerivedAlgorithmParameters? = nil) {
|
||||
self.bytes = bytes
|
||||
self.byteString = bytes.reduce("", {$0 + String(format: "%02X", arguments: [$1])})
|
||||
/// - parameters :
|
||||
/// - bytes: raw data bytes as read from the sensor
|
||||
/// - slope: slope to calculate glucose from raw value in (mg/dl)/raw
|
||||
/// - offset: glucose offset to be added in mg/dl
|
||||
/// - date: date of the measurement
|
||||
/// - minuteCounter : minute counter of this measurement
|
||||
/// - libre1DerivedAlgorithmParameters : Libre1DerivedAlgorithmParameters for the sensor
|
||||
init(bytes: [UInt8], slope: Double = 0.1, offset: Double = 0.0, minuteCounter: Int = 0, date: Date, libre1DerivedAlgorithmParameters: Libre1DerivedAlgorithmParameters? = nil) {
|
||||
|
||||
self.rawGlucose = (Int(bytes[1] & 0x1F) << 8) + Int(bytes[0]) // switched to 13 bit mask on 2018-03-15
|
||||
self.rawTemperature = (Int(bytes[4] & 0x3F) << 8) + Int(bytes[3]) // 14 bit-mask for raw temperature
|
||||
self.slope = slope
|
||||
self.offset = offset
|
||||
self.glucose = offset + slope * Double(rawGlucose)
|
||||
self.date = date
|
||||
self.counter = minuteCounter
|
||||
self.minuteCounter = minuteCounter
|
||||
|
||||
self.temperatureAlgorithmParameterSet = LibreDerivedAlgorithmParameterSet
|
||||
if let LibreDerivedAlgorithmParameterSet = self.temperatureAlgorithmParameterSet {
|
||||
self.oopSlope = LibreDerivedAlgorithmParameterSet.slope_slope * Double(rawTemperature) + LibreDerivedAlgorithmParameterSet.offset_slope
|
||||
self.oopOffset = LibreDerivedAlgorithmParameterSet.slope_offset * Double(rawTemperature) + LibreDerivedAlgorithmParameterSet.offset_offset
|
||||
// local algorithm
|
||||
self.temperatureAlgorithmParameterSet = libre1DerivedAlgorithmParameters
|
||||
if let libreDerivedAlgorithmParameterSet = self.temperatureAlgorithmParameterSet {
|
||||
self.oopSlope = libreDerivedAlgorithmParameterSet.slope_slope * Double(rawTemperature) + libreDerivedAlgorithmParameterSet.offset_slope
|
||||
self.oopOffset = libreDerivedAlgorithmParameterSet.slope_offset * Double(rawTemperature) + libreDerivedAlgorithmParameterSet.offset_offset
|
||||
// self.oopSlope = slope_slope * Double(rawTemperature) + slope_offset
|
||||
// self.oopOffset = offset_slope * Double(rawTemperature) + offset_offset
|
||||
let oopGlucose = oopSlope * Double(rawGlucose) + oopOffset
|
||||
//self.temperatureAlgorithmGlucose = oopGlucose
|
||||
// Final correction, if sensor values are very low and need to be compensated
|
||||
self.temperatureAlgorithmGlucose = oopGlucose * LibreDerivedAlgorithmParameterSet.extraSlope + LibreDerivedAlgorithmParameterSet.extraOffset
|
||||
self.temperatureAlgorithmGlucose = oopGlucose * libreDerivedAlgorithmParameterSet.extraSlope + libreDerivedAlgorithmParameterSet.extraOffset
|
||||
} else {
|
||||
self.oopSlope = 0
|
||||
self.oopOffset = 0
|
||||
self.temperatureAlgorithmGlucose = 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
var description: String {
|
||||
var aString = String("Glucose: \(glucose) (mg/dl), date: \(date), slope: \(slope), offset: \(offset), rawGlucose: \(rawGlucose), rawTemperature: \(rawTemperature), bytes: \(bytes) \n")
|
||||
aString.append("OOP: slope_slope: \(String(describing: temperatureAlgorithmParameterSet?.slope_slope)), slope_offset: \(String(describing: temperatureAlgorithmParameterSet?.slope_offset)), offset_slope: \(String(describing: temperatureAlgorithmParameterSet?.offset_slope)), offset_offset: \(String(describing: temperatureAlgorithmParameterSet?.offset_offset))\n")
|
||||
aString.append("OOP: slope: \(oopSlope), \noffset: \(oopOffset)")
|
||||
aString.append("oopGlucose: \(temperatureAlgorithmGlucose) (mg/dl)" )
|
||||
|
||||
return aString
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,311 +22,249 @@ class LibreOOPClient {
|
|||
|
||||
// MARK: - properties
|
||||
|
||||
private static let filePath: String = NSHomeDirectory() + ConstantsLibre.filePathForParameterStorage
|
||||
|
||||
/// for trace
|
||||
private static let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryLibreOOPClient)
|
||||
|
||||
// MARK: - public functions
|
||||
|
||||
public static func handleLibreData(libreData: Data, timeStampLastBgReading: Date, serialNumber: String, oopWebSite: String, oopWebToken: String, _ callback: @escaping ((glucoseData: [GlucoseData], sensorState: LibreSensorState, sensorTimeInMinutes: Int, errorDescription:String?)) -> Void) {
|
||||
/// get the libre glucose data by server
|
||||
/// - Parameters:
|
||||
/// - libreData: the 344 bytes from Libre sensor
|
||||
/// - libreSensorSerialNumber : sensor sn
|
||||
/// - patchInfo : will be used by server to out the glucose data
|
||||
/// - oopWebSite: the site url to use if oop web would be enabled
|
||||
/// - oopWebToken: the token to use if oop web would be enabled
|
||||
/// - callback: server data that contains the 344 bytes details, is called in DispatchQueue.main.async
|
||||
static func getLibreRawGlucoseOOPData(libreData: Data, libreSensorSerialNumber: LibreSensorSerialNumber, patchInfo: String, oopWebSite: String, oopWebToken: String, callback:@escaping (LibreRawGlucoseOOPData) -> Void) {
|
||||
|
||||
let sensorState = LibreSensorState(stateByte: libreData[4])
|
||||
|
||||
LibreOOPClient.calibrateSensor(bytes: libreData, serialNumber: serialNumber, site: oopWebSite, token: oopWebToken) {
|
||||
(libreDerivedAlgorithmParameters, errorDescription) in
|
||||
|
||||
// define default result that will be returned in defer statement
|
||||
var finalResult:[GlucoseData] = []
|
||||
|
||||
let sensorTimeInMinutes:Int = 256 * (Int)(libreData.uint8(position: 317) & 0xFF) + (Int)(libreData.uint8(position: 316) & 0xFF)
|
||||
var errorDescription = errorDescription
|
||||
|
||||
// before existing call callback function
|
||||
defer {
|
||||
callback((finalResult, sensorState, sensorTimeInMinutes, errorDescription))
|
||||
}
|
||||
|
||||
// if errorDescription received from call to LibreOOPClient.calibrateSensor not nil then no need to continue
|
||||
if errorDescription != nil {return}
|
||||
|
||||
guard let libreDerivedAlgorithmParameters = libreDerivedAlgorithmParameters else {
|
||||
// shouldn't happen because if libreDerivedAlgorithmParameters is nil, it means something went wrong in call to calibrateSensor and so errorDescription should not be nil
|
||||
errorDescription = "libreDerivedAlgorithmParameters is nil"
|
||||
return
|
||||
}
|
||||
|
||||
// iterates through glucoseData, compares timestamp, if still higher than timeStampLastBgReading (+ 30 seconds) then adds it to finalResult
|
||||
let processGlucoseData = { (glucoseData: [LibreRawGlucoseData], timeStampLastAddedGlucoseData: Date) in
|
||||
|
||||
var timeStampLastAddedGlucoseDataAsDouble = timeStampLastAddedGlucoseData.toMillisecondsAsDouble()
|
||||
|
||||
for glucose in glucoseData {
|
||||
|
||||
let timeStampOfNewGlucoseData = glucose.timeStamp
|
||||
if timeStampOfNewGlucoseData.toMillisecondsAsDouble() > (timeStampLastBgReading.toMillisecondsAsDouble() + 30000.0) {
|
||||
|
||||
// return only readings that are at least 5 minutes away from each other, except the first, same approach as in LibreDataParser.parse
|
||||
if timeStampOfNewGlucoseData.toMillisecondsAsDouble() < timeStampLastAddedGlucoseDataAsDouble - (5 * 60 * 1000 - 10000) {
|
||||
timeStampLastAddedGlucoseDataAsDouble = timeStampOfNewGlucoseData.toMillisecondsAsDouble()
|
||||
finalResult.append(glucose)
|
||||
}
|
||||
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// get last16 from trend data
|
||||
// latest reading will get date of now
|
||||
let last16 = trendMeasurements(bytes: libreData, mostRecentReadingDate: Date(), timeStampLastBgReading: timeStampLastBgReading, LibreDerivedAlgorithmParameterSet: libreDerivedAlgorithmParameters)
|
||||
|
||||
// process last16, new readings should be smaller than now + 5 minutes
|
||||
processGlucoseData(trendToLibreGlucose(last16), Date(timeIntervalSinceNow: 5 * 60))
|
||||
|
||||
// get last32 from history data
|
||||
let last32 = historyMeasurements(bytes: libreData, timeStampLastBgReading: timeStampLastBgReading, LibreDerivedAlgorithmParameterSet: libreDerivedAlgorithmParameters)
|
||||
|
||||
// process last 32 with date earlier than the earliest in last16
|
||||
var timeStampLastAddedGlucoseData = Date()
|
||||
if last16.count > 0, let last = last16.last {
|
||||
timeStampLastAddedGlucoseData = last.date
|
||||
}
|
||||
processGlucoseData(historyToLibreGlucose(last32), timeStampLastAddedGlucoseData)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static func calibrateSensor(bytes: Data, serialNumber: String, site: String, token: String, callback: @escaping (LibreDerivedAlgorithmParameters?, _ errorDescription:String?) -> Void) {
|
||||
let item = URLQueryItem(name: "accesstoken", value: oopWebToken)
|
||||
let item1 = URLQueryItem(name: "patchUid", value: libreSensorSerialNumber.uidString.uppercased())
|
||||
let item2 = URLQueryItem(name: "patchInfo", value: patchInfo)
|
||||
let item3 = URLQueryItem(name: "content", value: libreData.hexEncodedString())
|
||||
|
||||
/// first try to get libreDerivedAlgorithmParameters for the sensor from disk
|
||||
let url = URL.init(fileURLWithPath: filePath)
|
||||
if FileManager.default.fileExists(atPath: url.path) {
|
||||
let decoder = JSONDecoder()
|
||||
do {
|
||||
let data = try Data.init(contentsOf: url)
|
||||
let libreDerivedAlgorithmParameters = try decoder.decode(LibreDerivedAlgorithmParameters.self, from: data)
|
||||
|
||||
// there were cases where libreDerivedAlgorithmParameters.slope_slope was 0.0, in which case result glucose value would always be 0.0 That's probably an error that occurred during the initial download of the parameters. Therefore in that case, skip so that download re-occurs
|
||||
if libreDerivedAlgorithmParameters.serialNumber == serialNumber && libreDerivedAlgorithmParameters.slope_slope != 0.0 {
|
||||
// successfully retrieved libreDerivedAlgorithmParameters for current sensor, from disk
|
||||
callback(libreDerivedAlgorithmParameters, nil)
|
||||
return
|
||||
} else if libreDerivedAlgorithmParameters.slope_slope == 0.0 {
|
||||
trace("in calibrateSensor, libreDerivedAlgorithmParameters.slope_slope == 0.0, will redownload libreDerivedAlgorithmParameters", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error)
|
||||
}
|
||||
} catch {
|
||||
// data not found on disk, we need to continue
|
||||
}
|
||||
}
|
||||
var urlComponents = URLComponents(string: "\(oopWebSite)/libreoop2")!
|
||||
|
||||
// get libreDerivedAlgorithmParameters from remote server
|
||||
post(bytes: bytes, site: site, token: token, { (data, errorDescription) in
|
||||
|
||||
trace("in calibrateSensor, initiating download libreDerivedAlgorithmParameters", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .info)
|
||||
|
||||
// define default result that will be returned in defer statement
|
||||
var libreDerivedAlgorithmParameters:LibreDerivedAlgorithmParameters? = nil
|
||||
var errorDescription = errorDescription
|
||||
defer {
|
||||
callback(libreDerivedAlgorithmParameters, errorDescription)
|
||||
}
|
||||
|
||||
// if errorDescription is not nil then something went wrong
|
||||
if errorDescription != nil {return}
|
||||
|
||||
// if data is nil then no need to continue
|
||||
guard let data = data else {
|
||||
// shouldn't happen because if data is nil it means something went wrong in call to post and so errorDescription should not be nil
|
||||
errorDescription = "data received form remote server is nil"
|
||||
return
|
||||
}
|
||||
|
||||
var getCalibrationStatus:GetCalibrationStatus?
|
||||
do {
|
||||
getCalibrationStatus = try JSONDecoder().decode(GetCalibrationStatus.self, from: data)
|
||||
} catch {
|
||||
trace("Failed to decode data received from remote server. data received from remote server = %{public}@", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error, String(bytes: data, encoding: .utf8) ?? "")
|
||||
errorDescription = String(bytes: data, encoding: .utf8) ?? "Failed to decode data received from remote server."
|
||||
return
|
||||
}
|
||||
|
||||
if let getCalibrationStatus = getCalibrationStatus, let slope = getCalibrationStatus.slope {
|
||||
libreDerivedAlgorithmParameters = LibreDerivedAlgorithmParameters(slope_slope: slope.slopeSlope ?? 0, slope_offset: slope.slopeOffset ?? 0, offset_slope: slope.offsetSlope ?? 0, offset_offset: slope.offsetOffset ?? 0, isValidForFooterWithReverseCRCs: Int(slope.isValidForFooterWithReverseCRCs ?? 1), extraSlope: 1.0, extraOffset: 0.0, sensorSerialNumber: serialNumber)
|
||||
do {
|
||||
let data = try JSONEncoder().encode(libreDerivedAlgorithmParameters)
|
||||
save(data: data)
|
||||
} catch {
|
||||
// encoding data failed, no need to handle as an error, it means probably next time a new post will be done to the oop web server
|
||||
trace("in calibrateSensor, error while encoding data : %{public}@", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error, error.localizedDescription)
|
||||
}
|
||||
} else {
|
||||
trace("in calibrateSensor, slope is nil", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error)
|
||||
errorDescription = "slope is nil"
|
||||
return
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - private functions
|
||||
|
||||
/// - parameters:
|
||||
/// - bytes : the data to post
|
||||
/// - site : the oop web site (inclusive http ...)
|
||||
/// - token : the token to use
|
||||
/// - completion : takes data returned from the remote server optional, errorDescription which is a string if anything went wrong, eg host could not be reached, if errorDescription not nil then it failed
|
||||
private static func post(bytes: Data, site: String, token: String, _ completion:@escaping (( _ data_: Data?, _ errorDescription: String?) -> Void)) {
|
||||
urlComponents.queryItems = [item, item1, item2, item3]
|
||||
|
||||
let date = Date().toMillisecondsAsInt64()
|
||||
|
||||
let json: [String: String] = [
|
||||
"token": token,
|
||||
"content": "\(bytes.hexEncodedString())",
|
||||
"timestamp": "\(date)"]
|
||||
|
||||
if let uploadURL = URL.init(string: site) {
|
||||
|
||||
if let uploadURL = URL.init(string: urlComponents.url?.absoluteString.removingPercentEncoding ?? "") {
|
||||
let request = NSMutableURLRequest(url: uploadURL)
|
||||
request.httpMethod = "POST"
|
||||
request.setBodyContent(contentMap: json)
|
||||
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let task = URLSession.shared.dataTask(with: request as URLRequest) {
|
||||
data, urlResponse, error in
|
||||
data, response, error in
|
||||
|
||||
// TODO: check urlResponse and http error code ? (see also NightScoutUploadManager)
|
||||
|
||||
// define default result that will be returned in defer statement
|
||||
var errorDescription:String? = nil
|
||||
defer {
|
||||
DispatchQueue.main.sync {
|
||||
completion(data, errorDescription)
|
||||
DispatchQueue.main.async {
|
||||
|
||||
guard let data = data else {
|
||||
trace("in getLibreRawGlucoseOOPData, data is nil", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// error cases
|
||||
if let error = error {
|
||||
|
||||
trace("post failed, error = %{public}@", log: self.log, category: ConstantsLog.categoryLibreOOPClient, type: .error, error.localizedDescription)
|
||||
errorDescription = error.localizedDescription
|
||||
return
|
||||
// trace the data as String, level debug
|
||||
if let dataAsString = String(bytes: data, encoding: .utf8) {
|
||||
|
||||
trace("in getLibreRawGlucoseOOPData, data received frop oop web server = %{public}@", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .debug, dataAsString)
|
||||
|
||||
}
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
if let oopValue = try? decoder.decode(LibreRawGlucoseOOPData.self, from: data) {
|
||||
|
||||
callback(oopValue)
|
||||
|
||||
} else {
|
||||
|
||||
// json parsing failed
|
||||
trace("in getLibreRawGlucoseOOPData, could not do json parsing", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error)
|
||||
|
||||
// if response is not nil then trace
|
||||
if let response = String(data: data, encoding: String.Encoding.utf8) {
|
||||
|
||||
trace("in getLibreRawGlucoseOOPData, data as string = %{public}@", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error, response)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
task.resume()
|
||||
|
||||
} else {
|
||||
completion(nil, "failed to create url from " + site)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static func save(data: Data) {
|
||||
let url = URL.init(fileURLWithPath: filePath)
|
||||
do {
|
||||
try data.write(to: url)
|
||||
} catch {
|
||||
trace("in save, failed to save data", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error)
|
||||
}
|
||||
}
|
||||
/// if `patchInfo.hasPrefix("A2") 'Libre 1 A2', server uses another arithmetic to handle the 344 bytes
|
||||
/// - Parameters:
|
||||
/// - libreData: the 344 bytes from Libre sensor
|
||||
/// - oopWebSite: the site url to use if oop web would be enabled
|
||||
/// - callback: server data that contains the 344 bytes details
|
||||
static func getLibreRawGlucoseOOPOA2Data (libreData: Data, oopWebSite: String, callback:@escaping (LibreRawGlucoseOOPA2Data) -> Void) {
|
||||
|
||||
private static func trendMeasurements(bytes: Data, mostRecentReadingDate: Date, timeStampLastBgReading: Date, _ offset: Double = 0.0, slope: Double = 0.1, LibreDerivedAlgorithmParameterSet: LibreDerivedAlgorithmParameters?) -> [LibreMeasurement] {
|
||||
|
||||
// let headerRange = 0..<24 // 24 bytes, i.e. 3 blocks a 8 bytes
|
||||
let bodyRange = 24..<320 // 296 bytes, i.e. 37 blocks a 8 bytes
|
||||
// let footerRange = 320..<344 // 24 bytes, i.e. 3 blocks a 8 bytes
|
||||
|
||||
let body = Array(bytes[bodyRange])
|
||||
let nextTrendBlock = Int(body[2])
|
||||
|
||||
var measurements = [LibreMeasurement]()
|
||||
// Trend data is stored in body from byte 4 to byte 4+96=100 in units of 6 bytes. Index on data such that most recent block is first.
|
||||
for blockIndex in 0...15 {
|
||||
var index = 4 + (nextTrendBlock - 1 - blockIndex) * 6 // runs backwards
|
||||
if index < 4 {
|
||||
index = index + 96 // if end of ring buffer is reached shift to beginning of ring buffer
|
||||
if let uploadURL = URL(string: "\(oopWebSite)/callnox") {
|
||||
do {
|
||||
var request = URLRequest(url: uploadURL)
|
||||
request.httpMethod = "POST"
|
||||
let data = try JSONSerialization.data(withJSONObject: [["timestamp": "\(Int(Date().timeIntervalSince1970 * 1000))",
|
||||
"content": libreData.hexEncodedString()]], options: [])
|
||||
let string = String.init(data: data, encoding: .utf8)
|
||||
let json: [String: String] = ["userId": "1",
|
||||
"list": string!]
|
||||
request.setBodyContent(contentMap: json)
|
||||
let task = URLSession.shared.dataTask(with: request as URLRequest) {
|
||||
data, response, error in
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
if let error = error {
|
||||
trace("in getLibreRawGlucoseOOPOA2Data, error is not nil, error = %{public}@", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error, error.localizedDescription)
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
trace("in getLibreRawGlucoseOOPOA2Data, data is nil", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error)
|
||||
return
|
||||
}
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
do {
|
||||
|
||||
|
||||
let oopValue = try decoder.decode(LibreRawGlucoseOOPA2Data.self, from: data)
|
||||
|
||||
callback(oopValue)
|
||||
|
||||
} catch {
|
||||
|
||||
// json parsing failed
|
||||
trace("in getLibreRawGlucoseOOPOA2Data, could not do json parsing", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error)
|
||||
|
||||
// if response is not nil then trace
|
||||
if let response = String(data: data, encoding: String.Encoding.utf8) {
|
||||
|
||||
trace("in getLibreRawGlucoseOOPOA2Data, data as string = %{public}@", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error, response)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
task.resume()
|
||||
|
||||
} catch let error {
|
||||
|
||||
trace(" failed to upload, error = %{public}@", log: log, category: ConstantsLog.categoryDexcomShareUploadManager, type: .info, error.localizedDescription)
|
||||
return
|
||||
|
||||
}
|
||||
let range = index..<index+6
|
||||
let measurementBytes = Array(body[range])
|
||||
let measurementDate = mostRecentReadingDate.addingTimeInterval(Double(-60 * blockIndex))
|
||||
}
|
||||
}
|
||||
|
||||
/// get the `Libre1DerivedAlgorithmParameters for Libre1 Sensor, either from UserDefaults (if already fetched earlier for that sensor), or from oopWeb. If oopWeb fetch fails, then default values are used
|
||||
/// - Parameters:
|
||||
/// - bytes: the 344 bytes from Libre sensor
|
||||
/// - libreSensorSerialNumber: LibreSensorSerialNumber is a structure that hold the serial number
|
||||
/// - oopWebSite: the site url to use if oop web would be enabled
|
||||
/// - oopWebToken: the token to use if oop web would be enabled
|
||||
/// - callback: takes LibreDerivedAlgorithmParameters`as parameter, will not be called if there's no result for instance because oop web server can not be reached
|
||||
static func getLibre1DerivedAlgorithmParameters(bytes: Data, libreSensorSerialNumber: LibreSensorSerialNumber, oopWebSite: String, oopWebToken: String, callback: @escaping (Libre1DerivedAlgorithmParameters) -> Void) {
|
||||
|
||||
// the parameters of one sensor will not be changed, if the values are already available in userdefaults , then use those values
|
||||
if let libre1DerivedAlgorithmParameters = UserDefaults.standard.libre1DerivedAlgorithmParameters {
|
||||
if libre1DerivedAlgorithmParameters.serialNumber == libreSensorSerialNumber.serialNumber {
|
||||
|
||||
callback(libre1DerivedAlgorithmParameters)
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// calibration parameters not available yet, get them from oopWebSite
|
||||
let json: [String: String] = [
|
||||
"token": oopWebToken,
|
||||
"content": "\(bytes.hexEncodedString())",
|
||||
"timestamp": "\(Date().toMillisecondsAsInt64())",
|
||||
]
|
||||
|
||||
if let uploadURL = URL(string: "\(oopWebSite)/calibrateSensor") {
|
||||
var request = URLRequest(url: uploadURL)
|
||||
request.httpMethod = "POST"
|
||||
request.setBodyContent(contentMap: json)
|
||||
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
if let error = error {
|
||||
|
||||
trace("in getOopWebCalibrationStatus, received error : %{public}@", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error, error.localizedDescription)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
|
||||
trace("in getOopWebCalibrationStatus, data is nil", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// data is not nil, let's try to do json decoding
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
do {
|
||||
|
||||
let response = try decoder.decode(OopWebCalibrationStatus.self, from: data)
|
||||
|
||||
if let slope = response.slope {
|
||||
|
||||
if let libreDerivedAlgorithmParameters = Libre1DerivedAlgorithmParameters(slope_slope: slope.slopeSlope ?? 0, slope_offset: slope.slopeOffset ?? 0, offset_slope: slope.offsetSlope ?? 0, offset_offset: slope.offsetOffset ?? 0, isValidForFooterWithReverseCRCs: Int(slope.isValidForFooterWithReverseCRCs ?? 1), extraSlope: 1.0, extraOffset: 0.0, sensorSerialNumber: libreSensorSerialNumber.serialNumber) {
|
||||
|
||||
// store result in UserDefaults, next time, server will not be used anymore, we will use the stored value
|
||||
UserDefaults.standard.libre1DerivedAlgorithmParameters = libreDerivedAlgorithmParameters
|
||||
|
||||
callback(libreDerivedAlgorithmParameters)
|
||||
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
// json parsing failed
|
||||
trace("in calibrateSensor, could not do json parsing", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error)
|
||||
|
||||
// if response is not nil then trace
|
||||
if let response = String(data: data, encoding: String.Encoding.utf8) {
|
||||
|
||||
trace(" data as string = %{public}@", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error, response)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if measurementDate > timeStampLastBgReading {
|
||||
let measurement = LibreMeasurement(bytes: measurementBytes, slope: slope, offset: offset, date: measurementDate, LibreDerivedAlgorithmParameterSet: LibreDerivedAlgorithmParameterSet)
|
||||
measurements.append(measurement)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return measurements
|
||||
}
|
||||
|
||||
private static func historyMeasurements(bytes: Data, timeStampLastBgReading: Date, _ offset: Double = 0.0, slope: Double = 0.1, LibreDerivedAlgorithmParameterSet: LibreDerivedAlgorithmParameters?) -> [LibreMeasurement] {
|
||||
// let headerRange = 0..<24 // 24 bytes, i.e. 3 blocks a 8 bytes
|
||||
let bodyRange = 24..<320 // 296 bytes, i.e. 37 blocks a 8 bytes
|
||||
// let footerRange = 320..<344 // 24 bytes, i.e. 3 blocks a 8 bytes
|
||||
|
||||
let body = Array(bytes[bodyRange])
|
||||
let nextHistoryBlock = Int(body[3])
|
||||
let minutesSinceStart = Int(body[293]) << 8 + Int(body[292])
|
||||
let sensorStartTimeInMilliseconds:Double = Date().toMillisecondsAsDouble() - (Double)(minutesSinceStart * 60 * 1000)
|
||||
|
||||
var measurements = [LibreMeasurement]()
|
||||
|
||||
// History data is stored in body from byte 100 to byte 100+192-1=291 in units of 6 bytes. Index on data such that most recent block is first.
|
||||
for blockIndex in 0..<32 {
|
||||
|
||||
let timeInMinutes = max(0,(Double)(abs(minutesSinceStart - 3)/15)*15 - (Double)(blockIndex*15))
|
||||
|
||||
var index = 100 + (nextHistoryBlock - 1 - blockIndex) * 6 // runs backwards
|
||||
if index < 100 {
|
||||
index = index + 192 // if end of ring buffer is reached shift to beginning of ring buffer
|
||||
}
|
||||
|
||||
let range = index..<index+6
|
||||
let measurementBytes = Array(body[range])
|
||||
|
||||
let measurementDate = Date(timeIntervalSince1970: sensorStartTimeInMilliseconds/1000 + timeInMinutes * 60)
|
||||
|
||||
if measurementDate > timeStampLastBgReading {
|
||||
|
||||
let measurement = LibreMeasurement(bytes: measurementBytes, slope: slope, offset: offset, minuteCounter: Int(timeInMinutes.rawValue), date: measurementDate, LibreDerivedAlgorithmParameterSet: LibreDerivedAlgorithmParameterSet)
|
||||
measurements.append(measurement)
|
||||
|
||||
} else {
|
||||
break
|
||||
}
|
||||
task.resume()
|
||||
|
||||
}
|
||||
|
||||
return measurements
|
||||
}
|
||||
|
||||
private static func historyToLibreGlucose(_ measurements: [LibreMeasurement]) -> [LibreRawGlucoseData] {
|
||||
|
||||
var origarr = [LibreRawGlucoseData]()
|
||||
|
||||
for history in measurements {
|
||||
let glucose = LibreRawGlucoseData(timeStamp: history.date, unsmoothedGlucose: history.temperatureAlgorithmGlucose)
|
||||
origarr.append(glucose)
|
||||
}
|
||||
|
||||
return origarr
|
||||
|
||||
}
|
||||
|
||||
private static func trendToLibreGlucose(_ measurements: [LibreMeasurement]) -> [LibreRawGlucoseData]{
|
||||
|
||||
var origarr = [LibreRawGlucoseData]()
|
||||
|
||||
for trend in measurements {
|
||||
let glucose = LibreRawGlucoseData(timeStamp: trend.date, unsmoothedGlucose: trend.temperatureAlgorithmGlucose)
|
||||
origarr.append(glucose)
|
||||
}
|
||||
|
||||
return LibreGlucoseSmoothing.CalculateSmothedData5Points(origtrends: origarr)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,18 +10,31 @@ import Foundation
|
|||
|
||||
// MARK: Encode/decode helpers
|
||||
|
||||
struct GetCalibrationStatus: Codable {
|
||||
let error: Bool?
|
||||
let command: String?
|
||||
let slope: GetCalibrationStatusResult?
|
||||
let result: GetCalibrationStatusResult?
|
||||
|
||||
/// the parameters from server
|
||||
struct OopWebCalibrationStatus: Codable, CustomStringConvertible {
|
||||
var error: Bool?
|
||||
var command: String?
|
||||
var slope: OopWebCalibrationStatusResult?
|
||||
|
||||
var description: String {
|
||||
return """
|
||||
slope_slope = \(slope?.slopeSlope ?? 0)
|
||||
slope_offset = \(slope?.slopeOffset ?? 0)
|
||||
offset_slope = \(slope?.offsetSlope ?? 0)
|
||||
offset_offset = \(slope?.offsetOffset ?? 0)
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
struct GetCalibrationStatusResult: Codable, CustomStringConvertible{
|
||||
let status: String?
|
||||
let slopeSlope, slopeOffset, offsetOffset, offsetSlope: Double?
|
||||
let uuid: String?
|
||||
let isValidForFooterWithReverseCRCs: Double?
|
||||
struct OopWebCalibrationStatusResult: Codable {
|
||||
var status: String?
|
||||
var slopeSlope: Double?
|
||||
var slopeOffset: Double?
|
||||
var offsetOffset: Double?
|
||||
var offsetSlope: Double?
|
||||
var uuid: String?
|
||||
var isValidForFooterWithReverseCRCs: Double?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case status
|
||||
|
@ -32,6 +45,7 @@ struct GetCalibrationStatusResult: Codable, CustomStringConvertible{
|
|||
case uuid
|
||||
case isValidForFooterWithReverseCRCs = "isValidForFooterWithReverseCRCs"
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "calibrationparams:: slopeslope: \(String(describing: slopeSlope)), slopeoffset: \(String(describing: slopeOffset)), offsetoffset: \(String(describing: offsetOffset)), offsetSlope: \(String(describing: offsetSlope)), isValidForFooterWithReverseCRCs: \(String(describing: isValidForFooterWithReverseCRCs))"
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ class LibreRawGlucoseData: GlucoseData {
|
|||
}
|
||||
|
||||
convenience init(timeStamp:Date, glucoseLevelRaw:Double) {
|
||||
debuglogging("creating LibreRawGlucoseData with timestamp " + timeStamp.description(with: .current) + " and glucoseLevelRaw = " + glucoseLevelRaw.description)
|
||||
self.init(timeStamp: timeStamp, glucoseLevelRaw: glucoseLevelRaw, glucoseLevelFiltered: glucoseLevelRaw)
|
||||
}
|
||||
|
||||
|
@ -19,4 +20,11 @@ class LibreRawGlucoseData: GlucoseData {
|
|||
self.init(timeStamp: timeStamp, glucoseLevelRaw: unsmoothedGlucose, glucoseLevelFiltered: unsmoothedGlucose, unsmoothedGlucose: unsmoothedGlucose)
|
||||
}
|
||||
|
||||
/// description
|
||||
override var description: String {
|
||||
|
||||
return "\nLibreRawGlucoseData\nunsmoothedGlucose = " + unsmoothedGlucose.description + "\n" + "GlucoseData = \n" + super.description
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import Foundation
|
||||
|
||||
class LibreRawGlucoseOOPA2Cotent: NSObject, Codable {
|
||||
|
||||
/// current sensor time
|
||||
var currentTime: Int?
|
||||
|
||||
/// histories
|
||||
var historicBg: [LibreHistoricGlucoseA2]?
|
||||
|
||||
/// current glucose value
|
||||
var currentBg: Double?
|
||||
|
||||
/// description
|
||||
override var description: String {
|
||||
|
||||
var returnValue = "LibreRawGlucoseOOPA2Cotent = \ncurrentTime = " + (currentTime != nil ? currentTime!.description : "nil") + "\n"
|
||||
|
||||
if let historicBg = historicBg {
|
||||
|
||||
for bg in historicBg {
|
||||
|
||||
returnValue = returnValue + bg.description + "\n"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if let currentBg = currentBg {returnValue = returnValue + currentBg.description}
|
||||
|
||||
return returnValue
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
//
|
||||
// LibreRawGlucoseOOPA2Data.swift
|
||||
// xdrip
|
||||
//
|
||||
// Created by Johan Degraeve on 08/06/2020.
|
||||
// Copyright © 2020 Johan Degraeve. All rights reserved.
|
||||
//
|
||||
|
||||
// source https://github.com/JohanDegraeve/xdripswift/blob/bd5b3060f3a7d4c68dce767b5c86306239d06d14/xdrip/BluetoothTransmitter/CGM/Libre/Utilities/LibreRawGlucoseData.swift#L208
|
||||
|
||||
import Foundation
|
||||
|
||||
public class LibreRawGlucoseOOPA2Data: NSObject, Decodable, LibreRawGlucoseWeb {
|
||||
|
||||
var errcode: Int?
|
||||
|
||||
var list: [LibreRawGlucoseOOPA2List]?
|
||||
|
||||
/// - time when instance of LibreRawGlucoseOOPData was created
|
||||
/// - this can be created to calculate the timestamp of realtimeLibreRawGlucoseData
|
||||
let creationTimeStamp = Date()
|
||||
|
||||
/// server parse value
|
||||
var content: LibreRawGlucoseOOPA2Cotent? {
|
||||
return list?.first?.content
|
||||
}
|
||||
|
||||
/// if the server value is error return true
|
||||
var isError: Bool {
|
||||
if content?.currentBg ?? 0 <= 10 {
|
||||
return true
|
||||
}
|
||||
return list?.first?.content?.historicBg?.isEmpty ?? true
|
||||
}
|
||||
|
||||
/// sensor state
|
||||
var sensorState: LibreSensorState {
|
||||
if let id = content?.currentTime {
|
||||
if id < 60 { // if sensor time < 60, the sensor is starting
|
||||
return LibreSensorState.starting
|
||||
} else if id >= 20880 { // if sensor time >= 20880, the sensor expired
|
||||
return LibreSensorState.expired
|
||||
}
|
||||
}
|
||||
|
||||
let state = LibreSensorState.ready
|
||||
return state
|
||||
}
|
||||
|
||||
func glucoseData(timeStampLastBgReading: Date?) -> (libreRawGlucoseData:[LibreRawGlucoseData], sensorState:LibreSensorState, sensorTimeInMinutes:Int?) {
|
||||
|
||||
// initialize returnvalue, empty glucoseData array, sensorState, and nil as sensorTimeInMinutes
|
||||
var returnValue: ([LibreRawGlucoseData], LibreSensorState, Int?) = ([LibreRawGlucoseData](), sensorState, nil)
|
||||
|
||||
// if isError function returns true, then return empty array
|
||||
guard !isError else { return returnValue }
|
||||
|
||||
// if sensorState is not .ready, then return empty array
|
||||
if sensorState != .ready { return returnValue }
|
||||
|
||||
// content should be non nil, content.currentBg not nil and currentBg != 0, content.currentTime (sensorTimeInMinutes) not nil
|
||||
guard let content = content, let currentBg = content.currentBg, currentBg != 0, let sensorTimeInMinutes = content.currentTime else { return returnValue }
|
||||
|
||||
// set senorTimeInMinutes in returnValue
|
||||
returnValue.2 = sensorTimeInMinutes
|
||||
|
||||
// create realtimeLibreRawGlucoseData, which is the current glucose data
|
||||
let realtimeLibreRawGlucoseData = LibreRawGlucoseData(timeStamp: creationTimeStamp, glucoseLevelRaw: currentBg)
|
||||
|
||||
// add first element
|
||||
returnValue.0.append(realtimeLibreRawGlucoseData)
|
||||
|
||||
// history should be non nil, otherwise return only the first value
|
||||
guard var history = content.historicBg else { return returnValue }
|
||||
|
||||
// check the order, first should be the highest value, time is sensor time in minutes, means first should be the most recent or the highest sensor time
|
||||
// if not, reverse it
|
||||
if (history.first?.time ?? 0) < (history.last?.time ?? 0) {
|
||||
history = history.reversed()
|
||||
}
|
||||
|
||||
// iterate through history, only add readings with timetamp > timeStampLastBgReading
|
||||
for libreHistoricGlucoseA2 in history {
|
||||
|
||||
// if quality != 0, the value is error, don't add it
|
||||
if libreHistoricGlucoseA2.quality != 0 {continue}
|
||||
|
||||
// if time is nil, (which is sensorTimeInMinutes at the moment this reading was created), then we can't calculate the timestamp, don't add it
|
||||
if libreHistoricGlucoseA2.time == nil {continue}
|
||||
|
||||
// create timestamp of the reading
|
||||
let readingTimeStamp = creationTimeStamp.addingTimeInterval(-60 * Double(sensorTimeInMinutes - libreHistoricGlucoseA2.time!))
|
||||
|
||||
// only add the new reading if at least 30 seconds older than timeStampLastBgReading (if not nil) - when nil then 0.0 is used, readingTimeStamp will never be < 0.0
|
||||
// as soon as a reading is found which is too old to process then break the loop
|
||||
if readingTimeStamp.toMillisecondsAsDouble() < (timeStampLastBgReading != nil ? (timeStampLastBgReading!.toMillisecondsAsDouble() + 30000) : 0.0) {break}
|
||||
|
||||
// only add readings that are at least 5 minutes away from each other, same approach as in LibreDataParser.parse
|
||||
if let lastElement = returnValue.0.last {
|
||||
|
||||
if lastElement.timeStamp.toMillisecondsAsDouble() - readingTimeStamp.toMillisecondsAsDouble() < (5 * 60 * 1000 - 10000) {continue}
|
||||
|
||||
}
|
||||
|
||||
// bg value should be non nil and > 0.0
|
||||
if libreHistoricGlucoseA2.bg == nil {continue}
|
||||
if libreHistoricGlucoseA2.bg! == 0.0 {continue}
|
||||
|
||||
let libreRawGlucoseData = LibreRawGlucoseData(timeStamp: readingTimeStamp, glucoseLevelRaw: libreHistoricGlucoseA2.bg!)
|
||||
|
||||
returnValue.0.append(libreRawGlucoseData)
|
||||
|
||||
}
|
||||
|
||||
return (returnValue)
|
||||
|
||||
}
|
||||
|
||||
override public var description: String {
|
||||
|
||||
var returnValue = "LibreRawGlucoseOOPA2Data =\n"
|
||||
|
||||
// a description created by LibreRawGlucoseWeb
|
||||
returnValue = returnValue + (self as LibreRawGlucoseWeb).description
|
||||
|
||||
if let errcode = errcode {
|
||||
returnValue = returnValue + " errcode = " + errcode.description + "\n"
|
||||
}
|
||||
|
||||
return returnValue
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import Foundation
|
||||
|
||||
class LibreRawGlucoseOOPA2List: NSObject, Codable {
|
||||
|
||||
var content: LibreRawGlucoseOOPA2Cotent?
|
||||
|
||||
/// description
|
||||
override var description: String {
|
||||
|
||||
guard let content = content else {return "LibreRawGlucoseOOPA2List = \ncontent = nil (type LibreRawGlucoseOOPA2List)"}
|
||||
|
||||
return "LibreRawGlucoseOOPA2List = \ncontent = " + content.description
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
// https://github.com/JohanDegraeve/xdripswift/blob/bd5b3060f3a7d4c68dce767b5c86306239d06d14/xdrip/BluetoothTransmitter/CGM/Libre/Utilities/LibreRawGlucoseData.swift#L208
|
||||
|
||||
import Foundation
|
||||
|
||||
public class LibreRawGlucoseOOPData: NSObject, Decodable, LibreRawGlucoseWeb {
|
||||
|
||||
/// histories by server
|
||||
var historicGlucose : [LibreRawGlucoseOOPGlucose]?
|
||||
|
||||
/// current glucose
|
||||
var realTimeGlucose : LibreRawGlucoseOOPGlucose?
|
||||
|
||||
/// trend arrow by server
|
||||
var trendArrow : String?
|
||||
|
||||
/// sensor message
|
||||
var msg: String?
|
||||
|
||||
var errcode: String?
|
||||
|
||||
/// if endTime != 0, the sensor expired
|
||||
var endTime: Int?
|
||||
|
||||
/// - time when instance of LibreRawGlucoseOOPData was created
|
||||
/// - this can be created to calculate the timestamp of realTimeGlucose
|
||||
let creationTimeStamp = Date()
|
||||
|
||||
enum Error: String {
|
||||
typealias RawValue = String
|
||||
case RESCAN_SENSOR_BAD_CRC // crc failed
|
||||
// sensor terminate
|
||||
case TERMINATE_SENSOR_NORMAL_TERMINATED_STATE
|
||||
case TERMINATE_SENSOR_ERROR_TERMINATED_STATE
|
||||
case TERMINATE_SENSOR_CORRUPT_PAYLOAD
|
||||
// http request bad arguments
|
||||
case FATAL_ERROR_BAD_ARGUMENTS
|
||||
// the follow messages is sensor state
|
||||
case TYPE_SENSOR_NOT_STARTED
|
||||
case TYPE_SENSOR_STARTING
|
||||
case TYPE_SENSOR_Expired
|
||||
case TYPE_SENSOR_END
|
||||
case TYPE_SENSOR_ERROR
|
||||
case TYPE_SENSOR_OK
|
||||
case TYPE_SENSOR_DETERMINED
|
||||
}
|
||||
/// if the server value is error return true
|
||||
var isError: Bool {
|
||||
if let msg = msg {
|
||||
switch Error(rawValue: msg) { // sensor terminate
|
||||
case .TERMINATE_SENSOR_CORRUPT_PAYLOAD,
|
||||
.TERMINATE_SENSOR_NORMAL_TERMINATED_STATE,
|
||||
.TERMINATE_SENSOR_ERROR_TERMINATED_STATE:
|
||||
return false
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
// if parse the 344 failed, historicGlucose will be nil, return is error
|
||||
return historicGlucose?.isEmpty ?? true
|
||||
}
|
||||
|
||||
var sensorState: LibreSensorState {
|
||||
|
||||
/// if sensor time < 60, sensor state is starting
|
||||
if let dataQuality = realTimeGlucose?.dataQuality, let id = realTimeGlucose?.id {
|
||||
if dataQuality != 0 && id < 60 {
|
||||
return LibreSensorState.starting
|
||||
}
|
||||
}
|
||||
|
||||
var state = LibreSensorState.ready
|
||||
// parse the sensor state from msg
|
||||
if let msg = msg {
|
||||
switch Error(rawValue: msg) {
|
||||
case .TYPE_SENSOR_NOT_STARTED:
|
||||
state = .notYetStarted
|
||||
break;
|
||||
case .TYPE_SENSOR_STARTING:
|
||||
state = .starting
|
||||
break;
|
||||
case .TYPE_SENSOR_Expired,
|
||||
.TERMINATE_SENSOR_CORRUPT_PAYLOAD,
|
||||
.TERMINATE_SENSOR_NORMAL_TERMINATED_STATE,
|
||||
.TERMINATE_SENSOR_ERROR_TERMINATED_STATE:
|
||||
state = .expired
|
||||
break;
|
||||
case .TYPE_SENSOR_END:
|
||||
state = .expired
|
||||
break;
|
||||
case .TYPE_SENSOR_ERROR:
|
||||
state = .failure
|
||||
break;
|
||||
case .TYPE_SENSOR_OK:
|
||||
state = .ready
|
||||
case .TYPE_SENSOR_DETERMINED:
|
||||
state = .unknown
|
||||
break
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if endTime != 0, the sensor expired
|
||||
if let endTime = endTime, endTime != 0 {
|
||||
state = .expired
|
||||
}
|
||||
|
||||
return state
|
||||
|
||||
}
|
||||
|
||||
func glucoseData(timeStampLastBgReading: Date?) -> (libreRawGlucoseData:[LibreRawGlucoseData], sensorState:LibreSensorState, sensorTimeInMinutes:Int?) {
|
||||
|
||||
// initialize returnvalue, empty glucoseData array, sensorState, and nil as sensorTimeInMinutes
|
||||
var returnValue: ([LibreRawGlucoseData], LibreSensorState, Int?) = ([LibreRawGlucoseData](), sensorState, nil)
|
||||
|
||||
// if isError function returns true, then return empty array
|
||||
guard !isError else { return returnValue }
|
||||
|
||||
// if sensorState is not .ready, then return empty array
|
||||
if sensorState != .ready { return returnValue }
|
||||
|
||||
// realTimeGlucose must be non-nil and realTimeGlucose.dataQuality must be 0, id (sensor time) must be non nil, otherwise return empty array
|
||||
guard let realTimeGlucose = realTimeGlucose, realTimeGlucose.dataQuality == 0, let value = realTimeGlucose.value, let sensorTimeInMinutes = realTimeGlucose.id else { return returnValue }
|
||||
|
||||
// set senorTimeInMinutes in returnValue
|
||||
returnValue.2 = sensorTimeInMinutes
|
||||
|
||||
// get realtimeLibreRawGlucoseData which is the first element to add, with timestamp the time this instance of LibreRawGlucoseOOPData was created
|
||||
let realtimeLibreRawGlucoseData = LibreRawGlucoseData(timeStamp: creationTimeStamp, glucoseLevelRaw: value)
|
||||
|
||||
// add first element to returnValue
|
||||
returnValue.0.append(realtimeLibreRawGlucoseData)
|
||||
|
||||
// if historicGlucose is nil then return currentLibreRawGlucoseData and an empty array
|
||||
guard var history = historicGlucose else {return returnValue}
|
||||
|
||||
// check the order, first should be the highest value, time is sensor time in minutes, means first should be the most recent or the highest sensor time
|
||||
// if not, reverse it
|
||||
if (history.first?.id ?? 0) < (history.last?.id ?? 0) {
|
||||
history = history.reversed()
|
||||
}
|
||||
|
||||
// go through history
|
||||
for realTimeGlucose in history {
|
||||
|
||||
// if dataQuality != 0, the value is error, don't add it
|
||||
if realTimeGlucose.dataQuality != 0 { continue }
|
||||
|
||||
// if id is nil, (which is sensorTimeInMinutes at the moment this reading was created), then we can't calculate the timestamp, don't add it
|
||||
if realTimeGlucose.id == nil {continue}
|
||||
|
||||
|
||||
// create timestamp of the reading
|
||||
let readingTimeStamp = creationTimeStamp.addingTimeInterval(-60 * Double(sensorTimeInMinutes - realTimeGlucose.id!))
|
||||
|
||||
|
||||
// only add the new reading if at least 30 seconds older than timeStampLastBgReading (if not nil) - when nil then 0.0 is used, readingTimeStamp will never be < 0.0
|
||||
// as soon as a reading is found which is too old to process then break the loop
|
||||
if readingTimeStamp.toMillisecondsAsDouble() < (timeStampLastBgReading != nil ? (timeStampLastBgReading!.toMillisecondsAsDouble() + 30000) : 0.0) {break}
|
||||
|
||||
// only add readings that are at least 5 minutes away from each other, same approach as in LibreDataParser.parse
|
||||
if let lastElement = returnValue.0.last {
|
||||
if lastElement.timeStamp.toMillisecondsAsDouble() - readingTimeStamp.toMillisecondsAsDouble() < (5 * 60 * 1000 - 10000) {continue}
|
||||
}
|
||||
|
||||
// realTimeGlucose.value should be non nil and not 0
|
||||
if realTimeGlucose.value == nil {continue}
|
||||
if realTimeGlucose.value! == 0.0 {continue}
|
||||
|
||||
let libreRawGlucoseData = LibreRawGlucoseData(timeStamp: readingTimeStamp, glucoseLevelRaw: realTimeGlucose.value!)
|
||||
|
||||
returnValue.0.append(libreRawGlucoseData)
|
||||
|
||||
}
|
||||
|
||||
return (returnValue)
|
||||
|
||||
}
|
||||
|
||||
public override var description: String {
|
||||
|
||||
var returnValue = "LibreRawGlucoseWeb =\n"
|
||||
|
||||
// a description created by LibreRawGlucoseWeb
|
||||
returnValue = returnValue + (self as LibreRawGlucoseWeb).description
|
||||
|
||||
if let errcode = errcode {
|
||||
returnValue = returnValue + " errcode = " + errcode.description + "\n"
|
||||
}
|
||||
|
||||
return returnValue
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import Foundation
|
||||
|
||||
/// a readings value, id (time in minutes since sensor start) and dataQuality
|
||||
class LibreRawGlucoseOOPGlucose: NSObject, Codable {
|
||||
|
||||
/// if dataQuality != 0, it means the value is error
|
||||
let dataQuality : Int?
|
||||
|
||||
/// the time of this reading, in minutes, since sensor start
|
||||
let id: Int?
|
||||
|
||||
/// glucose value
|
||||
let value : Double?
|
||||
|
||||
/// description
|
||||
override var description: String {
|
||||
|
||||
return "LibreRawGlucoseOOPGlucose = \ndataQuality = " + (dataQuality != nil ? dataQuality!.description : "nil") + ", id = " + (id != nil ? id!.description : "nil") + ", value = " + (value != nil ? value!.description : "nil")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import Foundation
|
||||
|
||||
protocol LibreRawGlucoseWeb {
|
||||
|
||||
/// if the server value is error return true
|
||||
var isError: Bool { get }
|
||||
|
||||
/// sensor state
|
||||
var sensorState: LibreSensorState { get }
|
||||
|
||||
/// - parameters:
|
||||
/// - timeStampLastBgReading: this is of the timestamp of the latest reading we already received during previous session, optional
|
||||
/// - returns:
|
||||
/// - array of libreRawGlucoseData, first is the most recent. Only returns recent readings, ie not the ones that are older than timeStampLastBgReading. 30 seconds are added here, meaning, new reading should be at least 30 seconds more recent than timeStampLastBgReading
|
||||
/// - sensorState: status of the sensor
|
||||
/// - sensorTimeInMinutes: age of sensor in minutes, optional
|
||||
func glucoseData(timeStampLastBgReading: Date?) -> (libreRawGlucoseData:[LibreRawGlucoseData], sensorState:LibreSensorState, sensorTimeInMinutes:Int?)
|
||||
|
||||
}
|
||||
|
||||
// to implement a var description
|
||||
extension LibreRawGlucoseWeb {
|
||||
|
||||
var description: String {
|
||||
|
||||
var returnValue = " isError = " + isError.description + "\n"
|
||||
|
||||
returnValue = returnValue + " sensorState = " + sensorState.description + "\n"
|
||||
|
||||
let libreGlucoseData = glucoseData(timeStampLastBgReading: nil)
|
||||
|
||||
returnValue = returnValue + "\nSize of [LibreRawGlucoseData] = " + libreGlucoseData.libreRawGlucoseData.count.description + "\n"
|
||||
|
||||
if libreGlucoseData.libreRawGlucoseData.count > 0 {
|
||||
returnValue = returnValue + "list = \n"
|
||||
|
||||
for glucoseData in libreGlucoseData.libreRawGlucoseData {
|
||||
returnValue = returnValue + glucoseData.description + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
if let sensorTimeInMinutes = libreGlucoseData.sensorTimeInMinutes {
|
||||
|
||||
returnValue = returnValue + "sensor time in minutes = " + sensorTimeInMinutes.description + "\n"
|
||||
|
||||
} else {
|
||||
|
||||
returnValue = returnValue + "sensor time in minutes is unknown\n"
|
||||
|
||||
}
|
||||
|
||||
return returnValue
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
struct LibreSensorSerialNumber: CustomStringConvertible {
|
||||
public struct LibreSensorSerialNumber: CustomStringConvertible {
|
||||
|
||||
let uid: Data
|
||||
|
||||
|
@ -102,8 +102,10 @@ struct LibreSensorSerialNumber: CustomStringConvertible {
|
|||
return stringArray.dropFirst().reduce(stringArray.first!, {$0 + ":" + $1} )
|
||||
}
|
||||
|
||||
// MARK: - CustomStringConvertible Protocoll
|
||||
var description: String {
|
||||
// MARK: - CustomStringConvertible Protocol
|
||||
|
||||
public var description: String {
|
||||
return "Uid is \(prettyUidString) and derived serial number is \(serialNumber)"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import Foundation
|
|||
/// - shutdown: 0x05 sensor stops operation after 15d after start
|
||||
/// - failure: 0x06 sensor has an error
|
||||
/// - unknown: any other state
|
||||
enum LibreSensorState {
|
||||
public enum LibreSensorState {
|
||||
case notYetStarted
|
||||
case starting
|
||||
case ready
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
import Foundation
|
||||
|
||||
// sourcre https://github.com/gui-dos/DiaBLE
|
||||
|
||||
public enum LibreSensorType: String {
|
||||
|
||||
// TODO: can this be improved ? raw type UInt8 iso String ? in which case all patchInfo variables would need to become of type LibreSensorType in stead of a String. In the bluetooth transmitters, then when assigning a value, immediately set to LibreSensorType iso patchInfo
|
||||
|
||||
// Libre 1
|
||||
case libre1 = "DF"
|
||||
|
||||
case libre1A2 = "A2"
|
||||
|
||||
case libre2 = "9D"
|
||||
|
||||
case libreUS = "E5"
|
||||
|
||||
case libreProH = "70"
|
||||
|
||||
var description: String {
|
||||
|
||||
switch self {
|
||||
|
||||
case .libre1:
|
||||
return "Libre 1"
|
||||
|
||||
case .libre1A2:
|
||||
return "Libre 1"
|
||||
|
||||
case .libre2:
|
||||
return "Libre 2"
|
||||
|
||||
case .libreUS:
|
||||
return "Libre US"
|
||||
|
||||
case .libreProH:
|
||||
return "Libre PRO H"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// some of the Libre types can not work without webOOP. In case returnvalue is true, then user can not change the value
|
||||
func needsWebOOP() -> Bool {
|
||||
|
||||
switch self {
|
||||
|
||||
case .libre1:
|
||||
return false
|
||||
|
||||
case .libre1A2, .libre2, .libreUS, .libreProH :
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// - reads the first byte in patchInfo and dependent on that value, returns type of sensor
|
||||
/// - if patchInfo = nil, then returnvalue is Libre1
|
||||
/// - if first byte is unknown, then returns nil
|
||||
static func type(patchInfo: String?) -> LibreSensorType? {
|
||||
|
||||
guard let patchInfo = patchInfo else {return .libre1}
|
||||
|
||||
guard patchInfo.count > 1 else {return nil}
|
||||
|
||||
let firstTwoChars = patchInfo[0..<2]
|
||||
|
||||
switch firstTwoChars {
|
||||
|
||||
case "DF":
|
||||
return .libre1
|
||||
|
||||
case "A2":
|
||||
return .libre1A2
|
||||
|
||||
case "9D":
|
||||
return .libre2
|
||||
|
||||
case "E5":
|
||||
return .libreUS
|
||||
|
||||
case "70":
|
||||
return .libreProH
|
||||
|
||||
default:
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +119,9 @@ final class WatlaaBluetoothTransmitter: BluetoothTransmitter {
|
|||
// current sensor serial number, if nil then it's not known yet
|
||||
private var sensorSerialNumber:String?
|
||||
|
||||
/// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send
|
||||
var emptyArray: [GlucoseData] = []
|
||||
|
||||
// MARK: - public functions
|
||||
|
||||
/// - parameters:
|
||||
|
@ -288,10 +291,8 @@ final class WatlaaBluetoothTransmitter: BluetoothTransmitter {
|
|||
trace("in peripheral didUpdateValueFor, Buffer complete", log: log, category: ConstantsLog.categoryWatlaa, type: .info)
|
||||
|
||||
if (Crc.LibreCrc(data: &rxBuffer, headerOffset: miaoMiaoHeaderLength)) {
|
||||
|
||||
//get MiaoMiao info from MiaoMiao header
|
||||
let firmware = String(describing: rxBuffer[14...15].hexEncodedString())
|
||||
let hardware = String(describing: rxBuffer[16...17].hexEncodedString())
|
||||
|
||||
// get batteryPercentage
|
||||
let batteryPercentage = Int(rxBuffer[13])
|
||||
|
||||
// get sensor serialNumber and if changed inform delegate
|
||||
|
@ -320,11 +321,17 @@ final class WatlaaBluetoothTransmitter: BluetoothTransmitter {
|
|||
// send battery level to delegate
|
||||
watlaaBluetoothTransmitterDelegate?.received(transmitterBatteryLevel: batteryPercentage, watlaaBluetoothTransmitter: self)
|
||||
|
||||
LibreDataParser.libreDataProcessor(sensorSerialNumber: LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 5..<13)))?.serialNumber, webOOPEnabled: webOOPEnabled, oopWebSite: oopWebSite, oopWebToken: oopWebToken, libreData: (rxBuffer.subdata(in: miaoMiaoHeaderLength..<(344 + miaoMiaoHeaderLength))), cgmTransmitterDelegate: cgmTransmitterDelegate, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), firmware: firmware, hardware: hardware, hardwareSerialNumber: nil, bootloader: nil, timeStampLastBgReading: timeStampLastBgReading, completionHandler: {(timeStampLastBgReading:Date) in
|
||||
self.timeStampLastBgReading = timeStampLastBgReading
|
||||
|
||||
})
|
||||
// send batteryPercentage to delegate
|
||||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorTimeInMinutes: nil)
|
||||
|
||||
LibreDataParser.libreDataProcessor(libreSensorSerialNumber: LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 5..<13))), patchInfo: nil, webOOPEnabled: webOOPEnabled, oopWebSite: oopWebSite, oopWebToken: oopWebToken, libreData: (rxBuffer.subdata(in: miaoMiaoHeaderLength..<(344 + miaoMiaoHeaderLength))), cgmTransmitterDelegate: cgmTransmitterDelegate, timeStampLastBgReading: timeStampLastBgReading, completionHandler: { (timeStampLastBgReading: Date?, sensorState: LibreSensorState?) in
|
||||
|
||||
if let timeStampLastBgReading = timeStampLastBgReading {
|
||||
self.timeStampLastBgReading = timeStampLastBgReading
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
//reset the buffer
|
||||
resetRxBuffer()
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ enum ConstantsLibre {
|
|||
static let defaultWebOOPEnabled = false
|
||||
|
||||
/// site for libreOOP client
|
||||
static let site = "http://www.glucose.space/calibrateSensor"
|
||||
static let site = "http://www.glucose.space"
|
||||
|
||||
/// token to use to access site
|
||||
static let token = "bubble-201907"
|
||||
|
|
|
@ -106,6 +106,9 @@ enum ConstantsLog {
|
|||
/// LibreOOPClient
|
||||
static let categoryLibreOOPClient = "LibreOOPClient "
|
||||
|
||||
/// libreDataParser
|
||||
static let categoryLibreDataParser = "LibreDataParser "
|
||||
|
||||
/// for use in M5Stack
|
||||
static let categoryM5StackBluetoothTransmitter = "M5StackBluetoothTransmitter "
|
||||
|
||||
|
@ -124,8 +127,10 @@ enum ConstantsLog {
|
|||
/// bluetoothPeripheralViewController
|
||||
static let categoryBluetoothPeripheralViewController = "blePeripheralViewController "
|
||||
|
||||
/// nightscout view model
|
||||
static let categoryNightScoutSettingsViewModel = "nightScoutSettingsViewModel "
|
||||
|
||||
/// trace
|
||||
static let categoryTraceSettingsViewModel = "TraceSettingsViewModel"
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,12 @@ import CoreData
|
|||
/// - has attributes which are common to all kinds of BluetoothPeripheral : address, name, shouldconnect, alias, parameterUpdateNeededAtNextConnect.
|
||||
/// - the name is very close the the protocol BluetoothPeripheral, bad luck that's all
|
||||
public class BLEPeripheral: NSManagedObject {
|
||||
|
||||
|
||||
/// - libre sensor type, not stored in coreData, will only be available after having received it from the Bubble
|
||||
/// - if nil, then it's not known yet
|
||||
/// - will only be used for BLEPeripherals that support Libre
|
||||
public var libreSensorType: LibreSensorType?
|
||||
|
||||
/// create BLEPeripheral, shouldconnect default value = true
|
||||
init(address: String, name: String, alias: String?, nsManagedObjectContext:NSManagedObjectContext) {
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public class BgReading: NSManagedObject {
|
|||
c = 0
|
||||
ra = 0
|
||||
rb = 0
|
||||
rc = 0
|
||||
rc = 0
|
||||
hideSlope = false
|
||||
id = UniqueId.createEventId()
|
||||
}
|
||||
|
|
|
@ -3,9 +3,12 @@ import CoreData
|
|||
|
||||
public class Bubble: NSManagedObject {
|
||||
|
||||
/// batterylevel, not stored in coreData, will only be available after having received it from the M5Stack
|
||||
/// batterylevel, not stored in coreData, will only be available after having received it from the Bubble
|
||||
public var batteryLevel: Int = 0
|
||||
|
||||
// sensorState
|
||||
public var sensorState: LibreSensorState = .unknown
|
||||
|
||||
/// create Bubble
|
||||
/// - parameters:
|
||||
init(address: String, name: String, alias: String?, nsManagedObjectContext:NSManagedObjectContext) {
|
||||
|
|
|
@ -95,6 +95,10 @@ extension Data {
|
|||
append(UnsafeBufferPointer(start: &element, count: 1))
|
||||
}
|
||||
|
||||
func getByteAt(position:Int) -> Int {
|
||||
return Int(self[position])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import Foundation
|
||||
|
||||
extension NSMutableURLRequest {
|
||||
extension URLRequest {
|
||||
|
||||
/// Populate the HTTPBody of `application/x-www-form-urlencoded` request
|
||||
///
|
||||
/// - parameters:
|
||||
/// - contentMap : A dictionary of keys and values to be added to the request
|
||||
func setBodyContent(contentMap: [String: String]) {
|
||||
mutating func setBodyContent(contentMap: [String: String]) {
|
||||
let parameters = contentMap.map { (key, value) -> String in
|
||||
return "\(key)=\(value.stringByAddingPercentEscapesForQueryValue()!)"
|
||||
}
|
||||
|
|
|
@ -181,6 +181,9 @@ extension UserDefaults {
|
|||
/// to merge from 3.x to 4.x, can be deleted once 3.x is not used anymore
|
||||
case cgmTransmitterDeviceAddress = "cgmTransmitterDeviceAddress"
|
||||
|
||||
/// web oop parameters, only for Libre 1
|
||||
case libre1DerivedAlgorithmParameters = "algorithmParameters"
|
||||
|
||||
}
|
||||
|
||||
// MARK: - ===== User Configurable Settings ======
|
||||
|
@ -848,6 +851,22 @@ extension UserDefaults {
|
|||
}
|
||||
}
|
||||
|
||||
/// web oop parameters, only for bubble
|
||||
var libre1DerivedAlgorithmParameters: Libre1DerivedAlgorithmParameters? {
|
||||
get {
|
||||
guard let jsonString = string(forKey: Key.libre1DerivedAlgorithmParameters.rawValue) else { return nil }
|
||||
guard let jsonData = jsonString.data(using: .utf8) else { return nil }
|
||||
guard let value = try? JSONDecoder().decode(Libre1DerivedAlgorithmParameters.self, from: jsonData) else { return nil }
|
||||
return value
|
||||
}
|
||||
set {
|
||||
let encoder = JSONEncoder()
|
||||
guard let jsonData = try? encoder.encode(newValue) else { return }
|
||||
let jsonString = String(bytes: jsonData, encoding: .utf8)
|
||||
set(jsonString, forKey: Key.libre1DerivedAlgorithmParameters.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -13,6 +13,33 @@ extension BluetoothPeripheralManager: CGMBubbleTransmitterDelegate {
|
|||
|
||||
}
|
||||
|
||||
func received(sensorStatus: LibreSensorState, from cGMBubbleTransmitter: CGMBubbleTransmitter) {
|
||||
|
||||
guard let bubble = findTransmitter(cGMBubbleTransmitter: cGMBubbleTransmitter) else {return}
|
||||
|
||||
// store serial number in bubble object
|
||||
bubble.sensorState = sensorStatus
|
||||
|
||||
// no coredatamanager savechanges needed because batterylevel is not stored in coredata
|
||||
|
||||
}
|
||||
|
||||
func received(libreSensorType: LibreSensorType, from cGMBubbleTransmitter: CGMBubbleTransmitter) {
|
||||
|
||||
guard let bubble = findTransmitter(cGMBubbleTransmitter: cGMBubbleTransmitter) else {return}
|
||||
|
||||
// store serial number in bubble.blePeripheral object
|
||||
bubble.blePeripheral.libreSensorType = libreSensorType
|
||||
|
||||
// if the libreSensorType needs oopweb, then enable oopweb. (User may have set it to false, but if it's one that requires oopweb, then we force to true)
|
||||
if libreSensorType.needsWebOOP() {
|
||||
bubble.blePeripheral.webOOPEnabled = true
|
||||
}
|
||||
|
||||
// coredatamanager savechanges needed because webOOPEnabled is stored in coredata
|
||||
coreDataManager.saveChanges()
|
||||
|
||||
}
|
||||
|
||||
func received(serialNumber: String, from cGMBubbleTransmitter: CGMBubbleTransmitter) {
|
||||
|
||||
|
|
|
@ -521,7 +521,7 @@ class BluetoothPeripheralManager: NSObject {
|
|||
|
||||
if let watlaa = bluetoothPeripheral as? Watlaa {
|
||||
|
||||
newTransmitter = WatlaaBluetoothTransmitter(address: watlaa.blePeripheral.address, name: watlaa.blePeripheral.name, cgmTransmitterDelegate: cgmTransmitterDelegate, bluetoothTransmitterDelegate: self, watlaaBluetoothTransmitterDelegate: self, timeStampLastBgReading: nil, sensorSerialNumber: watlaa.blePeripheral.sensorSerialNumber, webOOPEnabled: watlaa.blePeripheral.webOOPEnabled, oopWebSite: watlaa.blePeripheral.oopWebSite, oopWebToken: watlaa.blePeripheral.oopWebSite)
|
||||
newTransmitter = WatlaaBluetoothTransmitter(address: watlaa.blePeripheral.address, name: watlaa.blePeripheral.name, cgmTransmitterDelegate: cgmTransmitterDelegate, bluetoothTransmitterDelegate: self, watlaaBluetoothTransmitterDelegate: self, timeStampLastBgReading: nil, sensorSerialNumber: watlaa.blePeripheral.sensorSerialNumber, webOOPEnabled: watlaa.blePeripheral.webOOPEnabled, oopWebSite: watlaa.blePeripheral.oopWebSite, oopWebToken: watlaa.blePeripheral.oopWebToken)
|
||||
|
||||
}
|
||||
|
||||
|
@ -570,7 +570,7 @@ class BluetoothPeripheralManager: NSObject {
|
|||
|
||||
if let cgmTransmitterDelegate = cgmTransmitterDelegate {
|
||||
|
||||
newTransmitter = CGMBubbleTransmitter(address: bubble.blePeripheral.address, name: bubble.blePeripheral.name, bluetoothTransmitterDelegate: self, cGMBubbleTransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, timeStampLastBgReading: nil, sensorSerialNumber: bubble.blePeripheral.sensorSerialNumber, webOOPEnabled: bubble.blePeripheral.webOOPEnabled, oopWebSite: bubble.blePeripheral.oopWebSite, oopWebToken: bubble.blePeripheral.oopWebSite)
|
||||
newTransmitter = CGMBubbleTransmitter(address: bubble.blePeripheral.address, name: bubble.blePeripheral.name, bluetoothTransmitterDelegate: self, cGMBubbleTransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, timeStampLastBgReading: nil, sensorSerialNumber: bubble.blePeripheral.sensorSerialNumber, webOOPEnabled: bubble.blePeripheral.webOOPEnabled, oopWebSite: bubble.blePeripheral.oopWebSite, oopWebToken: bubble.blePeripheral.oopWebToken)
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -585,7 +585,7 @@ class BluetoothPeripheralManager: NSObject {
|
|||
|
||||
if let cgmTransmitterDelegate = cgmTransmitterDelegate {
|
||||
|
||||
newTransmitter = CGMMiaoMiaoTransmitter(address: miaoMiao.blePeripheral.address, name: miaoMiao.blePeripheral.name, bluetoothTransmitterDelegate: self, cGMMiaoMiaoTransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, timeStampLastBgReading: nil, sensorSerialNumber: miaoMiao.blePeripheral.sensorSerialNumber, webOOPEnabled: miaoMiao.blePeripheral.webOOPEnabled, oopWebSite: miaoMiao.blePeripheral.oopWebSite, oopWebToken: miaoMiao.blePeripheral.oopWebSite)
|
||||
newTransmitter = CGMMiaoMiaoTransmitter(address: miaoMiao.blePeripheral.address, name: miaoMiao.blePeripheral.name, bluetoothTransmitterDelegate: self, cGMMiaoMiaoTransmitterDelegate: self, cGMTransmitterDelegate: cgmTransmitterDelegate, timeStampLastBgReading: nil, sensorSerialNumber: miaoMiao.blePeripheral.sensorSerialNumber, webOOPEnabled: miaoMiao.blePeripheral.webOOPEnabled, oopWebSite: miaoMiao.blePeripheral.oopWebSite, oopWebToken: miaoMiao.blePeripheral.oopWebToken)
|
||||
|
||||
} else {
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"confirmDeletionPeripheral" = "Do you want to delete bluetooth device: ";
|
||||
"bluetoothPeripheralAlias" = "Alias:";
|
||||
"SensorSerialNumber" = "Sensor Serial Number:";
|
||||
"sensorType" = "Sensor Type:";
|
||||
"serialNumber" = "Serial Number:";
|
||||
"battery" = "Battery:";
|
||||
"needsTransmitterId" = "Missing Transmitter ID";
|
||||
|
|
|
@ -32,3 +32,4 @@
|
|||
"firmware" = "Firmware";
|
||||
"hardware" = "Hardware";
|
||||
"unknown" = "Unknown";
|
||||
"sensorStatus" = "Sensor status";
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>3889</string>
|
||||
<string>3882</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>AppGroupIdentifier</key>
|
||||
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>xDrip4iOS</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<<<<<<< HEAD
|
||||
<string>3884</string>
|
||||
=======
|
||||
<string>3881</string>
|
||||
>>>>>>> 74e797ad31f6fadf1504e2ab9c1a54d9489f3c85
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>Connect to CGM Transmitter and M5Stack</string>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<string>Connect to CGM Transmitter and M5Stack</string>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>To create events with reading values as title, so that it can be viewed on Apple Watch</string>
|
||||
<key>NSHealthShareUsageDescription</key>
|
||||
<string>Store bloodglucose readings</string>
|
||||
<key>NSHealthUpdateUsageDescription</key>
|
||||
<string>Store bloodglucose readings</string>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>bluetooth-central</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<false/>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleLightContent</string>
|
||||
<key>UIStatusBarTintParameters</key>
|
||||
<dict>
|
||||
<key>UINavigationBar</key>
|
||||
<dict>
|
||||
<key>Style</key>
|
||||
<string>UIBarStyleDefault</string>
|
||||
<key>Translucent</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Light</string>
|
||||
<key>view controller-based status bar</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -40,6 +40,10 @@ class Texts_BluetoothPeripheralView {
|
|||
return NSLocalizedString("SensorSerialNumber", tableName: filename, bundle: Bundle.main, value: "Sensor Serial Number:", comment: "BluetoothPeripheral view, text of the cell with the sensor serial number")
|
||||
}()
|
||||
|
||||
static let sensorType: String = {
|
||||
return NSLocalizedString("sensorType", tableName: filename, bundle: Bundle.main, value: "Sensor Type:", comment: "BluetoothPeripheral view, text of the cell with the sensor type (only used for Libre)")
|
||||
}()
|
||||
|
||||
static let serialNumber: String = {
|
||||
return NSLocalizedString("serialNumber", tableName: filename, bundle: Bundle.main, value: "Serial Number:", comment: "BluetoothPeripheral view, text of the cell with the serial number (this is not the sensor serial number")
|
||||
}()
|
||||
|
|
|
@ -145,4 +145,8 @@ class Texts_Common {
|
|||
return NSLocalizedString("unknown", tableName: filename, bundle: Bundle.main, value: "Unknown", comment: "general usage")
|
||||
}()
|
||||
|
||||
static let sensorStatus = {
|
||||
return NSLocalizedString("sensorStatus", tableName: filename, bundle: Bundle.main, value: "Sensor status", comment: "to show the sensor status")
|
||||
}()
|
||||
|
||||
}
|
||||
|
|
|
@ -23,8 +23,6 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
/// timestamp when connection changed to connected or not connected
|
||||
case connectOrDisconnectTimeStamp = 4
|
||||
|
||||
/// ANY NEW SETTINGS SHOULD BE INSERTED HERE
|
||||
|
||||
/// transmitterID, only for devices that need it
|
||||
case transmitterId = 5
|
||||
|
||||
|
@ -859,15 +857,36 @@ extension BluetoothPeripheralViewController: UITableViewDataSource, UITableViewD
|
|||
|
||||
case .webOOPEnabled:
|
||||
|
||||
// set row text and set default row label to nil
|
||||
cell.textLabel?.text = Texts_SettingsView.labelWebOOPTransmitter
|
||||
cell.detailTextLabel?.text = nil
|
||||
|
||||
var currentStatus = false
|
||||
// get current value of webOOPEnabled, default false
|
||||
var currentWebOOPEnabledValue = false
|
||||
if let bluetoothPeripheral = bluetoothPeripheral {
|
||||
currentStatus = bluetoothPeripheral.blePeripheral.webOOPEnabled
|
||||
|
||||
currentWebOOPEnabledValue = bluetoothPeripheral.blePeripheral.webOOPEnabled
|
||||
|
||||
}
|
||||
|
||||
cell.accessoryView = UISwitch(isOn: currentStatus, action: { (isOn:Bool) in
|
||||
// set accessoryView to UISwitch with currentWebOOPEnabledValue assigned
|
||||
|
||||
let test = UISwitch(isOn: currentWebOOPEnabledValue, action: { (isOn:Bool) in
|
||||
|
||||
self.bluetoothPeripheral?.blePeripheral.webOOPEnabled = isOn
|
||||
|
||||
// send info to bluetoothPeripheralManager
|
||||
if let bluetoothPeripheral = self.bluetoothPeripheral {
|
||||
|
||||
bluetoothPeripheralManager.receivedNewValue(webOOPEnabled: isOn, for: bluetoothPeripheral)
|
||||
|
||||
tableView.reloadSections(IndexSet(integer: self.webOOPSettingsSectionNumber), with: .none)
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
cell.accessoryView = UISwitch(isOn: currentWebOOPEnabledValue, action: { (isOn:Bool) in
|
||||
|
||||
self.bluetoothPeripheral?.blePeripheral.webOOPEnabled = isOn
|
||||
|
||||
|
@ -882,6 +901,15 @@ extension BluetoothPeripheralViewController: UITableViewDataSource, UITableViewD
|
|||
|
||||
})
|
||||
|
||||
// if it's a bluetoothPeripheral that supports libre and if it's a libre sensor type that needs oopWeb, then value can not be changed,
|
||||
if let bluetoothPeripheral = self.bluetoothPeripheral, let libreSensorType = bluetoothPeripheral.blePeripheral.libreSensorType {
|
||||
if libreSensorType.needsWebOOP() {
|
||||
|
||||
cell.accessoryView?.isUserInteractionEnabled = false
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
cell.accessoryType = .none
|
||||
|
||||
case .webOOPsite:
|
||||
|
|
|
@ -36,6 +36,4 @@ protocol BluetoothPeripheralViewModel {
|
|||
/// how many sections does this viewmodel define, in addition to the section already defined in BluetoothPeripheralViewController
|
||||
func numberOfSections() -> Int
|
||||
|
||||
/// as weboop is a general setting, but only applicable to specific types of transmitter, the model will need to be able to tell if it's supported or not
|
||||
///
|
||||
}
|
||||
|
|
|
@ -7,17 +7,23 @@ class BubbleBluetoothPeripheralViewModel {
|
|||
/// settings specific for bubble
|
||||
private enum Settings:Int, CaseIterable {
|
||||
|
||||
/// battery level
|
||||
case batteryLevel = 0
|
||||
|
||||
/// firmware version
|
||||
case firmWare = 1
|
||||
|
||||
/// hardware version
|
||||
case hardWare = 2
|
||||
/// Libre sensor type
|
||||
case sensorType = 0
|
||||
|
||||
/// Sensor serial number
|
||||
case sensorSerialNumber = 3
|
||||
case sensorSerialNumber = 1
|
||||
|
||||
/// sensor State
|
||||
case sensorState = 2
|
||||
|
||||
/// battery level
|
||||
case batteryLevel = 3
|
||||
|
||||
/// firmware version
|
||||
case firmWare = 4
|
||||
|
||||
/// hardware version
|
||||
case hardWare = 5
|
||||
|
||||
}
|
||||
|
||||
|
@ -155,6 +161,29 @@ extension BubbleBluetoothPeripheralViewModel: BluetoothPeripheralViewModel {
|
|||
}
|
||||
|
||||
|
||||
case .sensorType:
|
||||
|
||||
cell.accessoryType = .none
|
||||
|
||||
cell.textLabel?.text = Texts_BluetoothPeripheralView.sensorType
|
||||
|
||||
if let libreSensorType = bubble.blePeripheral.libreSensorType {
|
||||
|
||||
cell.detailTextLabel?.text = libreSensorType.description
|
||||
|
||||
} else {
|
||||
|
||||
cell.detailTextLabel?.text = nil
|
||||
}
|
||||
|
||||
case .sensorState:
|
||||
|
||||
cell.accessoryType = .none
|
||||
|
||||
cell.textLabel?.text = Texts_Common.sensorStatus
|
||||
|
||||
cell.detailTextLabel?.text = bubble.sensorState.description
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -170,7 +199,7 @@ extension BubbleBluetoothPeripheralViewModel: BluetoothPeripheralViewModel {
|
|||
|
||||
switch setting {
|
||||
|
||||
case .batteryLevel:
|
||||
case .batteryLevel, .sensorType, .sensorState:
|
||||
return .nothing
|
||||
|
||||
case .firmWare:
|
||||
|
@ -225,6 +254,16 @@ extension BubbleBluetoothPeripheralViewModel: CGMBubbleTransmitterDelegate {
|
|||
|
||||
}
|
||||
|
||||
func received(sensorStatus: LibreSensorState, from cGMBubbleTransmitter: CGMBubbleTransmitter) {
|
||||
|
||||
// inform also bluetoothPeripheralManager
|
||||
(bluetoothPeripheralManager as? CGMBubbleTransmitterDelegate)?.received(sensorStatus: sensorStatus, from: cGMBubbleTransmitter)
|
||||
|
||||
// here's the trigger to update the table
|
||||
reloadRow(row: Settings.sensorState.rawValue)
|
||||
|
||||
}
|
||||
|
||||
func received(serialNumber: String, from cGMBubbleTransmitter: CGMBubbleTransmitter) {
|
||||
|
||||
// inform also bluetoothPeripheralManager
|
||||
|
@ -247,7 +286,7 @@ extension BubbleBluetoothPeripheralViewModel: CGMBubbleTransmitterDelegate {
|
|||
|
||||
func received(hardware: String, from cGMBubbleTransmitter: CGMBubbleTransmitter) {
|
||||
|
||||
// inform also bluetoothPeripheralManager
|
||||
// inform bluetoothPeripheralManager, bluetoothPeripheralManager will store the hardware in the bubble object
|
||||
(bluetoothPeripheralManager as? CGMBubbleTransmitterDelegate)?.received(hardware: hardware, from: cGMBubbleTransmitter)
|
||||
|
||||
// here's the trigger to update the table
|
||||
|
@ -255,6 +294,18 @@ extension BubbleBluetoothPeripheralViewModel: CGMBubbleTransmitterDelegate {
|
|||
|
||||
}
|
||||
|
||||
func received(libreSensorType: LibreSensorType, from cGMBubbleTransmitter: CGMBubbleTransmitter) {
|
||||
|
||||
// inform bluetoothPeripheralManager, bluetoothPeripheralManager will store the libreSensorType in the bubble object
|
||||
(bluetoothPeripheralManager as? CGMBubbleTransmitterDelegate)?.received(libreSensorType: libreSensorType, from: cGMBubbleTransmitter)
|
||||
|
||||
// here's the trigger to update the table row for sensorType
|
||||
reloadRow(row: Settings.sensorType.rawValue)
|
||||
|
||||
// ideally we should also reload the row which allows to change the value of OOP web enabled, because it may have been set by the BluetoothPeripheralManager to true, but that's in the class BluetoothPeripheralViewController, tant pis. Might be confusing, user may see it as disabled but still it's enabled
|
||||
|
||||
}
|
||||
|
||||
private func reloadRow(row: Int) {
|
||||
|
||||
if let bluetoothPeripheralViewController = bluetoothPeripheralViewController {
|
||||
|
|
|
@ -507,6 +507,8 @@ final class RootViewController: UIViewController {
|
|||
// also for cases where calibration is not needed, we go through this code
|
||||
if let activeSensor = activeSensor, let calibrator = calibrator, let bgReadingsAccessor = bgReadingsAccessor {
|
||||
|
||||
// TODO : check on sensorage should be done in another way, because 10 day sensors will now also be supported
|
||||
|
||||
// assuming it's a 14 day sensor (as 10 day sensor is not supported), if it's a Libre sensor and age > 14.5 days, then don't process the readings
|
||||
if cgmTransmitter.cgmTransmitterType().sensorType() == .Libre{
|
||||
|
||||
|
|
Loading…
Reference in New Issue