resolve merge conflicts plus code changes to Libre Web OOP , to make more clean

This commit is contained in:
Johan Degraeve 2019-08-10 00:38:17 +02:00
commit 6ab8fe9653
20 changed files with 1487 additions and 41 deletions

View File

@ -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 */,

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

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

View File

@ -8,7 +8,7 @@
import Foundation
struct SensorSerialNumber: CustomStringConvertible {
struct LibreSensorSerialNumber: CustomStringConvertible {
let uid: Data

View File

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

View File

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