add oop
This commit is contained in:
commit
6623bfe6dd
|
@ -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 = "<group>"; };
|
||||
2CE6FBBA22F6BC090063712B /* LibreOOPClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPClient.swift; sourceTree = "<group>"; };
|
||||
2CE6FBBC22F6BC540063712B /* LibreOOPResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPResponse.swift; sourceTree = "<group>"; };
|
||||
2CE6FBBE22F6BC9B0063712B /* LibreOOPWebExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPWebExtensions.swift; sourceTree = "<group>"; };
|
||||
2CE6FBC022F6BCAB0063712B /* DerivedAlgorithmRunner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DerivedAlgorithmRunner.swift; sourceTree = "<group>"; };
|
||||
2CE6FBC222F6BCC20063712B /* LibreOOPDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPDefaults.swift; sourceTree = "<group>"; };
|
||||
2CE6FBC422F6C2AE0063712B /* Measurement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Measurement.swift; sourceTree = "<group>"; };
|
||||
2CE6FBC622F6C4DF0063712B /* GlucoseSmoothing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseSmoothing.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
EE9947E722EEDD2E00DCB876 /* CGMBubbleTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMBubbleTransmitter.swift; sourceTree = "<group>"; };
|
||||
|
@ -479,6 +494,7 @@
|
|||
F8BDD451221DEAB1006EAB84 /* TextsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextsSettingsView.swift; sourceTree = "<group>"; };
|
||||
F8BDD456221DEF22006EAB84 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/SettingsViews.strings; sourceTree = "<group>"; };
|
||||
F8BDD458221DEF24006EAB84 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/SettingsViews.strings; sourceTree = "<group>"; };
|
||||
F8C5EBE422F297EF00563B5F /* SensorSerialNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorSerialNumber.swift; sourceTree = "<group>"; };
|
||||
F8E3C3AA21FE17B700907A04 /* StringProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringProtocol.swift; sourceTree = "<group>"; };
|
||||
F8E3C3AC21FE551C00907A04 /* DexcomCalibrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrator.swift; sourceTree = "<group>"; };
|
||||
F8EA6C8121B723BC0082976B /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
|
||||
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -17,11 +17,16 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.4.2</string>
|
||||
<string>2.5.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.4.2</string>
|
||||
<string>2.5.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<string>Connect to CGM Transmitter</string>
|
||||
<key>NSHealthShareUsageDescription</key>
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
251
xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPResponse.swift
Executable file
251
xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreOOPResponse.swift
Executable file
|
@ -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))"
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 = " )
|
||||
}
|
||||
}
|
|
@ -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..<index+6
|
||||
let measurementBytes = Array(body[range])
|
||||
let measurementDate = date.addingTimeInterval(Double(-60 * blockIndex))
|
||||
let measurement = Measurement(bytes: measurementBytes, slope: slope, offset: offset, date: measurementDate, derivedAlgorithmParameterSet: derivedAlgorithmParameterSet)
|
||||
measurements.append(measurement)
|
||||
}
|
||||
return measurements
|
||||
}
|
||||
|
||||
|
||||
private func trendToLibreGlucose(_ measurements: [Measurement]) -> [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
|
||||
}
|
||||
|
|
|
@ -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)"
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue