Reworked for support oop web, however sitll not used because wrong values are being shown

This commit is contained in:
Johan Degraeve 2019-08-10 12:13:40 +02:00
parent e2cd0d9676
commit 3f98a5c3dc
45 changed files with 856 additions and 1278 deletions

View File

@ -19,7 +19,6 @@
F80610C4222D4E4D00D8F236 /* ActionClosureable-extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80610C3222D4E4D00D8F236 /* ActionClosureable-extension.swift */; };
F81D6D4822BD5F62005EFAE2 /* DexcomShareUploadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81D6D4722BD5F62005EFAE2 /* DexcomShareUploadManager.swift */; };
F81D6D4E22BFC762005EFAE2 /* TextsDexcomShareTestResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81D6D4D22BFC762005EFAE2 /* TextsDexcomShareTestResult.swift */; };
F81D6D5022BFC7DC005EFAE2 /* DexcomShareTestResult.strings in Resources */ = {isa = PBXBuildFile; fileRef = F81D6D4F22BFC7DC005EFAE2 /* DexcomShareTestResult.strings */; };
F81D6D5222C27F18005EFAE2 /* BgReading+DexcomShare.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81D6D5122C27F18005EFAE2 /* BgReading+DexcomShare.swift */; };
F81F9FF822861E6D0028C70F /* KeyValueObserverTimeKeeper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81F9FF722861E6D0028C70F /* KeyValueObserverTimeKeeper.swift */; };
F81F9FFC2288C7530028C70F /* NewAlertSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81F9FFB2288C7530028C70F /* NewAlertSettingsViewController.swift */; };
@ -79,7 +78,7 @@
F8A54AB722D9111900934E7A /* CGMTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AB322D9111900934E7A /* CGMTransmitter.swift */; };
F8A54AB822D9111900934E7A /* TransmitterBatteryInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AB422D9111900934E7A /* TransmitterBatteryInfo.swift */; };
F8A54AB922D9111900934E7A /* CGMTransmitterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AB522D9111900934E7A /* CGMTransmitterDelegate.swift */; };
F8A54ABA22D9111900934E7A /* RawGlucoseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AB622D9111900934E7A /* RawGlucoseData.swift */; };
F8A54ABA22D9111900934E7A /* GlucoseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AB622D9111900934E7A /* GlucoseData.swift */; };
F8A54AD722D911BA00934E7A /* CGMG6Transmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54ABD22D911BA00934E7A /* CGMG6Transmitter.swift */; };
F8A54AD822D911BA00934E7A /* CGMG5Transmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54ABF22D911BA00934E7A /* CGMG5Transmitter.swift */; };
F8A54AD922D911BA00934E7A /* TransmitterVersionTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AC122D911BA00934E7A /* TransmitterVersionTxMessage.swift */; };
@ -104,7 +103,7 @@
F8A54AF622D9156600934E7A /* CGMGNSEntryTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AEE22D9156600934E7A /* CGMGNSEntryTransmitter.swift */; };
F8A54AFA22D9156600934E7A /* CGMMiaoMiaoTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AF422D9156600934E7A /* CGMMiaoMiaoTransmitter.swift */; };
F8A54AFF22D9179100934E7A /* CRC.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AFC22D9179100934E7A /* CRC.swift */; };
F8A54B0022D9179100934E7A /* ParseLibreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AFD22D9179100934E7A /* ParseLibreData.swift */; };
F8A54B0022D9179100934E7A /* LibreDataParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AFD22D9179100934E7A /* LibreDataParser.swift */; };
F8A54B0122D9179100934E7A /* LibreSensorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AFE22D9179100934E7A /* LibreSensorState.swift */; };
F8A7406E22D9C0E700967CFC /* CGMBluconTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A7406D22D9C0E700967CFC /* CGMBluconTransmitter.swift */; };
F8A7407022DBB24800967CFC /* BluconTransmitterOpCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A7406F22DBB24800967CFC /* BluconTransmitterOpCode.swift */; };
@ -190,11 +189,13 @@
F8EEDD4422FD80A600D2D610 /* LibreOOPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD3D22FD80A500D2D610 /* LibreOOPResponse.swift */; };
F8EEDD4522FD80A600D2D610 /* LibreOOPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD3E22FD80A600D2D610 /* LibreOOPClient.swift */; };
F8EEDD4622FD80A600D2D610 /* LibreMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD3F22FD80A600D2D610 /* LibreMeasurement.swift */; };
F8EEDD4722FD80A600D2D610 /* LibreOOPWebExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD4022FD80A600D2D610 /* LibreOOPWebExtensions.swift */; };
F8EEDD4922FD80A600D2D610 /* LibreOOPDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD4222FD80A600D2D610 /* LibreOOPDefaults.swift */; };
F8EEDD4C22FE224B00D2D610 /* LibreRawGlucoseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD4B22FE224B00D2D610 /* LibreRawGlucoseData.swift */; };
F8EEDD4E22FE259D00D2D610 /* LibreDerivedAlgorithmRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD4D22FE259D00D2D610 /* LibreDerivedAlgorithmRunner.swift */; };
F8EEDD5022FE25D400D2D610 /* LibreGlucoseSmoothing.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD4F22FE25D400D2D610 /* LibreGlucoseSmoothing.swift */; };
F8EEDD5222FECE3800D2D610 /* ConstantsLibreOOP.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD5122FECE3800D2D610 /* ConstantsLibreOOP.swift */; };
F8EEDD5422FF685400D2D610 /* NSMutableURLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD5322FF685400D2D610 /* NSMutableURLRequest.swift */; };
F8EEDD552300136F00D2D610 /* DexcomShareTestResult.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8EEDD572300136F00D2D610 /* DexcomShareTestResult.strings */; };
F8EEDD6423020FAD00D2D610 /* NoCalibrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD6323020FAD00D2D610 /* NoCalibrator.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -213,7 +214,6 @@
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>"; };
F81D6D4F22BFC7DC005EFAE2 /* DexcomShareTestResult.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = DexcomShareTestResult.strings; sourceTree = "<group>"; };
F81D6D5122C27F18005EFAE2 /* BgReading+DexcomShare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BgReading+DexcomShare.swift"; sourceTree = "<group>"; };
F81F9FF722861E6D0028C70F /* KeyValueObserverTimeKeeper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueObserverTimeKeeper.swift; sourceTree = "<group>"; };
F81F9FFB2288C7530028C70F /* NewAlertSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewAlertSettingsViewController.swift; sourceTree = "<group>"; };
@ -274,7 +274,7 @@
F8A54AB322D9111900934E7A /* CGMTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMTransmitter.swift; sourceTree = "<group>"; };
F8A54AB422D9111900934E7A /* TransmitterBatteryInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransmitterBatteryInfo.swift; sourceTree = "<group>"; };
F8A54AB522D9111900934E7A /* CGMTransmitterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMTransmitterDelegate.swift; sourceTree = "<group>"; };
F8A54AB622D9111900934E7A /* RawGlucoseData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawGlucoseData.swift; sourceTree = "<group>"; };
F8A54AB622D9111900934E7A /* GlucoseData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseData.swift; sourceTree = "<group>"; };
F8A54ABD22D911BA00934E7A /* CGMG6Transmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMG6Transmitter.swift; sourceTree = "<group>"; };
F8A54ABF22D911BA00934E7A /* CGMG5Transmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMG5Transmitter.swift; sourceTree = "<group>"; };
F8A54AC122D911BA00934E7A /* TransmitterVersionTxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransmitterVersionTxMessage.swift; sourceTree = "<group>"; };
@ -300,7 +300,7 @@
F8A54AEE22D9156600934E7A /* CGMGNSEntryTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMGNSEntryTransmitter.swift; sourceTree = "<group>"; };
F8A54AF422D9156600934E7A /* CGMMiaoMiaoTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMMiaoMiaoTransmitter.swift; sourceTree = "<group>"; };
F8A54AFC22D9179100934E7A /* CRC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC.swift; sourceTree = "<group>"; };
F8A54AFD22D9179100934E7A /* ParseLibreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseLibreData.swift; sourceTree = "<group>"; };
F8A54AFD22D9179100934E7A /* LibreDataParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreDataParser.swift; sourceTree = "<group>"; };
F8A54AFE22D9179100934E7A /* LibreSensorState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreSensorState.swift; sourceTree = "<group>"; };
F8A54B0A22D9215500934E7A /* xdrip-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "xdrip-Bridging-Header.h"; sourceTree = "<group>"; };
F8A7406D22D9C0E700967CFC /* CGMBluconTransmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMBluconTransmitter.swift; sourceTree = "<group>"; };
@ -501,11 +501,24 @@
F8EEDD3D22FD80A500D2D610 /* LibreOOPResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPResponse.swift; sourceTree = "<group>"; };
F8EEDD3E22FD80A600D2D610 /* LibreOOPClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPClient.swift; sourceTree = "<group>"; };
F8EEDD3F22FD80A600D2D610 /* LibreMeasurement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreMeasurement.swift; sourceTree = "<group>"; };
F8EEDD4022FD80A600D2D610 /* LibreOOPWebExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPWebExtensions.swift; sourceTree = "<group>"; };
F8EEDD4222FD80A600D2D610 /* LibreOOPDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPDefaults.swift; sourceTree = "<group>"; };
F8EEDD4B22FE224B00D2D610 /* LibreRawGlucoseData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreRawGlucoseData.swift; sourceTree = "<group>"; };
F8EEDD4D22FE259D00D2D610 /* LibreDerivedAlgorithmRunner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreDerivedAlgorithmRunner.swift; sourceTree = "<group>"; };
F8EEDD4F22FE25D400D2D610 /* LibreGlucoseSmoothing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreGlucoseSmoothing.swift; sourceTree = "<group>"; };
F8EEDD5122FECE3800D2D610 /* ConstantsLibreOOP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsLibreOOP.swift; sourceTree = "<group>"; };
F8EEDD5322FF685400D2D610 /* NSMutableURLRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMutableURLRequest.swift; sourceTree = "<group>"; };
F8EEDD562300136F00D2D610 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/DexcomShareTestResult.strings; sourceTree = "<group>"; };
F8EEDD582300138500D2D610 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/DexcomShareTestResult.strings; sourceTree = "<group>"; };
F8EEDD592300138700D2D610 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/DexcomShareTestResult.strings"; sourceTree = "<group>"; };
F8EEDD5A2300139000D2D610 /* es-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-ES"; path = "es-ES.lproj/DexcomShareTestResult.strings"; sourceTree = "<group>"; };
F8EEDD5B2300139200D2D610 /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/DexcomShareTestResult.strings; sourceTree = "<group>"; };
F8EEDD5C2300139300D2D610 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/DexcomShareTestResult.strings; sourceTree = "<group>"; };
F8EEDD5D2300139500D2D610 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/DexcomShareTestResult.strings"; sourceTree = "<group>"; };
F8EEDD5E2300139500D2D610 /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/DexcomShareTestResult.strings"; sourceTree = "<group>"; };
F8EEDD5F2300139600D2D610 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/DexcomShareTestResult.strings; sourceTree = "<group>"; };
F8EEDD602300139700D2D610 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/DexcomShareTestResult.strings; sourceTree = "<group>"; };
F8EEDD612300139800D2D610 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/DexcomShareTestResult.strings; sourceTree = "<group>"; };
F8EEDD622300139A00D2D610 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/DexcomShareTestResult.strings; sourceTree = "<group>"; };
F8EEDD6323020FAD00D2D610 /* NoCalibrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCalibrator.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -563,6 +576,7 @@
F8B3A78A225D473D004BA588 /* UIAlertController.swift */,
F8B3A857227F6971004BA588 /* UISwitch.swift */,
F8B3A85C22821BB6004BA588 /* Int.swift */,
F8EEDD5322FF685400D2D610 /* NSMutableURLRequest.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -729,7 +743,7 @@
F8AC426321ADEBD60078C348 /* Main.storyboard */,
F8B3A788225D4473004BA588 /* NightScoutTestResult.strings */,
F8BDD457221DEF22006EAB84 /* SettingsViews.strings */,
F81D6D4F22BFC7DC005EFAE2 /* DexcomShareTestResult.strings */,
F8EEDD572300136F00D2D610 /* DexcomShareTestResult.strings */,
);
path = Storyboards;
sourceTree = "<group>";
@ -776,7 +790,7 @@
F8A54AB322D9111900934E7A /* CGMTransmitter.swift */,
F8A54AB422D9111900934E7A /* TransmitterBatteryInfo.swift */,
F8A54AB522D9111900934E7A /* CGMTransmitterDelegate.swift */,
F8A54AB622D9111900934E7A /* RawGlucoseData.swift */,
F8A54AB622D9111900934E7A /* GlucoseData.swift */,
);
path = Generic;
sourceTree = "<group>";
@ -887,13 +901,11 @@
F8EEDD4F22FE25D400D2D610 /* LibreGlucoseSmoothing.swift */,
F8EEDD4D22FE259D00D2D610 /* LibreDerivedAlgorithmRunner.swift */,
F8EEDD3E22FD80A600D2D610 /* LibreOOPClient.swift */,
F8EEDD4222FD80A600D2D610 /* LibreOOPDefaults.swift */,
F8EEDD3D22FD80A500D2D610 /* LibreOOPResponse.swift */,
F8EEDD4022FD80A600D2D610 /* LibreOOPWebExtensions.swift */,
F8EEDD3F22FD80A600D2D610 /* LibreMeasurement.swift */,
F8C5EBE422F297EF00563B5F /* LibreSensorSerialNumber.swift */,
F8A54AFC22D9179100934E7A /* CRC.swift */,
F8A54AFD22D9179100934E7A /* ParseLibreData.swift */,
F8A54AFD22D9179100934E7A /* LibreDataParser.swift */,
F8A54AFE22D9179100934E7A /* LibreSensorState.swift */,
F8EEDD4B22FE224B00D2D610 /* LibreRawGlucoseData.swift */,
);
@ -1142,6 +1154,7 @@
F8A1587022EDC865007F5B5D /* ConstantsSpeakReadingLanguages.swift */,
F8A1587222EDC893007F5B5D /* ConstantsDexcomShare.swift */,
F856CE5A22EDC8E50083E436 /* ConstantsBluetoothPairing.swift */,
F8EEDD5122FECE3800D2D610 /* ConstantsLibreOOP.swift */,
);
name = Constants;
path = xdrip/Constants;
@ -1180,10 +1193,11 @@
F8EA6CA521BAD5AD0082976B /* Calibration */ = {
isa = PBXGroup;
children = (
F8A54AAC22D6859200934E7A /* SlopeParameters.swift */,
F8025E5221EE8CE500ECF0C0 /* Protocol */,
F8025E5321EE8D2100ECF0C0 /* Libre1Calibrator.swift */,
F8E3C3AC21FE551C00907A04 /* DexcomCalibrator.swift */,
F8025E5321EE8D2100ECF0C0 /* Libre1Calibrator.swift */,
F8EEDD6323020FAD00D2D610 /* NoCalibrator.swift */,
F8025E5221EE8CE500ECF0C0 /* Protocol */,
F8A54AAC22D6859200934E7A /* SlopeParameters.swift */,
);
path = Calibration;
sourceTree = "<group>";
@ -1290,7 +1304,7 @@
F8B3A80D227A3E98004BA588 /* AlertTypesSettingsView.strings in Resources */,
F8B3A7D3226CC0B7004BA588 /* xdripalert.aif in Resources */,
F8AC426721ADEBD70078C348 /* Assets.xcassets in Resources */,
F81D6D5022BFC7DC005EFAE2 /* DexcomShareTestResult.strings in Resources */,
F8EEDD552300136F00D2D610 /* DexcomShareTestResult.strings in Resources */,
F8B3A7CF226CC0B7004BA588 /* shorthigh4.mp3 in Resources */,
F821CF7D22A46CDD005C1E43 /* 1-millisecond-of-silence.mp3 in Resources */,
F8B3A7CE226CC0B7004BA588 /* shorthigh2.mp3 in Resources */,
@ -1378,8 +1392,7 @@
F8EA6C8221B723BC0082976B /* Date.swift in Sources */,
F8A54AE622D911BA00934E7A /* KeepAliveTxMessage.swift in Sources */,
F81FA006228E09D40028C70F /* TextsCalibration.swift in Sources */,
F8EEDD4922FD80A600D2D610 /* LibreOOPDefaults.swift in Sources */,
F8A54ABA22D9111900934E7A /* RawGlucoseData.swift in Sources */,
F8A54ABA22D9111900934E7A /* GlucoseData.swift in Sources */,
F8B3A84A227F090E004BA588 /* SettingsViewGeneralSettingsViewModel.swift in Sources */,
F8B3A85B2280CCD1004BA588 /* AlertSettingsViewController.swift in Sources */,
F8B3A81B227DEC92004BA588 /* SensorsAccessor.swift in Sources */,
@ -1413,13 +1426,12 @@
F8A7406E22D9C0E700967CFC /* CGMBluconTransmitter.swift in Sources */,
F8A1586B22EDB967007F5B5D /* ConstantsMaster.swift in Sources */,
F8B3A7B2226A0878004BA588 /* TextsAlerts.swift in Sources */,
F8A54B0022D9179100934E7A /* ParseLibreData.swift in Sources */,
F8A54B0022D9179100934E7A /* LibreDataParser.swift in Sources */,
F8025E5421EE8D2100ECF0C0 /* Libre1Calibrator.swift in Sources */,
F81FA00A228F53680028C70F /* TextsHomeView.swift in Sources */,
F8E3C3AD21FE551C00907A04 /* DexcomCalibrator.swift in Sources */,
F821CF61229BF4A2005C1E43 /* NightScoutUploadManager.swift in Sources */,
F8A1587122EDC865007F5B5D /* ConstantsSpeakReadingLanguages.swift in Sources */,
F8EEDD4722FD80A600D2D610 /* LibreOOPWebExtensions.swift in Sources */,
F8A54ADD22D911BA00934E7A /* BatteryStatusTxMessage.swift in Sources */,
F8EEDD5022FE25D400D2D610 /* LibreGlucoseSmoothing.swift in Sources */,
F8A1585522EDB706007F5B5D /* ConstantsCalibrationAlgorithms.swift in Sources */,
@ -1456,6 +1468,8 @@
F8B3A81C227DEC92004BA588 /* AlertEntriesAccessor.swift in Sources */,
F8A1585B22EDB7EA007F5B5D /* ConstantsDexcomG5.swift in Sources */,
F8BDD452221DEAB2006EAB84 /* TextsSettingsView.swift in Sources */,
F8EEDD6423020FAD00D2D610 /* NoCalibrator.swift in Sources */,
F8EEDD5422FF685400D2D610 /* NSMutableURLRequest.swift in Sources */,
F897AAFB2201018800CDDD10 /* String.swift in Sources */,
F8B3A847227F090E004BA588 /* SettingsViewNightScoutSettingsViewModel.swift in Sources */,
F8B3A79622635A25004BA588 /* AlertEntry+CoreDataClass.swift in Sources */,
@ -1493,6 +1507,7 @@
F8B3A834227F08AC004BA588 /* PickerViewData.swift in Sources */,
F8B3A79522635A25004BA588 /* AlertType+CoreDataProperties.swift in Sources */,
F8B3A84C227F090E004BA588 /* SettingsViewController.swift in Sources */,
F8EEDD5222FECE3800D2D610 /* ConstantsLibreOOP.swift in Sources */,
F8AC426021ADEBD60078C348 /* RootViewController.swift in Sources */,
F8B3A78B225D473D004BA588 /* UIAlertController.swift in Sources */,
F8A54AEB22D911BA00934E7A /* CGMG4xDripTransmitter.swift in Sources */,
@ -1739,6 +1754,25 @@
name = SettingsViews.strings;
sourceTree = "<group>";
};
F8EEDD572300136F00D2D610 /* DexcomShareTestResult.strings */ = {
isa = PBXVariantGroup;
children = (
F8EEDD562300136F00D2D610 /* en */,
F8EEDD582300138500D2D610 /* nl */,
F8EEDD592300138700D2D610 /* es-MX */,
F8EEDD5A2300139000D2D610 /* es-ES */,
F8EEDD5B2300139200D2D610 /* sl */,
F8EEDD5C2300139300D2D610 /* ru */,
F8EEDD5D2300139500D2D610 /* pt-BR */,
F8EEDD5E2300139500D2D610 /* pl-PL */,
F8EEDD5F2300139600D2D610 /* it */,
F8EEDD602300139700D2D610 /* fr */,
F8EEDD612300139800D2D610 /* zh */,
F8EEDD622300139A00D2D610 /* pt */,
);
name = DexcomShareTestResult.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */

View File

@ -0,0 +1,53 @@
import Foundation
import CoreData
/// calibrator for cases where transmitter gives calibrated values, ie no calibration necessary. It will simply create readings with the value of rawData in the reading
class NoCalibrator: Calibrator {
/// value nil, not needed
var sParams: SlopeParameters = SlopeParameters(LOW_SLOPE_1: 1, LOW_SLOPE_2: 1, HIGH_SLOPE_1: 1, HIGH_SLOPE_2: 1, DEFAULT_LOW_SLOPE_LOW: 1, DEFAULT_LOW_SLOPE_HIGH: 1, DEFAULT_SLOPE: 1, DEFAULT_HIGH_SLOPE_HIGH: 1, DEFAUL_HIGH_SLOPE_LOW: 1)
/// value false, not needed
var ageAdjustMentNeeded: Bool = false
/// create a new BgReading
/// - parameters:
/// - rawData : the rawdata value
/// - filteredData : the filtered data
/// - timeStamp : optional, if nil then actualy date and time is used
/// - sensor : actual sensor, optional
/// - last3Readings : empty array
/// - nsManagedObjectContext : the nsManagedObjectContext
/// - lastCalibrationsForActiveSensorInLastXDays : empty array
/// - firstCalibration : nil
/// - lastCalibration : nil
/// - returns:
/// - the created bgreading
func createNewBgReading(rawData:Double, filteredData:Double, timeStamp:Date?, sensor:Sensor?, last3Readings:inout Array<BgReading>, lastCalibrationsForActiveSensorInLastXDays:inout Array<Calibration>, firstCalibration:Calibration?, lastCalibration:Calibration?, deviceName:String?, nsManagedObjectContext:NSManagedObjectContext ) -> BgReading {
var timeStampToUse:Date = Date()
if let timeStamp = timeStamp {
timeStampToUse = timeStamp
}
let bgReading:BgReading = BgReading(
timeStamp:timeStampToUse,
sensor:sensor,
calibration:lastCalibration,
rawData:rawData,
filteredData:filteredData,
deviceName:deviceName,
nsManagedObjectContext:nsManagedObjectContext
)
bgReading.calculatedValue = rawData
findSlope(for: bgReading, last2Readings: &last3Readings)
return bgReading
}
}

View File

@ -570,7 +570,7 @@ extension Calibrator {
/// - parameters:
/// - bgReading : reading that will be updated
/// - last2Readings result of call to BgReadings.getLatestBgReadings(2, sensor) ignoreRawData and ignoreCalculatedValue false - inout parameter to improve performance
private func findSlope(for bgReading: BgReading, last2Readings:inout Array<BgReading>) {
public func findSlope(for bgReading: BgReading, last2Readings:inout Array<BgReading>) {
bgReading.hideSlope = true;
if (last2Readings.count >= 2) {
let (slope, hide) = bgReading.calculateSlope(lastBgReading:last2Readings[1]);

View File

@ -0,0 +1,13 @@
/// constants related to Libre OOP
enum ConstantsLibreOOP {
/// site for libreOOP client
static let site = "http://www.glucose.space/calibrateSensor"
/// token to use to access site
static let token = "bubble-201907"
/// calibration parameters will be stored locally on disk, this is the path
static let filePathForParameterStorage = "/Documents/LibreSensorParameters"
}

View File

@ -50,5 +50,7 @@ enum ConstantsLog {
static let categoryDexcomShareUploadManager = "categoryDexcomShareUploadManager"
/// droplet 1
static let categoryCGMDroplet1 = "categoryCGMDroplet1"
/// LibreOOPClient
static let categoryLibreOOPClient = "LibreOOPClient"
}

View File

@ -37,10 +37,13 @@ extension Date {
return Date(timeIntervalSinceNow: timeInterval)
}
/// defines method to format date to "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
func toNightScoutFormat() -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
return formatter.string(from: self)
func ISOStringFromDate() -> String {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
return dateFormatter.string(from: self).appending("Z")
}
}

View File

@ -0,0 +1,16 @@
import Foundation
extension NSMutableURLRequest {
/// 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]) {
let parameters = contentMap.map { (key, value) -> String in
return "\(key)=\(value.stringByAddingPercentEscapesForQueryValue()!)"
}
httpBody = parameters.joined(separator: "&").data(using: .utf8)
}
}

View File

@ -114,3 +114,46 @@ extension String {
return nil
}
}
extension String {
/// Percent escape value to be added to a URL query value as specified in RFC 3986
///
/// This percent-escapes all characters except the alphanumeric character set and "-", ".", "_", and "~".
///
/// http://www.ietf.org/rfc/rfc3986.txt
///
/// - returns: Return precent escaped string.
func stringByAddingPercentEscapesForQueryValue() -> String? {
let characterSet = NSMutableCharacterSet.alphanumeric()
characterSet.addCharacters(in: "-._~")
return self.addingPercentEncoding(withAllowedCharacters: characterSet as CharacterSet)
}
/// Base64 encoding a string
func base64Encoded() -> String? {
if let data = self.data(using: .utf8) {
return data.base64EncodedString()
}
return nil
}
/// Base64 decoding a string
func base64Decoded() -> [UInt8]? {
if let data = Data(base64Encoded: self) {
return [UInt8](data)
}
return nil
}
}
extension String {
func dateFromISOString() -> Date? {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone.autoupdatingCurrent
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
return dateFormatter.date(from: self)
}
}

View File

@ -9,14 +9,14 @@ extension BgReading {
"_id": id,
"device": deviceName ?? "",
"date": timeStamp.toMillisecondsAsInt64(),
"dateString": timeStamp.toNightScoutFormat(),
"dateString": timeStamp.ISOStringFromDate(),
"type": "sgv",
"sgv": Int(calculatedValue.roundToDecimal(0)),
"direction": slopeName,
"filtered": round(ageAdjustedFiltered() * 1000),
"unfiltered": round(ageAdjustedRawValue * 1000),
"noise": 1,
"sysTime": timeStamp.toNightScoutFormat()
"sysTime": timeStamp.ISOStringFromDate()
]
}

View File

@ -24,7 +24,7 @@ extension Endpoint {
path: "/api/v1/entries/sgv.json",
queryItems: [
URLQueryItem(name: "count", value: count.description),
URLQueryItem(name: "find[dateString][$gte]", value: timeStamp.toNightScoutFormat())
URLQueryItem(name: "find[dateString][$gte]", value: timeStamp.ISOStringFromDate())
]
)
}

