resolve merge conflicts plus code changes to Libre Web OOP , to make more clean
This commit is contained in:
commit
6ab8fe9653
|
@ -105,7 +105,7 @@
|
|||
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 */; };
|
||||
F8A54B0122D9179100934E7A /* LibreSensorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AFE22D9179100934E7A /* LibreSensorState.swift */; };
|
||||
F8A7406E22D9C0E700967CFC /* CGMBluconTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A7406D22D9C0E700967CFC /* CGMBluconTransmitter.swift */; };
|
||||
F8A7407022DBB24800967CFC /* BluconTransmitterOpCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A7406F22DBB24800967CFC /* BluconTransmitterOpCode.swift */; };
|
||||
F8A7407222DCDA3E00967CFC /* BluconUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A7407122DCDA3E00967CFC /* BluconUtilities.swift */; };
|
||||
|
@ -179,7 +179,7 @@
|
|||
F8BDD450221CAA64006EAB84 /* TextsCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8BDD44F221CAA64006EAB84 /* TextsCommon.swift */; };
|
||||
F8BDD452221DEAB2006EAB84 /* TextsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8BDD451221DEAB1006EAB84 /* TextsSettingsView.swift */; };
|
||||
F8BDD455221DEF22006EAB84 /* SettingsViews.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8BDD457221DEF22006EAB84 /* SettingsViews.strings */; };
|
||||
F8C5EBE522F297F000563B5F /* SensorSerialNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C5EBE422F297EF00563B5F /* SensorSerialNumber.swift */; };
|
||||
F8C5EBE522F297F000563B5F /* LibreSensorSerialNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C5EBE422F297EF00563B5F /* LibreSensorSerialNumber.swift */; };
|
||||
F8C5EBE722F38F0E00563B5F /* Trace.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C5EBE622F38F0E00563B5F /* Trace.swift */; };
|
||||
F8C5EBEA22F49AC700563B5F /* CGMDroplet1Transmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C5EBE922F49AC700563B5F /* CGMDroplet1Transmitter.swift */; };
|
||||
F8E3C3AB21FE17B700907A04 /* StringProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E3C3AA21FE17B700907A04 /* StringProtocol.swift */; };
|
||||
|
@ -187,6 +187,14 @@
|
|||
F8EA6C8221B723BC0082976B /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EA6C8121B723BC0082976B /* Date.swift */; };
|
||||
F8EA6CA921BBE3010082976B /* UniqueId.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EA6CA821BBE3010082976B /* UniqueId.swift */; };
|
||||
F8EA6CAD21BC2CA40082976B /* BluetoothTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EA6CAC21BC2CA40082976B /* BluetoothTransmitter.swift */; };
|
||||
F8EEDD4422FD80A600D2D610 /* LibreOOPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD3D22FD80A500D2D610 /* LibreOOPResponse.swift */; };
|
||||
F8EEDD4522FD80A600D2D610 /* LibreOOPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD3E22FD80A600D2D610 /* LibreOOPClient.swift */; };
|
||||
F8EEDD4622FD80A600D2D610 /* LibreMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD3F22FD80A600D2D610 /* LibreMeasurement.swift */; };
|
||||
F8EEDD4722FD80A600D2D610 /* LibreOOPWebExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD4022FD80A600D2D610 /* LibreOOPWebExtensions.swift */; };
|
||||
F8EEDD4922FD80A600D2D610 /* LibreOOPDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD4222FD80A600D2D610 /* LibreOOPDefaults.swift */; };
|
||||
F8EEDD4C22FE224B00D2D610 /* LibreRawGlucoseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD4B22FE224B00D2D610 /* LibreRawGlucoseData.swift */; };
|
||||
F8EEDD4E22FE259D00D2D610 /* LibreDerivedAlgorithmRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD4D22FE259D00D2D610 /* LibreDerivedAlgorithmRunner.swift */; };
|
||||
F8EEDD5022FE25D400D2D610 /* LibreGlucoseSmoothing.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD4F22FE25D400D2D610 /* LibreGlucoseSmoothing.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -293,7 +301,7 @@
|
|||
F8A54AF422D9156600934E7A /* CGMMiaoMiaoTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMMiaoMiaoTransmitter.swift; sourceTree = "<group>"; };
|
||||
F8A54AFC22D9179100934E7A /* CRC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC.swift; sourceTree = "<group>"; };
|
||||
F8A54AFD22D9179100934E7A /* ParseLibreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseLibreData.swift; sourceTree = "<group>"; };
|
||||
F8A54AFE22D9179100934E7A /* SensorState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorState.swift; sourceTree = "<group>"; };
|
||||
F8A54AFE22D9179100934E7A /* LibreSensorState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreSensorState.swift; sourceTree = "<group>"; };
|
||||
F8A54B0A22D9215500934E7A /* xdrip-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "xdrip-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
F8A7406D22D9C0E700967CFC /* CGMBluconTransmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMBluconTransmitter.swift; sourceTree = "<group>"; };
|
||||
F8A7406F22DBB24800967CFC /* BluconTransmitterOpCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluconTransmitterOpCode.swift; sourceTree = "<group>"; };
|
||||
|
@ -480,7 +488,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>"; };
|
||||
F8C5EBE422F297EF00563B5F /* LibreSensorSerialNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreSensorSerialNumber.swift; sourceTree = "<group>"; };
|
||||
F8C5EBE622F38F0E00563B5F /* Trace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Trace.swift; sourceTree = "<group>"; };
|
||||
F8C5EBE922F49AC700563B5F /* CGMDroplet1Transmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMDroplet1Transmitter.swift; sourceTree = "<group>"; };
|
||||
F8C5EBED22F5A52400563B5F /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Common.strings"; sourceTree = "<group>"; };
|
||||
|
@ -490,6 +498,14 @@
|
|||
F8EA6C8121B723BC0082976B /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
|
||||
F8EA6CA821BBE3010082976B /* UniqueId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniqueId.swift; sourceTree = "<group>"; };
|
||||
F8EA6CAC21BC2CA40082976B /* BluetoothTransmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothTransmitter.swift; sourceTree = "<group>"; };
|
||||
F8EEDD3D22FD80A500D2D610 /* LibreOOPResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPResponse.swift; sourceTree = "<group>"; };
|
||||
F8EEDD3E22FD80A600D2D610 /* LibreOOPClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPClient.swift; sourceTree = "<group>"; };
|
||||
F8EEDD3F22FD80A600D2D610 /* LibreMeasurement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreMeasurement.swift; sourceTree = "<group>"; };
|
||||
F8EEDD4022FD80A600D2D610 /* LibreOOPWebExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPWebExtensions.swift; sourceTree = "<group>"; };
|
||||
F8EEDD4222FD80A600D2D610 /* LibreOOPDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPDefaults.swift; sourceTree = "<group>"; };
|
||||
F8EEDD4B22FE224B00D2D610 /* LibreRawGlucoseData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreRawGlucoseData.swift; sourceTree = "<group>"; };
|
||||
F8EEDD4D22FE259D00D2D610 /* LibreDerivedAlgorithmRunner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreDerivedAlgorithmRunner.swift; sourceTree = "<group>"; };
|
||||
F8EEDD4F22FE25D400D2D610 /* LibreGlucoseSmoothing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreGlucoseSmoothing.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -868,10 +884,18 @@
|
|||
F8A54AFB22D9179100934E7A /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F8C5EBE422F297EF00563B5F /* SensorSerialNumber.swift */,
|
||||
F8EEDD4F22FE25D400D2D610 /* LibreGlucoseSmoothing.swift */,
|
||||
F8EEDD4D22FE259D00D2D610 /* LibreDerivedAlgorithmRunner.swift */,
|
||||
F8EEDD3E22FD80A600D2D610 /* LibreOOPClient.swift */,
|
||||
F8EEDD4222FD80A600D2D610 /* LibreOOPDefaults.swift */,
|
||||
F8EEDD3D22FD80A500D2D610 /* LibreOOPResponse.swift */,
|
||||
F8EEDD4022FD80A600D2D610 /* LibreOOPWebExtensions.swift */,
|
||||
F8EEDD3F22FD80A600D2D610 /* LibreMeasurement.swift */,
|
||||
F8C5EBE422F297EF00563B5F /* LibreSensorSerialNumber.swift */,
|
||||
F8A54AFC22D9179100934E7A /* CRC.swift */,
|
||||
F8A54AFD22D9179100934E7A /* ParseLibreData.swift */,
|
||||
F8A54AFE22D9179100934E7A /* SensorState.swift */,
|
||||
F8A54AFE22D9179100934E7A /* LibreSensorState.swift */,
|
||||
F8EEDD4B22FE224B00D2D610 /* LibreRawGlucoseData.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1346,13 +1370,15 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F8BDD450221CAA64006EAB84 /* TextsCommon.swift in Sources */,
|
||||
F8EEDD4E22FE259D00D2D610 /* LibreDerivedAlgorithmRunner.swift in Sources */,
|
||||
F8A54ADF22D911BA00934E7A /* DexcomTransmitterOpCode.swift in Sources */,
|
||||
F81D6D4E22BFC762005EFAE2 /* TextsDexcomShareTestResult.swift in Sources */,
|
||||
F8A54AE922D911BA00934E7A /* AuthChallengeRxMessage.swift in Sources */,
|
||||
F8A54B0122D9179100934E7A /* SensorState.swift in Sources */,
|
||||
F8A54B0122D9179100934E7A /* LibreSensorState.swift in Sources */,
|
||||
F8EA6C8221B723BC0082976B /* Date.swift in Sources */,
|
||||
F8A54AE622D911BA00934E7A /* KeepAliveTxMessage.swift in Sources */,
|
||||
F81FA006228E09D40028C70F /* TextsCalibration.swift in Sources */,
|
||||
F8EEDD4922FD80A600D2D610 /* LibreOOPDefaults.swift in Sources */,
|
||||
F8A54ABA22D9111900934E7A /* RawGlucoseData.swift in Sources */,
|
||||
F8B3A84A227F090E004BA588 /* SettingsViewGeneralSettingsViewModel.swift in Sources */,
|
||||
F8B3A85B2280CCD1004BA588 /* AlertSettingsViewController.swift in Sources */,
|
||||
|
@ -1369,6 +1395,7 @@
|
|||
F85DC2F521CFE3D400B9F74A /* BgReading+CoreDataClass.swift in Sources */,
|
||||
F8A54AEA22D911BA00934E7A /* PairRequestTxMessage.swift in Sources */,
|
||||
F8C5EBEA22F49AC700563B5F /* CGMDroplet1Transmitter.swift in Sources */,
|
||||
F8EEDD4422FD80A600D2D610 /* LibreOOPResponse.swift in Sources */,
|
||||
F821CF56229BF43A005C1E43 /* AlertKind.swift in Sources */,
|
||||
F85DC2ED21CFE2F500B9F74A /* BgReading+CoreDataProperties.swift in Sources */,
|
||||
F8A54AE422D911BA00934E7A /* NSData+CRC.swift in Sources */,
|
||||
|
@ -1392,7 +1419,9 @@
|
|||
F8E3C3AD21FE551C00907A04 /* DexcomCalibrator.swift in Sources */,
|
||||
F821CF61229BF4A2005C1E43 /* NightScoutUploadManager.swift in Sources */,
|
||||
F8A1587122EDC865007F5B5D /* ConstantsSpeakReadingLanguages.swift in Sources */,
|
||||
F8EEDD4722FD80A600D2D610 /* LibreOOPWebExtensions.swift in Sources */,
|
||||
F8A54ADD22D911BA00934E7A /* BatteryStatusTxMessage.swift in Sources */,
|
||||
F8EEDD5022FE25D400D2D610 /* LibreGlucoseSmoothing.swift in Sources */,
|
||||
F8A1585522EDB706007F5B5D /* ConstantsCalibrationAlgorithms.swift in Sources */,
|
||||
F8A54ADA22D911BA00934E7A /* TransmitterMessage.swift in Sources */,
|
||||
F897AAF92200F2D200CDDD10 /* CBPeripheralState.swift in Sources */,
|
||||
|
@ -1406,11 +1435,13 @@
|
|||
F8B3A81D227DEC92004BA588 /* CalibrationsAccessor.swift in Sources */,
|
||||
F8A54AE122D911BA00934E7A /* SensorDataTxMessage.swift in Sources */,
|
||||
F821CF9D22AEF483005C1E43 /* BGReadingSpeaker.swift in Sources */,
|
||||
F8EEDD4622FD80A600D2D610 /* LibreMeasurement.swift in Sources */,
|
||||
F8B3A848227F090E004BA588 /* SettingsViewHealthKitSettingsViewModel.swift in Sources */,
|
||||
F8025E5021EE746400ECF0C0 /* Calibrator.swift in Sources */,
|
||||
F8A54AE722D911BA00934E7A /* FirmwareVersionTxMessage.swift in Sources */,
|
||||
F821CF59229BF43A005C1E43 /* AlertEntryUnit.swift in Sources */,
|
||||
F85DC2F421CFE3D400B9F74A /* Sensor+CoreDataClass.swift in Sources */,
|
||||
F8EEDD4C22FE224B00D2D610 /* LibreRawGlucoseData.swift in Sources */,
|
||||
F8B3A844227F090E004BA588 /* SettingsViewAlertSettingsViewModel.swift in Sources */,
|
||||
F8A54AD822D911BA00934E7A /* CGMG5Transmitter.swift in Sources */,
|
||||
F8A1586322EDB86E007F5B5D /* ConstantsSounds.swift in Sources */,
|
||||
|
@ -1487,8 +1518,9 @@
|
|||
F8A54AB922D9111900934E7A /* CGMTransmitterDelegate.swift in Sources */,
|
||||
F8B3A856227F28DC004BA588 /* AlertTypeSettingsViewController.swift in Sources */,
|
||||
F8A54AE822D911BA00934E7A /* BatteryStatusRxMessage.swift in Sources */,
|
||||
F8EEDD4522FD80A600D2D610 /* LibreOOPClient.swift in Sources */,
|
||||
F8A1584F22ECB281007F5B5D /* SettingsViewInfoViewModel.swift in Sources */,
|
||||
F8C5EBE522F297F000563B5F /* SensorSerialNumber.swift in Sources */,
|
||||
F8C5EBE522F297F000563B5F /* LibreSensorSerialNumber.swift in Sources */,
|
||||
F8B3A845227F090E004BA588 /* SettingsViewDexcomSettingsViewModel.swift in Sources */,
|
||||
F8A1585F22EDB81E007F5B5D /* ConstantsLog.swift in Sources */,
|
||||
F8A1586522EDB89D007F5B5D /* ConstantsDefaultAlertTypeSettings.swift in Sources */,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -108,14 +108,14 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
trace("transmitterID length not 6, init CGMG5Transmitter fails", log: log, type: .error)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
//verify allowed chars
|
||||
let regex = try! NSRegularExpression(pattern: "[a-zA-Z0-9]", options: .caseInsensitive)
|
||||
guard transmitterID.validate(withRegex: regex) else {
|
||||
trace("transmitterID has non-allowed characters a-zA-Z0-9", log: log, type: .error)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// assign addressname and name or expected devicename
|
||||
var newAddressAndName:BluetoothTransmitter.DeviceAddressAndName = BluetoothTransmitter.DeviceAddressAndName.notYetConnected(expectedName: "DEXCOM" + transmitterID[transmitterID.index(transmitterID.startIndex, offsetBy: 4)..<transmitterID.endIndex])
|
||||
if let address = address {
|
||||
|
|
|
@ -127,6 +127,34 @@ enum CGMTransmitterType:String, CaseIterable {
|
|||
}
|
||||
}
|
||||
|
||||
func canWebOOP() -> Bool {
|
||||
|
||||
switch self {
|
||||
|
||||
case .dexcomG4:
|
||||
return false
|
||||
|
||||
case .dexcomG5, .dexcomG6:
|
||||
return false
|
||||
|
||||
case .miaomiao:
|
||||
return false
|
||||
|
||||
case .Bubble:
|
||||
return true
|
||||
|
||||
case .GNSentry:
|
||||
return false
|
||||
|
||||
case .Blucon:
|
||||
return false
|
||||
|
||||
case .Droplet1:
|
||||
return false
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// returns nil if id to validate has expected length and type of characters etc.
|
||||
func validateTransimtterId(idtovalidate:String) -> String? {
|
||||
switch self {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import Foundation
|
||||
|
||||
/// raw glucose as received from transmitter
|
||||
struct RawGlucoseData {
|
||||
//TODO: move this class to other location ?
|
||||
public class RawGlucoseData {
|
||||
|
||||
var timeStamp:Date
|
||||
var glucoseLevelRaw:Double
|
||||
var glucoseLevelFiltered:Double
|
||||
|
@ -13,9 +13,12 @@ struct RawGlucoseData {
|
|||
self.glucoseLevelFiltered = glucoseLevelFiltered
|
||||
}
|
||||
|
||||
init(timeStamp:Date, glucoseLevelRaw:Double) {
|
||||
convenience init(timeStamp:Date, glucoseLevelRaw:Double) {
|
||||
self.init(timeStamp: timeStamp, glucoseLevelRaw: glucoseLevelRaw, glucoseLevelFiltered: glucoseLevelRaw)
|
||||
}
|
||||
|
||||
|
||||
convenience init(timeStamp:Date) {
|
||||
self.init(timeStamp: timeStamp, glucoseLevelRaw: 0.0, glucoseLevelFiltered: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -132,14 +132,16 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C
|
|||
|
||||
// confirm receipt
|
||||
_ = writeDataToPeripheral(data: Data([0x02, 0x00, 0x00, 0x00, 0x00, 0x2B]), type: .withoutResponse)
|
||||
|
||||
case .serialNumber:
|
||||
rxBuffer.append(value.subdata(in: 2..<10))
|
||||
case .dataPacket:
|
||||
rxBuffer.append(value.suffix(from: 4))
|
||||
if rxBuffer.count >= 352 {
|
||||
if (Crc.LibreCrc(data: &rxBuffer, headerOffset: BubbleHeaderLength)) {
|
||||
|
||||
if let sensorSerialNumberData = SensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 5..<13))) {
|
||||
let newSerialNumber = sensorSerialNumberData.serialNumber
|
||||
var newSerialNumber = ""
|
||||
if let sensorSerialNumberData = LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 0..<8))) {
|
||||
newSerialNumber = sensorSerialNumberData.serialNumber
|
||||
|
||||
// verify serial number and if changed inform delegate
|
||||
if newSerialNumber != sensorSerialNumber {
|
||||
|
@ -160,19 +162,21 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
//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: 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
|
||||
if let sensorSerialNumber = sensorSerialNumber, UserDefaults.standard.webOOPEnabled {
|
||||
handleLibreReading(bytes: [UInt8](rxBuffer.subdata(in: 8..<352)), serialNumber: sensorSerialNumber) {
|
||||
[weak self] (result) in
|
||||
if let res = result {
|
||||
self?.handleGlucoseData(result: res)
|
||||
} else {
|
||||
/// failed, use parseLibreData
|
||||
self?.parse()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// use local parser
|
||||
self.parse()
|
||||
}
|
||||
|
||||
//reset the buffer
|
||||
resetRxBuffer()
|
||||
}
|
||||
}
|
||||
case .noSensor:
|
||||
|
@ -205,12 +209,34 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C
|
|||
startDate = Date()
|
||||
resendPacketCounter = 0
|
||||
}
|
||||
|
||||
/// get readings from rxBuffer and send to delegate
|
||||
private func parse() {
|
||||
//get readings from buffer and send to delegate
|
||||
let result = parseLibreData(data: &rxBuffer, timeStampLastBgReadingStoredInDatabase: timeStampLastBgReading, headerOffset: BubbleHeaderLength)
|
||||
//TODO: sort glucosedata before calling newReadingsReceived
|
||||
handleGlucoseData(result: result)
|
||||
}
|
||||
|
||||
private func handleGlucoseData(result: (glucoseData:[RawGlucoseData], sensorState:LibreSensorState, sensorTimeInMinutes:Int)) {
|
||||
var result = result
|
||||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: nil, sensorState: result.sensorState, sensorTimeInMinutes: result.sensorTimeInMinutes, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)
|
||||
|
||||
//set timeStampLastBgReading to timestamp of latest reading in the response so that next time we parse only the more recent readings
|
||||
if result.glucoseData.count > 0 {
|
||||
timeStampLastBgReading = result.glucoseData[0].timeStamp
|
||||
}
|
||||
//reset the buffer
|
||||
resetRxBuffer()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fileprivate enum BubbleResponseType: UInt8 {
|
||||
case dataPacket = 130
|
||||
case dataInfo = 128
|
||||
case noSensor = 191
|
||||
case serialNumber = 192
|
||||
}
|
||||
|
||||
extension BubbleResponseType: CustomStringConvertible {
|
||||
|
@ -222,6 +248,8 @@ extension BubbleResponseType: CustomStringConvertible {
|
|||
return "No sensor detected"
|
||||
case .dataInfo:
|
||||
return "Data info received"
|
||||
case .serialNumber:
|
||||
return "serial number received"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ 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 ?? "-"
|
||||
let serialNumber = LibreSensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 5..<13)))?.serialNumber ?? "-"
|
||||
debuglogging("serialNumber = " + serialNumber)
|
||||
|
||||
//get readings from buffer and send to delegate
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
//
|
||||
// LibreDerivedAlgorithmRunner.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 LibreDerivedAlgorithmParameters: 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 "LibreDerivedAlgorithmParameters:: 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 LibreDerivedAlgorithmRunner{
|
||||
private var params: LibreDerivedAlgorithmParameters
|
||||
init(_ params:LibreDerivedAlgorithmParameters) {
|
||||
self.params = params
|
||||
}
|
||||
/* Result:
|
||||
Parameters
|
||||
slope1: 0.09130434782608696
|
||||
offset1: -20.913043478260875
|
||||
slope2: 0.11130434782608696
|
||||
offset2: -20.913043478260875
|
||||
|
||||
slope_slope: 1.5290519877675845e-05
|
||||
slope_offset: -0.0
|
||||
offset_slope: 0.0023746842175242366
|
||||
offset_offset: -20.913043478260875
|
||||
|
||||
*/
|
||||
|
||||
|
||||
// These three functions should be implemented by the client
|
||||
// wanting to do offline calculations
|
||||
// of glucose
|
||||
private func slopefunc(raw_temp: Int) -> Double{
|
||||
|
||||
return self.params.slope_slope * Double(raw_temp) + self.params.offset_slope
|
||||
// rawglucose 7124: 0.1130434605
|
||||
//0.00001562292 * 7124 + 0.0017457784869033700
|
||||
|
||||
// rawglucose 5816: 0.0926086812
|
||||
//0.00001562292 * 5816 + 0.0017457784869033700
|
||||
|
||||
|
||||
}
|
||||
|
||||
private func offsetfunc(raw_temp: Int) -> Double{
|
||||
return self.params.slope_offset * Double(raw_temp) + self.params.offset_offset
|
||||
//rawglucose 7124: -21.1304349
|
||||
//-0.00023267185 * 7124 + -19.4728806406
|
||||
// rawglucose 5816: -20.8261001202
|
||||
//-0.00023267185 * 5816 + -19.4728806406
|
||||
}
|
||||
|
||||
|
||||
public func GetGlucoseValue(from_raw_glucose raw_glucose: Int, raw_temp: Int) -> Double{
|
||||
return self.slopefunc(raw_temp: raw_temp) * Double(raw_glucose) + self.offsetfunc(raw_temp: raw_temp)
|
||||
}
|
||||
|
||||
private func serializeAlgorithmParameters(_ params: LibreDerivedAlgorithmParameters) -> String{
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
|
||||
var ret = ""
|
||||
|
||||
do {
|
||||
let jsonData = try encoder.encode(params)
|
||||
|
||||
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
||||
ret = jsonString
|
||||
}
|
||||
} catch {
|
||||
print("Could not serialize parameters: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
public func SaveAlgorithmParameters(){
|
||||
let fm = FileManager.default
|
||||
|
||||
|
||||
guard let dir = fm.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||
print ("cannot construct url dir for writing parameters")
|
||||
return
|
||||
}
|
||||
|
||||
let fileUrl = dir.appendingPathComponent("LibreParamsForCurrentSensor").appendingPathExtension("txt")
|
||||
|
||||
print("Saving algorithm parameters to \(fileUrl.path)")
|
||||
|
||||
do{
|
||||
try serializeAlgorithmParameters(params).write(to: fileUrl, atomically: true, encoding: String.Encoding.utf8)
|
||||
} catch let error as NSError {
|
||||
print("Error: fileUrl failed to write to \(fileUrl.path): \n\(error)" )
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
public static func CreateInstanceFromParamsFile() -> LibreDerivedAlgorithmRunner?{
|
||||
let fm = FileManager.default
|
||||
|
||||
guard let dir = fm.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||
print ("cannot construct url dir for writing parameters")
|
||||
return nil
|
||||
}
|
||||
|
||||
let fileUrl = dir.appendingPathComponent("LibreParamsForCurrentSensor").appendingPathExtension("txt")
|
||||
let text: String
|
||||
do{
|
||||
text = try String(contentsOf: fileUrl, encoding: .utf8)
|
||||
} catch {
|
||||
print("")
|
||||
return nil
|
||||
}
|
||||
|
||||
if let jsonData = text.data(using: .utf8) {
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
do {
|
||||
let params = try decoder.decode(LibreDerivedAlgorithmParameters.self, from: jsonData)
|
||||
|
||||
return LibreDerivedAlgorithmRunner(params)
|
||||
} catch {
|
||||
print("Could not create instance: \(error.localizedDescription)")
|
||||
}
|
||||
} else {
|
||||
print("Did not create instance")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
class LibreGlucoseSmoothing {
|
||||
|
||||
public static func CalculateSmothedData5Points(origtrends: [LibreRawGlucoseData]) -> [LibreRawGlucoseData] {
|
||||
// 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
|
||||
}
|
||||
|
||||
}
|
||||
|
163
xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreMeasurement.swift
Executable file
163
xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/LibreMeasurement.swift
Executable 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 LibreMeasurement {
|
||||
/// 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: LibreDerivedAlgorithmParameters?
|
||||
|
||||
|
||||
///
|
||||
/// - 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, LibreDerivedAlgorithmParameterSet: LibreDerivedAlgorithmParameters? = 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 = LibreDerivedAlgorithmParameterSet
|
||||
if let LibreDerivedAlgorithmParameterSet = self.temperatureAlgorithmParameterSet {
|
||||
self.oopSlope = LibreDerivedAlgorithmParameterSet.slope_slope * Double(rawTemperature) + LibreDerivedAlgorithmParameterSet.offset_slope
|
||||
self.oopOffset = LibreDerivedAlgorithmParameterSet.slope_offset * Double(rawTemperature) + LibreDerivedAlgorithmParameterSet.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 * LibreDerivedAlgorithmParameterSet.extraSlope + LibreDerivedAlgorithmParameterSet.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 = " )
|
||||
}
|
||||
}
|
|
@ -0,0 +1,387 @@
|
|||
////
|
||||
//// RemoteBG.swift
|
||||
//// SwitftOOPWeb
|
||||
////
|
||||
//// Created by Bjørn Inge Berg on 08.04.2018.
|
||||
//// Copyright © 2018 Bjørn Inge Berg. All rights reserved.
|
||||
////
|
||||
//
|
||||
//
|
||||
// LibreOOPClient.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
|
||||
private static let filePath: String = NSHomeDirectory() + "/Documents/paras"
|
||||
|
||||
init(accessToken: String, site: String = "https://libreoopweb.azurewebsites.net") {
|
||||
self.accessToken = accessToken
|
||||
self.uploadEndpoint = site + "/api/CreateRequestAsync"
|
||||
self.statusEndpoint = site + "/api/GetStatus"
|
||||
self.calibrationEndpoint = site + "/api/CreateCalibrationRequestAsync"
|
||||
self.calibrationStatusEndpoint = site + "/api/GetCalibrationStatus"
|
||||
}
|
||||
|
||||
private static 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()
|
||||
|
||||
}
|
||||
|
||||
private func getStatusIntervalled(uuid: String, intervalSeconds: UInt32=10, maxTries: Int8=8, _ completion:@escaping (( _ success: Bool, _ message: String, _ oopCurrentValue: OOPCurrentValue?, _ newState: String) -> Void)) {
|
||||
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
var oopCurrentValue: OOPCurrentValue? = nil
|
||||
var succeeded = false
|
||||
var error = ""
|
||||
var newState2 = ""
|
||||
|
||||
DispatchQueue.global().async {
|
||||
for i in 1...maxTries {
|
||||
NSLog("Attempt \(i): Waiting \(intervalSeconds) seconds before calling getstatus")
|
||||
sleep(intervalSeconds)
|
||||
NSLog("Finished waiting \(intervalSeconds) seconds before calling getstatus")
|
||||
if (succeeded) {
|
||||
error = ""
|
||||
break
|
||||
|
||||
}
|
||||
self.getStatus(uuid: uuid, { (success, errormsg, response, newState) in
|
||||
if (success) {
|
||||
succeeded = true
|
||||
newState2 = newState ?? ""
|
||||
oopCurrentValue = self.getOOPCurrentValue(from: response)
|
||||
} else {
|
||||
error = errormsg
|
||||
}
|
||||
sem.signal()
|
||||
})
|
||||
|
||||
sem.wait()
|
||||
|
||||
/*if let oopCurrentValue = oopCurrentValue {
|
||||
|
||||
NSLog("Hey hop, response received with success: \(succeeded)");
|
||||
NSLog("Decoded content")
|
||||
NSLog(" Current trend: \(oopCurrentValue.currentTrend)")
|
||||
NSLog(" Current bg: \(oopCurrentValue.currentBg)")
|
||||
NSLog(" Current time: \(oopCurrentValue.currentTime)")
|
||||
NSLog(" Serial Number: \(oopCurrentValue.serialNumber ?? "-")")
|
||||
NSLog(" timeStamp: \(oopCurrentValue.timestamp)")
|
||||
var i = 0
|
||||
for historyValue in oopCurrentValue.historyValues {
|
||||
NSLog(String(format: " #%02d: time: \(historyValue.time), quality: \(historyValue.quality), bg: \(historyValue.bg)", i))
|
||||
i += 1
|
||||
}
|
||||
}*/
|
||||
|
||||
if (succeeded) {
|
||||
error = ""
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
completion(succeeded, error, oopCurrentValue, newState2)
|
||||
}
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
private func uploadReading(reading: String, oldState: String?=nil, sensorStartTimestamp: Int?=nil, sensorScanTimestamp: Int?=nil, currentUtcOffset: Int?=nil, _ completion:@escaping (( _ resp: LibreOOPResponse?, _ success: Bool, _ errorMessage: String) -> Void)) {
|
||||
var postParams = ["accesstoken": self.accessToken, "b64contents": reading]
|
||||
|
||||
if let oldState = oldState {
|
||||
postParams["oldState"] = oldState
|
||||
}
|
||||
|
||||
if let sensorStartTimestamp = sensorStartTimestamp {
|
||||
postParams["sensorStartTimestamp"] = "\(sensorStartTimestamp)"
|
||||
}
|
||||
|
||||
if let sensorScanTimestamp = sensorScanTimestamp {
|
||||
postParams["sensorScanTimestamp"] = "\(sensorScanTimestamp)"
|
||||
}
|
||||
|
||||
if let currentUtcOffset = currentUtcOffset {
|
||||
postParams["currentUtcOffset"] = "\(currentUtcOffset)"
|
||||
}
|
||||
|
||||
postToServer({ (data, _, success) in
|
||||
|
||||
if(!success) {
|
||||
|
||||
completion(nil, false, "network error!?")
|
||||
return
|
||||
}
|
||||
let decoder = JSONDecoder()
|
||||
do {
|
||||
let result = try decoder.decode(LibreOOPResponse.self, from: data)
|
||||
if let msg = result.message {
|
||||
|
||||
completion(nil, false, msg)
|
||||
return
|
||||
}
|
||||
|
||||
completion(result, true, "")
|
||||
return
|
||||
|
||||
} catch let error as NSError {
|
||||
completion(nil, false, error.localizedDescription)
|
||||
return
|
||||
}
|
||||
|
||||
}, postURL: uploadEndpoint, postparams: postParams)
|
||||
}
|
||||
|
||||
private func uploadDependantReadings(readings: [LibreReadingResult]) -> [(success: Bool, String, OOPCurrentValue?, String)]? {
|
||||
var ret = [(Bool, String, OOPCurrentValue?, String)]()
|
||||
|
||||
var prevReading: LibreReadingResult? = nil
|
||||
|
||||
for (_, var reading) in readings.enumerated() {
|
||||
|
||||
//the semaphore lets me do the requests in-order
|
||||
let awaiter = DispatchSemaphore( value: 0 )
|
||||
|
||||
let tempState = prevReading?.newState ?? LibreOOPDefaults.defaultState
|
||||
self.uploadReading(reading: reading.b64Contents, oldState: tempState, sensorStartTimestamp: LibreOOPDefaults.sensorStartTimestamp, sensorScanTimestamp: LibreOOPDefaults.sensorScanTimestamp, currentUtcOffset: LibreOOPDefaults.currentUtcOffset) { (response, success, errormessage) in
|
||||
if(!success) {
|
||||
NSLog("remote: upload reading failed! \(errormessage)")
|
||||
ret.append((success, errormessage, nil, ""))
|
||||
awaiter.signal()
|
||||
return
|
||||
}
|
||||
|
||||
if let response = response, let uuid = response.result?.uuid {
|
||||
print("uuid received: " + uuid)
|
||||
self.getStatusIntervalled(uuid: uuid, { (success, errormessage, oopCurrentValue, newState) in
|
||||
if let oopCurrentValue = oopCurrentValue {
|
||||
ret.append((success, errormessage, oopCurrentValue, newState))
|
||||
reading.newState = newState
|
||||
prevReading = reading
|
||||
|
||||
}
|
||||
awaiter.signal()
|
||||
})
|
||||
} else {
|
||||
awaiter.signal()
|
||||
}
|
||||
|
||||
}
|
||||
awaiter.wait()
|
||||
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
private func getCalibrationStatus(uuid: String, _ completion:@escaping (( _ success: Bool, _ message: String, _ response: LibreDerivedAlgorithmParameters?) -> Void)) {
|
||||
postToServer({ (data, response, success) in
|
||||
NSLog("getCalibrationStatus here:" + response + ", data: \(data)")
|
||||
if(!success) {
|
||||
NSLog("getCalibrationStatus failed")
|
||||
completion(false, response, nil)
|
||||
return
|
||||
}
|
||||
let decoder = JSONDecoder()
|
||||
do {
|
||||
let response = try decoder.decode(GetCalibrationStatus.self, from: data)
|
||||
|
||||
NSLog("getCalibrationStatus result received")
|
||||
|
||||
if response.error ?? false {
|
||||
completion(false, "getCalibrationStatus failes due to error", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if let slope = response.result, slope.status == "complete" {
|
||||
print("calibration ready")
|
||||
|
||||
let params = LibreDerivedAlgorithmParameters.init(slope_slope: slope.slopeSlope ?? 0, slope_offset: slope.slopeOffset ?? 0, offset_slope: slope.offsetSlope ?? 0, offset_offset: slope.offsetOffset ?? 0, isValidForFooterWithReverseCRCs: Int(slope.isValidForFooterWithReverseCRCs ?? 1), extraSlope: 1.0, extraOffset: 0.0)
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
private static func post(bytes: [UInt8],_ completion:@escaping (( _ data_: Data, _ response: String, _ success: Bool ) -> Void)) {
|
||||
let date = Int(Date().timeIntervalSince1970 * 1000)
|
||||
let bytesAsData = Data(bytes: bytes, count: bytes.count)
|
||||
let json: [String: String] = [
|
||||
"token": "bubble-201907",
|
||||
"content": "\(bytesAsData.hexEncodedString())",
|
||||
"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 static func save(data: Data) {
|
||||
let url = URL.init(fileURLWithPath: filePath)
|
||||
do {
|
||||
try data.write(to: url)
|
||||
} catch {
|
||||
print("write error:", error)
|
||||
}
|
||||
}
|
||||
|
||||
public static func calibrateSensor(bytes: [UInt8], serialNumber: String, callback: @escaping (LibreDerivedAlgorithmParameters?) -> Void) {
|
||||
let url = URL.init(fileURLWithPath: filePath)
|
||||
if FileManager.default.fileExists(atPath: url.path) {
|
||||
let decoder = JSONDecoder()
|
||||
do {
|
||||
let data = try Data.init(contentsOf: url)
|
||||
let response = try decoder.decode(LibreDerivedAlgorithmParameters.self, from: data)
|
||||
if response.serialNumber == serialNumber {
|
||||
callback(response)
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
print("decoder error:", error)
|
||||
}
|
||||
}
|
||||
|
||||
post(bytes: bytes, { (data, str, can) in
|
||||
let decoder = JSONDecoder()
|
||||
do {
|
||||
let response = try decoder.decode(GetCalibrationStatus.self, from: data)
|
||||
if let slope = response.slope {
|
||||
var para = LibreDerivedAlgorithmParameters.init(slope_slope: slope.slopeSlope ?? 0, slope_offset: slope.slopeOffset ?? 0, offset_slope: slope.offsetSlope ?? 0, offset_offset: slope.offsetOffset ?? 0, isValidForFooterWithReverseCRCs: Int(slope.isValidForFooterWithReverseCRCs ?? 1), extraSlope: 1.0, extraOffset: 0.0)
|
||||
para.serialNumber = serialNumber
|
||||
do {
|
||||
let data = try JSONEncoder().encode(para)
|
||||
save(data: data)
|
||||
} catch {
|
||||
print("encoder error:", error)
|
||||
}
|
||||
callback(para)
|
||||
} else {
|
||||
callback(nil)
|
||||
}
|
||||
} catch {
|
||||
print("got error trying to decode GetCalibrationStatus")
|
||||
callback(nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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,22 @@
|
|||
import Foundation
|
||||
|
||||
/// extends RawGlucoseData and adds property unsmoothedGlucose, because this is only used for Libre
|
||||
class LibreRawGlucoseData: RawGlucoseData {
|
||||
|
||||
var unsmoothedGlucose: Double
|
||||
|
||||
init(timeStamp:Date, glucoseLevelRaw:Double, glucoseLevelFiltered:Double, unsmoothedGlucose: Double = 0.0) {
|
||||
self.unsmoothedGlucose = unsmoothedGlucose
|
||||
|
||||
super.init(timeStamp: timeStamp, glucoseLevelRaw: glucoseLevelRaw, glucoseLevelFiltered: glucoseLevelFiltered)
|
||||
}
|
||||
|
||||
convenience init(timeStamp:Date, glucoseLevelRaw:Double) {
|
||||
self.init(timeStamp: timeStamp, glucoseLevelRaw: glucoseLevelRaw, glucoseLevelFiltered: glucoseLevelRaw)
|
||||
}
|
||||
|
||||
convenience init(timeStamp:Date, unsmoothedGlucose: Double) {
|
||||
self.init(timeStamp: timeStamp, glucoseLevelRaw: 0.0, glucoseLevelFiltered: 0.0, unsmoothedGlucose: unsmoothedGlucose)
|
||||
}
|
||||
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
struct SensorSerialNumber: CustomStringConvertible {
|
||||
struct LibreSensorSerialNumber: CustomStringConvertible {
|
||||
|
||||
let uid: Data
|
||||
|
|
@ -88,3 +88,78 @@ fileprivate func getGlucoseRaw(bytes:Data) -> Int {
|
|||
return ((256 * (getByteAt(buffer: bytes, position: 0) & 0xFF) + (getByteAt(buffer: bytes, position: 1) & 0xFF)) & 0x1FFF)
|
||||
}
|
||||
|
||||
/// calls web service to get calibrated reading
|
||||
func handleLibreReading(bytes: [UInt8], serialNumber: String, _ callback: @escaping ((glucoseData: [RawGlucoseData], sensorState: LibreSensorState, sensorTimeInMinutes: Int)?) -> Void) {
|
||||
//only care about the once per minute readings here, historical data will not be considered
|
||||
let sensorState = LibreSensorState(stateByte: bytes[4])
|
||||
LibreOOPClient.calibrateSensor(bytes: bytes, serialNumber: serialNumber) {
|
||||
(calibrationparams) in
|
||||
guard let params = calibrationparams else {
|
||||
NSLog("dabear:: could not calibrate sensor, check libreoopweb permissions and internet connection")
|
||||
callback(nil)
|
||||
return
|
||||
}
|
||||
//here we assume success, data is not changed,
|
||||
//and we trust that the remote endpoint returns correct data for the sensor
|
||||
let last16 = trendMeasurements(bytes: bytes, date: Date(), LibreDerivedAlgorithmParameterSet: params)
|
||||
if let glucoseData = trendToLibreGlucose(last16) {
|
||||
callback((glucoseData, sensorState, 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func trendMeasurements(bytes: [UInt8], date: Date, _ offset: Double = 0.0, slope: Double = 0.1, LibreDerivedAlgorithmParameterSet: LibreDerivedAlgorithmParameters?) -> [LibreMeasurement] {
|
||||
|
||||
// let headerRange = 0..<24 // 24 bytes, i.e. 3 blocks a 8 bytes
|
||||
let bodyRange = 24..<320 // 296 bytes, i.e. 37 blocks a 8 bytes
|
||||
// let footerRange = 320..<344 // 24 bytes, i.e. 3 blocks a 8 bytes
|
||||
|
||||
let body = Array(bytes[bodyRange])
|
||||
let nextTrendBlock = Int(body[2])
|
||||
|
||||
var measurements = [LibreMeasurement]()
|
||||
// Trend data is stored in body from byte 4 to byte 4+96=100 in units of 6 bytes. Index on data such that most recent block is first.
|
||||
for blockIndex in 0...15 {
|
||||
var index = 4 + (nextTrendBlock - 1 - blockIndex) * 6 // runs backwards
|
||||
if index < 4 {
|
||||
index = index + 96 // if end of ring buffer is reached shift to beginning of ring buffer
|
||||
}
|
||||
let range = index..<index+6
|
||||
let measurementBytes = Array(body[range])
|
||||
let measurementDate = date.addingTimeInterval(Double(-60 * blockIndex))
|
||||
let measurement = LibreMeasurement(bytes: measurementBytes, slope: slope, offset: offset, date: measurementDate, LibreDerivedAlgorithmParameterSet: LibreDerivedAlgorithmParameterSet)
|
||||
measurements.append(measurement)
|
||||
}
|
||||
return measurements
|
||||
}
|
||||
|
||||
|
||||
fileprivate func trendToLibreGlucose(_ measurements: [LibreMeasurement]) -> [LibreRawGlucoseData]?{
|
||||
var origarr = [LibreRawGlucoseData]()
|
||||
|
||||
//whether or not to return all the 16 latest trends or just every fifth element
|
||||
let returnAllTrends = true
|
||||
|
||||
for trend in measurements {
|
||||
let glucose = LibreRawGlucoseData.init(timeStamp: trend.date, unsmoothedGlucose: trend.temperatureAlgorithmGlucose)
|
||||
origarr.append(glucose)
|
||||
}
|
||||
//NSLog("dabear:: glucose samples before smoothing: \(String(describing: origarr))")
|
||||
var arr : [LibreRawGlucoseData]
|
||||
arr = LibreGlucoseSmoothing.CalculateSmothedData5Points(origtrends: origarr)
|
||||
|
||||
|
||||
|
||||
for i in 0 ..< arr.count {
|
||||
var trend = arr[i]
|
||||
//we know that the array "always" (almost) will contain 16 entries
|
||||
//the last five entries will get a trend arrow of flat, because it's not computable when we don't have
|
||||
//more entries in the array to base it on
|
||||
}
|
||||
|
||||
if returnAllTrends {
|
||||
return arr
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ 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
|
||||
/// is webOOP enabled or not
|
||||
case webOOP = 3
|
||||
}
|
||||
|
||||
/// conforms to SettingsViewModelProtocol for all transmitter settings in the first sections screen
|
||||
|
@ -26,7 +28,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 +70,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 +89,25 @@ 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
|
||||
}
|
||||
|
||||
// for now WebOOP is only for transmitters that don't need transmitterId and no reset possible.
|
||||
// So for those transmitters, if canWebOOP, then amount of rows = 2
|
||||
// Needs adaptation in case we would enable webOOP for transmitters with transmitterId, like Blucon
|
||||
if transmitterType.canWebOOP() {
|
||||
count = 2
|
||||
}
|
||||
|
||||
return count
|
||||
} else {
|
||||
// transmitterType nil, means this is initial setup, no need to show transmitter id field
|
||||
return 1
|
||||
|
@ -101,7 +115,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 +128,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 +144,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 +161,36 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - private helper functions
|
||||
|
||||
/// if it's a transmitterType that canWebOOP, then when user clicks second row (ie index = 1), then fix to 3 is done
|
||||
private func fixWebOOPIndex(_ index: Int) -> Int {
|
||||
|
||||
var index = index
|
||||
|
||||
if let transmitterType = UserDefaults.standard.transmitterType {
|
||||
if transmitterType.canWebOOP() && index == 1 {
|
||||
index = 3
|
||||
}
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
/// sets UserDefaults.standard.transmitterId with valud of id
|
||||
|
|
Loading…
Reference in New Issue