resolve merge conflict

This commit is contained in:
Johan Degraeve 2020-06-29 21:42:32 +02:00
commit 2402129e26
46 changed files with 1588 additions and 521 deletions

View File

@ -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

View File

@ -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 */,

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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)

View File

@ -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"
}
}
}

View File

@ -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)
}

View File

@ -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()

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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")
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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))"
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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")
}
}

View File

@ -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
}
}

View File

@ -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)"
}
}

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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()

View File

@ -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"

View File

@ -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"
}

View File

@ -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) {

View File

@ -35,7 +35,7 @@ public class BgReading: NSManagedObject {
c = 0
ra = 0
rb = 0
rc = 0
rc = 0
hideSlope = false
id = UniqueId.createEventId()
}

View File

@ -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) {

View File

@ -95,6 +95,10 @@ extension Data {
append(UnsafeBufferPointer(start: &element, count: 1))
}
func getByteAt(position:Int) -> Int {
return Int(self[position])
}
}

View File

@ -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()!)"
}

View File

@ -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)
}
}
}

View File

@ -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) {

View File

@ -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 {

View File

@ -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";

View File

@ -32,3 +32,4 @@
"firmware" = "Firmware";
"hardware" = "Hardware";
"unknown" = "Unknown";
"sensorStatus" = "Sensor status";

View File

@ -22,6 +22,7 @@
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>3889</string>
<string>3882</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>

View File

@ -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>

View File

@ -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")
}()

View File

@ -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")
}()
}

View File

@ -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:

View File

@ -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
///
}

View File

@ -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 {

View File

@ -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{