View File

@ -30,3 +30,4 @@
"transmitterResultResult" = "Transmitter reset result";
"success" = "success";
"failed" = "failed";
"calibrationNotNecessary" = "With Web OOP enabled, calibration is not necessary for the selected type of transmitter";

View File

@ -0,0 +1,11 @@
"dexcomsharetestresult_verificationsuccessfulalerttitle" = "Verification Successful";
"dexcomsharetestresult_verificationsuccessfulalertbody" = "Your dexcomshare account was verified successfully";
"dexcomsharetestresult_verificationerroralerttitle" = "Verification Error";
"dexcomsharetestresult_SSO_AuthenticateAccountNotFound" = "Account not found";
"dexcomsharetestresult_SSO_AuthenticateMaxAttemptsExceeed" = "Maximum login attempts exceeded. Wait 10 minutes and then retry.";
"dexcomsharetestresult_SSO_AuthenticatePasswordInvalid" = "Invalid Password";
"dexcomsharetestresult_uploadErrorWarning" = "Dexcom Share Upload Error";
"dexcomsharetestresult_monitored_receiver_sn_doesnotmatch" = "Dexcom Share Serial Number does not match the serial number for this account. Verify the Serial Number in the settings.";
"dexcomsharetestresult_monitored_receiver_not_assigned_1" = "It seems the transmitter id or serial number";
"dexcomsharetestresult_monitored_receiver_not_assigned_2" = "is not assigned to";
"dexcomsharetestresult_monitored_receiver_not_assigned_3" = "Use the official Dexcom app to register the transmitter (G5) or Share receiver (G4)\r\n\r\nPossibly you're just using the wrong url, verify the setting 'Use US url?'";

View File

@ -0,0 +1,11 @@
"dexcomsharetestresult_verificationsuccessfulalerttitle" = "Verification Successful";
"dexcomsharetestresult_verificationsuccessfulalertbody" = "Your dexcomshare account was verified successfully";
"dexcomsharetestresult_verificationerroralerttitle" = "Verification Error";
"dexcomsharetestresult_SSO_AuthenticateAccountNotFound" = "Account not found";
"dexcomsharetestresult_SSO_AuthenticateMaxAttemptsExceeed" = "Maximum login attempts exceeded. Wait 10 minutes and then retry.";
"dexcomsharetestresult_SSO_AuthenticatePasswordInvalid" = "Invalid Password";
"dexcomsharetestresult_uploadErrorWarning" = "Dexcom Share Upload Error";
"dexcomsharetestresult_monitored_receiver_sn_doesnotmatch" = "Dexcom Share Serial Number does not match the serial number for this account. Verify the Serial Number in the settings.";
"dexcomsharetestresult_monitored_receiver_not_assigned_1" = "It seems the transmitter id or serial number";
"dexcomsharetestresult_monitored_receiver_not_assigned_2" = "is not assigned to";
"dexcomsharetestresult_monitored_receiver_not_assigned_3" = "Use the official Dexcom app to register the transmitter (G5) or Share receiver (G4)\r\n\r\nPossibly you're just using the wrong url, verify the setting 'Use US url?'";

View File

@ -0,0 +1,11 @@
"dexcomsharetestresult_verificationsuccessfulalerttitle" = "Verification Successful";
"dexcomsharetestresult_verificationsuccessfulalertbody" = "Your dexcomshare account was verified successfully";
"dexcomsharetestresult_verificationerroralerttitle" = "Verification Error";
"dexcomsharetestresult_SSO_AuthenticateAccountNotFound" = "Account not found";
"dexcomsharetestresult_SSO_AuthenticateMaxAttemptsExceeed" = "Maximum login attempts exceeded. Wait 10 minutes and then retry.";
"dexcomsharetestresult_SSO_AuthenticatePasswordInvalid" = "Invalid Password";
"dexcomsharetestresult_uploadErrorWarning" = "Dexcom Share Upload Error";
"dexcomsharetestresult_monitored_receiver_sn_doesnotmatch" = "Dexcom Share Serial Number does not match the serial number for this account. Verify the Serial Number in the settings.";
"dexcomsharetestresult_monitored_receiver_not_assigned_1" = "It seems the transmitter id or serial number";
"dexcomsharetestresult_monitored_receiver_not_assigned_2" = "is not assigned to";
"dexcomsharetestresult_monitored_receiver_not_assigned_3" = "Use the official Dexcom app to register the transmitter (G5) or Share receiver (G4)\r\n\r\nPossibly you're just using the wrong url, verify the setting 'Use US url?'";

View File

@ -0,0 +1,11 @@
"dexcomsharetestresult_verificationsuccessfulalerttitle" = "Verification Successful";
"dexcomsharetestresult_verificationsuccessfulalertbody" = "Your dexcomshare account was verified successfully";
"dexcomsharetestresult_verificationerroralerttitle" = "Verification Error";
"dexcomsharetestresult_SSO_AuthenticateAccountNotFound" = "Account not found";
"dexcomsharetestresult_SSO_AuthenticateMaxAttemptsExceeed" = "Maximum login attempts exceeded. Wait 10 minutes and then retry.";
"dexcomsharetestresult_SSO_AuthenticatePasswordInvalid" = "Invalid Password";
"dexcomsharetestresult_uploadErrorWarning" = "Dexcom Share Upload Error";
"dexcomsharetestresult_monitored_receiver_sn_doesnotmatch" = "Dexcom Share Serial Number does not match the serial number for this account. Verify the Serial Number in the settings.";
"dexcomsharetestresult_monitored_receiver_not_assigned_1" = "It seems the transmitter id or serial number";
"dexcomsharetestresult_monitored_receiver_not_assigned_2" = "is not assigned to";
"dexcomsharetestresult_monitored_receiver_not_assigned_3" = "Use the official Dexcom app to register the transmitter (G5) or Share receiver (G4)\r\n\r\nPossibly you're just using the wrong url, verify the setting 'Use US url?'";

View File

@ -0,0 +1,11 @@
"dexcomsharetestresult_verificationsuccessfulalerttitle" = "Verification Successful";
"dexcomsharetestresult_verificationsuccessfulalertbody" = "Your dexcomshare account was verified successfully";
"dexcomsharetestresult_verificationerroralerttitle" = "Verification Error";
"dexcomsharetestresult_SSO_AuthenticateAccountNotFound" = "Account not found";
"dexcomsharetestresult_SSO_AuthenticateMaxAttemptsExceeed" = "Maximum login attempts exceeded. Wait 10 minutes and then retry.";
"dexcomsharetestresult_SSO_AuthenticatePasswordInvalid" = "Invalid Password";
"dexcomsharetestresult_uploadErrorWarning" = "Dexcom Share Upload Error";
"dexcomsharetestresult_monitored_receiver_sn_doesnotmatch" = "Dexcom Share Serial Number does not match the serial number for this account. Verify the Serial Number in the settings.";
"dexcomsharetestresult_monitored_receiver_not_assigned_1" = "It seems the transmitter id or serial number";
"dexcomsharetestresult_monitored_receiver_not_assigned_2" = "is not assigned to";
"dexcomsharetestresult_monitored_receiver_not_assigned_3" = "Use the official Dexcom app to register the transmitter (G5) or Share receiver (G4)\r\n\r\nPossibly you're just using the wrong url, verify the setting 'Use US url?'";

View File

@ -0,0 +1,11 @@
"dexcomsharetestresult_verificationsuccessfulalerttitle" = "Verification Successful";
"dexcomsharetestresult_verificationsuccessfulalertbody" = "Your dexcomshare account was verified successfully";
"dexcomsharetestresult_verificationerroralerttitle" = "Verification Error";
"dexcomsharetestresult_SSO_AuthenticateAccountNotFound" = "Account not found";
"dexcomsharetestresult_SSO_AuthenticateMaxAttemptsExceeed" = "Maximum login attempts exceeded. Wait 10 minutes and then retry.";
"dexcomsharetestresult_SSO_AuthenticatePasswordInvalid" = "Invalid Password";
"dexcomsharetestresult_uploadErrorWarning" = "Dexcom Share Upload Error";
"dexcomsharetestresult_monitored_receiver_sn_doesnotmatch" = "Dexcom Share Serial Number does not match the serial number for this account. Verify the Serial Number in the settings.";
"dexcomsharetestresult_monitored_receiver_not_assigned_1" = "It seems the transmitter id or serial number";
"dexcomsharetestresult_monitored_receiver_not_assigned_2" = "is not assigned to";
"dexcomsharetestresult_monitored_receiver_not_assigned_3" = "Use the official Dexcom app to register the transmitter (G5) or Share receiver (G4)\r\n\r\nPossibly you're just using the wrong url, verify the setting 'Use US url?'";

