Reworked for support oop web, however sitll not used because wrong values are being shown
This commit is contained in:
parent
e2cd0d9676
commit
3f98a5c3dc
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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]);
|
||||
|
|
|
@ -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"
|
||||
|
||||
}
|
|
@ -50,5 +50,7 @@ enum ConstantsLog {
|
|||
static let categoryDexcomShareUploadManager = "categoryDexcomShareUploadManager"
|
||||
/// droplet 1
|
||||
static let categoryCGMDroplet1 = "categoryCGMDroplet1"
|
||||
/// LibreOOPClient
|
||||
static let categoryLibreOOPClient = "LibreOOPClient"
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
]
|
||||
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
]
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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?'";
|
|
@ -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?'";
|
|
@ -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?'";
|
|
@ -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?'";
|
|
@ -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?'";
|
|
@ -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?'";
|
|
@ -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?'";
|
|
@ -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?'";
|
|
@ -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?'";
|
|
@ -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?'";
|
|
@ -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你可能选错了服务器地址,请确认你的账号是否使用了美国服务器?'";
|
|
@ -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")
|
||||
}()
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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?) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = " )
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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?
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue