diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index a1a1cdb3..060329f3 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -7,6 +7,16 @@ objects = { /* Begin PBXBuildFile section */ + 2CE6FBB722F6BB680063712B /* CRC.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AFC22D9179100934E7A /* CRC.swift */; }; + 2CE6FBB822F6BBD60063712B /* SensorSerialNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C5EBE422F297EF00563B5F /* SensorSerialNumber.swift */; }; + 2CE6FBB922F6BBD80063712B /* SensorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AFE22D9179100934E7A /* SensorState.swift */; }; + 2CE6FBBB22F6BC090063712B /* LibreOOPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE6FBBA22F6BC090063712B /* LibreOOPClient.swift */; }; + 2CE6FBBD22F6BC540063712B /* LibreOOPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE6FBBC22F6BC540063712B /* LibreOOPResponse.swift */; }; + 2CE6FBBF22F6BC9B0063712B /* LibreOOPWebExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE6FBBE22F6BC9B0063712B /* LibreOOPWebExtensions.swift */; }; + 2CE6FBC122F6BCAB0063712B /* DerivedAlgorithmRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE6FBC022F6BCAB0063712B /* DerivedAlgorithmRunner.swift */; }; + 2CE6FBC322F6BCC20063712B /* LibreOOPDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE6FBC222F6BCC20063712B /* LibreOOPDefaults.swift */; }; + 2CE6FBC522F6C2AE0063712B /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE6FBC422F6C2AE0063712B /* Measurement.swift */; }; + 2CE6FBC722F6C4DF0063712B /* GlucoseSmoothing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE6FBC622F6C4DF0063712B /* GlucoseSmoothing.swift */; }; A48D2DE552F4A356AA32746A /* Pods_xdrip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 662BEA7F7991B9BD2E7D3EA4 /* Pods_xdrip.framework */; }; EE9947E822EEDD2E00DCB876 /* CGMBubbleTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9947E722EEDD2E00DCB876 /* CGMBubbleTransmitter.swift */; }; F8025C0A21D94FD700ECF0C0 /* CBManagerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8025C0921D94FD700ECF0C0 /* CBManagerState.swift */; }; @@ -104,9 +114,7 @@ F8A54AEB22D911BA00934E7A /* CGMG4xDripTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AD622D911BA00934E7A /* CGMG4xDripTransmitter.swift */; }; 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 */; }; - F8A54B0122D9179100934E7A /* SensorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AFE22D9179100934E7A /* SensorState.swift */; }; F8A7406E22D9C0E700967CFC /* CGMBluconTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A7406D22D9C0E700967CFC /* CGMBluconTransmitter.swift */; }; F8A7407022DBB24800967CFC /* BluconTransmitterOpCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A7406F22DBB24800967CFC /* BluconTransmitterOpCode.swift */; }; F8A7407222DCDA3E00967CFC /* BluconUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A7407122DCDA3E00967CFC /* BluconUtilities.swift */; }; @@ -189,6 +197,13 @@ /* Begin PBXFileReference section */ 148E05A6AF0290AE5815B0F9 /* Pods-xdrip.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xdrip.debug.xcconfig"; path = "Target Support Files/Pods-xdrip/Pods-xdrip.debug.xcconfig"; sourceTree = ""; }; + 2CE6FBBA22F6BC090063712B /* LibreOOPClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPClient.swift; sourceTree = ""; }; + 2CE6FBBC22F6BC540063712B /* LibreOOPResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPResponse.swift; sourceTree = ""; }; + 2CE6FBBE22F6BC9B0063712B /* LibreOOPWebExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPWebExtensions.swift; sourceTree = ""; }; + 2CE6FBC022F6BCAB0063712B /* DerivedAlgorithmRunner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DerivedAlgorithmRunner.swift; sourceTree = ""; }; + 2CE6FBC222F6BCC20063712B /* LibreOOPDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPDefaults.swift; sourceTree = ""; }; + 2CE6FBC422F6C2AE0063712B /* Measurement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Measurement.swift; sourceTree = ""; }; + 2CE6FBC622F6C4DF0063712B /* GlucoseSmoothing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseSmoothing.swift; sourceTree = ""; }; 662BEA7F7991B9BD2E7D3EA4 /* Pods_xdrip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xdrip.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E2648F65F347D56D7DFFFAB7 /* Pods-xdrip.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xdrip.release.xcconfig"; path = "Target Support Files/Pods-xdrip/Pods-xdrip.release.xcconfig"; sourceTree = ""; }; EE9947E722EEDD2E00DCB876 /* CGMBubbleTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMBubbleTransmitter.swift; sourceTree = ""; }; @@ -479,6 +494,7 @@ F8BDD451221DEAB1006EAB84 /* TextsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextsSettingsView.swift; sourceTree = ""; }; F8BDD456221DEF22006EAB84 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/SettingsViews.strings; sourceTree = ""; }; F8BDD458221DEF24006EAB84 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/SettingsViews.strings; sourceTree = ""; }; + F8C5EBE422F297EF00563B5F /* SensorSerialNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorSerialNumber.swift; sourceTree = ""; }; F8E3C3AA21FE17B700907A04 /* StringProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringProtocol.swift; sourceTree = ""; }; F8E3C3AC21FE551C00907A04 /* DexcomCalibrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrator.swift; sourceTree = ""; }; F8EA6C8121B723BC0082976B /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; @@ -861,7 +877,15 @@ F8A54AFB22D9179100934E7A /* Utilities */ = { isa = PBXGroup; children = ( + F8C5EBE422F297EF00563B5F /* SensorSerialNumber.swift */, F8A54AFC22D9179100934E7A /* CRC.swift */, + 2CE6FBC222F6BCC20063712B /* LibreOOPDefaults.swift */, + 2CE6FBBA22F6BC090063712B /* LibreOOPClient.swift */, + 2CE6FBBC22F6BC540063712B /* LibreOOPResponse.swift */, + 2CE6FBBE22F6BC9B0063712B /* LibreOOPWebExtensions.swift */, + 2CE6FBC422F6C2AE0063712B /* Measurement.swift */, + 2CE6FBC622F6C4DF0063712B /* GlucoseSmoothing.swift */, + 2CE6FBC022F6BCAB0063712B /* DerivedAlgorithmRunner.swift */, F8A54AFD22D9179100934E7A /* ParseLibreData.swift */, F8A54AFE22D9179100934E7A /* SensorState.swift */, ); @@ -1160,6 +1184,7 @@ F8AC425721ADEBD60078C348 /* Frameworks */, F8AC425821ADEBD60078C348 /* Resources */, F5EEC1C2586E9EA94DC6DBC7 /* [CP] Embed Pods Frameworks */, + F84F2BEC22EF80B300313474 /* ShellScript */, ); buildRules = ( ); @@ -1304,6 +1329,23 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-xdrip/Pods-xdrip-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + F84F2BEC22EF80B300313474 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1311,11 +1353,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2CE6FBC322F6BCC20063712B /* LibreOOPDefaults.swift in Sources */, F8BDD450221CAA64006EAB84 /* TextsCommon.swift in Sources */, F8A54ADF22D911BA00934E7A /* DexcomTransmitterOpCode.swift in Sources */, F81D6D4E22BFC762005EFAE2 /* TextsDexcomShareTestResult.swift in Sources */, F8A54AE922D911BA00934E7A /* AuthChallengeRxMessage.swift in Sources */, - F8A54B0122D9179100934E7A /* SensorState.swift in Sources */, F8EA6C8221B723BC0082976B /* Date.swift in Sources */, F8A54AE622D911BA00934E7A /* KeepAliveTxMessage.swift in Sources */, F81FA006228E09D40028C70F /* TextsCalibration.swift in Sources */, @@ -1334,8 +1376,10 @@ F8B3A7FA2278E0E8004BA588 /* SettingsViewModelProtocol.swift in Sources */, F85DC2F521CFE3D400B9F74A /* BgReading+CoreDataClass.swift in Sources */, F8A54AEA22D911BA00934E7A /* PairRequestTxMessage.swift in Sources */, + 2CE6FBBF22F6BC9B0063712B /* LibreOOPWebExtensions.swift in Sources */, F821CF56229BF43A005C1E43 /* AlertKind.swift in Sources */, F85DC2ED21CFE2F500B9F74A /* BgReading+CoreDataProperties.swift in Sources */, + 2CE6FBBD22F6BC540063712B /* LibreOOPResponse.swift in Sources */, F8A54AE422D911BA00934E7A /* NSData+CRC.swift in Sources */, F867E2612252ADAB000FD265 /* Calibration+CoreDataProperties.swift in Sources */, F8025E6B21F7CD7600ECF0C0 /* UIStoryboard.swift in Sources */, @@ -1343,6 +1387,7 @@ F821CF8122A5C814005C1E43 /* RepeatingTimer.swift in Sources */, F821CF6F229FC280005C1E43 /* Endpoint+NightScout.swift in Sources */, F821CF5D229BF43A005C1E43 /* NSDateFormatter.swift in Sources */, + 2CE6FBC122F6BCAB0063712B /* DerivedAlgorithmRunner.swift in Sources */, F8025E4C21E6618200ECF0C0 /* Log.swift in Sources */, F8AC42A121B31F170078C348 /* xdrip.xcdatamodeld in Sources */, F8A54ADB22D911BA00934E7A /* AuthRequestTxMessage.swift in Sources */, @@ -1355,16 +1400,17 @@ F8A54B0022D9179100934E7A /* ParseLibreData.swift in Sources */, F8025E5421EE8D2100ECF0C0 /* Libre1Calibrator.swift in Sources */, F81FA00A228F53680028C70F /* TextsHomeView.swift in Sources */, + 2CE6FBB922F6BBD80063712B /* SensorState.swift in Sources */, F8E3C3AD21FE551C00907A04 /* DexcomCalibrator.swift in Sources */, F821CF61229BF4A2005C1E43 /* NightScoutUploadManager.swift in Sources */, F8A1587122EDC865007F5B5D /* ConstantsSpeakReadingLanguages.swift in Sources */, F8A54ADD22D911BA00934E7A /* BatteryStatusTxMessage.swift in Sources */, F8A1585522EDB706007F5B5D /* ConstantsCalibrationAlgorithms.swift in Sources */, F8A54ADA22D911BA00934E7A /* TransmitterMessage.swift in Sources */, + 2CE6FBB722F6BB680063712B /* CRC.swift in Sources */, F897AAF92200F2D200CDDD10 /* CBPeripheralState.swift in Sources */, F8A54AE522D911BA00934E7A /* TransmitterVersionRxMessage.swift in Sources */, F821CF57229BF43A005C1E43 /* SnoozeParameters.swift in Sources */, - F8A54AFF22D9179100934E7A /* CRC.swift in Sources */, F8B3A79722635A25004BA588 /* AlertEntry+CoreDataProperties.swift in Sources */, F80610C4222D4E4D00D8F236 /* ActionClosureable-extension.swift in Sources */, F8B3A835227F08AC004BA588 /* PickerViewController.swift in Sources */, @@ -1381,6 +1427,7 @@ F8A54AD822D911BA00934E7A /* CGMG5Transmitter.swift in Sources */, F8A1586322EDB86E007F5B5D /* ConstantsSounds.swift in Sources */, F8A1587322EDC893007F5B5D /* ConstantsDexcomShare.swift in Sources */, + 2CE6FBC522F6C2AE0063712B /* Measurement.swift in Sources */, F8A1586F22EDC7EE007F5B5D /* ConstantsSuspensionPrevention.swift in Sources */, F8A54AB822D9111900934E7A /* TransmitterBatteryInfo.swift in Sources */, F8B3A82D227F07D6004BA588 /* SettingsNavigationController.swift in Sources */, @@ -1402,6 +1449,7 @@ F81F9FF822861E6D0028C70F /* KeyValueObserverTimeKeeper.swift in Sources */, F8B3A858227F6971004BA588 /* UISwitch.swift in Sources */, F8A1586722EDB8BF007F5B5D /* ConstantsHomeView.swift in Sources */, + 2CE6FBC722F6C4DF0063712B /* GlucoseSmoothing.swift in Sources */, F8A1585922EDB7C6007F5B5D /* ConstantsDefaultAlertLevels.swift in Sources */, F8A54AAD22D6859200934E7A /* SlopeParameters.swift in Sources */, F8B3A783225D37F2004BA588 /* TextsNightScoutTestResult.swift in Sources */, @@ -1415,6 +1463,7 @@ F8E3C3AB21FE17B700907A04 /* StringProtocol.swift in Sources */, F8B3A78E22622954004BA588 /* AlertType+CoreDataClass.swift in Sources */, F821CF5A229BF43A005C1E43 /* CoreDataManager.swift in Sources */, + 2CE6FBB822F6BBD60063712B /* SensorSerialNumber.swift in Sources */, F85DC2F321CFE3D400B9F74A /* Calibration+CoreDataClass.swift in Sources */, F821CF7B22A1D359005C1E43 /* NightScoutFollowerDelegate.swift in Sources */, F81F9FFC2288C7530028C70F /* NewAlertSettingsViewController.swift in Sources */, @@ -1431,6 +1480,7 @@ F8B3A78B225D473D004BA588 /* UIAlertController.swift in Sources */, F8A54AEB22D911BA00934E7A /* CGMG4xDripTransmitter.swift in Sources */, F8BDD4242218790E006EAB84 /* UserDefaults.swift in Sources */, + 2CE6FBBB22F6BC090063712B /* LibreOOPClient.swift in Sources */, F81D6D5222C27F18005EFAE2 /* BgReading+DexcomShare.swift in Sources */, F821CF66229EE68B005C1E43 /* NightScoutFollowManager.swift in Sources */, F8A54AF622D9156600934E7A /* CGMGNSEntryTransmitter.swift in Sources */, @@ -1798,14 +1848,14 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = xdrip/xdrip.entitlements; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 8DKSZ5HNLB; + DEVELOPMENT_TEAM = EG7BNV2CC4; INFOPLIST_FILE = "$(SRCROOT)/xdrip/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = net.johandegraeve.iosxdripreader; + PRODUCT_BUNDLE_IDENTIFIER = net.yan.iosxdripreader; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "xdrip/xdrip-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1822,14 +1872,14 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = xdrip/xdrip.entitlements; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 8DKSZ5HNLB; + DEVELOPMENT_TEAM = EG7BNV2CC4; INFOPLIST_FILE = "$(SRCROOT)/xdrip/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = net.johandegraeve.iosxdripreader; + PRODUCT_BUNDLE_IDENTIFIER = net.yan.iosxdripreader; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "xdrip/xdrip-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/xdrip/Extensions/UserDefaults.swift b/xdrip/Extensions/UserDefaults.swift index bf423d04..ae01a34b 100644 --- a/xdrip/Extensions/UserDefaults.swift +++ b/xdrip/Extensions/UserDefaults.swift @@ -114,7 +114,9 @@ extension UserDefaults { /// G6 factor2 - for testing G6 scaling case G6v2ScalingFactor2 = "G6v2ScalingFactor2" - + + /// Bubble web oop + case webOOPEnabled = "webOOPEnabled" } // MARK: - ===== User Configurable Settings ====== @@ -571,7 +573,16 @@ extension UserDefaults { } } - + + /// web oop enabled + @objc dynamic var webOOPEnabled: Bool { + get { + return bool(forKey: Key.webOOPEnabled.rawValue) + } + set { + set(newValue, forKey: Key.webOOPEnabled.rawValue) + } + } } diff --git a/xdrip/Supporting Files/Info.plist b/xdrip/Supporting Files/Info.plist index 7380c1e4..d56df73b 100644 --- a/xdrip/Supporting Files/Info.plist +++ b/xdrip/Supporting Files/Info.plist @@ -17,11 +17,16 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.4.2 + 2.5.0 CFBundleVersion - 2.4.2 + 2.5.0 LSRequiresIPhoneOS + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSBluetoothPeripheralUsageDescription Connect to CGM Transmitter NSHealthShareUsageDescription diff --git a/xdrip/Texts/TextsSettingsView.swift b/xdrip/Texts/TextsSettingsView.swift index 53d858f4..6bcbd6ea 100644 --- a/xdrip/Texts/TextsSettingsView.swift +++ b/xdrip/Texts/TextsSettingsView.swift @@ -62,6 +62,10 @@ class Texts_SettingsView { return NSLocalizedString("settingsviews_resettransmitter", tableName: filename, bundle: Bundle.main, value: "Reset Transmitter", comment: "transmitter settings, to explain that settings is about resetting the transmitter") }() + static let labelWebOOPTransmitter:String = { + return NSLocalizedString("settingsviews_webooptransmitter", tableName: filename, bundle: Bundle.main, value: "Web OOP Enabled", comment: "transmitter settings, to explain that settings is about resetting the transmitter") + }() + // MARK: - Section Alerts static let sectionTitleAlerting: String = { diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitter.swift index fff90ab7..652a7483 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitter.swift @@ -116,6 +116,27 @@ enum CGMTransmitterType:String, CaseIterable { } } + func canWebOOP() -> Bool { + + switch self { + + case .dexcomG4: + return false + + case .dexcomG5, .dexcomG6: + return false + + case .miaomiao, .Bubble: + return true + + case .GNSentry: + return false + + case .Blucon: + return false + } + } + /// returns nil if id to validate has expected length and type of characters etc. func validateTransimtterId(idtovalidate:String) -> String? { switch self { diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/RawGlucoseData.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/RawGlucoseData.swift index 8b32cc21..0838189c 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/RawGlucoseData.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/RawGlucoseData.swift @@ -1,21 +1,26 @@ import Foundation /// raw glucose as received from transmitter -struct RawGlucoseData { +public struct RawGlucoseData { //TODO: move this class to other location ? var timeStamp:Date var glucoseLevelRaw:Double var glucoseLevelFiltered:Double + var unsmoothedGlucose: Double - init(timeStamp:Date, glucoseLevelRaw:Double, glucoseLevelFiltered:Double) { + init(timeStamp:Date, glucoseLevelRaw:Double, glucoseLevelFiltered:Double, unsmoothedGlucose: Double = 0.0) { self.timeStamp = timeStamp self.glucoseLevelRaw = glucoseLevelRaw self.glucoseLevelFiltered = glucoseLevelFiltered + self.unsmoothedGlucose = unsmoothedGlucose } init(timeStamp:Date, glucoseLevelRaw:Double) { self.init(timeStamp: timeStamp, glucoseLevelRaw: glucoseLevelRaw, glucoseLevelFiltered: glucoseLevelRaw) } - + + init(timeStamp:Date, unsmoothedGlucose: Double) { + self.init(timeStamp: timeStamp, glucoseLevelRaw: 0.0, glucoseLevelFiltered: 0.0, unsmoothedGlucose: unsmoothedGlucose) + } } diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Bubble/CGMBubbleTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Bubble/CGMBubbleTransmitter.swift index 912d737d..d37f8565 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Bubble/CGMBubbleTransmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Bubble/CGMBubbleTransmitter.swift @@ -18,9 +18,6 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C /// will be used to pass back bluetooth and cgm related events private(set) weak var cgmTransmitterDelegate: CGMTransmitterDelegate? - // maximum times resend request due to crc error - let maxPacketResendRequests = 3; - /// for OS_log private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryCGMBubble) @@ -39,10 +36,16 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C // length of header added by Bubble in front of data dat is received from Libre sensor private let BubbleHeaderLength = 8 + /// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send + var emptyArray: [RawGlucoseData] = [] + + // 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) { + init(address:String?, delegate:CGMTransmitterDelegate, timeStampLastBgReading:Date, sensorSerialNumber:String?) { // assign addressname and name or expected devicename var newAddressAndName:BluetoothTransmitter.DeviceAddressAndName = BluetoothTransmitter.DeviceAddressAndName.notYetConnected(expectedName: expectedDeviceNameBubble) @@ -50,6 +53,9 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C newAddressAndName = BluetoothTransmitter.DeviceAddressAndName.alreadyConnectedBefore(address: address) } + // initialize sensorSerialNumber + self.sensorSerialNumber = sensorSerialNumber + // assign CGMTransmitterDelegate cgmTransmitterDelegate = delegate @@ -100,9 +106,6 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C } } - var hardware = "" - var firmware = "" - var batteryPercentage = 0 func peripheralDidUpdateValueFor(characteristic: CBCharacteristic, error: Error?) { if let value = characteristic.value { @@ -117,23 +120,60 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C if let bubbleResponseState = BubbleResponseType(rawValue: firstByte) { switch bubbleResponseState { case .dataInfo: - hardware = value[2].description + ".0" - firmware = value[1].description + ".0" - batteryPercentage = Int(value[4]) - let _ = writeDataToPeripheral(data: Data([0x02, 0x00, 0x00, 0x00, 0x00, 0x2B]), type: .withoutResponse) + // get hardware, firmware and batteryPercentage + let hardware = value[2].description + ".0" + let firmware = value[1].description + ".0" + let batteryPercentage = Int(value[4]) + + // send hardware, firmware and batteryPercentage to delegate + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorState: nil, sensorTimeInMinutes: nil, firmware: firmware, hardware: hardware, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil) + + // 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)) { - //get readings from buffer and send to delegate - var result = parseLibreData(data: &rxBuffer, timeStampLastBgReadingStoredInDatabase: timeStampLastBgReading, headerOffset: BubbleHeaderLength) - //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 + var newSerialNumber = "" + if let sensorSerialNumberData = SensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 0..<8))) { + newSerialNumber = sensorSerialNumberData.serialNumber + + // verify serial number and if changed inform delegate + if newSerialNumber != sensorSerialNumber { + + os_log(" new sensor detected : %{public}@", log: log, type: .info, newSerialNumber) + + sensorSerialNumber = newSerialNumber + + // inform delegate about new sensor detected + cgmTransmitterDelegate?.newSensorDetected() + + // 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 + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: sensorSerialNumber) + + } + + } + + if UserDefaults.standard.webOOPEnabled { + handleGoodReading(bytes: [UInt8](rxBuffer.subdata(in: 8..<352)), serialNumber: newSerialNumber) { + [weak self] (result) in + if let res = result { + self?.handleGlucoseData(result: res) + } + } + } else { + //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) } //reset the buffer @@ -150,9 +190,19 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C } } + 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 + } + } + // MARK: CGMTransmitter protocol functions - /// to ask pairing - empty function because G4 doesn't need pairing + /// to ask pairing - empty function because Bubble doesn't need pairing /// /// this function is not implemented in BluetoothTransmitter.swift, otherwise it might be forgotten to look at in future CGMTransmitter developments func initiatePairing() {} @@ -176,6 +226,7 @@ fileprivate enum BubbleResponseType: UInt8 { case dataPacket = 130 case dataInfo = 128 case noSensor = 191 + case serialNumber = 192 } extension BubbleResponseType: CustomStringConvertible { @@ -187,6 +238,8 @@ extension BubbleResponseType: CustomStringConvertible { return "No sensor detected" case .dataInfo: return "Data info received" + case .serialNumber: + return "serial number received" } } } diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/GNSEntry/CGMGNSEntryTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/GNSEntry/CGMGNSEntryTransmitter.swift index ed666c0d..3868e460 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/GNSEntry/CGMGNSEntryTransmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/GNSEntry/CGMGNSEntryTransmitter.swift @@ -88,7 +88,7 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, /// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send var emptyArray: [RawGlucoseData] = [] - // MARK: - functions + // MARK: - public functions /// - parameters: /// - address: if already connected before, then give here the address that was received during previous connect, if not give nil @@ -325,7 +325,9 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, } } - /// creates readable representation of characteristicUUID, for logging only + // MARK: - private helper functions + + /// creates CBUUID_Characteristic_UUID for the characteristicUUID private func receivedCharacteristicUUIDToCharacteristic(characteristicUUID:String) -> CBUUID_Characteristic_UUID? { if CBUUID_Characteristic_UUID.CBUUID_BatteryLevel.rawValue.containsIgnoringCase(find: characteristicUUID) { return CBUUID_Characteristic_UUID.CBUUID_BatteryLevel diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift index a6f3632d..3196dd96 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift @@ -130,6 +130,9 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, let hardware = String(describing: rxBuffer[16...17].hexEncodedString()) let batteryPercentage = Int(rxBuffer[13]) + let serialNumber = SensorSerialNumber(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 diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/DerivedAlgorithmRunner.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/DerivedAlgorithmRunner.swift new file mode 100755 index 00000000..aa0db763 --- /dev/null +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/DerivedAlgorithmRunner.swift @@ -0,0 +1,161 @@ +// +// DerivedAlgorithmRunner.swift +// SwitftOOPWeb +// +// Created by Bjørn Inge Berg on 18.10.2018. +// Copyright © 2018 Bjørn Inge Berg. All rights reserved. +// + +import Foundation + +public struct DerivedAlgorithmParameters: Codable, CustomStringConvertible { + public var slope_slope: Double + public var slope_offset: Double + public var offset_slope: Double + public var offset_offset: Double + public var isValidForFooterWithReverseCRCs: Int + public var extraSlope : Double = 1 + public var extraOffset: Double = 0 + public var serialNumber: String? + + public var description: String { + return "DerivedAlgorithmParameters:: slopeslope: \(slope_slope), slopeoffset: \(slope_offset), offsetoffset: \(offset_offset), offsetSlope: \(offset_slope), extraSlope: \(extraSlope), extraOffset: \(extraOffset), isValidForFooterWithReverseCRCs: \(isValidForFooterWithReverseCRCs)" + } + + public init(slope_slope: Double, slope_offset:Double, offset_slope: Double, offset_offset: Double, isValidForFooterWithReverseCRCs: Int, extraSlope: Double, extraOffset: Double) { + self.slope_slope = slope_slope + self.slope_offset = slope_offset + self.offset_slope = offset_slope + self.offset_offset = offset_offset + self.isValidForFooterWithReverseCRCs = isValidForFooterWithReverseCRCs + self.extraSlope = extraSlope + self.extraOffset = extraOffset + } +} + + + + +public class DerivedAlgorithmRunner{ + private var params: DerivedAlgorithmParameters + init(_ params:DerivedAlgorithmParameters) { + 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: DerivedAlgorithmParameters) -> 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() -> DerivedAlgorithmRunner?{ + 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(DerivedAlgorithmParameters.self, from: jsonData) + + return DerivedAlgorithmRunner(params) + } catch { + print("Could not create instance: \(error.localizedDescription)") + } + } else { + print("Did not create instance") + } + return nil + } + +} + diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/GlucoseSmoothing.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/GlucoseSmoothing.swift new file mode 100755 index 00000000..341fb782 --- /dev/null +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/GlucoseSmoothing.swift @@ -0,0 +1,44 @@ +// +// GlucoseSmoothing.swift +// BubbleClientUI +// +// Created by Bjørn Inge Berg on 25/03/2019. +// Copyright © 2019 Mark Wilson. All rights reserved. +// + +import Foundation + +//https://github.com/NightscoutFoundation/xDrip/pull/828/files + + +//private func trendToLibreGlucose(_ measurements: [Measurement]) -> [LibreGlucose]?{ + +func CalculateSmothedData5Points(origtrends: [RawGlucoseData]) -> [RawGlucoseData] { + // In all places in the code, there should be exactly 16 points. + // Since that might change, and I'm doing an average of 5, then in the case of less then 5 points, + // I'll only copy the data as is (to make sure there are reasonable values when the function returns). + + var trends = origtrends + //this is an adoptation, doesn't follow the original directly + if(trends.count < 5) { + for i in 0 ..< trends.count { + trends[i].glucoseLevelRaw = trends[i].unsmoothedGlucose + } + + return trends; + } + for i in 0 ..< trends.count - 4 { + + trends[i].glucoseLevelRaw = (trends[i].unsmoothedGlucose + trends[i+1].glucoseLevelRaw + trends[i+2].unsmoothedGlucose + trends[i+3].glucoseLevelRaw + trends[i+4].unsmoothedGlucose) / 5 + + + } + trends[trends.count - 4].glucoseLevelRaw = (trends[trends.count - 4].unsmoothedGlucose + trends[trends.count - 3].unsmoothedGlucose + trends[trends.count - 2].unsmoothedGlucose + trends[trends.count - 1].unsmoothedGlucose) / 4 + trends[trends.count - 3].glucoseLevelRaw = (trends[trends.count - 3].unsmoothedGlucose + trends[trends.count - 2].unsmoothedGlucose + trends[trends.count - 1].unsmoothedGlucose ) / 3 + + trends[trends.count - 2].glucoseLevelRaw = (trends[trends.count - 2].unsmoothedGlucose + trends[trends.count - 1].unsmoothedGlucose ) / 2 + + trends[trends.count - 1].glucoseLevelRaw = trends[trends.count - 2].glucoseLevelRaw + + return trends +} diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPClient.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPClient.swift new file mode 100755 index 00000000..6792ef22 --- /dev/null +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPClient.swift @@ -0,0 +1,678 @@ +//// +//// RemoteBG.swift +//// SwitftOOPWeb +//// +//// Created by Bjørn Inge Berg on 08.04.2018. +//// Copyright © 2018 Bjørn Inge Berg. All rights reserved. +//// +// +//import Foundation +//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" +// +// init(accessToken: String, site: String = "https://libreoopweb.azurewebsites.net") { +// self.accessToken = accessToken +// uploadEndpoint = site + "/api/CreateRequestAsync" +// statusEndpoint = site + "/api/GetStatus" +// } +// +// 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]) { +// +// let request = NSMutableURLRequest(url: NSURL(string: postURL)! as URL) +// request.httpMethod = "POST" +// +// +// 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, error in +// +// guard let data = data else { +// completion("network error".data(using: .utf8)!, "network error", false) +// return +// } +// +// if let response = String(data: data, encoding: String.Encoding.utf8) { +// completion(data, response, true) +// } +// +// } +// task.resume() +// +// +// +// } +// +// public func getStatusIntervalled(uuid: String, intervalSeconds:UInt32=10, maxTries: Int8=6, _ completion:@escaping (( _ success: Bool, _ message: String, _ response: String )-> Void)) { +// let q = DispatchQueue.global() +// let sem = DispatchSemaphore(value: 0) +// var remoteResponse = "N/A" +// var succeeded = false; +// var error = "" +// q.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) { +// break +// } +// self.getStatus(uuid: uuid, { (success, errormsg, response) in +// if(success) { +// succeeded = true +// remoteResponse = response! +// } else { +// error = errormsg +// } +// +// sem.signal() +// }) +// +// sem.wait(); +// NSLog("Hey hop, response received: \(remoteResponse) , success: \(succeeded)"); +// if(succeeded) { +// break +// } +// +// } +// +// completion(succeeded, error, remoteResponse) +// +// +// +// } +// } +// +// private func getStatus(uuid: String, _ completion:@escaping (( _ success: Bool, _ message: String, _ response: String? )-> Void)){ +// postToServer({ (data, response, success) in +// NSLog("getstatus here:" + response) +// if(!success) { +// NSLog("Get status failed") +// completion(false, response, response) +// return +// } +// 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) +// //failureHandler(msg) +// return; +// } +// if let resp = response.result, let result2 = resp.result { +// NSLog("GetStatus returned a valid result:" + result2) +// completion(true, "", result2) +// return +// } else { +// NSLog("Result was not ready,") +// completion(false, "Result was not ready", nil) +// return; +// } +// +// } catch (let error as NSError){ +// NSLog("getstatus error decoding:" + error.localizedDescription) +// completion(false, error.localizedDescription, nil) +// return +// } +// +// }, postURL: statusEndpoint, postparams: ["accesstoken": self.accessToken, "uuid": uuid]) +// } +// public func uploadReading(reading: [UInt8], _ completion:@escaping (( _ resp: LibreOOPResponse?, _ success: Bool, _ errorMessage: String)-> Void)){ +// +// let r = LibreOOPClient.readingToString(reading) +// NSLog("uploading reading! " + r) +// postToServer({ (data, response, success) in +// NSLog("uploadreading completed with response: " + response) +// if(!success) { +// NSLog("Did not succeed uploading request!") +// completion(nil, false, "network error!?") +// return +// } +// let decoder = JSONDecoder() +// do { +// let result = try decoder.decode(LibreOOPResponse.self, from: data) +// if let msg = result.message { +// NSLog("Error sending request " + msg) +// return; +// } +// +// NSLog("result was successsfully received!") +// completion(result, true, ""); +// return; +// +// } catch let error as NSError{ +// NSLog("uploadreading error decoding:" + error.localizedDescription) +// completion(nil, false, error.localizedDescription) +// return +// } +// +// }, postURL: uploadEndpoint, postparams: ["accesstoken": self.accessToken, "b64contents": r]) +// } +// +//} +// + + +// +// RemoteBG.swift +// SwitftOOPWeb +// +// Created by Bjørn Inge Berg on 08.04.2018. +// Copyright © 2018 Bjørn Inge Berg. All rights reserved. +// + +import Foundation +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 + + 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" + + } + + public 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]) { + + let request = NSMutableURLRequest(url: NSURL(string: postURL)! as URL) + request.httpMethod = "POST" + + 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) + return + } + + if let response = String(data: data, encoding: String.Encoding.utf8) { + completion(data, response, true) + } + + } + task.resume() + + } + + public 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) + } + } + + 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 + } + 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) + return + } + + } catch (let error as NSError) { + + completion(false, error.localizedDescription, nil, nil) + return + } + + }, postURL: statusEndpoint, postparams: ["accesstoken": self.accessToken, "uuid": uuid]) + } + + public func uploadReading(reading: [UInt8], oldState: String?=nil, sensorStartTimestamp: Int?=nil, sensorScanTimestamp: Int?=nil, currentUtcOffset: Int?=nil, _ completion:@escaping (( _ resp: LibreOOPResponse?, _ success: Bool, _ errorMessage: String) -> Void)) { + + self.uploadReading(reading: LibreOOPClient.readingToString(reading), oldState: oldState, sensorStartTimestamp: sensorStartTimestamp, sensorScanTimestamp: sensorScanTimestamp, currentUtcOffset: currentUtcOffset, completion) + + } + public 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) + } + + public 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() + } + + } + awaiter.wait() + + } + + return ret + } + public func uploadCalibration(reading: [UInt8], _ completion:@escaping (( _ resp: CalibrationResult?, _ success: Bool, _ errorMessage: String) -> Void)) { +// return uploadCalibration(reading: LibreOOPClient.readingToString(patch), completion) + return uploadCalibration(reading: LibreOOPClient.readingToString(reading), completion) + } + + public func uploadCalibration(reading: String, _ completion:@escaping (( _ resp: CalibrationResult?, _ success: Bool, _ errorMessage: String) -> Void)) { + let postParams = ["accesstoken": self.accessToken, "b64contents": reading] + + postToServer({ (data, _, success) in + + if(!success) { + + completion(nil, false, "network error!?") + return + } + let decoder = JSONDecoder() + do { + print("data: \(data)") + let response = try decoder.decode(CalibrationResponse.self, from: data) + if response.error { + completion(nil, false, "error") + } + if let result = response.result { + + completion(result, true, "") + return + } + + completion(nil, false, "unknown error decoding") + return + + } catch let error as NSError { + completion(nil, false, error.localizedDescription) + return + } + + }, postURL: calibrationEndpoint, postparams: postParams) + } + + + public func getCalibrationStatusIntervalled(uuid: String, intervalSeconds: UInt32=10, maxTries: Int8=8, _ completion:@escaping (( _ success: Bool, _ errormessage: String, _ runner: DerivedAlgorithmParameters?) -> Void)) { + + let sem = DispatchSemaphore(value: 0) + var algoparams : DerivedAlgorithmParameters? = nil + var succeeded = false + var error = "" + + + DispatchQueue.global().async { + for i in 1...maxTries { + NSLog("Attempt \(i): Waiting \(intervalSeconds) seconds before calling getCalibrationStatus") + sleep(intervalSeconds) + NSLog("Finished waiting \(intervalSeconds) seconds before calling getCalibrationStatus") + if (succeeded) { + error = "" + break + + } + self.getCalibrationStatus(uuid: uuid, { (success, errormessage, params) in + print("inside handler for getCalibrationStatus in interval, success: \(success), message: \(errormessage), params:\(String(describing: params))") + if (success) { + succeeded = true + algoparams = params + + } else { + error = errormessage + } + sem.signal() + }) + + sem.wait() + + + if (succeeded) { + error = "" + break + } + } + + completion(succeeded, error, algoparams) + } + } + + + + + + + private func getCalibrationStatus(uuid: String, _ completion:@escaping (( _ success: Bool, _ message: String, _ response: DerivedAlgorithmParameters?) -> Void)) { + postToServer({ (data, response, success) in + NSLog("getCalibrationStatus here:" + response + ", data: \(data)") + if(!success) { + NSLog("getCalibrationStatus failed") + completion(false, response, nil) + return + } + 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 = DerivedAlgorithmParameters.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) + + completion(true, "complete", params ) + return + } + + + 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 + } + + }, postURL: calibrationStatusEndpoint, postparams: ["accesstoken": self.accessToken, "uuid": uuid]) + } + + + public static func getLibreReadingsFromFolderContents(subfolder: String) -> [String: String]? { + let fm = FileManager.default + var files: [URL] = [] + var assoc = [String: String]() + guard var dir = fm.urls(for: .documentDirectory, in: .userDomainMask).first else { + print ("cannot construct url dir") + return nil + } + + dir = dir.appendingPathComponent(subfolder, isDirectory: true) + + do { + files = try fm.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil) + + } catch { + print("Error while enumerating files for \(dir.path): \(error.localizedDescription)") + return nil + } + for fileURL in files where fileURL.pathExtension == "txt" { + //reading + do { + let text = try String(contentsOf: fileURL, encoding: .utf8) + assoc[fileURL.lastPathComponent] = text + } catch let error as NSError { + NSLog("reading file \(fileURL) failed, due to: \(error.localizedDescription)") + + } + } + + return assoc + + } +} + +func post(bytes: [UInt8],_ completion:@escaping (( _ data_: Data, _ response: String, _ success: Bool ) -> Void)) { + let date = Int(Date().timeIntervalSince1970 * 1000) + let json: [String: String] = [ + "token": "bubble-201907", + "content": "\(bytes.hex)", + "timestamp": "\(date)"] + if let uploadURL = URL.init(string: "http://www.glucose.space/calibrateSensor") { + let request = NSMutableURLRequest(url: uploadURL) + request.httpMethod = "POST" + + request.setBodyContent(contentMap: json) + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + + let task = URLSession.shared.dataTask(with: request as URLRequest) { + data, response, _ in + + guard let data = data else { + completion("network error".data(using: .utf8)!, "network error", false) + return + } + + if let response = String(data: data, encoding: String.Encoding.utf8) { + completion(data, response, true) + } + + } + task.resume() + } +} + +private let filePath: String = NSHomeDirectory() + "/Documents/paras" +func save(data: Data) { + let url = URL.init(fileURLWithPath: filePath) + do { + try data.write(to: url) + } catch { + print("write error:", error) + } +} + +public func calibrateSensor(bytes: [UInt8], serialNumber: String, callback: @escaping (DerivedAlgorithmParameters?) -> 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(DerivedAlgorithmParameters.self, from: data) + if response.serialNumber == serialNumber { + callback(response) + return + } + } catch { + print("decoder error:", error) + } + } + + 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 = DerivedAlgorithmParameters.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) + } + }) +} + + +extension Collection where Element == UInt8 { + var data: Data { + return Data(self) + } + var hex: String { + return map{ String(format: "%02X", $0) }.joined() + } +} diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPDefaults.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPDefaults.swift new file mode 100755 index 00000000..0e2b71c5 --- /dev/null +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPDefaults.swift @@ -0,0 +1,77 @@ +// +// 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 +} diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPResponse.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPResponse.swift new file mode 100755 index 00000000..4434fece --- /dev/null +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPResponse.swift @@ -0,0 +1,251 @@ +//// +//// CreateRequestResponse.swift +//// SwitftOOPWeb +//// +//// 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. +// +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? + let command: String? + let slope: GetCalibrationStatusResult? + let result: GetCalibrationStatusResult? +} + +struct GetCalibrationStatusResult: Codable, CustomStringConvertible{ + let status: String? + let slopeSlope, slopeOffset, offsetOffset, offsetSlope: Double? + let uuid: String? + let isValidForFooterWithReverseCRCs: Double? + + enum CodingKeys: String, CodingKey { + case status + case slopeSlope = "slope_slope" + case slopeOffset = "slope_offset" + case offsetOffset = "offset_offset" + case offsetSlope = "offset_slope" + case uuid + case isValidForFooterWithReverseCRCs = "isValidForFooterWithReverseCRCs" + } + var description: String { + return "calibrationparams:: slopeslope: \(String(describing: slopeSlope)), slopeoffset: \(String(describing: slopeOffset)), offsetoffset: \(String(describing: offsetOffset)), offsetSlope: \(String(describing: offsetSlope)), isValidForFooterWithReverseCRCs: \(String(describing: isValidForFooterWithReverseCRCs))" + } +} diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPWebExtensions.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPWebExtensions.swift new file mode 100755 index 00000000..c0fd0739 --- /dev/null +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPWebExtensions.swift @@ -0,0 +1,118 @@ +//// +//// 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) + } +} diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/Measurement.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/Measurement.swift new file mode 100755 index 00000000..bb0c539c --- /dev/null +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/Measurement.swift @@ -0,0 +1,163 @@ +// +// Measurement.swift +// LibreMonitor +// +// Created by Uwe Petersen on 25.08.16. +// Copyright © 2016 Uwe Petersen. All rights reserved. +// + +import Foundation + + +/// Structure for one glucose measurement including value, date and raw data bytes +struct Measurement { + /// The date for this measurement + let date: Date + /// The minute counter for this measurement + let counter: Int + /// The bytes as read from the sensor. All data is derived from this \"raw data" + let bytes: [UInt8] + /// The bytes as String + let byteString: String + /// The raw glucose as read from the sensor + let rawGlucose: Int + /// The raw temperature as read from the sensor + let rawTemperature: Int + /// slope to calculate glucose from raw value in (mg/dl)/raw + let slope: Double + /// glucose offset to be added in mg/dl + let offset: Double + /// The glucose value in mg/dl + let glucose: Double + /// Initialize a new glucose measurement +// let slope_slope: Double = 0.0 +// let slope_offset: Double = 0.0 +// let offset_slope: Double = 0.0 +// let offset_offset: Double = 0.0 + 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 + /// + let temperatureAlgorithmParameterSet: DerivedAlgorithmParameters? + + + /// + /// - parameter bytes: raw data bytes as read from the sensor + /// - parameter slope: slope to calculate glucose from raw value in (mg/dl)/raw + /// - parameter offset: glucose offset to be added in mg/dl + /// - parameter date: date of the measurement + /// + /// - returns: Measurement + init(bytes: [UInt8], slope: Double = 0.1, offset: Double = 0.0, counter: Int = 0, date: Date, derivedAlgorithmParameterSet: DerivedAlgorithmParameters? = nil) { + self.bytes = bytes + self.byteString = bytes.reduce("", {$0 + String(format: "%02X", arguments: [$1])}) + self.rawGlucose = (Int(bytes[1] & 0x1F) << 8) + Int(bytes[0]) // switched to 13 bit mask on 2018-03-15 + self.rawTemperature = (Int(bytes[4] & 0x3F) << 8) + Int(bytes[3]) // 14 bit-mask for raw temperature + self.slope = slope + self.offset = offset + self.glucose = offset + slope * Double(rawGlucose) + self.date = date + self.counter = 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 = derivedAlgorithmParameterSet + if let derivedAlgorithmParameterSet = self.temperatureAlgorithmParameterSet { + self.oopSlope = derivedAlgorithmParameterSet.slope_slope * Double(rawTemperature) + derivedAlgorithmParameterSet.offset_slope + self.oopOffset = derivedAlgorithmParameterSet.slope_offset * Double(rawTemperature) + derivedAlgorithmParameterSet.offset_offset + // self.oopSlope = slope_slope * Double(rawTemperature) + slope_offset + // self.oopOffset = offset_slope * Double(rawTemperature) + offset_offset + let oopGlucose = oopSlope * Double(rawGlucose) + oopOffset + //self.temperatureAlgorithmGlucose = oopGlucose + // Final correction, if sensor values are very low and need to be compensated + self.temperatureAlgorithmGlucose = oopGlucose * derivedAlgorithmParameterSet.extraSlope + derivedAlgorithmParameterSet.extraOffset + } else { + self.oopSlope = 0 + self.oopOffset = 0 + self.temperatureAlgorithmGlucose = 0 + } + + 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") + aString.append("OOP: slope_slope: \(String(describing: temperatureAlgorithmParameterSet?.slope_slope)), slope_offset: \(String(describing: temperatureAlgorithmParameterSet?.slope_offset)), offset_slope: \(String(describing: temperatureAlgorithmParameterSet?.offset_slope)), offset_offset: \(String(describing: temperatureAlgorithmParameterSet?.offset_offset))\n") + aString.append("OOP: slope: \(oopSlope), \noffset: \(oopOffset)") + aString.append("oopGlucose: \(temperatureAlgorithmGlucose) (mg/dl)" ) + + return aString +// return String("Glucose: \(glucose) (mg/dl), date: \(date), slope: \(slope), offset: \(offset), rawGlucose: \(rawGlucose), rawTemperature: \(rawTemperature), bytes: \(bytes) /n oop: slope_slope = " ) + } +} diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/ParseLibreData.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/ParseLibreData.swift index 6703541e..9b73f88a 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/ParseLibreData.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/ParseLibreData.swift @@ -88,3 +88,77 @@ fileprivate func getGlucoseRaw(bytes:Data) -> Int { return ((256 * (getByteAt(buffer: bytes, position: 0) & 0xFF) + (getByteAt(buffer: bytes, position: 1) & 0xFF)) & 0x1FFF) } +func handleGoodReading(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]) + 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(), derivedAlgorithmParameterSet: params) + if let glucoseData = trendToLibreGlucose(last16) { + callback((glucoseData, sensorState, 0)) + } + } +} + +private func trendMeasurements(bytes: [UInt8], date: Date, _ offset: Double = 0.0, slope: Double = 0.1, derivedAlgorithmParameterSet: DerivedAlgorithmParameters?) -> [Measurement] { + +// 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 = [Measurement]() + // 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.. [RawGlucoseData]?{ + var origarr = [RawGlucoseData]() + + //whether or not to return all the 16 latest trends or just every fifth element + let returnAllTrends = true + + for trend in measurements { + let glucose = RawGlucoseData.init(timeStamp: trend.date, unsmoothedGlucose: trend.temperatureAlgorithmGlucose) + origarr.append(glucose) + } + //NSLog("dabear:: glucose samples before smoothing: \(String(describing: origarr))") + var arr : [RawGlucoseData] + arr = 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 +} diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/SensorSerialNumber.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/SensorSerialNumber.swift new file mode 100644 index 00000000..844c61c0 --- /dev/null +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/SensorSerialNumber.swift @@ -0,0 +1,109 @@ +// +// FreestyleLibreSensor.swift +// LibreMonitor +// +// Created by Uwe Petersen on 01.04.18. +// Copyright © 2018 Uwe Petersen. All rights reserved. +// + +import Foundation + +struct SensorSerialNumber: CustomStringConvertible { + + let uid: Data + + fileprivate let lookupTable = ["0","1","2","3","4","5","6","7","8","9","A","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","T","U","V","W","X","Y","Z"] + + init?(withUID uid: Data) { + guard uid.count == 8 else {return nil} + self.uid = uid + } + + // MARK: - computed properties + + var serialNumber: String { + + // The serial number of the sensor can be derived from its uid. + // + // The numbers an letters of the serial number are coded a compressed scheme that uses only 32 numbers and letters, + // by omitting the letters B, I, O and S. This information is stored in consecutive units of five bits. + // + // The encoding thus is as follows: + // index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + // char: 0 1 2 3 4 5 6 7 8 9 A (B) C D E F G H (I) J K L M N (O) P Q R (S) T U V W X Y Z + // + // Example: 75 ce 86 00 00 a0 07 e0 + // Uid is E0 07 A0 00 00 25 90 5E, and the corresponding serial number is "0M00009DHCR" + // \ / \ / + // -+- -----+-------- + // | | + // | +-- This part encodes the serial number, see below + // +-- Standard first two bytes, where 0x07 is the code for "Texas Instruments Tag-it™", see https://en.wikipedia.org/wiki/ISO/IEC_15693 + // + // 1.) Convert the part without E007, i.e. A0 00 00 25 90 5E to binary representation + // + // A 0 0 0 0 0 2 5 9 0 5 E + // 1010 0000 0000 0000 0000 0000 0010 0101 1001 0000 0101 1110 + // + // 2.) Split this binary array in units of five bits length from the beginning and pad with two zeros at the end and + // calculate the corresponding integer and retreive the corresponding char from the table above + // + // Byte # 0 1 2 3 4 5 + // Bit # 8765 4321 8765 4321 8765 4321 8765 4321 8765 4321 8765 4321 + // + // +-- 1010 0000 0000 0000 0000 0000 0010 0101 1001 0000 0101 1110 + 00 + // | \ /\ /\ /\ / \ /\ /\ /\ / \ /\ / + // +-> 10100 00000 00000 00000 00000 01001 01100 10000 01011 11000 + // | | | | | | | | | | + // | | | | | | | | | +- = 24 -> "R" (Byte 6) << 2 Mask 0x1F + // | | | | | | | | +-------- = 11 -> "C" (Byte 5) >> 3 Mask 0x1F + // | | | | | | | +--------------- = 16 -> "H" (Byte 4) Mask 0x1F + // | | | | | | +---------------------- = 12 -> "D" (Byte 3) << 2 + (Byte 4) >> 5 Mask 0x1F + // | | | | | +----------------------------- = 9 -> "9" (Byte 3) >> 2 Mask 0x1F + // | | | | +------------------------------------ = 0 -> "0" (Byte 2) << 1 + (Byte 3) >> 7 Mask 0x1F + // | | | +------------------------------------------- = 0 -> "0" (Byte 1) << 4 + (Byte 2) >> 4 Mask 0x1F + // | | +-------------------------------------------------- = 0 -> "0" (Byte 1) >> 1 Mask 0x1F + // | +--------------------------------------------------------- = 0 -> "0" (Byte 0) << 2 + (Byte 1) >> 6 Mask 0x1F + // +---------------------------------------------------------------- = 20 -> "M" (byte 0) >> 3 Mask 0x1F + // + // + // 3.) Prepend "0" at the beginning an thus receive "0M00009DHCR" + + guard uid.count == 8 else {return "invalid uid"} + + let bytes = Array(uid.reversed().suffix(6)) // 5E 90 25 00 00 A0 07 E0" -> E0 07 A0 00 00 25 90 5E -> A0 00 00 25 90 5E + + // A0 00 00 25 90 5E -> "M00009DHCR" + var fiveBitsArray = [UInt8]() // Mask later with 0x1F to use only five bits + + fiveBitsArray.append( bytes[0] >> 3 ) + fiveBitsArray.append( bytes[0] << 2 + bytes[1] >> 6 ) + fiveBitsArray.append( bytes[1] >> 1 ) + fiveBitsArray.append( bytes[1] << 4 + bytes[2] >> 4 ) + fiveBitsArray.append( bytes[2] << 1 + bytes[3] >> 7 ) + fiveBitsArray.append( bytes[3] >> 2 ) + fiveBitsArray.append( bytes[3] << 3 + bytes[4] >> 5 ) + fiveBitsArray.append( bytes[4] ) + fiveBitsArray.append( bytes[5] >> 3 ) + fiveBitsArray.append( bytes[5] << 2 ) + + let serialNumber = fiveBitsArray.reduce("0", { // prepend with "0" according to step 3.) + $0 + lookupTable[ Int(0x1F & $1) ] // Mask with 0x1F to only take the five relevant bits + }) + return serialNumber + } + + var uidString: String { + return Data(self.uid).hexEncodedString() + } + + var prettyUidString: String { + let stringArray = self.uid.map({String(format: "%02X", $0)}) + return stringArray.dropFirst().reduce(stringArray.first!, {$0 + ":" + $1} ) + } + + // MARK: - CustomStringConvertible Protocoll + var description: String { + return "Uid is \(prettyUidString) and derived serial number is \(serialNumber)" + } +} diff --git a/xdrip/Transmitter/GenericBluetoothTransmitter/BluetoothTransmitter.swift b/xdrip/Transmitter/GenericBluetoothTransmitter/BluetoothTransmitter.swift index 56abd88c..f9893a3e 100644 --- a/xdrip/Transmitter/GenericBluetoothTransmitter/BluetoothTransmitter.swift +++ b/xdrip/Transmitter/GenericBluetoothTransmitter/BluetoothTransmitter.swift @@ -392,7 +392,8 @@ class BluetoothTransmitter: NSObject, CBCentralManagerDelegate, CBPeripheralDele func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { timeStampLastStatusUpdate = Date() - os_log("didDiscoverCharacteristicsFor", log: log, type: .info) + os_log("didDiscoverCharacteristicsFor for service with uuid %{public}@", log: log, type: .info, String(describing:service.uuid)) + if let error = error { os_log(" didDiscoverCharacteristicsFor error: %{public}@", log: log, type: .error , error.localizedDescription) } diff --git a/xdrip/View Controllers/Root View Controller/RootViewController.swift b/xdrip/View Controllers/Root View Controller/RootViewController.swift index 321ed434..fbbc65d2 100644 --- a/xdrip/View Controllers/Root View Controller/RootViewController.swift +++ b/xdrip/View Controllers/Root View Controller/RootViewController.swift @@ -596,7 +596,7 @@ final class RootViewController: UIViewController { calibrator = Libre1Calibrator() case .Bubble: - cgmTransmitter = CGMBubbleTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0)) + cgmTransmitter = CGMBubbleTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0), sensorSerialNumber: UserDefaults.standard.sensorSerialNumber) calibrator = Libre1Calibrator() case .GNSentry: @@ -1179,7 +1179,7 @@ extension RootViewController:CGMTransmitterDelegate { stopSensor() } - // MioaMiao and Bubble will call this + // MioaMiao and Bubble will call this (and Blucon, maybe others in the future) func sensorNotDetected() { os_log("sensor not detected", log: log, type: .info) diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDexcomSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDexcomSettingsViewModel.swift index 8fac6760..299f6f36 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDexcomSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDexcomSettingsViewModel.swift @@ -28,11 +28,11 @@ class SettingsViewDexcomSettingsViewModel:SettingsViewModelProtocol { if let transmitterType = UserDefaults.standard.transmitterType { switch transmitterType { - case .dexcomG4, .miaomiao, .GNSentry, .Blucon, .Bubble: - return true - case .dexcomG5, .dexcomG6: return false + + default: + return true } } else { return true diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewTransmitterSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewTransmitterSettingsViewModel.swift index 22dd7b9b..3919a44f 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewTransmitterSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewTransmitterSettingsViewModel.swift @@ -7,6 +7,7 @@ fileprivate enum Setting:Int, CaseIterable { case transmitterId = 1 /// is transmitter reset required or not (only applicable to Dexcom G5 and later also G6) case resetRequired = 2 + case webOOP = 3 } /// conforms to SettingsViewModelProtocol for all transmitter settings in the first sections screen @@ -26,7 +27,7 @@ struct SettingsViewTransmitterSettingsViewModel:SettingsViewModelProtocol { } func onRowSelect(index: Int) -> SettingsSelectedRowAction { - guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Setting in SettingsViewTransmitterSettingsViewModel onRowSelect") } + guard let setting = Setting(rawValue: fixWebOOPIndex(index)) else { fatalError("Unexpected Setting in SettingsViewTransmitterSettingsViewModel onRowSelect") } switch setting { @@ -68,6 +69,8 @@ struct SettingsViewTransmitterSettingsViewModel:SettingsViewModelProtocol { case .resetRequired: return SettingsSelectedRowAction.callFunction(function: {UserDefaults.standard.transmitterResetRequired ? (UserDefaults.standard.transmitterResetRequired) = false : (UserDefaults.standard.transmitterResetRequired = true)}) + case .webOOP: + return SettingsSelectedRowAction.nothing } } @@ -85,15 +88,20 @@ struct SettingsViewTransmitterSettingsViewModel:SettingsViewModelProtocol { if let transmitterType = UserDefaults.standard.transmitterType { // if transmitter doesn't need transmitterid (like MiaoMiao) then the settings row that asks for transmitterid doesn't need to be shown. That row is the second row - also reset transmitter not necessary in that case // if ever there would be a transmitter that doesn't need a transmitter id but that supports reset transmitter, then some recoding will be necessary here + var count = 0 if transmitterType.needsTransmitterId() { if transmitterType.resetPossible() { - return 3 + count = 3 } else { - return 2 + count = 2 } } else { - return 1 + count = 1 } + if transmitterType.canWebOOP() { + count += 1 + } + return count } else { // transmitterType nil, means this is initial setup, no need to show transmitter id field return 1 @@ -101,7 +109,7 @@ struct SettingsViewTransmitterSettingsViewModel:SettingsViewModelProtocol { } func settingsRowText(index: Int) -> String { - guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + guard let setting = Setting(rawValue: fixWebOOPIndex(index)) else { fatalError("Unexpected Section") } switch (setting) { @@ -114,11 +122,13 @@ struct SettingsViewTransmitterSettingsViewModel:SettingsViewModelProtocol { case .resetRequired: return Texts_SettingsView.labelResetTransmitter + case .webOOP: + return Texts_SettingsView.labelWebOOPTransmitter } } func accessoryType(index: Int) -> UITableViewCell.AccessoryType { - guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + guard let setting = Setting(rawValue: fixWebOOPIndex(index)) else { fatalError("Unexpected Section") } switch setting { @@ -128,12 +138,14 @@ struct SettingsViewTransmitterSettingsViewModel:SettingsViewModelProtocol { return UITableViewCell.AccessoryType.disclosureIndicator case .resetRequired: return UITableViewCell.AccessoryType.none + case .webOOP: + return UITableViewCell.AccessoryType.none } } func detailedText(index: Int) -> String? { - guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + guard let setting = Setting(rawValue: fixWebOOPIndex(index)) else { fatalError("Unexpected Section") } switch (setting) { @@ -143,12 +155,30 @@ struct SettingsViewTransmitterSettingsViewModel:SettingsViewModelProtocol { return UserDefaults.standard.transmitterType?.rawValue case .resetRequired: return UserDefaults.standard.transmitterResetRequired ? Texts_Common.yes:Texts_Common.no - + case .webOOP: + return nil } } func uiView(index: Int) -> UIView? { - return nil + guard let setting = Setting(rawValue: fixWebOOPIndex(index)) else { fatalError("Unexpected Section") } + + switch setting { + case .webOOP: + return UISwitch(isOn: UserDefaults.standard.webOOPEnabled, action: {(isOn:Bool) in UserDefaults.standard.webOOPEnabled = isOn}) + default: + return nil + } + } + + private func fixWebOOPIndex(_ index: Int) -> Int { + var index = index + if let transmitterType = UserDefaults.standard.transmitterType { + if transmitterType.canWebOOP() && index == 1 { + index += 2 + } + } + return index } /// sets UserDefaults.standard.transmitterId with valud of id