View File

@ -0,0 +1,11 @@
"dexcomsharetestresult_verificationsuccessfulalerttitle" = "Verification Successful";
"dexcomsharetestresult_verificationsuccessfulalertbody" = "Your dexcomshare account was verified successfully";
"dexcomsharetestresult_verificationerroralerttitle" = "Verification Error";
"dexcomsharetestresult_SSO_AuthenticateAccountNotFound" = "Account not found";
"dexcomsharetestresult_SSO_AuthenticateMaxAttemptsExceeed" = "Maximum login attempts exceeded. Wait 10 minutes and then retry.";
"dexcomsharetestresult_SSO_AuthenticatePasswordInvalid" = "Invalid Password";
"dexcomsharetestresult_uploadErrorWarning" = "Dexcom Share Upload Error";
"dexcomsharetestresult_monitored_receiver_sn_doesnotmatch" = "Dexcom Share Serial Number does not match the serial number for this account. Verify the Serial Number in the settings.";
"dexcomsharetestresult_monitored_receiver_not_assigned_1" = "It seems the transmitter id or serial number";
"dexcomsharetestresult_monitored_receiver_not_assigned_2" = "is not assigned to";
"dexcomsharetestresult_monitored_receiver_not_assigned_3" = "Use the official Dexcom app to register the transmitter (G5) or Share receiver (G4)\r\n\r\nPossibly you're just using the wrong url, verify the setting 'Use US url?'";

View File

@ -0,0 +1,11 @@
"dexcomsharetestresult_verificationsuccessfulalerttitle" = "Verification Successful";
"dexcomsharetestresult_verificationsuccessfulalertbody" = "Your dexcomshare account was verified successfully";
"dexcomsharetestresult_verificationerroralerttitle" = "Verification Error";
"dexcomsharetestresult_SSO_AuthenticateAccountNotFound" = "Account not found";
"dexcomsharetestresult_SSO_AuthenticateMaxAttemptsExceeed" = "Maximum login attempts exceeded. Wait 10 minutes and then retry.";
"dexcomsharetestresult_SSO_AuthenticatePasswordInvalid" = "Invalid Password";
"dexcomsharetestresult_uploadErrorWarning" = "Dexcom Share Upload Error";
"dexcomsharetestresult_monitored_receiver_sn_doesnotmatch" = "Dexcom Share Serial Number does not match the serial number for this account. Verify the Serial Number in the settings.";
"dexcomsharetestresult_monitored_receiver_not_assigned_1" = "It seems the transmitter id or serial number";
"dexcomsharetestresult_monitored_receiver_not_assigned_2" = "is not assigned to";
"dexcomsharetestresult_monitored_receiver_not_assigned_3" = "Use the official Dexcom app to register the transmitter (G5) or Share receiver (G4)\r\n\r\nPossibly you're just using the wrong url, verify the setting 'Use US url?'";

View File

@ -0,0 +1,11 @@
"dexcomsharetestresult_verificationsuccessfulalerttitle" = "Verification Successful";
"dexcomsharetestresult_verificationsuccessfulalertbody" = "Your dexcomshare account was verified successfully";
"dexcomsharetestresult_verificationerroralerttitle" = "Verification Error";
"dexcomsharetestresult_SSO_AuthenticateAccountNotFound" = "Account not found";
"dexcomsharetestresult_SSO_AuthenticateMaxAttemptsExceeed" = "Maximum login attempts exceeded. Wait 10 minutes and then retry.";
"dexcomsharetestresult_SSO_AuthenticatePasswordInvalid" = "Invalid Password";
"dexcomsharetestresult_uploadErrorWarning" = "Dexcom Share Upload Error";
"dexcomsharetestresult_monitored_receiver_sn_doesnotmatch" = "Dexcom Share Serial Number does not match the serial number for this account. Verify the Serial Number in the settings.";
"dexcomsharetestresult_monitored_receiver_not_assigned_1" = "It seems the transmitter id or serial number";
"dexcomsharetestresult_monitored_receiver_not_assigned_2" = "is not assigned to";
"dexcomsharetestresult_monitored_receiver_not_assigned_3" = "Use the official Dexcom app to register the transmitter (G5) or Share receiver (G4)\r\n\r\nPossibly you're just using the wrong url, verify the setting 'Use US url?'";

View File

@ -0,0 +1,11 @@
"dexcomsharetestresult_verificationsuccessfulalerttitle" = "Verification Successful";
"dexcomsharetestresult_verificationsuccessfulalertbody" = "Your dexcomshare account was verified successfully";
"dexcomsharetestresult_verificationerroralerttitle" = "Verification Error";
"dexcomsharetestresult_SSO_AuthenticateAccountNotFound" = "Account not found";
"dexcomsharetestresult_SSO_AuthenticateMaxAttemptsExceeed" = "Maximum login attempts exceeded. Wait 10 minutes and then retry.";
"dexcomsharetestresult_SSO_AuthenticatePasswordInvalid" = "Invalid Password";
"dexcomsharetestresult_uploadErrorWarning" = "Dexcom Share Upload Error";
"dexcomsharetestresult_monitored_receiver_sn_doesnotmatch" = "Dexcom Share Serial Number does not match the serial number for this account. Verify the Serial Number in the settings.";
"dexcomsharetestresult_monitored_receiver_not_assigned_1" = "It seems the transmitter id or serial number";
"dexcomsharetestresult_monitored_receiver_not_assigned_2" = "is not assigned to";
"dexcomsharetestresult_monitored_receiver_not_assigned_3" = "Use the official Dexcom app to register the transmitter (G5) or Share receiver (G4)\r\n\r\nPossibly you're just using the wrong url, verify the setting 'Use US url?'";

View File

@ -0,0 +1,11 @@
"dexcomsharetestresult_verificationsuccessfulalerttitle" = "账号验证成功";
"dexcomsharetestresult_verificationsuccessfulalertbody" = "你的德康账号已经验证通过";
"dexcomsharetestresult_verificationerroralerttitle" = "账号验证错误";
"dexcomsharetestresult_SSO_AuthenticateAccountNotFound" = "没有查到到这个账号";
"dexcomsharetestresult_SSO_AuthenticateMaxAttemptsExceeed" = "超过最大登陆尝试次数限制. 等待10分钟然后再试.";
"dexcomsharetestresult_SSO_AuthenticatePasswordInvalid" = "密码错误";
"dexcomsharetestresult_uploadErrorWarning" = "上传到德康服务器遇到错误";
"dexcomsharetestresult_monitored_receiver_sn_doesnotmatch" = "Dexcom Share 序列号与这个账号的序列号不匹配,检查序列号设置";
"dexcomsharetestresult_monitored_receiver_not_assigned_1" = "看起来发射器ID或者序列号";
"dexcomsharetestresult_monitored_receiver_not_assigned_2" = "未分配给";
"dexcomsharetestresult_monitored_receiver_not_assigned_3" = "使用德康官方app注册G5发射器或者使用 Share 接收器 (G4)\r\n\r\n你可能选错了服务器地址请确认你的账号是否使用了美国服务器?'";

View File

@ -136,4 +136,8 @@ enum Texts_HomeView {
return NSLocalizedString("failed", tableName: filename, bundle: Bundle.main, value: "failed", comment: "To give result about transitter result in notification body, failed")
}()
static let calibrationNotNecessary:String = {
return NSLocalizedString("calibrationNotNecessary", tableName: filename, bundle: Bundle.main, value: "With Web OOP enabled, calibration is not necessary for the selected type of transmitter", comment: "if web oop enabled, and also if transmitter supports this, user clicks calibrate button, but calibration is not possible")
}()
}

View File

@ -166,16 +166,20 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, BluetoothTransmitterDel
/// this function is not implemented in BluetoothTransmitter.swift, otherwise it might be forgotten to look at in future CGMTransmitter developments
func reset(requested:Bool) {}
/// this transmitter does not support oopWeb
func setWebOOPEnabled(enabled: Bool) {
}
// MARK: helper functions
private func processxBridgeDataPacket(value:Data) -> (glucoseData:RawGlucoseData?, batteryLevel:Int?, transmitterID:String?) {
private func processxBridgeDataPacket(value:Data) -> (glucoseData:GlucoseData?, batteryLevel:Int?, transmitterID:String?) {
guard value.count >= 10 else {
trace("processxBridgeDataPacket, value.count = %{public}d, expecting minimum 10 so that we can find at least rawdata and filtereddata", log: log, type: .info, value.count)
return (nil, nil, nil)
}
//initialize returnvalues
var glucoseData:RawGlucoseData?
var glucoseData:GlucoseData?
var batteryLevel:Int?
var transmitterID:String?
@ -196,7 +200,7 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, BluetoothTransmitterDel
}
//create glucosedata
glucoseData = RawGlucoseData(timeStamp: Date(), glucoseLevelRaw: Double(rawData), glucoseLevelFiltered: Double(filteredData))
glucoseData = GlucoseData(timeStamp: Date(), glucoseLevelRaw: Double(rawData), glucoseLevelFiltered: Double(filteredData))
return (glucoseData, batteryLevel, transmitterID)
}
@ -208,9 +212,9 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, BluetoothTransmitterDel
///Example 123632 218 0
///
///Those packets don't start with a fixed packet length and packet type, as they start with representation of an Integer
private func processBasicXdripDataPacket(value:Data) -> (glucoseData:RawGlucoseData?, batteryLevel:Int?) {
private func processBasicXdripDataPacket(value:Data) -> (glucoseData:GlucoseData?, batteryLevel:Int?) {
//initialize returnvalues
var glucoseData:RawGlucoseData?
var glucoseData:GlucoseData?
var batteryLevel:Int?
//convert value to string
@ -229,7 +233,7 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, BluetoothTransmitterDel
//create glucoseData
if let rawData = rawData {
trace("in peripheral didUpdateValueFor, dataPacket received with rawData = %{public}d and batteryInfo = %{public}d", log: log, type: .info, rawData, batteryLevel ?? 0)
glucoseData = RawGlucoseData(timeStamp: Date(), glucoseLevelRaw: Double(rawData))
glucoseData = GlucoseData(timeStamp: Date(), glucoseLevelRaw: Double(rawData))
} else {
trace("in peripheral didUpdateValueFor, no rawdata", log: log, type: .info)
}

View File

@ -89,7 +89,7 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
private var actualDeviceAddress:String?
/// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send
var emptyArray: [RawGlucoseData] = []
var emptyArray: [GlucoseData] = []
// for creating testreadings
private var testAmount:Double = 150000.0
@ -161,7 +161,7 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
/// for testing, used by temptesting
@objc private func createTestReading() {
let testdata = RawGlucoseData(timeStamp: Date(), glucoseLevelRaw: testAmount, glucoseLevelFiltered: testAmount)
let testdata = GlucoseData(timeStamp: Date(), glucoseLevelRaw: testAmount, glucoseLevelFiltered: testAmount)
debuglogging("timestamp testdata = " + testdata.timeStamp.description + ", with amount = " + testAmount.description)
var testdataasarray = [testdata]
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &testdataasarray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)
@ -261,7 +261,11 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
func reset(requested:Bool) {
G5ResetRequested = requested
}
/// this transmitter does not support oopWeb
func setWebOOPEnabled(enabled: Bool) {
}
// MARK: BluetoothTransmitterDelegate functions
func centralManagerDidConnect(address:String?, name:String?) {
@ -449,7 +453,7 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
} else {
timeStampOfLastG5Reading = Date()
let glucoseData = RawGlucoseData(timeStamp: sensorDataRxMessage.timestamp, glucoseLevelRaw: scaleRawValue(firmwareVersion: firmwareVersion, rawValue: sensorDataRxMessage.unfiltered), glucoseLevelFiltered: scaleRawValue(firmwareVersion: firmwareVersion, rawValue: sensorDataRxMessage.unfiltered))
let glucoseData = GlucoseData(timeStamp: sensorDataRxMessage.timestamp, glucoseLevelRaw: scaleRawValue(firmwareVersion: firmwareVersion, rawValue: sensorDataRxMessage.unfiltered), glucoseLevelFiltered: scaleRawValue(firmwareVersion: firmwareVersion, rawValue: sensorDataRxMessage.unfiltered))
var glucoseDataArray = [glucoseData]

View File

@ -40,7 +40,13 @@ protocol CGMTransmitter {
/// - requested : if true then transmitter must be reset
/// for transmitter types that don't support resetting, this will be an empty function. Only G5 (and in future maybe G6) will use it. The others can define an empty body
func reset(requested:Bool)
/// to set webOOPEnabled - called when user change the setting
///
/// for transmitters who don't support webOOP, there's no need to implemented this function<br>
/// --- for transmitters who support webOOP (Bubble, MiaoMiao, ..) this should be implemented
func setWebOOPEnabled(enabled:Bool)
}
/// cgm transmitter types
@ -129,7 +135,9 @@ enum CGMTransmitterType:String, CaseIterable {
func canWebOOP() -> Bool {
switch self {
return false
/*switch self {
case .dexcomG4:
return false
@ -138,10 +146,10 @@ enum CGMTransmitterType:String, CaseIterable {
return false
case .miaomiao:
return false
return true
case .Bubble:
return true
return false
case .GNSentry:
return false
@ -152,7 +160,7 @@ enum CGMTransmitterType:String, CaseIterable {
case .Droplet1:
return false
}
}*/
}
/// returns nil if id to validate has expected length and type of characters etc.

View File

@ -32,7 +32,7 @@ protocol CGMTransmitterDelegate:AnyObject {
/// - serialNumber : transmitter serial number, only if transmitter can give that info, eg G5, otherwise nil
/// - bootloader : for the moment only used by GNSentry, otherwise nil
/// - sensorSerialNumber : serial number of the sensor, only applicable for Libre transmitters (MiaoMiao, Blucon, ...)
func cgmTransmitterInfoReceived(glucoseData:inout [RawGlucoseData], transmitterBatteryInfo:TransmitterBatteryInfo?, sensorState:LibreSensorState?, sensorTimeInMinutes:Int?, firmware:String?, hardware:String?, hardwareSerialNumber:String?, bootloader:String?, sensorSerialNumber:String?)
func cgmTransmitterInfoReceived(glucoseData:inout [GlucoseData], transmitterBatteryInfo:TransmitterBatteryInfo?, sensorState:LibreSensorState?, sensorTimeInMinutes:Int?, firmware:String?, hardware:String?, hardwareSerialNumber:String?, bootloader:String?, sensorSerialNumber:String?)
/// transmitter needs bluetooth pairing
func cgmTransmitterNeedsPairing()

View File

@ -1,7 +1,9 @@
import Foundation
/// raw glucose as received from transmitter
public class RawGlucoseData {
/// glucose,
public class GlucoseData {
// TODO: is there ever a difference between raw and filtered ? why not remove one ?
var timeStamp:Date
var glucoseLevelRaw:Double
@ -20,5 +22,10 @@ public class RawGlucoseData {
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
}
}

View File

@ -37,7 +37,7 @@ class CGMBluconTransmitter: BluetoothTransmitter {
private var sensorSerialNumber:String?
/// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send
private var emptyArray: [RawGlucoseData] = []
private var emptyArray: [GlucoseData] = []
/// timestamp when wakeUpResponse was sent to Blucon
private var timeStampLastWakeUpResponse:Date?
@ -170,7 +170,7 @@ class CGMBluconTransmitter: BluetoothTransmitter {
}
//get readings from buffer and send to delegate
var result = parseLibreData(data: &rxBuffer, timeStampLastBgReadingStoredInDatabase: timeStampLastBgReading, headerOffset: 0)
var result = LibreDataParser.parse(libreData: rxBuffer, timeStampLastBgReading: timeStampLastBgReading)
//TODO: sort glucosedata before calling newReadingsReceived
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: nil, sensorState: result.sensorState, sensorTimeInMinutes: result.sensorTimeInMinutes, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)
@ -247,6 +247,10 @@ extension CGMBluconTransmitter: CGMTransmitter {
return
}
/// this transmitter does not support oopWeb
func setWebOOPEnabled(enabled: Bool) {
}
}
extension CGMBluconTransmitter: BluetoothTransmitterDelegate {
@ -510,7 +514,7 @@ extension CGMBluconTransmitter: BluetoothTransmitterDelegate {
// get glucoseValue from value
let glucoseValue = nowGetGlucoseValue(input: value)
let glucoseData = RawGlucoseData(timeStamp: timeStampLastBgReading, glucoseLevelRaw: glucoseValue, glucoseLevelFiltered: glucoseValue)
let glucoseData = GlucoseData(timeStamp: timeStampLastBgReading, glucoseLevelRaw: glucoseValue, glucoseLevelFiltered: glucoseValue)
var glucoseDataArray = [glucoseData]
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)

View File

@ -35,18 +35,25 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C
// how long to wait for next packet before sending startreadingcommand
private static let maxWaitForpacketInSeconds = 60.0
// length of header added by Bubble in front of data dat is received from Libre sensor
private let BubbleHeaderLength = 8
private let bubbleHeaderLength = 8
/// is the transmitter oop web enabled or not
private var webOOPEnabled: Bool
/// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send
var emptyArray: [RawGlucoseData] = []
var emptyArray: [GlucoseData] = []
// current sensor serial number, if nil then it's not known yet
private var sensorSerialNumber:String?
// MARK: - Initialization
/// - parameters:
/// - address: if already connected before, then give here the address that was received during previous connect, if not give nil
init(address:String?, delegate:CGMTransmitterDelegate, timeStampLastBgReading:Date, sensorSerialNumber:String?) {
/// - delegate : CGMTransmitterDelegate intance
/// - timeStampLastBgReading : timestamp of last bgReading
/// - webOOPEnabled : enabled or not
init(address:String?, delegate:CGMTransmitterDelegate, timeStampLastBgReading:Date, sensorSerialNumber:String?, webOOPEnabled: Bool) {
// assign addressname and name or expected devicename
var newAddressAndName:BluetoothTransmitter.DeviceAddressAndName = BluetoothTransmitter.DeviceAddressAndName.notYetConnected(expectedName: expectedDeviceNameBubble)
@ -67,6 +74,9 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C
//initialize timeStampLastBgReading
self.timeStampLastBgReading = timeStampLastBgReading
// initialize webOOPEnabled
self.webOOPEnabled = webOOPEnabled
super.init(addressAndName: newAddressAndName, CBUUID_Advertisement: nil, servicesCBUUIDs: [CBUUID(string: CBUUID_Service_Bubble)], CBUUID_ReceiveCharacteristic: CBUUID_ReceiveCharacteristic_Bubble, CBUUID_WriteCharacteristic: CBUUID_WriteCharacteristic_Bubble, startScanningAfterInit: CGMTransmitterType.Bubble.startScanningAfterInit())
// set self as delegate for BluetoothTransmitterDelegate - this parameter is defined in the parent class BluetoothTransmitter
@ -132,23 +142,26 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C
// confirm receipt
_ = writeDataToPeripheral(data: Data([0x02, 0x00, 0x00, 0x00, 0x00, 0x2B]), type: .withoutResponse)
case .serialNumber:
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)) {
if (Crc.LibreCrc(data: &rxBuffer, headerOffset: bubbleHeaderLength)) {
var newSerialNumber = ""
if let sensorSerialNumberData = LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 0..<8))) {
newSerialNumber = sensorSerialNumberData.serialNumber
if let libreSensorSerialNumber = LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 0..<8))) {
// verify serial number and if changed inform delegate
if newSerialNumber != sensorSerialNumber {
if libreSensorSerialNumber.serialNumber != sensorSerialNumber {
trace(" new sensor detected : %{public}@", log: log, type: .info, newSerialNumber)
sensorSerialNumber = libreSensorSerialNumber.serialNumber
sensorSerialNumber = newSerialNumber
trace(" new sensor detected : %{public}@", log: log, type: .info, libreSensorSerialNumber.serialNumber)
// inform delegate about new sensor detected
cgmTransmitterDelegate?.newSensorDetected()
@ -156,27 +169,21 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C
// also reset timestamp last reading, to be sure that if new sensor is started, we get historic data
timeStampLastBgReading = Date(timeIntervalSince1970: 0)
// inform delegate about sensorSerialNumber
// inform delegate about new sensorSerialNumber
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: sensorSerialNumber)
}
}
if let sensorSerialNumber = sensorSerialNumber, UserDefaults.standard.webOOPEnabled {
handleLibreReading(bytes: [UInt8](rxBuffer.subdata(in: 8..<352)), serialNumber: sensorSerialNumber) {
[weak self] (result) in
if let res = result {
self?.handleGlucoseData(result: res)
} else {
/// failed, use parseLibreData
self?.parse()
}
}
} else {
// use local parser
self.parse()
}
LibreDataParser.libreDataProcessor(sensorSerialNumber: sensorSerialNumber, webOOPEnabled: webOOPEnabled, 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
})
//reset the buffer
resetRxBuffer()
}
}
case .noSensor:
@ -210,26 +217,11 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C
resendPacketCounter = 0
}
/// get readings from rxBuffer and send to delegate
private func parse() {
//get readings from buffer and send to delegate
let result = parseLibreData(data: &rxBuffer, timeStampLastBgReadingStoredInDatabase: timeStampLastBgReading, headerOffset: BubbleHeaderLength)
//TODO: sort glucosedata before calling newReadingsReceived
handleGlucoseData(result: result)
/// this transmitter supports oopWeb
func setWebOOPEnabled(enabled: Bool) {
webOOPEnabled = enabled
}
private func handleGlucoseData(result: (glucoseData:[RawGlucoseData], sensorState:LibreSensorState, sensorTimeInMinutes:Int)) {
var result = result
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: nil, sensorState: result.sensorState, sensorTimeInMinutes: result.sensorTimeInMinutes, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)
//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 {
timeStampLastBgReading = result.glucoseData[0].timeStamp
}
//reset the buffer
resetRxBuffer()
}
}
fileprivate enum BubbleResponseType: UInt8 {

View File

@ -22,7 +22,7 @@ class CGMDroplet1Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryCGMDroplet1)
/// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send
var emptyArray: [RawGlucoseData] = []
var emptyArray: [GlucoseData] = []
// MARK: - Initialization
/// - parameters:
@ -120,7 +120,7 @@ class CGMDroplet1Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
}
// send to delegate
var glucoseDataArray = [RawGlucoseData(timeStamp: Date(), glucoseLevelRaw: rawValueAsDouble)]
var glucoseDataArray = [GlucoseData(timeStamp: Date(), glucoseLevelRaw: rawValueAsDouble)]
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorState: nil, sensorTimeInMinutes: sensorTimeInMinutes * 10, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)
} else {
@ -140,4 +140,8 @@ class CGMDroplet1Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
/// this function is not implemented in BluetoothTransmitter.swift, otherwise it might be forgotten to look at in future CGMTransmitter developments
func reset(requested:Bool) {}
/// this transmitter does not support oopWeb
func setWebOOPEnabled(enabled: Bool) {
}
}

