This commit is contained in:
huyan_18040019 2019-08-04 17:19:46 +08:00
commit 6623bfe6dd
22 changed files with 1911 additions and 51 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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