add remark

This commit is contained in:
huyan_18040019 2020-05-25 11:38:38 +08:00
parent 14cb8caa60
commit bd2b79f0d1
9 changed files with 197 additions and 87 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,6 +10,8 @@ import Foundation
// MARK: Encode/decode helpers
/// the parameters from server
struct GetCalibrationStatus: Codable, CustomStringConvertible {
var error: Bool?
var command: String?

View File

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

View File

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