View File

@ -86,7 +86,7 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
var actualBootLoader:String?
/// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send
var emptyArray: [RawGlucoseData] = []
var emptyArray: [GlucoseData] = []
// MARK: - public functions
@ -201,7 +201,7 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
let sensorStatus = LibreSensorState(stateByte: UInt8(getIntAtPosition(numberOfBytes: 1, position: 5, data: &valueDecoded)))
// initialize empty array of bgreadings
var readings:Array<RawGlucoseData> = []
var readings:Array<GlucoseData> = []
// amountofReadingsPerMinute = how many readings per minute - see example code GNSEntry, if only one packet of 20 bytes transmitted, then only 5 readings 1 minute seperated
var amountOfPerMinuteReadings:Double = 5.0
@ -227,7 +227,7 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
// sometimes 0 values are received, skip those
if readingValueInMgDl > 0 {
if readingTimeStampInMinutes * 60 * 1000 < timeStampLastAddedGlucoseDataInMinutes * 60 * 1000 - (5 * 60 * 1000 - 10000) {
let glucoseData = RawGlucoseData(timeStamp: Date(timeIntervalSince1970: Double(readingTimeStampInMinutes) * 60.0), glucoseLevelRaw: Double(readingValueInMgDl) * ConstantsBloodGlucose.libreMultiplier)
let glucoseData = GlucoseData(timeStamp: Date(timeIntervalSince1970: Double(readingTimeStampInMinutes) * 60.0), glucoseLevelRaw: Double(readingValueInMgDl) * ConstantsBloodGlucose.libreMultiplier)
readings.append(glucoseData)
timeStampLastAddedGlucoseDataInMinutes = readingTimeStampInMinutes
}
@ -264,6 +264,9 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
/// this function is not implemented in BluetoothTransmitter.swift, otherwise it might be forgotten to look at in future CGMTransmitter developments
func reset(requested:Bool) {}
func setWebOOPEnabled(enabled: Bool) {
}
// MARK: CBCentralManager overriden functions
override func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {

View File

@ -3,6 +3,7 @@ import CoreBluetooth
import os
class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTransmitter {
// MARK: - properties
/// service to be discovered
@ -27,23 +28,31 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
// used in parsing packet
private var timeStampLastBgReading:Date
// counts number of times resend was requested due to crc error
/// counts number of times resend was requested due to crc error
private var resendPacketCounter:Int = 0
/// used when processing MiaoMiao data packet
private var timestampFirstPacketReception:Date
// receive buffer for miaomiao packets
/// receive buffer for miaomiao packets
private var rxBuffer:Data
// how long to wait for next packet before sending startreadingcommand
/// how long to wait for next packet before sending startreadingcommand
private static let maxWaitForpacketInSeconds = 60.0
// length of header added by MiaoMiao in front of data dat is received from Libre sensor
/// length of header added by MiaoMiao in front of data dat is received from Libre sensor
private let miaoMiaoHeaderLength = 18
/// is the transmitter oop web enabled or not
private var webOOPEnabled: Bool
// MARK: - Initialization
/// - parameters:
/// - address: if already connected before, then give here the address that was received during previous connect, if not give nil
init(address:String?, delegate:CGMTransmitterDelegate, timeStampLastBgReading:Date) {
/// - delegate : CGMTransmitterDelegate intance
/// - timeStampLastBgReading : timestamp of last bgReading
/// - webOOPEnabled : enabled or not
init(address:String?, delegate:CGMTransmitterDelegate, timeStampLastBgReading:Date, webOOPEnabled: Bool) {
// assign addressname and name or expected devicename
var newAddressAndName:BluetoothTransmitter.DeviceAddressAndName = BluetoothTransmitter.DeviceAddressAndName.notYetConnected(expectedName: expectedDeviceNameMiaoMiao)
@ -61,6 +70,9 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
//initialize timeStampLastBgReading
self.timeStampLastBgReading = timeStampLastBgReading
// initialize webOOPEnabled
self.webOOPEnabled = webOOPEnabled
super.init(addressAndName: newAddressAndName, CBUUID_Advertisement: nil, servicesCBUUIDs: [CBUUID(string: CBUUID_Service_MiaoMiao)], CBUUID_ReceiveCharacteristic: CBUUID_ReceiveCharacteristic_MiaoMiao, CBUUID_WriteCharacteristic: CBUUID_WriteCharacteristic_MiaoMiao, startScanningAfterInit: CGMTransmitterType.miaomiao.startScanningAfterInit())
// set self as delegate for BluetoothTransmitterDelegate - this parameter is defined in the parent class BluetoothTransmitter
@ -130,21 +142,14 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
let hardware = String(describing: rxBuffer[16...17].hexEncodedString())
let batteryPercentage = Int(rxBuffer[13])
let serialNumber = LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 5..<13)))?.serialNumber ?? "-"
debuglogging("serialNumber = " + serialNumber)
//get readings from buffer and send to delegate
var result = parseLibreData(data: &rxBuffer, timeStampLastBgReadingStoredInDatabase: timeStampLastBgReading, headerOffset: miaoMiaoHeaderLength)
//TODO: sort glucosedata before calling newReadingsReceived
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorState: result.sensorState, sensorTimeInMinutes: result.sensorTimeInMinutes, firmware: firmware, hardware: hardware, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)
//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 {
timeStampLastBgReading = result.glucoseData[0].timeStamp
}
LibreDataParser.libreDataProcessor(sensorSerialNumber: LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 5..<13)))?.serialNumber, webOOPEnabled: webOOPEnabled, 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
})
//reset the buffer
resetRxBuffer()
} else {
let temp = resendPacketCounter
resetRxBuffer()
@ -222,6 +227,15 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
resendPacketCounter = 0
}
/// this transmitter supports oopWeb
func setWebOOPEnabled(enabled: Bool) {
webOOPEnabled = enabled
// immediately request a new reading
// there's no check here to see if peripheral, characteristic, connection, etc.. exists, but that's no issue. If anything's missing, write will simply fail,
_ = sendStartReadingCommmand()
}
}
fileprivate enum MiaoMiaoResponseType: UInt8 {

View File

@ -0,0 +1,147 @@
import Foundation
class LibreDataParser {
/// parses libre block
/// - parameters:
/// - libreData: the 344 bytes block from Libre
/// - timeStampLastBgReadingStoredInDatabase: this is of the timestamp of the latest reading we already received during previous session
/// - returns:
/// - array of GlucoseData, first is the most recent, LibreSensorState. Only returns recent readings, ie not the ones that are older than timeStampLastBgReadingStoredInDatabase. 30 seconds are added here, meaning, new reading should be at least 30 seconds more recent than timeStampLastBgReadingStoredInDatabase
/// - 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) {
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 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)
// for that variable timeStampLastAddedGlucoseData is used. It's initially set to now + 5 minutes
var timeStampLastAddedGlucoseData = Date().toMillisecondsAsDouble() + 5 * 60 * 1000
trendloop: for index in 0..<16 {
i = indexTrend - index - 1
if i < 0 {i += 16}
timeInMinutes = max(0, (Double)(sensorTimeInMinutes - index))
let timeStampOfNewGlucoseData = sensorStartTimeInMilliseconds + timeInMinutes * 60 * 1000
//new reading should be at least 30 seconds younger than timeStampLastBgReadingStoredInDatabase
if timeStampOfNewGlucoseData > (timeStampLastBgReading.toMillisecondsAsDouble() + 30000.0)
{
if timeStampOfNewGlucoseData < timeStampLastAddedGlucoseData - (5 * 60 * 1000 - 10000) {
byte = Data()
byte.append(libreData[(i * 6 + 29)])
byte.append(libreData[(i * 6 + 28)])
let glucoseLevelRaw = Double(getGlucoseRaw(bytes: byte))
if (glucoseLevelRaw > 0) {
glucoseData = GlucoseData(timeStamp: Date(timeIntervalSince1970: sensorStartTimeInMilliseconds/1000 + timeInMinutes * 60), glucoseLevelRaw: Double(getGlucoseRaw(bytes: byte)) * ConstantsBloodGlucose.libreMultiplier)
returnValue.append(glucoseData)
timeStampLastAddedGlucoseData = timeStampOfNewGlucoseData
}
}
} else {
break trendloop
}
}
// loads history values
historyloop: for index in 0..<32 {
i = indexHistory - index - 1
if i < 0 {i += 32}
timeInMinutes = max(0,(Double)(abs(sensorTimeInMinutes - 3)/15)*15 - (Double)(index*15))
let timeStampOfNewGlucoseData = sensorStartTimeInMilliseconds + timeInMinutes * 60 * 1000
//new reading should be at least 30 seconds younger than timeStampLastBgReadingStoredInDatabase
if timeStampOfNewGlucoseData > (timeStampLastBgReading.toMillisecondsAsDouble() + 30000.0)
{
if timeStampOfNewGlucoseData < timeStampLastAddedGlucoseData - (5 * 60 * 1000 - 10000) {
byte = Data()
byte.append(libreData[(i * 6 + 125)])
byte.append(libreData[(i * 6 + 124)])
let glucoseLevelRaw = Double(getGlucoseRaw(bytes: byte))
if (glucoseLevelRaw > 0) {
glucoseData = GlucoseData(timeStamp: Date(timeIntervalSince1970: sensorStartTimeInMilliseconds/1000 + timeInMinutes * 60), glucoseLevelRaw: Double(getGlucoseRaw(bytes: byte)) * ConstantsBloodGlucose.libreMultiplier)
returnValue.append(glucoseData)
timeStampLastAddedGlucoseData = timeStampOfNewGlucoseData
}
}
} else {
break historyloop
}
}
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.
/// - parameters:
/// - sensorSerialNumber : if nil, then webOOP will not be used and local parsing will be done
/// - 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
/// - cgmTransmitterDelegate : the cgmTransmitterDelegate
/// - 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
/// - 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
///
/// 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, libreData: Data, cgmTransmitterDelegate : CGMTransmitterDelegate?, transmitterBatteryInfo:TransmitterBatteryInfo?, firmware: String?, hardware: String?, hardwareSerialNumber: String?, bootloader:String?, timeStampLastBgReading: Date, completionHandler:@escaping ((_ timeStampLastBgReading: Date) -> ())) {
if let sensorSerialNumber = sensorSerialNumber, webOOPEnabled {
LibreOOPClient.handleLibreData(libreData: [UInt8](libreData), timeStampLastBgReading: timeStampLastBgReading, serialNumber: sensorSerialNumber) {
(result) in
if let res = result {
handleGlucoseData(result: res, cgmTransmitterDelegate: cgmTransmitterDelegate, transmitterBatteryInfo: transmitterBatteryInfo, firmware: firmware, hardware: hardware, hardwareSerialNumber: hardwareSerialNumber, bootloader: bootloader, sensorSerialNumber: sensorSerialNumber, 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)
}
}
}
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)
}
/// 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) -> ())) {
//get readings from buffer and send to delegate
let result = LibreDataParser.parse(libreData: libreData, timeStampLastBgReading: timeStampLastBgReading)
handleGlucoseData(result: result, cgmTransmitterDelegate: cgmTransmitterDelegate, transmitterBatteryInfo: transmitterBatteryInfo, firmware: firmware, hardware: hardware, hardwareSerialNumber: hardwareSerialNumber, bootloader: bootloader, sensorSerialNumber: sensorSerialNumber, completionHandler: completionHandler)
}
/// calls delegate with parameters from result, will change value of timeStampLastBgReading
fileprivate func handleGlucoseData(result: (glucoseData:[GlucoseData], sensorState: LibreSensorState, sensorTimeInMinutes:Int), cgmTransmitterDelegate : CGMTransmitterDelegate?, transmitterBatteryInfo:TransmitterBatteryInfo?, firmware:String?, hardware:String?, hardwareSerialNumber:String?, bootloader:String?, sensorSerialNumber:String?, completionHandler:((_ timeStampLastBgReading: Date) -> ())) {
var result = result
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: transmitterBatteryInfo, sensorState: result.sensorState, sensorTimeInMinutes: result.sensorTimeInMinutes, firmware: firmware, hardware: hardware, hardwareSerialNumber: hardwareSerialNumber, bootloader: bootloader, sensorSerialNumber: sensorSerialNumber)
//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)
}
}

View File

