add remark
This commit is contained in:
parent
14cb8caa60
commit
bd2b79f0d1
|
@ -295,7 +295,6 @@
|
|||
F8F9721D23A5915900C3F17D /* LibreDerivedAlgorithmRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E323A5915900C3F17D /* LibreDerivedAlgorithmRunner.swift */; };
|
||||
F8F9721E23A5915900C3F17D /* LibreSensorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E423A5915900C3F17D /* LibreSensorState.swift */; };
|
||||
F8F9721F23A5915900C3F17D /* LibreOOPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E523A5915900C3F17D /* LibreOOPClient.swift */; };
|
||||
F8F9722023A5915900C3F17D /* LibreGlucoseSmoothing.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E623A5915900C3F17D /* LibreGlucoseSmoothing.swift */; };
|
||||
F8F9722123A5915900C3F17D /* LibreSensorSerialNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E723A5915900C3F17D /* LibreSensorSerialNumber.swift */; };
|
||||
F8F9722223A5915900C3F17D /* CRC.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E823A5915900C3F17D /* CRC.swift */; };
|
||||
F8F9722323A5915900C3F17D /* LibreDataParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F971E923A5915900C3F17D /* LibreDataParser.swift */; };
|
||||
|
@ -711,7 +710,6 @@
|
|||
F8F971E323A5915900C3F17D /* LibreDerivedAlgorithmRunner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreDerivedAlgorithmRunner.swift; sourceTree = "<group>"; };
|
||||
F8F971E423A5915900C3F17D /* LibreSensorState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreSensorState.swift; sourceTree = "<group>"; };
|
||||
F8F971E523A5915900C3F17D /* LibreOOPClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreOOPClient.swift; sourceTree = "<group>"; };
|
||||
F8F971E623A5915900C3F17D /* LibreGlucoseSmoothing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreGlucoseSmoothing.swift; sourceTree = "<group>"; };
|
||||
F8F971E723A5915900C3F17D /* LibreSensorSerialNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreSensorSerialNumber.swift; sourceTree = "<group>"; };
|
||||
F8F971E823A5915900C3F17D /* CRC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC.swift; sourceTree = "<group>"; };
|
||||
F8F971E923A5915900C3F17D /* LibreDataParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreDataParser.swift; sourceTree = "<group>"; };
|
||||
|
@ -1831,7 +1829,6 @@
|
|||
F8F971E323A5915900C3F17D /* LibreDerivedAlgorithmRunner.swift */,
|
||||
F8F971E423A5915900C3F17D /* LibreSensorState.swift */,
|
||||
F8F971E523A5915900C3F17D /* LibreOOPClient.swift */,
|
||||
F8F971E623A5915900C3F17D /* LibreGlucoseSmoothing.swift */,
|
||||
F8F971E723A5915900C3F17D /* LibreSensorSerialNumber.swift */,
|
||||
F8F971E823A5915900C3F17D /* CRC.swift */,
|
||||
F8F971E923A5915900C3F17D /* LibreDataParser.swift */,
|
||||
|
@ -2366,7 +2363,6 @@
|
|||
F808D2D2240329E80084B5DB /* Bubble+BluetoothPeripheral.swift in Sources */,
|
||||
F8025C1321DA683400ECF0C0 /* Data.swift in Sources */,
|
||||
F825286A2443AE190067AF77 /* DexcomG6BluetoothPeripheralViewModel.swift in Sources */,
|
||||
F8F9722023A5915900C3F17D /* LibreGlucoseSmoothing.swift in Sources */,
|
||||
F80859272364355F00F3829D /* ConstantsGlucoseChart.swift in Sources */,
|
||||
F825286E2443C1000067AF77 /* BluetoothPeripheralManager+CGMG6TransmitterDelegate.swift in Sources */,
|
||||
F816E10324367389009EE65B /* GNSEntryBluetoothPeripheralViewModel.swift in Sources */,
|
||||
|
|
|
@ -86,6 +86,8 @@ class LibreDataParser {
|
|||
/// Function which groups common functionality used for transmitters that support the 344 Libre block. It checks if webOOP is enabled, if yes tries to use the webOOP, response is processed and delegate is called. If webOOP is not enabled, then local parsing is done.
|
||||
/// - parameters:
|
||||
/// - sensorSerialNumber : if nil, then webOOP will not be used and local parsing will be done
|
||||
/// - patchUid : sensor sn hex string
|
||||
/// - patchInfo : will be used by server to out the glucose data
|
||||
/// - libreData : the 344 bytes from Libre sensor
|
||||
/// - timeStampLastBgReading : timestamp of last reading, older readings will be ignored
|
||||
/// - webOOPEnabled : is webOOP enabled or not, if not enabled, local parsing is used
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
// adapted by Johan Degraeve for xdrip ios
|
||||
import Foundation
|
||||
|
||||
|
||||
/// local algorithm use this
|
||||
public struct LibreDerivedAlgorithmParameters: Codable, CustomStringConvertible {
|
||||
public var slope_slope: Double
|
||||
public var slope_offset: Double
|
||||
|
@ -18,6 +20,7 @@ public struct LibreDerivedAlgorithmParameters: Codable, CustomStringConvertible
|
|||
public var extraOffset: Double = 0
|
||||
public var serialNumber: String
|
||||
|
||||
/// if values all 0, is wrong parameters
|
||||
var isErrorParameters: Bool {
|
||||
return slope_slope == 0 &&
|
||||
slope_offset == 0 &&
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
//
|
||||
// GlucoseSmoothing.swift
|
||||
// BubbleClientUI
|
||||
//
|
||||
// Created by Bjørn Inge Berg on 25/03/2019.
|
||||
// Copyright © 2019 Mark Wilson. All rights reserved.
|
||||
//
|
||||
// adapted by Johan Degraeve for xdrip ios
|
||||
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).
|
||||
|
||||
let 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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -61,6 +61,7 @@ struct LibreMeasurement {
|
|||
self.date = date
|
||||
self.counter = minuteCounter
|
||||
|
||||
// local algorithm
|
||||
self.temperatureAlgorithmParameterSet = LibreDerivedAlgorithmParameterSet
|
||||
if let LibreDerivedAlgorithmParameterSet = self.temperatureAlgorithmParameterSet {
|
||||
self.oopSlope = LibreDerivedAlgorithmParameterSet.slope_slope * Double(rawTemperature) + LibreDerivedAlgorithmParameterSet.offset_slope
|
||||
|
|
|
@ -24,7 +24,15 @@ import UserNotifications
|
|||
public class LibreOOPClient {
|
||||
private static let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryLibreOOPClient)
|
||||
|
||||
// MARK: - public functions
|
||||
|
||||
/// get the libre glucose data by server
|
||||
/// - Parameters:
|
||||
/// - libreData: the 344 bytes from Libre sensor
|
||||
/// - patchUid : sensor sn hex string
|
||||
/// - patchInfo : will be used by server to out the glucose data
|
||||
/// - oopWebSite: the site url to use if oop web would be enabled
|
||||
/// - oopWebToken: the token to use if oop web would be enabled
|
||||
/// - callback: server data that contains the 344 bytes details
|
||||
static func webOOP(libreData: [UInt8], patchUid: String, patchInfo: String, oopWebSite: String, oopWebToken: String, callback: ((LibreRawGlucoseOOPData?) -> Void)?) {
|
||||
let bytesAsData = Data(bytes: libreData, count: libreData.count)
|
||||
let item = URLQueryItem(name: "accesstoken", value: oopWebToken)
|
||||
|
@ -59,28 +67,48 @@ public class LibreOOPClient {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// if server failed, will parse the 344 bytes by `LibreMeasurement`
|
||||
/// - Parameters:
|
||||
/// - libreData: the 344 bytes from Libre sensor
|
||||
/// - params: local algorithm use this
|
||||
/// - timeStampLastBgReading: timestamp of last reading, older readings will be ignored
|
||||
/// - Returns: glucose data
|
||||
static func oopParams(libreData: [UInt8], params: LibreDerivedAlgorithmParameters, timeStampLastBgReading: Date) -> [LibreRawGlucoseData] {
|
||||
// get current glucose from 344 bytes
|
||||
let last16 = trendMeasurements(bytes: libreData, date: Date(), timeStampLastBgReading: timeStampLastBgReading, LibreDerivedAlgorithmParameterSet: params)
|
||||
if var glucoseData = trendToLibreGlucose(last16), let first = glucoseData.first {
|
||||
if let glucoseData = trendToLibreGlucose(last16), let first = glucoseData.first {
|
||||
// get histories from 344 bytes
|
||||
let last32 = historyMeasurements(bytes: libreData, date: first.timeStamp, LibreDerivedAlgorithmParameterSet: params)
|
||||
// every 15 minutes apart
|
||||
let glucose32 = trendToLibreGlucose(last32) ?? []
|
||||
// every 5 minutes apart, fill data by `SKKeyframeSequence`
|
||||
let last96 = split(current: first, glucoseData: glucose32.reversed())
|
||||
glucoseData = last96
|
||||
return glucoseData
|
||||
return last96
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/// get the parameters and local parse
|
||||
/// - Parameters:
|
||||
/// - libreData: the 344 bytes from Libre sensor
|
||||
/// - serialNumber: sensor serial number
|
||||
/// - timeStampLastBgReading: timestamp of last reading, older readings will be ignored
|
||||
/// - oopWebSite: the site url to use if oop web would be enabled
|
||||
/// - oopWebToken: the token to use if oop web would be enabled
|
||||
/// - callback: will be called when glucose data is read with as parameter the timestamp of the last reading.
|
||||
static func oop(libreData: [UInt8], serialNumber: String, timeStampLastBgReading: Date, oopWebSite: String, oopWebToken: String, _ callback: @escaping ((glucoseData: [GlucoseData], sensorState: LibreSensorState, sensorTimeInMinutes: Int, errorDescription: String?)) -> Void) {
|
||||
let sensorState = LibreSensorState(stateByte: libreData[4])
|
||||
let body = Array(libreData[24 ..< 320])
|
||||
let sensorTime = Int(body[293]) << 8 + Int(body[292])
|
||||
// if sensor time < 60, it can not get the parameters from the server
|
||||
guard sensorTime >= 60 else {
|
||||
callback(([], .starting, sensorTime, nil))
|
||||
return
|
||||
}
|
||||
|
||||
// get LibreDerivedAlgorithmParameters
|
||||
calibrateSensor(bytes: [UInt8](libreData), serialNumber: serialNumber, oopWebSite: oopWebSite, oopWebToken: oopWebToken) {
|
||||
(calibrationparams) in
|
||||
callback((oopParams(libreData: [UInt8](libreData), params: calibrationparams, timeStampLastBgReading: timeStampLastBgReading),
|
||||
|
@ -89,6 +117,11 @@ public class LibreOOPClient {
|
|||
}
|
||||
}
|
||||
|
||||
/// if `patchInfo.hasPrefix("A2")`, server uses another arithmetic to handle the 344 bytes
|
||||
/// - Parameters:
|
||||
/// - libreData: the 344 bytes from Libre sensor
|
||||
/// - oopWebSite: the site url to use if oop web would be enabled
|
||||
/// - callback: server data that contains the 344 bytes details
|
||||
static func handleLibreA2Data(libreData: [UInt8], oopWebSite: String, callback: ((LibreRawGlucoseOOPA2Data?) -> Void)?) {
|
||||
let bytesAsData = Data(bytes: libreData, count: libreData.count)
|
||||
if let uploadURL = URL.init(string: "\(oopWebSite)/callnox") {
|
||||
|
@ -122,6 +155,17 @@ public class LibreOOPClient {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// handle server data from two functions `webOOP` and `handleLibreA2Data`, parse the data to glucse data
|
||||
/// - Parameters:
|
||||
/// - libreData: the 344 bytes from Libre sensor
|
||||
/// - patchInfo: sensor sn hex string
|
||||
/// - oopValue: parsed value
|
||||
/// - timeStampLastBgReading: timestamp of last reading, older readings will be ignored
|
||||
/// - serialNumber: serial number
|
||||
/// - oopWebSite: the site url to use if oop web would be enabled
|
||||
/// - oopWebToken: the token to use if oop web would be enabled
|
||||
/// - callback: will be called when glucose data is read with as parameter the timestamp of the last reading.
|
||||
static func handleGlucose(libreData: [UInt8], patchInfo: String, oopValue: LibreRawGlucoseWeb?, timeStampLastBgReading: Date, serialNumber: String, oopWebSite: String, oopWebToken: String, _ callback: @escaping ((glucoseData: [GlucoseData], sensorState: LibreSensorState, sensorTimeInMinutes: Int, errorDescription: String?)) -> Void) {
|
||||
if let oopValue = oopValue, !oopValue.isError {
|
||||
if oopValue.valueError {
|
||||
|
@ -152,7 +196,8 @@ public class LibreOOPClient {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if patchInfo.contains(find: "70") || patchInfo.contains(find: "E5") {
|
||||
// only patchInfo `hasPrefix` "70" and "E5" can use the local parse
|
||||
if patchInfo.hasPrefix("70") || patchInfo.hasPrefix("E5") {
|
||||
oop(libreData: libreData, serialNumber: serialNumber, timeStampLastBgReading: timeStampLastBgReading, oopWebSite: oopWebSite, oopWebToken: oopWebToken, callback)
|
||||
} else {
|
||||
callback(([], .failure, 0, nil))
|
||||
|
@ -160,13 +205,24 @@ public class LibreOOPClient {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// handle 344 bytes
|
||||
/// - Parameters:
|
||||
/// - libreData: the 344 bytes from Libre sensor
|
||||
/// - patchUid: sensor sn hex string
|
||||
/// - patchInfo: will be used by server to out the glucose data
|
||||
/// - timeStampLastBgReading: timestamp of last reading, older readings will be ignored
|
||||
/// - serialNumber: serial number
|
||||
/// - oopWebSite: the site url to use if oop web would be enabled
|
||||
/// - oopWebToken: the token to use if oop web would be enabled
|
||||
/// - callback: will be called when glucose data is read with as parameter the timestamp of the last reading.
|
||||
static func handleLibreData(libreData: Data, patchUid: String?, patchInfo: String?, timeStampLastBgReading: Date, serialNumber: String, oopWebSite: String, oopWebToken: String, _ callback: @escaping ((glucoseData: [GlucoseData], sensorState: LibreSensorState, sensorTimeInMinutes: Int, errorDescription: String?)) -> Void) {
|
||||
let bytes = [UInt8](libreData)
|
||||
guard let patchUid = patchUid, let patchInfo = patchInfo else {
|
||||
oop(libreData: bytes, serialNumber: serialNumber, timeStampLastBgReading: timeStampLastBgReading, oopWebSite: oopWebSite, oopWebToken: oopWebToken, callback)
|
||||
return
|
||||
}
|
||||
|
||||
// if patchInfo.hasPrefix("A2"), server uses another arithmetic to handle the 344 bytes
|
||||
if patchInfo.hasPrefix("A2") {
|
||||
handleLibreA2Data(libreData: bytes, oopWebSite: oopWebSite) { (data) in
|
||||
DispatchQueue.main.async {
|
||||
|
@ -182,6 +238,12 @@ public class LibreOOPClient {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// 15 minutes apart to 5 minutes apart
|
||||
/// - Parameters:
|
||||
/// - current: current glucose
|
||||
/// - glucoseData: histories
|
||||
/// - Returns: contains current glucose and histories, 5 minutes apart
|
||||
static func split(current: LibreRawGlucoseData?, glucoseData: [LibreRawGlucoseData]) -> [LibreRawGlucoseData] {
|
||||
var x = [Double]()
|
||||
var y = [Double]()
|
||||
|
@ -201,11 +263,13 @@ public class LibreOOPClient {
|
|||
let startTime = x.first ?? 0
|
||||
let endTime = x.last ?? 0
|
||||
|
||||
// add glucoses to `SKKeyframeSequence`
|
||||
let frameS = SKKeyframeSequence.init(keyframeValues: y, times: x as [NSNumber])
|
||||
frameS.interpolationMode = .spline
|
||||
var items = [LibreRawGlucoseData]()
|
||||
var ptime = endTime
|
||||
while ptime >= startTime {
|
||||
// get value from SKKeyframeSequence
|
||||
let value = (frameS.sample(atTime: CGFloat(ptime)) as? Double) ?? 0
|
||||
let item = LibreRawGlucoseData.init(timeStamp: Date.init(timeIntervalSince1970: ptime / 1000), glucoseLevelRaw: value)
|
||||
items.append(item)
|
||||
|
@ -214,7 +278,15 @@ public class LibreOOPClient {
|
|||
return items
|
||||
}
|
||||
|
||||
/// get the `LibreDerivedAlgorithmParameters`
|
||||
/// - Parameters:
|
||||
/// - bytes: the 344 bytes from Libre sensor
|
||||
/// - serialNumber: serial number
|
||||
/// - oopWebSite: the site url to use if oop web would be enabled
|
||||
/// - oopWebToken: the token to use if oop web would be enabled
|
||||
/// - callback: return `LibreDerivedAlgorithmParameters`
|
||||
public static func calibrateSensor(bytes: [UInt8], serialNumber: String, oopWebSite: String, oopWebToken: String, callback: @escaping (LibreDerivedAlgorithmParameters) -> Void) {
|
||||
// the parameters of one sensor will not be changed, if have cached it, get it from userdefaults
|
||||
if let parameters = UserDefaults.standard.algorithmParameters {
|
||||
if parameters.serialNumber == serialNumber {
|
||||
callback(parameters)
|
||||
|
@ -222,7 +294,7 @@ public class LibreOOPClient {
|
|||
}
|
||||
}
|
||||
|
||||
/// default parameters
|
||||
// default parameters
|
||||
let params = LibreDerivedAlgorithmParameters.init(slope_slope: 0.00001729,
|
||||
slope_offset: -0.0006316,
|
||||
offset_slope: 0.002080,
|
||||
|
@ -231,12 +303,13 @@ public class LibreOOPClient {
|
|||
extraSlope: 1.0,
|
||||
extraOffset: 0.0,
|
||||
sensorSerialNumber: serialNumber)
|
||||
|
||||
post(bytes: bytes, oopWebSite: oopWebSite, oopWebToken: oopWebToken, { (data, str, can) in
|
||||
let decoder = JSONDecoder()
|
||||
do {
|
||||
let response = try decoder.decode(GetCalibrationStatus.self, from: data)
|
||||
if let slope = response.slope {
|
||||
var p = LibreDerivedAlgorithmParameters.init(slope_slope: slope.slopeSlope ?? 0,
|
||||
var libreDerivedAlgorithmParameters = LibreDerivedAlgorithmParameters.init(slope_slope: slope.slopeSlope ?? 0,
|
||||
slope_offset: slope.slopeOffset ?? 0,
|
||||
offset_slope: slope.offsetSlope ?? 0,
|
||||
offset_offset: slope.offsetOffset ?? 0,
|
||||
|
@ -244,27 +317,36 @@ public class LibreOOPClient {
|
|||
extraSlope: 1.0,
|
||||
extraOffset: 0.0,
|
||||
sensorSerialNumber: serialNumber)
|
||||
p.serialNumber = serialNumber
|
||||
if p.slope_slope != 0 ||
|
||||
p.slope_offset != 0 ||
|
||||
p.offset_slope != 0 ||
|
||||
p.offset_offset != 0 {
|
||||
UserDefaults.standard.algorithmParameters = p
|
||||
callback(p)
|
||||
libreDerivedAlgorithmParameters.serialNumber = serialNumber
|
||||
if !libreDerivedAlgorithmParameters.isErrorParameters {
|
||||
UserDefaults.standard.algorithmParameters = libreDerivedAlgorithmParameters
|
||||
callback(libreDerivedAlgorithmParameters)
|
||||
} else {
|
||||
// server values all 0, `isErrorParameters` is true
|
||||
// return the default parameters
|
||||
callback(params)
|
||||
}
|
||||
} else {
|
||||
// encoding data failed, no need to handle as an error, it means probably next time a new post will be done to the oop web server
|
||||
trace("in calibrateSensor, slope is nil", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error)
|
||||
// return the default parameters
|
||||
callback(params)
|
||||
}
|
||||
} catch {
|
||||
// encoding data failed, return the default parameters
|
||||
callback(params)
|
||||
trace("in calibrateSensor, error while encoding data : %{public}@", log: log, category: ConstantsLog.categoryLibreOOPClient, type: .error, error.localizedDescription)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - private functions
|
||||
|
||||
/// get `LibreDerivedAlgorithmParameters` from server
|
||||
/// - Parameters:
|
||||
/// - bytes: the 344 bytes from Libre sensor
|
||||
/// - oopWebSite: the site url to use if oop web would be enabled
|
||||
/// - oopWebToken: the token to use if oop web would be enabled
|
||||
/// - completion: network result
|
||||
static func post(bytes: [UInt8], oopWebSite: String, oopWebToken: String,_ completion:@escaping (( _ data_: Data, _ response: String, _ success: Bool ) -> Void)) {
|
||||
let date = Date().toMillisecondsAsInt64()
|
||||
let bytesAsData = Data(bytes: bytes, count: bytes.count)
|
||||
|
@ -308,6 +390,15 @@ public class LibreOOPClient {
|
|||
}
|
||||
|
||||
|
||||
/// current glucose value from 344 bytes
|
||||
/// - Parameters:
|
||||
/// - bytes: the 344 bytes from Libre sensor
|
||||
/// - date: the current date
|
||||
/// - timeStampLastBgReading: timestamp of last reading, older readings will be ignored
|
||||
/// - offset: glucose offset to be added in mg/dl
|
||||
/// - slope: slope to calculate glucose from raw value in (mg/dl)/raw
|
||||
/// - LibreDerivedAlgorithmParameterSet: algorithm parameters
|
||||
/// - Returns: return parsed values
|
||||
static func trendMeasurements(bytes: [UInt8], date: Date, timeStampLastBgReading: Date, _ offset: Double = 0.0, slope: Double = 0.1, LibreDerivedAlgorithmParameterSet: LibreDerivedAlgorithmParameters?) -> [LibreMeasurement] {
|
||||
guard bytes.count >= 320 else { return [] }
|
||||
// let headerRange = 0..<24 // 24 bytes, i.e. 3 blocks a 8 bytes
|
||||
|
@ -337,6 +428,14 @@ public class LibreOOPClient {
|
|||
return measurements
|
||||
}
|
||||
|
||||
/// histories for 344 bytes
|
||||
/// - Parameters:
|
||||
/// - bytes: the 344 bytes from Libre sensor
|
||||
/// - date: the current date
|
||||
/// - offset: glucose offset to be added in mg/dl
|
||||
/// - slope: slope to calculate glucose from raw value in (mg/dl)/raw
|
||||
/// - LibreDerivedAlgorithmParameterSet: algorithm parameters
|
||||
/// - Returns: return parsed values
|
||||
static func historyMeasurements(bytes: [UInt8], date: Date, _ offset: Double = 0.0, slope: Double = 0.1, LibreDerivedAlgorithmParameterSet: LibreDerivedAlgorithmParameters?) -> [LibreMeasurement] {
|
||||
guard bytes.count >= 320 else { return [] }
|
||||
let bodyRange = 24..<320 // 296 bytes, i.e. 37 blocks a 8 bytes
|
||||
|
@ -366,6 +465,14 @@ public class LibreOOPClient {
|
|||
return measurements
|
||||
}
|
||||
|
||||
|
||||
/// Get date of most recent history value.
|
||||
/// History values are updated every 15 minutes. Their corresponding time from start of the sensor in minutes is 15, 30, 45, 60, ..., but the value is delivered three minutes later, i.e. at the minutes 18, 33, 48, 63, ... and so on. So for instance if the current time in minutes (since start of sensor) is 67, the most recent value is 7 minutes old. This can be calculated from the minutes since start. Unfortunately sometimes the history index is incremented earlier than the minutes counter and they are not in sync. This has to be corrected.
|
||||
/// - Parameters:
|
||||
/// - minutesSinceStart: /// Minutes (approx) since start of sensor
|
||||
/// - nextHistoryBlock: /// Index on the next block of trend data that the sensor will measure and store
|
||||
/// - date: the current date
|
||||
/// - Returns: the date of the most recent history value and the corresponding minute counter
|
||||
static func dateOfMostRecentHistoryValue(minutesSinceStart: Int, nextHistoryBlock: Int, date: Date) -> (date: Date, counter: Int) {
|
||||
let nextHistoryIndexCalculatedFromMinutesCounter = ( (minutesSinceStart - 3) / 15 ) % 32
|
||||
let delay = (minutesSinceStart - 3) % 15 + 3 // in minutes
|
||||
|
@ -377,6 +484,9 @@ public class LibreOOPClient {
|
|||
}
|
||||
|
||||
|
||||
/// to glucose data
|
||||
/// - Parameter measurements: measurements
|
||||
/// - Returns: glucose data
|
||||
static func trendToLibreGlucose(_ measurements: [LibreMeasurement]) -> [LibreRawGlucoseData]?{
|
||||
|
||||
var origarr = [LibreRawGlucoseData]()
|
||||
|
|
|
@ -10,6 +10,8 @@ import Foundation
|
|||
|
||||
// MARK: Encode/decode helpers
|
||||
|
||||
|
||||
/// the parameters from server
|
||||
struct GetCalibrationStatus: Codable, CustomStringConvertible {
|
||||
var error: Bool?
|
||||
var command: String?
|
||||
|
|
|
@ -22,34 +22,43 @@ class LibreRawGlucoseData: GlucoseData {
|
|||
}
|
||||
|
||||
protocol LibreRawGlucoseWeb {
|
||||
/// if the server value is error return true
|
||||
var isError: Bool { get }
|
||||
/// sensor time
|
||||
var sensorTime: Int? { get }
|
||||
/// if `false`, it means current 344 bytes can not get the parameters from server
|
||||
var canGetParameters: Bool { get }
|
||||
/// sensor state
|
||||
var sensorState: LibreSensorState { get }
|
||||
/// when sensor return error 344 bytes, server will return wrong glucose data
|
||||
var valueError: Bool { get }
|
||||
/// get glucoses from server data
|
||||
func glucoseData(date: Date) ->(LibreRawGlucoseData?, [LibreRawGlucoseData])
|
||||
}
|
||||
|
||||
public class LibreRawGlucoseOOPData: NSObject, Codable, LibreRawGlucoseWeb {
|
||||
var alarm : String?
|
||||
var esaMinutesToWait : Int?
|
||||
var historicGlucose : [HistoricGlucose]?
|
||||
var isActionable : Bool?
|
||||
var lsaDetected : Bool?
|
||||
var realTimeGlucose : HistoricGlucose?
|
||||
/// histories by server
|
||||
var historicGlucose : [LibreRawGlucoseOOPGlucose]?
|
||||
/// current glucose
|
||||
var realTimeGlucose : LibreRawGlucoseOOPGlucose?
|
||||
/// trend arrow by server
|
||||
var trendArrow : String?
|
||||
/// sensor message
|
||||
var msg: String?
|
||||
var errcode: String?
|
||||
/// if endTime != 0, the sensor expired
|
||||
var endTime: Int?
|
||||
|
||||
enum Error: String {
|
||||
typealias RawValue = String
|
||||
case RESULT_SENSOR_STORAGE_STATE
|
||||
case RESCAN_SENSOR_BAD_CRC
|
||||
case RESCAN_SENSOR_BAD_CRC // crc failed
|
||||
// sensor terminate
|
||||
case TERMINATE_SENSOR_NORMAL_TERMINATED_STATE
|
||||
case TERMINATE_SENSOR_ERROR_TERMINATED_STATE
|
||||
case TERMINATE_SENSOR_CORRUPT_PAYLOAD
|
||||
// http request bad arguments
|
||||
case FATAL_ERROR_BAD_ARGUMENTS
|
||||
// the follow messages is sensor state
|
||||
case TYPE_SENSOR_NOT_STARTED
|
||||
case TYPE_SENSOR_STARTING
|
||||
case TYPE_SENSOR_Expired
|
||||
|
@ -58,10 +67,10 @@ public class LibreRawGlucoseOOPData: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
case TYPE_SENSOR_OK
|
||||
case TYPE_SENSOR_DETERMINED
|
||||
}
|
||||
|
||||
/// if the server value is error return true
|
||||
var isError: Bool {
|
||||
if let msg = msg {
|
||||
switch Error(rawValue: msg) {
|
||||
switch Error(rawValue: msg) { // sensor terminate
|
||||
case .TERMINATE_SENSOR_CORRUPT_PAYLOAD,
|
||||
.TERMINATE_SENSOR_NORMAL_TERMINATED_STATE,
|
||||
.TERMINATE_SENSOR_ERROR_TERMINATED_STATE:
|
||||
|
@ -70,16 +79,19 @@ public class LibreRawGlucoseOOPData: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
break
|
||||
}
|
||||
}
|
||||
// if parse the 344 failed, historicGlucose will be nil, return is error
|
||||
return historicGlucose?.isEmpty ?? true
|
||||
}
|
||||
|
||||
/// sensor time
|
||||
var sensorTime: Int? {
|
||||
// if endTime != 0, the sensor expired
|
||||
if let endTime = endTime, endTime != 0 {
|
||||
return 24 * 6 * 149
|
||||
}
|
||||
return realTimeGlucose?.id
|
||||
}
|
||||
|
||||
/// if `false`, it means current 344 bytes can not get the parameters from server
|
||||
var canGetParameters: Bool {
|
||||
if let dataQuality = realTimeGlucose?.dataQuality, let id = realTimeGlucose?.id {
|
||||
if dataQuality == 0 && id >= 60 {
|
||||
|
@ -90,6 +102,7 @@ public class LibreRawGlucoseOOPData: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
}
|
||||
|
||||
var sensorState: LibreSensorState {
|
||||
/// if sensor time < 60, sensor state is starting
|
||||
if let dataQuality = realTimeGlucose?.dataQuality, let id = realTimeGlucose?.id {
|
||||
if dataQuality != 0 && id < 60 {
|
||||
return LibreSensorState.starting
|
||||
|
@ -97,6 +110,7 @@ public class LibreRawGlucoseOOPData: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
}
|
||||
|
||||
var state = LibreSensorState.ready
|
||||
// parse the sensor state from msg
|
||||
if let msg = msg {
|
||||
switch Error(rawValue: msg) {
|
||||
case .TYPE_SENSOR_NOT_STARTED:
|
||||
|
@ -126,12 +140,17 @@ public class LibreRawGlucoseOOPData: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if endTime != 0, the sensor expired
|
||||
if let endTime = endTime, endTime != 0 {
|
||||
state = .expired
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
/// get glucoses from server data
|
||||
/// - Parameter date: timestamp of last reading
|
||||
/// - Returns: return current glucose and histories
|
||||
func glucoseData(date: Date) ->(LibreRawGlucoseData?, [LibreRawGlucoseData]) {
|
||||
if endTime != 0 {
|
||||
return (nil, [])
|
||||
|
@ -140,6 +159,7 @@ public class LibreRawGlucoseOOPData: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
guard let g = realTimeGlucose, g.dataQuality == 0 else { return(nil, []) }
|
||||
current = LibreRawGlucoseData.init(timeStamp: date, glucoseLevelRaw: g.value ?? 0)
|
||||
var array = [LibreRawGlucoseData]()
|
||||
// every 15 minutes apart
|
||||
let gap: TimeInterval = 60 * 15
|
||||
var date = date
|
||||
if var history = historicGlucose {
|
||||
|
@ -149,6 +169,7 @@ public class LibreRawGlucoseOOPData: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
|
||||
for g in history {
|
||||
date = date.addingTimeInterval(-gap)
|
||||
// if dataQuality != 0, the value is error
|
||||
if g.dataQuality != 0 { continue }
|
||||
let glucose = LibreRawGlucoseData.init(timeStamp: date, glucoseLevelRaw: g.value ?? 0)
|
||||
array.insert(glucose, at: 0)
|
||||
|
@ -157,11 +178,14 @@ public class LibreRawGlucoseOOPData: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
return (current ,array)
|
||||
}
|
||||
|
||||
/// when sensor return error 344 bytes, server will return wrong glucose data
|
||||
var valueError: Bool {
|
||||
// sensor time < 60, the sensor is starting
|
||||
if let id = realTimeGlucose?.id, id < 60 {
|
||||
return false
|
||||
}
|
||||
|
||||
// current glucose is error, this parse failed, can not be use
|
||||
if let g = realTimeGlucose, let value = g.dataQuality {
|
||||
return value != 0
|
||||
}
|
||||
|
@ -169,9 +193,14 @@ public class LibreRawGlucoseOOPData: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
}
|
||||
}
|
||||
|
||||
class HistoricGlucose: NSObject, Codable {
|
||||
|
||||
/// glucose value
|
||||
class LibreRawGlucoseOOPGlucose: NSObject, Codable {
|
||||
/// if dataQuality != 0, it means the value is error
|
||||
let dataQuality : Int?
|
||||
/// the value's sensor time
|
||||
let id: Int?
|
||||
/// glucose value
|
||||
let value : Double?
|
||||
}
|
||||
|
||||
|
@ -180,21 +209,22 @@ public class LibreRawGlucoseOOPA2Data: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
var errcode: Int?
|
||||
var list: [LibreRawGlucoseOOPA2List]?
|
||||
|
||||
/// server parse value
|
||||
var content: LibreRawGlucoseOOPA2Cotent? {
|
||||
return list?.first?.content
|
||||
}
|
||||
|
||||
/// if the server value is error return true
|
||||
var isError: Bool {
|
||||
if content?.currentBg ?? 0 <= 10 {
|
||||
return true
|
||||
}
|
||||
return list?.first?.content?.historicBg?.isEmpty ?? true
|
||||
}
|
||||
|
||||
/// sensor time
|
||||
var sensorTime: Int? {
|
||||
return content?.currentTime
|
||||
}
|
||||
|
||||
/// if `false`, it means current 344 bytes can not get the parameters from server
|
||||
var canGetParameters: Bool {
|
||||
if let id = content?.currentTime {
|
||||
if id >= 60 {
|
||||
|
@ -203,12 +233,12 @@ public class LibreRawGlucoseOOPA2Data: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// sensor state
|
||||
var sensorState: LibreSensorState {
|
||||
if let id = content?.currentTime {
|
||||
if id < 60 {
|
||||
if id < 60 { // if sensor time < 60, the sensor is starting
|
||||
return LibreSensorState.starting
|
||||
} else if id >= 20880 {
|
||||
} else if id >= 20880 { // if sensor time >= 20880, the sensor expired
|
||||
return LibreSensorState.expired
|
||||
}
|
||||
}
|
||||
|
@ -217,11 +247,15 @@ public class LibreRawGlucoseOOPA2Data: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
return state
|
||||
}
|
||||
|
||||
/// get glucoses from server data
|
||||
/// - Parameter date: timestamp of last reading
|
||||
/// - Returns: return current glucose and histories
|
||||
func glucoseData(date: Date) ->(LibreRawGlucoseData?, [LibreRawGlucoseData]) {
|
||||
var current: LibreRawGlucoseData?
|
||||
guard !isError else { return(nil, []) }
|
||||
current = LibreRawGlucoseData.init(timeStamp: date, glucoseLevelRaw: content?.currentBg ?? 0)
|
||||
var array = [LibreRawGlucoseData]()
|
||||
// every 15 minutes apart
|
||||
let gap: TimeInterval = 60 * 15
|
||||
var date = date
|
||||
if var history = content?.historicBg {
|
||||
|
@ -231,6 +265,7 @@ public class LibreRawGlucoseOOPA2Data: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
|
||||
for g in history {
|
||||
date = date.addingTimeInterval(-gap)
|
||||
// if dataQuality != 0, the value is error
|
||||
if g.quality != 0 { continue }
|
||||
let glucose = LibreRawGlucoseData.init(timeStamp: date, glucoseLevelRaw: g.bg ?? 0)
|
||||
array.insert(glucose, at: 0)
|
||||
|
@ -239,11 +274,14 @@ public class LibreRawGlucoseOOPA2Data: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
return (current ,array)
|
||||
}
|
||||
|
||||
/// when sensor return error 344 bytes, server will return wrong glucose data
|
||||
var valueError: Bool {
|
||||
// sensor time < 60, the sensor is starting
|
||||
if let id = content?.currentTime, id < 60 {
|
||||
return false
|
||||
}
|
||||
|
||||
// current glucose is error
|
||||
if content?.currentBg ?? 0 <= 10 {
|
||||
return true
|
||||
}
|
||||
|
@ -253,20 +291,22 @@ public class LibreRawGlucoseOOPA2Data: NSObject, Codable, LibreRawGlucoseWeb {
|
|||
|
||||
class LibreRawGlucoseOOPA2List: NSObject, Codable {
|
||||
var content: LibreRawGlucoseOOPA2Cotent?
|
||||
var timestamp: Int?
|
||||
}
|
||||
|
||||
class LibreRawGlucoseOOPA2Cotent: NSObject, Codable {
|
||||
/// current sensor time
|
||||
var currentTime: Int?
|
||||
var currenTrend: Int?
|
||||
var serialNumber: String?
|
||||
/// histories
|
||||
var historicBg: [HistoricGlucoseA2]?
|
||||
/// current glucose value
|
||||
var currentBg: Double?
|
||||
var timestamp: Int?
|
||||
}
|
||||
|
||||
class HistoricGlucoseA2: NSObject, Codable {
|
||||
/// if quality != 0, it means the value is error
|
||||
let quality : Int?
|
||||
/// the value's sensor time
|
||||
let time: Int?
|
||||
/// glucose value
|
||||
let bg : Double?
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>3879</string>
|
||||
<string>3884</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
|
|
Loading…
Reference in New Issue