@ -5,7 +5,7 @@
// Created by Bjørn Inge Berg on 18.10.2018.
// Copyright © 2018 Bjørn Inge Berg. All rights reserved.
//
// adapted by Johan Degraeve for xdrip ios
import Foundation
public struct LibreDerivedAlgorithmParameters: Codable, CustomStringConvertible {
@ -33,129 +33,3 @@ public struct LibreDerivedAlgorithmParameters: Codable, CustomStringConvertible
}
}
public class LibreDerivedAlgorithmRunner{
private var params: LibreDerivedAlgorithmParameters
init(_ params:LibreDerivedAlgorithmParameters) {
self.params = params
}
/* Result:
Parameters
slope1: 0.09130434782608696
offset1: -20.913043478260875
slope2: 0.11130434782608696
offset2: -20.913043478260875
slope_slope: 1.5290519877675845e-05
slope_offset: -0.0
offset_slope: 0.0023746842175242366
offset_offset: -20.913043478260875
*/
// These three functions should be implemented by the client
// wanting to do offline calculations
// of glucose
private func slopefunc(raw_temp: Int) -> Double{
return self.params.slope_slope * Double(raw_temp) + self.params.offset_slope
// rawglucose 7124: 0.1130434605
//0.00001562292 * 7124 + 0.0017457784869033700
// rawglucose 5816: 0.0926086812
//0.00001562292 * 5816 + 0.0017457784869033700
}
private func offsetfunc(raw_temp: Int) -> Double{
return self.params.slope_offset * Double(raw_temp) + self.params.offset_offset
//rawglucose 7124: -21.1304349
//-0.00023267185 * 7124 + -19.4728806406
// rawglucose 5816: -20.8261001202
//-0.00023267185 * 5816 + -19.4728806406
}
public func GetGlucoseValue(from_raw_glucose raw_glucose: Int, raw_temp: Int) -> Double{
return self.slopefunc(raw_temp: raw_temp) * Double(raw_glucose) + self.offsetfunc(raw_temp: raw_temp)
}
private func serializeAlgorithmParameters(_ params: LibreDerivedAlgorithmParameters) -> String{
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
var ret = ""
do {
let jsonData = try encoder.encode(params)
if let jsonString = String(data: jsonData, encoding: .utf8) {
ret = jsonString
}
} catch {
print("Could not serialize parameters: \(error.localizedDescription)")
}
return ret
}
public func SaveAlgorithmParameters(){
let fm = FileManager.default
guard let dir = fm.urls(for: .documentDirectory, in: .userDomainMask).first else {
print ("cannot construct url dir for writing parameters")
return
}
let fileUrl = dir.appendingPathComponent("LibreParamsForCurrentSensor").appendingPathExtension("txt")
print("Saving algorithm parameters to \(fileUrl.path)")
do{
try serializeAlgorithmParameters(params).write(to: fileUrl, atomically: true, encoding: String.Encoding.utf8)
} catch let error as NSError {
print("Error: fileUrl failed to write to \(fileUrl.path): \n\(error)" )
return
}
}
public static func CreateInstanceFromParamsFile() -> LibreDerivedAlgorithmRunner?{
let fm = FileManager.default
guard let dir = fm.urls(for: .documentDirectory, in: .userDomainMask).first else {
print ("cannot construct url dir for writing parameters")
return nil
}
let fileUrl = dir.appendingPathComponent("LibreParamsForCurrentSensor").appendingPathExtension("txt")
let text: String
do{
text = try String(contentsOf: fileUrl, encoding: .utf8)
} catch {
print("")
return nil
}
if let jsonData = text.data(using: .utf8) {
let decoder = JSONDecoder()
do {
let params = try decoder.decode(LibreDerivedAlgorithmParameters.self, from: jsonData)
return LibreDerivedAlgorithmRunner(params)
} catch {
print("Could not create instance: \(error.localizedDescription)")
}
} else {
print("Did not create instance")
}
return nil
}
}

View File

@ -5,7 +5,7 @@
// 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 {

View File

@ -36,15 +36,7 @@ struct LibreMeasurement {
// let offset_offset: Double = 0.0
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 slope_slope = 1.7333333333333336e-05
// let slope_offset = -0.0006666666666666666
// let offset_slope = 0.0049999999999999906
// let offset_offset = -19.0
// let slope_slope = 0.00001816666666666667
// let slope_offset = -0.00016666666666666666
// let offset_slope = 0.007499999999999993
// let offset_offset = -21.5
let oopSlope: Double
let oopOffset: Double
///
@ -69,10 +61,6 @@ struct LibreMeasurement {
self.date = date
self.counter = counter
// self.oopSlope = slope_slope * Double(rawTemperature) + offset_slope
// self.oopOffset = slope_offset * Double(rawTemperature) + offset_offset
// self.oopGlucose = oopSlope * Double(rawGlucose) + oopOffset
self.temperatureAlgorithmParameterSet = LibreDerivedAlgorithmParameterSet
if let LibreDerivedAlgorithmParameterSet = self.temperatureAlgorithmParameterSet {
self.oopSlope = LibreDerivedAlgorithmParameterSet.slope_slope * Double(rawTemperature) + LibreDerivedAlgorithmParameterSet.offset_slope
@ -92,64 +80,6 @@ struct LibreMeasurement {
print(self.description)
}
//
//
// private func slopefunc(raw_temp: Int) -> Double{
//
// return self.params.slope_slope * Double(raw_temp) + self.params.offset_slope
// // rawglucose 7124: 0.1130434605
// //0.00001562292 * 7124 + 0.0017457784869033700
//
// // rawglucose 5816: 0.0926086812
// //0.00001562292 * 5816 + 0.0017457784869033700
// }
//
// private func offsetfunc(raw_temp: Int) -> Double{
// return self.params.slope_offset * Double(raw_temp) + self.params.offset_offset
// //rawglucose 7124: -21.1304349
// //-0.00023267185 * 7124 + -19.4728806406
// // rawglucose 5816: -20.8261001202
// //-0.00023267185 * 5816 + -19.4728806406
// }
//
//
// public func GetGlucoseValue(from_raw_glucose raw_glucose: Int, raw_temp: Int) -> Double{
// return self.slopefunc(raw_temp: raw_temp) * Double(raw_glucose) + self.offsetfunc(raw_temp: raw_temp)
// }
// func temp1() -> Double {
// let anInt = (Int(self.bytes[4] & 0x3F) << 8) + Int(self.bytes[5])
// return 0.5 * (-273.16 + sqrt(abs(273.16*273.16 + 4.0 * Double(anInt))))
// }
// func temp2() -> Double {
// let anInt = (Int(self.bytes[4] & 0x3F) << 8) + Int(self.bytes[3])
// return 0.5 * (-273.16 + sqrt(abs(273.16*273.16 + 4.0 * Double(anInt))))
// }
//
// // Gitter
// func temp3() -> Double {
// let anInt = (Int(self.bytes[4] & 0x3F) << 8) + Int(self.bytes[5])
// return 22.22 * log(311301.0/(11.44 * Double(anInt)))
// }
// //Pierre Vandevenne 1
// func temp4() -> Double {
// let anInt = 16384 - (Int(self.bytes[4] & 0x3F) << 8) + Int(self.bytes[5])
//
// let a = 1.0
// let b = 273.0
// let c = -Double(anInt)
// let d = (b*b) - (4*a*c)
// let res = -b + sqrt( d ) / (2*a)
// return abs(res*0.0027689+9.53)
// }
//
// // Pierre Vandevenne 2
// func temp5() -> Double {
// let anInt = 16383 - (Int(self.bytes[4] & 0x3F) << 8) + Int(self.bytes[5])
// return abs(Double(anInt)*0.0027689+9.53)
// }
// Temp = 22.22 * log(311301/NTC)
var description: String {
var aString = String("Glucose: \(glucose) (mg/dl), date: \(date), slope: \(slope), offset: \(offset), rawGlucose: \(rawGlucose), rawTemperature: \(rawTemperature), bytes: \(bytes) \n")
@ -158,6 +88,6 @@ struct LibreMeasurement {
aString.append("oopGlucose: \(temperatureAlgorithmGlucose) (mg/dl)" )
return aString
// return String("Glucose: \(glucose) (mg/dl), date: \(date), slope: \(slope), offset: \(offset), rawGlucose: \(rawGlucose), rawTemperature: \(rawTemperature), bytes: \(bytes) /n oop: slope_slope = " )
}
}

View File

@ -14,305 +14,100 @@
// Copyright © 2018 Bjørn Inge Berg. All rights reserved.
//
//
// adapted by Johan Degraeve for xdrip ios
import Foundation
import os
class LibreOOPClient {
private var accessToken: String
private var uploadEndpoint: String // = "https://libreoopweb.azurewebsites.net/api/CreateRequestAsync"
private var statusEndpoint: String // = "https://libreoopweb.azurewebsites.net/api/GetStatus"
private var calibrationEndpoint: String
private var calibrationStatusEndpoint: String
private static let filePath: String = NSHomeDirectory() + "/Documents/paras"
// MARK: - properties
init(accessToken: String, site: String = "https://libreoopweb.azurewebsites.net") {
self.accessToken = accessToken
self.uploadEndpoint = site + "/api/CreateRequestAsync"
self.statusEndpoint = site + "/api/GetStatus"
self.calibrationEndpoint = site + "/api/CreateCalibrationRequestAsync"
self.calibrationStatusEndpoint = site + "/api/GetCalibrationStatus"
}
private static let filePath: String = NSHomeDirectory() + ConstantsLibreOOP.filePathForParameterStorage
private static func readingToString(_ a: [UInt8]) -> String {
return Data(a).base64EncodedString()
}
private func postToServer(_ completion:@escaping (( _ data_: Data, _ response: String, _ success: Bool ) -> Void), postURL: String, postparams: [String: String]) {
/// for trace
private static let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryLibreOOPClient)
// MARK: - public functions
public static func handleLibreData(libreData: [UInt8], timeStampLastBgReading: Date, serialNumber: String, _ callback: @escaping ((glucoseData: [GlucoseData], sensorState: LibreSensorState, sensorTimeInMinutes: Int)?) -> Void) {
let request = NSMutableURLRequest(url: NSURL(string: postURL)! as URL)
request.httpMethod = "POST"
//only care about the once per minute readings here, historical data will not be considered
request.setBodyContent(contentMap: postparams)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, _ in
guard let data = data else {
completion("network error".data(using: .utf8)!, "network error", false)
let sensorState = LibreSensorState(stateByte: libreData[4])
LibreOOPClient.calibrateSensor(bytes: libreData, serialNumber: serialNumber) {
(calibrationparams) in
guard let params = calibrationparams else {
callback(nil)
return
}
if let response = String(data: data, encoding: String.Encoding.utf8) {
completion(data, response, true)
}
}
task.resume()
}
private func getStatusIntervalled(uuid: String, intervalSeconds: UInt32=10, maxTries: Int8=8, _ completion:@escaping (( _ success: Bool, _ message: String, _ oopCurrentValue: OOPCurrentValue?, _ newState: String) -> Void)) {
let sem = DispatchSemaphore(value: 0)
var oopCurrentValue: OOPCurrentValue? = nil
var succeeded = false
var error = ""
var newState2 = ""
DispatchQueue.global().async {
for i in 1...maxTries {
NSLog("Attempt \(i): Waiting \(intervalSeconds) seconds before calling getstatus")
sleep(intervalSeconds)
NSLog("Finished waiting \(intervalSeconds) seconds before calling getstatus")
if (succeeded) {
error = ""
break
}
self.getStatus(uuid: uuid, { (success, errormsg, response, newState) in
if (success) {
succeeded = true
newState2 = newState ?? ""
oopCurrentValue = self.getOOPCurrentValue(from: response)
} else {
error = errormsg
}
sem.signal()
})
sem.wait()
/*if let oopCurrentValue = oopCurrentValue {
NSLog("Hey hop, response received with success: \(succeeded)");
NSLog("Decoded content")
NSLog(" Current trend: \(oopCurrentValue.currentTrend)")
NSLog(" Current bg: \(oopCurrentValue.currentBg)")
NSLog(" Current time: \(oopCurrentValue.currentTime)")
NSLog(" Serial Number: \(oopCurrentValue.serialNumber ?? "-")")
NSLog(" timeStamp: \(oopCurrentValue.timestamp)")
var i = 0
for historyValue in oopCurrentValue.historyValues {
NSLog(String(format: " #%02d: time: \(historyValue.time), quality: \(historyValue.quality), bg: \(historyValue.bg)", i))
i += 1
}
}*/
if (succeeded) {
error = ""
break
}
}
completion(succeeded, error, oopCurrentValue, newState2)
//here we assume success, data is not changed,
//and we trust that the remote endpoint returns correct data for the sensor
let last16 = trendMeasurements(bytes: libreData, date: Date(), timeStampLastBgReading: timeStampLastBgReading, LibreDerivedAlgorithmParameterSet: params)
if let glucoseData = trendToLibreGlucose(last16) {
callback((glucoseData, sensorState, 0))
}
}
}
private func getOOPCurrentValue(from response: String?) -> OOPCurrentValue? {
// Decode json response string into OOPCurrentValue struct.
// This requires to remove the beginning of the response string up to "FullAlgoResults"
if let response = response,
let jsonStringStartIndex = response.range(of: "FullAlgoResults: ")?.upperBound {
do {
let jsonString = String(response.suffix(from: jsonStringStartIndex))
if let jsonData = jsonString.data(using: .utf8) {
let oopCurrentValue = try JSONDecoder().decode(OOPCurrentValue.self, from: jsonData)
return oopCurrentValue
}
} catch let error {
NSLog("Error decoding json respons: \(error)")
}
}
return nil
}
private func getStatus(uuid: String, _ completion:@escaping (( _ success: Bool, _ message: String, _ response: String?, _ newState: String? ) -> Void)) {
postToServer({ (data, response, success) in
NSLog("getstatus here:" + response)
if(!success) {
NSLog("Get status failed")
completion(false, response, response, nil)
return
}
private static func calibrateSensor(bytes: [UInt8], serialNumber: String, callback: @escaping (LibreDerivedAlgorithmParameters?) -> Void) {
let url = URL.init(fileURLWithPath: filePath)
if FileManager.default.fileExists(atPath: url.path) {
let decoder = JSONDecoder()
do {
let response = try decoder.decode(LibreOOPResponse.self, from: data)
NSLog("getstatus result received")
if let msg = response.message {
NSLog("Error sending GetStatus request " + msg)
completion(false, "Error sending GetStatus reques" + msg, nil, nil)
//failureHandler(msg)
return
}
if let resp = response.result, let result2 = resp.result {
NSLog("GetStatus returned a valid result:" + result2)
completion(true, "", result2, resp.newState!)
return
} else {
NSLog("Result was not ready,")
completion(false, "Result was not ready", nil, nil)
let data = try Data.init(contentsOf: url)
let response = try decoder.decode(LibreDerivedAlgorithmParameters.self, from: data)
if response.serialNumber == serialNumber {
callback(response)
return
}
} catch {
} catch (let error as NSError) {
completion(false, error.localizedDescription, nil, nil)
return
}
}, postURL: statusEndpoint, postparams: ["accesstoken": self.accessToken, "uuid": uuid])
}
private func uploadReading(reading: String, oldState: String?=nil, sensorStartTimestamp: Int?=nil, sensorScanTimestamp: Int?=nil, currentUtcOffset: Int?=nil, _ completion:@escaping (( _ resp: LibreOOPResponse?, _ success: Bool, _ errorMessage: String) -> Void)) {
var postParams = ["accesstoken": self.accessToken, "b64contents": reading]
if let oldState = oldState {
postParams["oldState"] = oldState
}
if let sensorStartTimestamp = sensorStartTimestamp {
postParams["sensorStartTimestamp"] = "\(sensorStartTimestamp)"
}
if let sensorScanTimestamp = sensorScanTimestamp {
postParams["sensorScanTimestamp"] = "\(sensorScanTimestamp)"
}
if let currentUtcOffset = currentUtcOffset {
postParams["currentUtcOffset"] = "\(currentUtcOffset)"
}
postToServer({ (data, _, success) in
if(!success) {
completion(nil, false, "network error!?")
return
}
let decoder = JSONDecoder()
do {
let result = try decoder.decode(LibreOOPResponse.self, from: data)
if let msg = result.message {
completion(nil, false, msg)
return
}
completion(result, true, "")
return
} catch let error as NSError {
completion(nil, false, error.localizedDescription)
return
}
}, postURL: uploadEndpoint, postparams: postParams)
}
private func uploadDependantReadings(readings: [LibreReadingResult]) -> [(success: Bool, String, OOPCurrentValue?, String)]? {
var ret = [(Bool, String, OOPCurrentValue?, String)]()
var prevReading: LibreReadingResult? = nil
for (_, var reading) in readings.enumerated() {
//the semaphore lets me do the requests in-order
let awaiter = DispatchSemaphore( value: 0 )
let tempState = prevReading?.newState ?? LibreOOPDefaults.defaultState
self.uploadReading(reading: reading.b64Contents, oldState: tempState, sensorStartTimestamp: LibreOOPDefaults.sensorStartTimestamp, sensorScanTimestamp: LibreOOPDefaults.sensorScanTimestamp, currentUtcOffset: LibreOOPDefaults.currentUtcOffset) { (response, success, errormessage) in
if(!success) {
NSLog("remote: upload reading failed! \(errormessage)")
ret.append((success, errormessage, nil, ""))
awaiter.signal()
return
}
if let response = response, let uuid = response.result?.uuid {
print("uuid received: " + uuid)
self.getStatusIntervalled(uuid: uuid, { (success, errormessage, oopCurrentValue, newState) in
if let oopCurrentValue = oopCurrentValue {
ret.append((success, errormessage, oopCurrentValue, newState))
reading.newState = newState
prevReading = reading
}
awaiter.signal()
})
} else {
awaiter.signal()
}
print("decoder error:", error)
}
awaiter.wait()
}
return ret
}
private func getCalibrationStatus(uuid: String, _ completion:@escaping (( _ success: Bool, _ message: String, _ response: LibreDerivedAlgorithmParameters?) -> Void)) {
postToServer({ (data, response, success) in
NSLog("getCalibrationStatus here:" + response + ", data: \(data)")
if(!success) {
NSLog("getCalibrationStatus failed")
completion(false, response, nil)
return
}
post(bytes: bytes, { (data, str, can) in
let decoder = JSONDecoder()
do {
let response = try decoder.decode(GetCalibrationStatus.self, from: data)
NSLog("getCalibrationStatus result received")
if response.error ?? false {
completion(false, "getCalibrationStatus failes due to error", nil)
return
}
if let slope = response.result, slope.status == "complete" {
print("calibration ready")
let params = LibreDerivedAlgorithmParameters.init(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)
if let slope = response.slope {
var para = LibreDerivedAlgorithmParameters.init(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)
para.serialNumber = serialNumber
do {
let data = try JSONEncoder().encode(para)
save(data: data)
} catch {
trace("in calibrateSensor, error : %{public}@@", log: log, type: .error, error.localizedDescription)
}
completion(true, "complete", params )
return
callback(para)
} else {
trace("in calibrateSensor, failed to decode", log: log, type: .error)
callback(nil)
}
print("calibration is not ready, status is not ready")
completion(false, "result not ready", nil)
} catch (let error as NSError) {
print("got error trying to decode GetCalibrationStatus")
completion(false, error.localizedDescription, nil)
return
} catch {
trace("in calibrateSensor, got error trying to decode GetCalibrationStatus", log: log, type: .error)
callback(nil)
}
}, postURL: calibrationStatusEndpoint, postparams: ["accesstoken": self.accessToken, "uuid": uuid])
})
}
// MARK: - private functions
private static func post(bytes: [UInt8],_ completion:@escaping (( _ data_: Data, _ response: String, _ success: Bool ) -> Void)) {
let date = Int(Date().timeIntervalSince1970 * 1000)
let date = Date().toMillisecondsAsInt64()
let bytesAsData = Data(bytes: bytes, count: bytes.count)
let json: [String: String] = [
"token": "bubble-201907",
"token": ConstantsLibreOOP.token,
"content": "\(bytesAsData.hexEncodedString())",
"timestamp": "\(date)"]
if let uploadURL = URL.init(string: "http://www.glucose.space/calibrateSensor") {
if let uploadURL = URL.init(string: ConstantsLibreOOP.site) {
let request = NSMutableURLRequest(url: uploadURL)
request.httpMethod = "POST"
@ -323,12 +118,32 @@ class LibreOOPClient {
data, response, _ in
guard let data = data else {
completion("network error".data(using: .utf8)!, "network error", false)
trace("in post, network error", log: log, type: .error)
DispatchQueue.main.sync {
completion("network error".data(using: .utf8)!, "network error", false)
}
return
}
if let response = String(data: data, encoding: String.Encoding.utf8) {
completion(data, response, true)
trace("in post, successful", log: log, type: .info)
DispatchQueue.main.sync {
completion(data, response, true)
}
return
}
trace("in post, response error", log: log, type: .error)
DispatchQueue.main.sync {
completion("response error".data(using: .utf8)!, "response error", false)
}
}
@ -345,43 +160,54 @@ class LibreOOPClient {
}
}
public static func calibrateSensor(bytes: [UInt8], serialNumber: String, callback: @escaping (LibreDerivedAlgorithmParameters?) -> Void) {
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 response = try decoder.decode(LibreDerivedAlgorithmParameters.self, from: data)
if response.serialNumber == serialNumber {
callback(response)
return
}
} catch {
print("decoder error:", error)
private static func trendMeasurements(bytes: [UInt8], date: 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
}
let range = index..<index+6
let measurementBytes = Array(body[range])
let measurementDate = date.addingTimeInterval(Double(-60 * blockIndex))
if measurementDate > timeStampLastBgReading {
let measurement = LibreMeasurement(bytes: measurementBytes, slope: slope, offset: offset, date: measurementDate, LibreDerivedAlgorithmParameterSet: LibreDerivedAlgorithmParameterSet)
measurements.append(measurement)
}
}
return measurements
}
private static func trendToLibreGlucose(_ measurements: [LibreMeasurement]) -> [LibreRawGlucoseData]?{
var origarr = [LibreRawGlucoseData]()
for trend in measurements {
let glucose = LibreRawGlucoseData(timeStamp: trend.date, unsmoothedGlucose: trend.temperatureAlgorithmGlucose)
debuglogging("in trendToLibreGlucose before CalculateSmothedData5Points, glucose.glucoseLevelRaw = " + glucose.glucoseLevelRaw.description + ", glucose.unsmoothedGlucose = " + glucose.unsmoothedGlucose.description)
origarr.append(glucose)
}
post(bytes: bytes, { (data, str, can) in
let decoder = JSONDecoder()
do {
let response = try decoder.decode(GetCalibrationStatus.self, from: data)
if let slope = response.slope {
var para = LibreDerivedAlgorithmParameters.init(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)
para.serialNumber = serialNumber
do {
let data = try JSONEncoder().encode(para)
save(data: data)
} catch {
print("encoder error:", error)
}
callback(para)
} else {
callback(nil)
}
} catch {
print("got error trying to decode GetCalibrationStatus")
callback(nil)
}
})
var arr : [LibreRawGlucoseData]
arr = LibreGlucoseSmoothing.CalculateSmothedData5Points(origtrends: origarr)
for glucose in arr {
debuglogging("in trendToLibreGlucose after CalculateSmothedData5Points, glucose.glucoseLevelRaw = " + glucose.glucoseLevelRaw.description + ", glucose.unsmoothedGlucose = " + glucose.unsmoothedGlucose.description)
}
return arr
}
}

View File

@ -1,77 +0,0 @@
//
// LibreOOPDefaults.swift
// SwitftOOPWeb
//
// Created by Bjørn Inge Berg on 23.04.2018.
// Copyright © 2018 Bjørn Inge Berg. All rights reserved.
//
import Foundation
struct LibreOOPDefaults {
private static var _defaultState: [UInt8] = [
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
// Example Libre patch contents.
// This would typically by a full readout of the sensor from a blureader,blucon, miaomiao or some other nfc to bluetooth bridge.
private static var _testPatchAlwaysReturning63: [UInt8] = [
0x3a, 0xcf, 0x10, 0x16, 0x03, 0x00, 0x00, 0x00, // 0x00 Begin of header
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x01
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x02 End of header
0x4f, 0x11, 0x08, 0x10, 0xad, 0x02, 0xc8, 0xd4, // 0x03 Begin of body. CRC shoud be 4f 11. trendIndex: 8, historyIndex: 16
0x5b, 0x00, 0xaa, 0x02, 0xc8, 0xb4, 0x1b, 0x80, // 0x04
0xa9, 0x02, 0xc8, 0x9c, 0x5b, 0x00, 0xa9, 0x02, // 0x05
0xc8, 0x8c, 0x1b, 0x80, 0xb0, 0x02, 0xc8, 0x30, // 0x06
0x5c, 0x80, 0xb0, 0x02, 0x88, 0xe6, 0x9c, 0x80, // 0x07
0xb8, 0x02, 0xc8, 0x3c, 0x9d, 0x80, 0xb8, 0x02, // 0x08
0xc8, 0x60, 0x9d, 0x80, 0xa1, 0x02, 0xc8, 0xdc, // 0x09
0x9e, 0x80, 0xab, 0x02, 0xc8, 0x14, 0x9e, 0x80, // 0x0A
0xa9, 0x02, 0xc8, 0xc0, 0x9d, 0x80, 0xab, 0x02, // 0x0B
0xc8, 0x78, 0x9d, 0x80, 0xaa, 0x02, 0xc8, 0x40, // 0x0C
0x9d, 0x80, 0xa8, 0x02, 0xc8, 0x08, 0x9d, 0x80, // 0x0D
0xa8, 0x02, 0xc8, 0x2c, 0x5c, 0x80, 0xad, 0x02, // 0x0E
0xc8, 0xf8, 0x5b, 0x00, 0x29, 0x06, 0xc8, 0xf4, // 0x0F
0x9b, 0x80, 0xc9, 0x05, 0xc8, 0x8c, 0xde, 0x80, // 0x10
0xc3, 0x05, 0xc8, 0x28, 0x9e, 0x80, 0x2c, 0x06, // 0x11
0xc8, 0xd0, 0x9e, 0x80, 0x7b, 0x06, 0x88, 0xa6, // 0x12
0x9e, 0x80, 0xf9, 0x05, 0xc8, 0xb0, 0x9e, 0x80, // 0x13
0x99, 0x05, 0xc8, 0xf0, 0x9e, 0x80, 0x2e, 0x05, // 0x14
0xc8, 0x00, 0x9f, 0x80, 0x81, 0x04, 0xc8, 0x48, // 0x15
0xa0, 0x80, 0x5d, 0x04, 0xc8, 0x38, 0x9d, 0x80, // 0x16
0x12, 0x04, 0xc8, 0x10, 0x9e, 0x80, 0xcf, 0x03, // 0x17
0xc8, 0x4c, 0x9e, 0x80, 0x6f, 0x03, 0xc8, 0xb8, // 0x18
0x9e, 0x80, 0x19, 0x03, 0xc8, 0x40, 0x9f, 0x80, // 0x19
0xc5, 0x02, 0xc8, 0xf4, 0x9e, 0x80, 0xaa, 0x02, // 0x1A
0xc8, 0xf8, 0x5b, 0x00, 0xa2, 0x04, 0xc8, 0x38, // 0x1B
0x9a, 0x00, 0xd1, 0x04, 0xc8, 0x28, 0x9b, 0x80, // 0x1C
0xe4, 0x04, 0xc8, 0xe0, 0x1a, 0x80, 0x8f, 0x04, // 0x1D
0xc8, 0x20, 0x9b, 0x80, 0x22, 0x06, 0xc8, 0x50, // 0x1E
0x5b, 0x80, 0xbc, 0x06, 0xc8, 0x54, 0x9c, 0x80, // 0x1F
0x7f, 0x05, 0xc8, 0x24, 0x5c, 0x80, 0xc9, 0x05, // 0x20
0xc8, 0x38, 0x5c, 0x80, 0x38, 0x05, 0xc8, 0xf4, // 0x21
0x1a, 0x80, 0x37, 0x07, 0xc8, 0x84, 0x5b, 0x80, // 0x22
0xfb, 0x08, 0xc8, 0x4c, 0x9c, 0x80, 0xfb, 0x09, // 0x23
0xc8, 0x7c, 0x9b, 0x80, 0x77, 0x0a, 0xc8, 0xe4, // 0x24
0x5a, 0x80, 0xdf, 0x09, 0xc8, 0x88, 0x9f, 0x80, // 0x25
0x6d, 0x08, 0xc8, 0x2c, 0x9f, 0x80, 0xc3, 0x06, // 0x26
0xc8, 0xb0, 0x9d, 0x80, 0xd9, 0x11, 0x00, 0x00, // 0x27 End of body. Time: 4569 (0xd911 -> bytes swapped -> 0x11d9 = 4569)
0x72, 0xc2, 0x00, 0x08, 0x82, 0x05, 0x09, 0x51, // 0x28 Beginn of footer
0x14, 0x07, 0x96, 0x80, 0x5a, 0x00, 0xed, 0xa6, // 0x29
0x0e, 0x6e, 0x1a, 0xc8, 0x04, 0xdd, 0x58, 0x6d // 0x2A End of footer
]
public static var TestPatchAlwaysReturning63:[UInt8] {
return _testPatchAlwaysReturning63.map { $0 }
}
public static var defaultState: String {
get {
return Data(_defaultState).base64EncodedString()
}
}
public static var sensorStartTimestamp = 0x0e181349
public static var sensorScanTimestamp = 0x0e1c4794
public static var currentUtcOffset = 0x0036ee80
}

View File

@ -5,223 +5,10 @@
//// Created by Bjørn Inge Berg on 08.04.2018.
//// Copyright © 2018 Bjørn Inge Berg. All rights reserved.
////
//
//import Foundation
//
//struct OOPCurrentValue: Codable {
// let currentTrend: Int
// let currentBg: Double
// let currentTime: Int
// let historyValues: [OOPHistoryValue]
// let serialNumber: String?
// let timestamp: Int
//
// enum CodingKeys: String, CodingKey {
// case currentTrend = "currenTrend" // TODO: rename currenTrend to currentTrend
// case currentBg
// case currentTime
// case historyValues = "historicBg"
// case serialNumber
// case timestamp
// }
//}
//
//struct OOPHistoryValue: Codable {
// let bg: Double
// let quality: Int
// let time: Int
//
// enum Codingkeys: String, CodingKey {
// case bg
// case quality
// case time
// }
//}
//
//struct LibreOOPResponse: Codable {
// let error: Bool
// let command: String
// let message: String?
// let result: LibreReadingResult?
//
// enum CodingKeys: String, CodingKey {
// case error = "Error"
// case command = "Command"
// case message = "Message"
// case result = "Result"
// }
//}
//
//struct LibreReadingResult: Codable {
// let createdOn, modifiedOn, uuid, b64Contents: String
// let status: String
// let result: String?
//
// enum CodingKeys: String, CodingKey {
// case createdOn = "CreatedOn"
// case modifiedOn = "ModifiedOn"
// case uuid
// case b64Contents = "b64contents"
// case status, result
// }
//}
//
//// MARK: Encode/decode helpers
//
//class JSONNull: Codable {
// public init() {}
//
// public required init(from decoder: Decoder) throws {
// let container = try decoder.singleValueContainer()
// if !container.decodeNil() {
// throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
// }
// }
//
// public func encode(to encoder: Encoder) throws {
// var container = encoder.singleValueContainer()
// try container.encodeNil()
// }
//}
//
//
//
//
//
// CreateRequestResponse.swift
// SwitftOOPWeb
//
// Created by Bjørn Inge Berg on 08.04.2018.
// Copyright © 2018 Bjørn Inge Berg. All rights reserved.
//
//// adapted by Johan Degraeve for xdrip ios
import Foundation
struct OOPCurrentValue: Codable {
let currentTrend: Int?
var currentBg: Double?
let currentTime: Int?
let historyValues: [OOPHistoryValue]?
var serialNumber: String?
let timestamp: Int?
enum CodingKeys: String, CodingKey {
case currentTrend = "currenTrend" // TODO: rename currenTrend to currentTrend
case currentBg
case currentTime
case historyValues = "historicBg"
case serialNumber
case timestamp
}
}
struct OOPHistoryValue: Codable {
let bg: Double
let quality: Int
let time: Int
enum Codingkeys: String, CodingKey {
case bg
case quality
case time
}
}
struct LibreOOPResponse: Codable {
let error: Bool?
let command: String?
let message: String?
let result: LibreReadingResult?
enum CodingKeys: String, CodingKey {
case error = "Error"
case command = "Command"
case message = "Message"
case result = "Result"
}
}
struct LibreReadingResult: Codable {
let createdOn, modifiedOn, uuid, b64Contents: String
let status: String
let result: String?
var newState: String?
enum CodingKeys: String, CodingKey {
case createdOn = "CreatedOn"
case modifiedOn = "ModifiedOn"
case uuid
case b64Contents = "b64contents"
case status, result, newState
}
}
extension LibreReadingResult {
var created: Date? {
get {
return Date.dateFromISOString(string: self.createdOn)
}
}
init(created: String, b64Contents: String, uuid: String="") {
self.init(createdOn: created, modifiedOn: created, uuid: uuid, b64Contents: b64Contents, status: "init", result: "", newState: "")
}
}
// MARK: Encode/decode helpers
struct CalibrationResponse: Codable {
let error: Bool
let command: String
let result: CalibrationResult?
enum CodingKeys: String, CodingKey {
case error = "Error"
case command = "Command"
case result = "Result"
}
}
struct CalibrationResult: Codable {
let createdOn, modifiedOn, uuid: String
let metadata: CalibrationMetadata
let requestids: [String]
enum CodingKeys: String, CodingKey {
case createdOn = "CreatedOn"
case modifiedOn = "ModifiedOn"
case uuid, metadata, requestids
}
}
struct CalibrationMetadata: Codable {
let glucoseLowerBound, glucoseUpperBound, rawTemp1, rawTemp2: Int
enum CodingKeys: String, CodingKey {
case glucoseLowerBound = "GLUCOSE_LOWER_BOUND"
case glucoseUpperBound = "GLUCOSE_UPPER_BOUND"
case rawTemp1 = "RAW_TEMP1"
case rawTemp2 = "RAW_TEMP2"
}
}
class JSONNull: Codable {
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
struct GetCalibrationStatus: Codable {
let error: Bool?

View File

@ -1,118 +0,0 @@
////
//// SwiftOOPWebExtensions.swift
//// SwitftOOPWeb
////
//// Created by Bjørn Inge Berg on 08.04.2018.
//// Copyright © 2018 Bjørn Inge Berg. All rights reserved.
////
//import Foundation
//
//extension NSMutableURLRequest {
//
// /// Populate the HTTPBody of `application/x-www-form-urlencoded` request
// ///
// /// :param: contentMap A dictionary of keys and values to be added to the request
//
// func setBodyContent(contentMap: [String : String]) {
// let parameters = contentMap.map { (key, value) -> String in
// return "\(key)=\(value.stringByAddingPercentEscapesForQueryValue()!)"
// }
//
// httpBody = parameters.joined(separator: "&").data(using: .utf8)
// }
//}
//
//extension String {
//
// /// Percent escape value to be added to a URL query value as specified in RFC 3986
// ///
// /// This percent-escapes all characters except the alphanumeric character set and "-", ".", "_", and "~".
// ///
// /// http://www.ietf.org/rfc/rfc3986.txt
// ///
// /// :returns: Return precent escaped string.
//
// func stringByAddingPercentEscapesForQueryValue() -> String? {
// let characterSet = NSMutableCharacterSet.alphanumeric()
// characterSet.addCharacters(in: "-._~")
// return self.addingPercentEncoding(withAllowedCharacters: characterSet as CharacterSet)
// }
//}
//
// SwiftOOPWebExtensions.swift
// SwitftOOPWeb
//
// Created by Bjørn Inge Berg on 08.04.2018.
// Copyright © 2018 Bjørn Inge Berg. All rights reserved.
//
import Foundation
extension NSMutableURLRequest {
/// Populate the HTTPBody of `application/x-www-form-urlencoded` request
///
/// :param: contentMap A dictionary of keys and values to be added to the request
func setBodyContent(contentMap: [String: String]) {
let parameters = contentMap.map { (key, value) -> String in
return "\(key)=\(value.stringByAddingPercentEscapesForQueryValue()!)"
}
httpBody = parameters.joined(separator: "&").data(using: .utf8)
}
}
extension String {
/// Percent escape value to be added to a URL query value as specified in RFC 3986
///
/// This percent-escapes all characters except the alphanumeric character set and "-", ".", "_", and "~".
///
/// http://www.ietf.org/rfc/rfc3986.txt
///
/// :returns: Return precent escaped string.
func stringByAddingPercentEscapesForQueryValue() -> String? {
let characterSet = NSMutableCharacterSet.alphanumeric()
characterSet.addCharacters(in: "-._~")
return self.addingPercentEncoding(withAllowedCharacters: characterSet as CharacterSet)
}
//: ### Base64 encoding a string
func base64Encoded() -> String? {
if let data = self.data(using: .utf8) {
return data.base64EncodedString()
}
return nil
}
//: ### Base64 decoding a string
func base64Decoded() -> [UInt8]? {
if let data = Data(base64Encoded: self) {
return [UInt8](data)
}
return nil
}
}
extension Date {
static func ISOStringFromDate(date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
return dateFormatter.string(from: date).appending("Z")
}
static func dateFromISOString(string: String) -> Date? {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone.autoupdatingCurrent
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
return dateFormatter.date(from: string)
}
}

View File

@ -1,7 +1,7 @@
import Foundation
/// extends RawGlucoseData and adds property unsmoothedGlucose, because this is only used for Libre
class LibreRawGlucoseData: RawGlucoseData {
class LibreRawGlucoseData: GlucoseData {
var unsmoothedGlucose: Double

View File

@ -1,165 +0,0 @@
import Foundation
/// parses libre block
/// - parameters:
/// - data: the block, inout but not changes, to save copying time
/// - timeStampLastBgReadingStoredInDatabase: this is of the timestamp of the latest reading we already received during previous session
/// - headerOffset: location of Libre block in the data because MiaoMiao (or other) header is not stripped off
/// - returns:
/// - array of GlucoseData, first is the most recent, LibreSensorState. Only returns recent readings, ie not the ones that are older than timeStampLastBgReadingStoredInDatabase. 30 seconds are added here, meaning, new reading should be at least 30 seconds more recent than timeStampLastBgReadingStoredInDatabase
/// - sensorState: status of the sensor
/// - sensorTimeInMinutes: age of sensor in minutes
func parseLibreData(data:inout Data, timeStampLastBgReadingStoredInDatabase:Date, headerOffset:Int) -> (glucoseData:[RawGlucoseData], sensorState:LibreSensorState, sensorTimeInMinutes:Int) {
var i:Int
var glucoseData:RawGlucoseData
var byte:Data
var timeInMinutes:Double
let ourTime:Date = Date()
let indexTrend:Int = getByteAt(buffer: data, position: headerOffset + 26) & 0xFF
let indexHistory:Int = getByteAt(buffer: data, position: headerOffset + 27) & 0xFF
let sensorTimeInMinutes:Int = 256 * (getByteAt(buffer:data, position: headerOffset + 317) & 0xFF) + (getByteAt(buffer:data, position: headerOffset + 316) & 0xFF)
let sensorStartTimeInMilliseconds:Double = ourTime.toMillisecondsAsDouble() - (Double)(sensorTimeInMinutes * 60 * 1000)
var returnValue:Array<RawGlucoseData> = []
let sensorState = LibreSensorState(stateByte: data[headerOffset + 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)
// for that variable timeStampLastAddedGlucoseData is used. It's initially set to now + 5 minutes
var timeStampLastAddedGlucoseData = Date().toMillisecondsAsDouble() + 5 * 60 * 1000
trendloop: for index in 0..<16 {
i = indexTrend - index - 1
if i < 0 {i += 16}
timeInMinutes = max(0, (Double)(sensorTimeInMinutes - index))
let timeStampOfNewGlucoseData = sensorStartTimeInMilliseconds + timeInMinutes * 60 * 1000
//new reading should be at least 30 seconds younger than timeStampLastBgReadingStoredInDatabase
if timeStampOfNewGlucoseData > (timeStampLastBgReadingStoredInDatabase.toMillisecondsAsDouble() + 30000.0)
{
if timeStampOfNewGlucoseData < timeStampLastAddedGlucoseData - (5 * 60 * 1000 - 10000) {
byte = Data()
byte.append(data[headerOffset + (i * 6 + 29)])
byte.append(data[headerOffset + (i * 6 + 28)])
let glucoseLevelRaw = Double(getGlucoseRaw(bytes: byte))
if (glucoseLevelRaw > 0) {
glucoseData = RawGlucoseData(timeStamp: Date(timeIntervalSince1970: sensorStartTimeInMilliseconds/1000 + timeInMinutes * 60), glucoseLevelRaw: Double(getGlucoseRaw(bytes: byte)) * ConstantsBloodGlucose.libreMultiplier)
returnValue.append(glucoseData)
timeStampLastAddedGlucoseData = timeStampOfNewGlucoseData
}
}
} else {
break trendloop
}
}
// loads history values
historyloop: for index in 0..<32 {
i = indexHistory - index - 1
if i < 0 {i += 32}
timeInMinutes = max(0,(Double)(abs(sensorTimeInMinutes - 3)/15)*15 - (Double)(index*15))
let timeStampOfNewGlucoseData = sensorStartTimeInMilliseconds + timeInMinutes * 60 * 1000
//new reading should be at least 30 seconds younger than timeStampLastBgReadingStoredInDatabase
if timeStampOfNewGlucoseData > (timeStampLastBgReadingStoredInDatabase.toMillisecondsAsDouble() + 30000.0)
{
if timeStampOfNewGlucoseData < timeStampLastAddedGlucoseData - (5 * 60 * 1000 - 10000) {
byte = Data()
byte.append(data[headerOffset + (i * 6 + 125)])
byte.append(data[headerOffset + (i * 6 + 124)])
let glucoseLevelRaw = Double(getGlucoseRaw(bytes: byte))
if (glucoseLevelRaw > 0) {
glucoseData = RawGlucoseData(timeStamp: Date(timeIntervalSince1970: sensorStartTimeInMilliseconds/1000 + timeInMinutes * 60), glucoseLevelRaw: Double(getGlucoseRaw(bytes: byte)) * ConstantsBloodGlucose.libreMultiplier)
returnValue.append(glucoseData)
timeStampLastAddedGlucoseData = timeStampOfNewGlucoseData
}
}
} else {
break historyloop
}
}
return (returnValue, sensorState, sensorTimeInMinutes)
}
fileprivate func getByteAt(buffer:Data, position:Int) -> Int {
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)
}
/// calls web service to get calibrated reading
func handleLibreReading(bytes: [UInt8], serialNumber: String, _ callback: @escaping ((glucoseData: [RawGlucoseData], sensorState: LibreSensorState, sensorTimeInMinutes: Int)?) -> Void) {
//only care about the once per minute readings here, historical data will not be considered
let sensorState = LibreSensorState(stateByte: bytes[4])
LibreOOPClient.calibrateSensor(bytes: bytes, serialNumber: serialNumber) {
(calibrationparams) in
guard let params = calibrationparams else {
NSLog("dabear:: could not calibrate sensor, check libreoopweb permissions and internet connection")
callback(nil)
return
}
//here we assume success, data is not changed,
//and we trust that the remote endpoint returns correct data for the sensor
let last16 = trendMeasurements(bytes: bytes, date: Date(), LibreDerivedAlgorithmParameterSet: params)
if let glucoseData = trendToLibreGlucose(last16) {
callback((glucoseData, sensorState, 0))
}
}
}
fileprivate func trendMeasurements(bytes: [UInt8], date: 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
}
let range = index..<index+6
let measurementBytes = Array(body[range])
let measurementDate = date.addingTimeInterval(Double(-60 * blockIndex))
let measurement = LibreMeasurement(bytes: measurementBytes, slope: slope, offset: offset, date: measurementDate, LibreDerivedAlgorithmParameterSet: LibreDerivedAlgorithmParameterSet)
measurements.append(measurement)
}
return measurements
}
fileprivate func trendToLibreGlucose(_ measurements: [LibreMeasurement]) -> [LibreRawGlucoseData]?{
var origarr = [LibreRawGlucoseData]()
//whether or not to return all the 16 latest trends or just every fifth element
let returnAllTrends = true
for trend in measurements {
let glucose = LibreRawGlucoseData.init(timeStamp: trend.date, unsmoothedGlucose: trend.temperatureAlgorithmGlucose)
origarr.append(glucose)
}
//NSLog("dabear:: glucose samples before smoothing: \(String(describing: origarr))")
var arr : [LibreRawGlucoseData]
arr = LibreGlucoseSmoothing.CalculateSmothedData5Points(origtrends: origarr)
for i in 0 ..< arr.count {
var trend = arr[i]
//we know that the array "always" (almost) will contain 16 entries
//the last five entries will get a trend arrow of flat, because it's not computable when we don't have
//more entries in the array to base it on
}
if returnAllTrends {
return arr
}
return arr
}

View File

@ -8,13 +8,19 @@ import AudioToolbox
/// viewcontroller for the home screen
final class RootViewController: UIViewController {
// MARK: - Properties - Outlets and Actions for buttons and labels in home screen
@IBOutlet weak var calibrateButtonOutlet: UIButton!
@IBAction func calibrateButtonAction(_ sender: UIButton) {
requestCalibration(userRequested: true)
if let transmitterType = UserDefaults.standard.transmitterType, transmitterType.canWebOOP(), UserDefaults.standard.webOOPEnabled {
UIAlertController(title: Texts_Common.warning, message: Texts_HomeView.calibrationNotNecessary, actionHandler: nil).presentInOwnWindow(animated: true, completion: nil)
} else {
requestCalibration(userRequested: true)
}
}
@IBOutlet weak var transmitterButtonOutlet: UIButton!
@ -31,7 +37,7 @@ final class RootViewController: UIViewController {
/// outlet for label that shows how many minutes ago and so on
@IBOutlet weak var minutesLabelOutlet: UILabel!
/// outlet for label that shows difference with previous reading
@IBOutlet weak var diffLabelOutlet: UILabel!
@ -39,7 +45,7 @@ final class RootViewController: UIViewController {
@IBOutlet weak var valueLabelOutlet: UILabel!
// MARK: - Constants for ApplicationManager usage
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - create updatelabelstimer
private let applicationManagerKeyCreateUpdateLabelsTimer = "RootViewController-CreateUpdateLabelsTimer"
@ -51,7 +57,7 @@ final class RootViewController: UIViewController {
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - initiate pairing
private let applicationManagerKeyInitiatePairing = "RootViewController-InitiatePairing"
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - initial calibration
private let applicationManagerKeyInitialCalibration = "RootViewController-InitialCalibration"
@ -68,7 +74,7 @@ final class RootViewController: UIViewController {
/// to solve problem that sometemes UserDefaults key value changes is triggered twice for just one change
private let keyValueObserverTimeKeeper:KeyValueObserverTimeKeeper = KeyValueObserverTimeKeeper()
/// calibrator to be used for calibration, value will depend on transmitter type
private var calibrator:Calibrator?
@ -98,7 +104,7 @@ final class RootViewController: UIViewController {
/// healthkit manager instance
private var healthKitManager:HealthKitManager?
/// reference to activeSensor
private var activeSensor:Sensor?
@ -112,7 +118,7 @@ final class RootViewController: UIViewController {
private var timeStampLastNotificationForPairing:Date?
// MARK: - View Life Cycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
@ -135,7 +141,7 @@ final class RootViewController: UIViewController {
// create transmitter based on UserDefaults
self.initializeCGMTransmitter()
})
// Setup View
@ -149,7 +155,9 @@ final class RootViewController: UIViewController {
, context: nil)
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.transmitterResetRequired.rawValue, options: .new
, context: nil)
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.webOOPEnabled.rawValue, options: .new
, context: nil)
// setup delegate for UNUserNotificationCenter
UNUserNotificationCenter.current().delegate = self
@ -212,7 +220,7 @@ final class RootViewController: UIViewController {
guard let coreDataManager = coreDataManager else {
fatalError("In setupApplicationData but coreDataManager == nil")
}
// get currently active sensor
activeSensor = SensorsAccessor.init(coreDataManager: coreDataManager).fetchActiveSensor()
@ -238,10 +246,10 @@ final class RootViewController: UIViewController {
// setup nightscoutmanager
nightScoutFollowManager = NightScoutFollowManager(coreDataManager: coreDataManager, nightScoutFollowerDelegate: self)
// setup alertmanager
alertManager = AlertManager(coreDataManager: coreDataManager, soundPlayer: soundPlayer)
// setup healthkitmanager
healthKitManager = HealthKitManager(coreDataManager: coreDataManager)
@ -258,15 +266,19 @@ final class RootViewController: UIViewController {
/// - parameters:
/// - glucoseData : array with new readings
/// - sensorTimeInMinutes : should be present only if it's the first reading(s) being processed for a specific sensor and is needed if it's a transmitterType that returns true to the function canDetectNewSensor
private func processNewGlucoseData(glucoseData: inout [RawGlucoseData], sensorTimeInMinutes: Int?) {
private func processNewGlucoseData(glucoseData: inout [GlucoseData], sensorTimeInMinutes: Int?) {
for glucose in glucoseData {
debuglogging(glucose.description)
}
// check that calibrations and coredata manager is not nil
guard let calibrationsAccessor = calibrationsAccessor, let coreDataManager = coreDataManager else {
fatalError("in processNewCGMInfo, calibrations or coreDataManager is nil")
}
if activeSensor == nil {
if let sensorTimeInMinutes = sensorTimeInMinutes {
activeSensor = Sensor(startDate: Date(timeInterval: -Double(sensorTimeInMinutes * 60), since: Date()),nsManagedObjectContext: coreDataManager.mainManagedObjectContext)
if let activeSensor = activeSensor {
@ -278,11 +290,12 @@ final class RootViewController: UIViewController {
// save the newly created Sensor permenantly in coredata
coreDataManager.saveChanges()
}
}
// also for cases where calibration is not needed, we go through this code
if let activeSensor = activeSensor, let calibrator = calibrator, let bgReadingsAccessor = bgReadingsAccessor {
// initialize help variables
var latest3BgReadings = bgReadingsAccessor.getLatestBgReadings(limit: 3, howOld: nil, forSensor: activeSensor, ignoreRawData: false, ignoreCalculatedValue: false)
var lastCalibrationsForActiveSensorInLastXDays = calibrationsAccessor.getLatestCalibrations(howManyDays: 4, forSensor: activeSensor)
@ -291,7 +304,7 @@ final class RootViewController: UIViewController {
// was a new reading created or not
var newReadingCreated = false
// assign value of timeStampLastBgReading
var timeStampLastBgReading = Date(timeIntervalSince1970: 0)
if let lastReading = bgReadingsAccessor.last(forSensor: activeSensor) {
@ -303,7 +316,7 @@ final class RootViewController: UIViewController {
if glucose.timeStamp > timeStampLastBgReading {
_ = calibrator.createNewBgReading(rawData: (Double)(glucose.glucoseLevelRaw), filteredData: (Double)(glucose.glucoseLevelRaw), timeStamp: glucose.timeStamp, sensor: activeSensor, last3Readings: &latest3BgReadings, lastCalibrationsForActiveSensorInLastXDays: &lastCalibrationsForActiveSensorInLastXDays, firstCalibration: firstCalibrationForActiveSensor, lastCalibration: lastCalibrationForActiveSensor, deviceName:UserDefaults.standard.bluetoothDeviceName, nsManagedObjectContext: coreDataManager.mainManagedObjectContext)
// save the newly created bgreading permenantly in coredata
coreDataManager.saveChanges()
@ -318,11 +331,11 @@ final class RootViewController: UIViewController {
// if a new reading is created, created either initial calibration request or bgreading notification - upload to nightscout and check alerts
if newReadingCreated {
// if no two calibration exist yet then create calibration request notification, otherwise a bgreading notification and update labels
if firstCalibrationForActiveSensor == nil && lastCalibrationForActiveSensor == nil {
// only for webOOPEnabled : if no two calibration exist yet then create calibration request notification, otherwise a bgreading notification and update labels
if firstCalibrationForActiveSensor == nil && lastCalibrationForActiveSensor == nil && !UserDefaults.standard.webOOPEnabled {
// there must be at least 2 readings
let latestReadings = bgReadingsAccessor.getLatestBgReadings(limit: 36, howOld: nil, forSensor: activeSensor, ignoreRawData: false, ignoreCalculatedValue: true)
if latestReadings.count > 1 {
createInitialCalibrationRequest()
}
@ -357,11 +370,11 @@ final class RootViewController: UIViewController {
}
}
// MARK:- observe function
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let keyPath = keyPath {
if let keyPathEnum = UserDefaults.Key(rawValue: keyPath) {
@ -386,13 +399,30 @@ final class RootViewController: UIViewController {
// set up na transmitter
initializeCGMTransmitter()
}
case UserDefaults.Key.transmitterResetRequired :
if (keyValueObserverTimeKeeper.verifyKey(forKey: keyPathEnum.rawValue, withMinimumDelayMilliSeconds: 200)) {
cgmTransmitter?.reset(requested: UserDefaults.standard.transmitterResetRequired)
}
case UserDefaults.Key.webOOPEnabled:
if (keyValueObserverTimeKeeper.verifyKey(forKey: keyPathEnum.rawValue, withMinimumDelayMilliSeconds: 200)) {
// set webOOPEnabled for transmitter to new value - there's no need to reinit the transmitter, values like device address, timstamp of last reading, connection status, ... can stay as is
cgmTransmitter?.setWebOOPEnabled(enabled: UserDefaults.standard.webOOPEnabled)
// call stopSensor which sets activeSensor to nil. Swapping from enabled to not enabled, requires that user will need to calibrate the sensor, it needs to be a new entry in the database. (it's probably nil anyway) - a new sensor will be created as soon as a reading arrives
stopSensor()
// reinitialize calibrator
// calling initializeCGMTransmitter is not a good idea here because that would mean set cgmTransmitter to nil, for some type of transmitters that would mean the user needs to scan again
if let selectedTransmitterType = UserDefaults.standard.transmitterType {
calibrator = RootViewController.getCalibrator(transmitterType: selectedTransmitterType, webOOPEnabled: UserDefaults.standard.webOOPEnabled)
}
}
default:
break
}
@ -401,7 +431,7 @@ final class RootViewController: UIViewController {
}
// MARK: - View Methods
/// Configure View, only stuff that is independent of coredata
private func setupView() {
@ -439,7 +469,7 @@ final class RootViewController: UIViewController {
///
/// should be called only once immediately after app start, ie in viewdidload
private func setupUpdateLabelsTimer() {
// this is the actual timer
var updateLabelsTimer:Timer?
@ -535,12 +565,12 @@ final class RootViewController: UIViewController {
if let dexcomShareUploadManager = self.dexcomShareUploadManager {
dexcomShareUploadManager.upload()
}
// check alerts
if let alertManager = self.alertManager {
alertManager.checkAlerts(maxAgeOfLastBgReadingInSeconds: ConstantsMaster.maximumBgReadingAgeForAlertsInSeconds)
}
// update labels
self.updateLabels()
}
@ -552,7 +582,6 @@ final class RootViewController: UIViewController {
self.present(alert, animated: true)
}
/// will set first cgmTransmitter to nil, reads transmittertype from userdefaults, if applicable also transmitterid and if available creates the property cgmTransmitter - if follower mode then cgmTransmitter is set to nil
///
/// depending on transmitter type, scanning will automatically start as soon as cgmTransmitter is created
@ -563,59 +592,73 @@ final class RootViewController: UIViewController {
cgmTransmitter = nil
// if transmitter type is set and device is master
if let currentTransmitterTypeAsEnum = UserDefaults.standard.transmitterType, UserDefaults.standard.isMaster {
if let selectedTransmitterType = UserDefaults.standard.transmitterType, UserDefaults.standard.isMaster {
// first create transmitter
switch currentTransmitterTypeAsEnum {
switch selectedTransmitterType {
case .dexcomG4:
if let currentTransmitterId = UserDefaults.standard.transmitterId {
cgmTransmitter = CGMG4xDripTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, transmitterID: currentTransmitterId, delegate:self)
calibrator = DexcomCalibrator()
}
case .dexcomG5:
if let currentTransmitterId = UserDefaults.standard.transmitterId {
calibrator = DexcomCalibrator()
cgmTransmitter = CGMG5Transmitter(address: UserDefaults.standard.bluetoothDeviceAddress, transmitterID: currentTransmitterId, delegate: self)
}
case .dexcomG6:
if let currentTransmitterId = UserDefaults.standard.transmitterId {
calibrator = DexcomCalibrator()
cgmTransmitter = CGMG6Transmitter(address: UserDefaults.standard.bluetoothDeviceAddress, transmitterID: currentTransmitterId, delegate: self)
}
case .miaomiao:
cgmTransmitter = CGMMiaoMiaoTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0))
calibrator = Libre1Calibrator()
cgmTransmitter = CGMMiaoMiaoTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0), webOOPEnabled: UserDefaults.standard.webOOPEnabled)
case .Bubble:
cgmTransmitter = CGMBubbleTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0), sensorSerialNumber: UserDefaults.standard.sensorSerialNumber)
calibrator = Libre1Calibrator()
cgmTransmitter = CGMBubbleTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0), sensorSerialNumber: UserDefaults.standard.sensorSerialNumber, webOOPEnabled: UserDefaults.standard.webOOPEnabled)
case .GNSentry:
cgmTransmitter = CGMGNSEntryTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0))
calibrator = Libre1Calibrator()
case .Blucon:
if let currentTransmitterId = UserDefaults.standard.transmitterId {
cgmTransmitter = CGMBluconTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, transmitterID: currentTransmitterId, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0), sensorSerialNumber: UserDefaults.standard.sensorSerialNumber)
calibrator = Libre1Calibrator()
}
case .Droplet1:
cgmTransmitter = CGMDroplet1Transmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self)
calibrator = Libre1Calibrator()
}
// assign calibrator
switch selectedTransmitterType {
case .dexcomG4, .dexcomG5, .dexcomG6:
calibrator = DexcomCalibrator()
case .miaomiao, .GNSentry, .Blucon, .Bubble, .Droplet1:
// for all transmitters used with Libre1, calibrator is either NoCalibrator or Libre1Calibrator, depending if oopWeb is supported by the transmitter and on value of webOOPEnabled in settings
calibrator = RootViewController.getCalibrator(transmitterType: selectedTransmitterType, webOOPEnabled: UserDefaults.standard.webOOPEnabled)
}
}
//reset UserDefaults.standard.transmitterResetRequired to false, might have been set to true.
UserDefaults.standard.transmitterResetRequired = false
}
/// if transmitterType.canWebOOP and UserDefaults.standard.webOOPEnabled then returns an instance of NoCalibrator otherwise returns an instance of Libre1Calibrator
///
/// this is just some functionality which is used frequently
private static func getCalibrator(transmitterType: CGMTransmitterType, webOOPEnabled: Bool) -> Calibrator {
if transmitterType.canWebOOP() && UserDefaults.standard.webOOPEnabled {
return NoCalibrator()
} else {
return Libre1Calibrator()
}
}
/// for debug purposes
private func logAllBgReadings() {
if let bgReadingsAccessor = bgReadingsAccessor {
@ -670,7 +713,7 @@ final class RootViewController: UIViewController {
self.requestCalibration(userRequested: false)
})
}
/// creates bgreading notification
@ -680,7 +723,7 @@ final class RootViewController: UIViewController {
guard let bgReadingsAccessor = bgReadingsAccessor else {
return
}
// get lastReading, with a calculatedValue - no check on activeSensor because in follower mode there is no active sensor
let lastReading = bgReadingsAccessor.getLatestBgReadings(limit: 2, howOld: nil, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false)
@ -715,7 +758,7 @@ final class RootViewController: UIViewController {
// must set a body otherwise notification doesn't show up on iOS10
notificationContent.body = " "
// Create Notification Request
let notificationRequest = UNNotificationRequest(identifier: ConstantsNotifications.NotificationIdentifierForBgReading.bgReadingNotificationRequest, content: notificationContent, trigger: nil)
@ -729,11 +772,11 @@ final class RootViewController: UIViewController {
/// updates the homescreen
@objc private func updateLabels() {
// check that bgReadingsAccessor exists, otherwise return - this happens if updateLabels is called from viewDidload at app launch
guard let bgReadingsAccessor = bgReadingsAccessor else {return}
// last reading and lateButOneReading variable definition - optional
var lastReading:BgReading?
var lastButOneReading:BgReading?
@ -749,7 +792,7 @@ final class RootViewController: UIViewController {
// get latest reading, doesn't matter if it's for an active sensor or not, but it needs to have calculatedValue > 0 / which means, if user would have started a new sensor, but didn't calibrate yet, and a reading is received, then there's no going to be a latestReading
if let lastReading = lastReading {
// start creating text for valueLabelOutlet, first the calculated value
var calculatedValueAsString = lastReading.unitizedString(unitIsMgDl: UserDefaults.standard.bloodGlucoseUnitIsMgDl)
@ -780,7 +823,7 @@ final class RootViewController: UIViewController {
// get minutes ago and create text for minutes ago label
let minutesAgo = -Int(lastReading.timeStamp.timeIntervalSinceNow) / 60
let minutesAgoText = minutesAgo.description + " " + (minutesAgo == 1 ? Texts_Common.minute:Texts_Common.minutes) + " " + Texts_HomeView.ago
minutesLabelOutlet.text = minutesAgoText
// create delta text
@ -883,7 +926,7 @@ final class RootViewController: UIViewController {
// display textoToshow
UIAlertController(title: Texts_HomeView.statusActionTitle, message: textToShow, actionHandler: nil).presentInOwnWindow(animated: true, completion: nil)
}
/// user clicked start scanning action, this function will check if bluetooth is on (?) and if not yet scanning, start the scanning
@ -927,20 +970,20 @@ final class RootViewController: UIViewController {
// set device address and name to nil in userdefaults
UserDefaults.standard.bluetoothDeviceAddress = nil
UserDefaults.standard.bluetoothDeviceName = nil
// setting cgmTransmitter to nil, the deinit function of the currently used cgmTransmitter will be called, which will disconnect the device
// set cgmTransmitter to nil, this will call the deinit function which will disconnect first
cgmTransmitter = nil
// by calling initializeCGMTransmitter, a new cgmTransmitter will be created, assuming it's not follower mode, and transmittertype is selected and if applicable transmitter id is set
initializeCGMTransmitter()
// reset also UserDefaults.standard.transmitterBatteryInfo
UserDefaults.standard.transmitterBatteryInfo = nil
// set lastdisconnecttimestamp to nil
UserDefaults.standard.lastdisConnectTimestamp = nil
}
// stops the active sensor and sets sensorSerialNumber in UserDefaults to nil
@ -952,9 +995,9 @@ final class RootViewController: UIViewController {
}
// save the changes
coreDataManager?.saveChanges()
activeSensor = nil
// reset also serialNubmer to nil
UserDefaults.standard.sensorSerialNumber = nil
@ -992,9 +1035,9 @@ final class RootViewController: UIViewController {
} else {
DatePickerViewController.displayDatePickerViewController(datePickerViewData: datePickerViewData, parentController: self)
}
}
}
// MARK: - conform to CGMTransmitter protocol
@ -1003,7 +1046,7 @@ final class RootViewController: UIViewController {
extension RootViewController:CGMTransmitterDelegate {
func reset(successful: Bool) {
// reset setting to false
UserDefaults.standard.transmitterResetRequired = false
@ -1039,7 +1082,7 @@ extension RootViewController:CGMTransmitterDelegate {
// remove existing notification if any
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [ConstantsNotifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing])
// invalidate transmitterPairingResponseTimer
if let transmitterPairingResponseTimer = transmitterPairingResponseTimer {
transmitterPairingResponseTimer.invalidate()
@ -1092,7 +1135,7 @@ extension RootViewController:CGMTransmitterDelegate {
// possibly scanning is already running, but that's ok if we call the startScanning function again
_ = cgmTransmitter.startScanning()
}
@unknown default:
break
}
@ -1101,7 +1144,7 @@ extension RootViewController:CGMTransmitterDelegate {
/// Transmitter is calling this delegate function to indicate that bluetooth pairing is needed. If the app is in the background, the user will be informed, after opening the app a pairing request will be initiated. if the app is in the foreground, the pairing request will be initiated immediately
func cgmTransmitterNeedsPairing() {
trace("transmitter needs pairing", log: log, type: .info)
if let timeStampLastNotificationForPairing = timeStampLastNotificationForPairing {
@ -1117,7 +1160,7 @@ extension RootViewController:CGMTransmitterDelegate {
// remove existing notification if any
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [ConstantsNotifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing])
// Create Notification Content
let notificationContent = UNMutableNotificationContent()
@ -1192,7 +1235,7 @@ extension RootViewController:CGMTransmitterDelegate {
// Create Notification Request
let notificationRequest = UNNotificationRequest(identifier: ConstantsNotifications.NotificationIdentifierForSensorNotDetected.sensorNotDetected, content: notificationContent, trigger: nil)
// Add Request to User Notification Center
UNUserNotificationCenter.current().add(notificationRequest) { (error) in
if let error = error {
@ -1203,7 +1246,7 @@ extension RootViewController:CGMTransmitterDelegate {
/// - parameters:
/// - readings: first entry is the most recent
func cgmTransmitterInfoReceived(glucoseData: inout [RawGlucoseData], transmitterBatteryInfo: TransmitterBatteryInfo?, sensorState: LibreSensorState?, sensorTimeInMinutes: Int?, firmware: String?, hardware: String?, hardwareSerialNumber: String?, bootloader: String?, sensorSerialNumber:String?) {
func cgmTransmitterInfoReceived(glucoseData: inout [GlucoseData], transmitterBatteryInfo: TransmitterBatteryInfo?, sensorState: LibreSensorState?, sensorTimeInMinutes: Int?, firmware: String?, hardware: String?, hardwareSerialNumber: String?, bootloader: String?, sensorSerialNumber:String?) {
trace("sensorstate %{public}@", log: log, type: .debug, sensorState?.description ?? "no sensor state found")
trace("firmware %{public}@", log: log, type: .debug, firmware ?? "no firmware version found")
@ -1214,7 +1257,7 @@ extension RootViewController:CGMTransmitterDelegate {
trace("transmitterBatteryInfo %{public}@", log: log, type: .debug, transmitterBatteryInfo?.description ?? 0)
trace("sensor time in minutes %{public}@", log: log, type: .debug, sensorTimeInMinutes?.description ?? "not received")
trace("glucoseData size = %{public}@", log: log, type: .debug, glucoseData.count.description)
// if received sensorSerialNumber not nil, and if value different from currently stored value, then store it
if let sensorSerialNumber = sensorSerialNumber {
if sensorSerialNumber != UserDefaults.standard.sensorSerialNumber {
@ -1230,7 +1273,7 @@ extension RootViewController:CGMTransmitterDelegate {
// process new readings
processNewGlucoseData(glucoseData: &glucoseData, sensorTimeInMinutes: sensorTimeInMinutes)
}
}
// MARK: - conform to UITabBarControllerDelegate protocol
@ -1261,7 +1304,7 @@ extension RootViewController:UNUserNotificationCenterDelegate {
/// remove applicationManagerKeyInitialCalibration from application key manager - there's no need to initiate the calibration via this closure
ApplicationManager.shared.removeClosureToRunWhenAppWillEnterForeground(key: self.applicationManagerKeyInitialCalibration)
// call completionhandler to avoid that notification is shown to the user
completionHandler([])
@ -1298,7 +1341,7 @@ extension RootViewController:UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
trace("userNotificationCenter didReceive", log: log, type: .info)
// call completionHandler when exiting function
defer {
// call completionhandler
@ -1311,20 +1354,20 @@ extension RootViewController:UNUserNotificationCenterDelegate {
trace(" userNotificationCenter didReceive, user pressed calibration notification to open the app, requestCalibration should be called because closure is added in ApplicationManager.shared", log: log, type: .info)
} else if response.notification.request.identifier == ConstantsNotifications.NotificationIdentifierForSensorNotDetected.sensorNotDetected {
// if user clicks notification "sensor not detected", then show uialert with title and body
UIAlertController(title: Texts_Common.warning, message: Texts_HomeView.sensorNotDetected, actionHandler: nil).presentInOwnWindow(animated: true, completion: nil)
} else if response.notification.request.identifier == ConstantsNotifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing {
// nothing required, the pairing function will be called as it's been added to ApplicationManager in function cgmTransmitterNeedsPairing
} else if response.notification.request.identifier == ConstantsNotifications.NotificationIdentifierForResetResult.transmitterResetResult {
// nothing required
} else {
// it's not an initial calibration request notification that the user clicked, by calling alertManager?.userNotificationCenter, we check if it was an alert notification that was clicked and if yes pickerViewData will have the list of alert snooze values
if let pickerViewData = alertManager?.userNotificationCenter(center, didReceive: response) {
@ -1345,13 +1388,13 @@ extension RootViewController:NightScoutFollowerDelegate {
func nightScoutFollowerInfoReceived(followGlucoseDataArray: inout [NightScoutBgReading]) {
if let coreDataManager = coreDataManager, let bgReadingsAccessor = bgReadingsAccessor, let nightScoutFollowManager = nightScoutFollowManager {
// assign value of timeStampLastBgReading
var timeStampLastBgReading = Date(timeIntervalSince1970: 0)
if let lastReading = bgReadingsAccessor.last(forSensor: activeSensor) {
timeStampLastBgReading = lastReading.timeStamp
}
// was a new reading created or not
var newReadingCreated = false
@ -1359,7 +1402,7 @@ extension RootViewController:NightScoutFollowerDelegate {
for (_, followGlucoseData) in followGlucoseDataArray.enumerated().reversed() {
if followGlucoseData.timeStamp > timeStampLastBgReading {
// creata a new reading
_ = nightScoutFollowManager.createBgReading(followGlucoseData: followGlucoseData)
@ -1376,18 +1419,18 @@ extension RootViewController:NightScoutFollowerDelegate {
// save in core data
coreDataManager.saveChanges()
// update notification
createBgReadingNotification()
// update all text in first screen
updateLabels()
// check alerts
if let alertManager = alertManager {
alertManager.checkAlerts(maxAgeOfLastBgReadingInSeconds: ConstantsFollower.maximumBgReadingAgeForAlertsInSeconds)
}
if let healthKitManager = healthKitManager {
healthKitManager.storeBgReadings()
}
@ -1395,9 +1438,10 @@ extension RootViewController:NightScoutFollowerDelegate {
if let bgReadingSpeaker = bgReadingSpeaker {
bgReadingSpeaker.speakNewReading()
}
}
}
}
}