如何使用 CoreBluetooth 从 1822 PulseOximeter 的蓝牙 LE 数据中提取 SFLOAT 值
How to extract SFLOAT value from Bluetooth LE data for 1822 PulseOximeter using CoreBluetooth
我正在开发一个 iOS 应用程序,该应用程序使用 CoreBluetooth[= 从支持 Bluetooth LE 的设备读取脉搏血氧计数据55=] 在 iOS 11.4 在 Swift 4.1.
我有 CBCentralManager 搜索外围设备,我找到了我感兴趣的 CBPeripheral,我验证它有0x1822 脉搏血氧计服务,如蓝牙 SIG here 所述。 (您可能需要向蓝牙 SIG 注册才能访问 link。它是免费的,但需要一两天时间。)
之后,我连接到它,然后发现服务:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.discoverServices(nil)
}
然后在我的peripheral:didDiscoverServices中我发现了GATT特性:
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?){
for service in peripheral.services ?? [] {
if service.uuid.uuidString == "1822" {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
从中我看到以下特征 (CBCharacteristic.uuid) 可用:0x2A5F、0x2A5E、0x2a60 和 0x2A52。然后我订阅 0x2A5F 的更新,即 PLX 连续测量,描述为 here:
if service.uuid.uuidString == "1822" && characteristic.uuid.uuidString == "2A5F" {
// pulseox continuous
print("[SUBSCRIBING TO UPDATES FOR SERVICE 1822 'PulseOx' for Characteristic 2A5F 'PLX Continuous']")
peripheral.setNotifyValue(true, for: characteristic)
}
然后我开始在我的 peripheral:didUpdateValueFor 方法中接收回 20 字节的数据包:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.service.uuid.uuidString == "1822" && characteristic.uuid.uuidString == "2A5F" {
if let data = characteristic.value {
var values = [UInt8](repeating:0, count:data.count)
data.copyBytes(to: &values, count: data.count)
}
}
}
从参考文档中您可以看到第一个字节是一堆位域,描述了数据包中包含哪些可选值。接下来的 2 个字节是 SFLOAT SpO2PR-Normal - SpO2(氧合)读数的值,接下来的 2 个字节是另一个 SFLOAT 的值
SpO2PR-正常 - PR(脉率)值。
蓝牙 SIG 将 SFLOAT 列为 IEEE-11073 16 位 SFLOAT here. IEEE's document on IEEE-11073 is not publicly listed, and is available for purchase,但我更喜欢避免这种情况。
知道如何解码吗?我在 Stack Overflow 上发现了另一个引用正常 的问题,但该问题针对的是不同类型的 Float,其答案不适用。
这里是:
Xcode 9.4.1 或 Xcode 10.0 测试版 3
iOS 11.4.1 或 iOS 12.0 测试版 3
Swift 4.1.2 或 Swift 4.2
func floatFromTwosComplementUInt16(_ value: UInt16, havingBitsInValueIncludingSign bitsInValueIncludingSign: Int) -> Float {
// calculate a signed float from a two's complement signed value
// represented in the lowest n ("bitsInValueIncludingSign") bits
// of the UInt16 value
let signMask: UInt16 = UInt16(0x1) << (bitsInValueIncludingSign - 1)
let signMultiplier: Float = (value & signMask == 0) ? 1.0 : -1.0
var valuePart = value
if signMultiplier < 0 {
// Undo two's complement if it's negative
var valueMask = UInt16(1)
for _ in 0 ..< bitsInValueIncludingSign - 2 {
valueMask = valueMask << 1
valueMask += 1
}
valuePart = ((~value) & valueMask) &+ 1
}
let floatValue = Float(valuePart) * signMultiplier
return floatValue
}
func extractSFloat(values: [UInt8], startingIndex index: Int) -> Float {
// IEEE-11073 16-bit SFLOAT -> Float
let full = UInt16(values[index+1]) * 256 + UInt16(values[index])
// Check special values defined by SFLOAT first
if full == 0x07FF {
return Float.nan
} else if full == 0x800 {
return Float.nan // This is really NRes, "Not at this Resolution"
} else if full == 0x7FE {
return Float.infinity
} else if full == 0x0802 {
return -Float.infinity // This is really negative infinity
} else if full == 0x801 {
return Float.nan // This is really RESERVED FOR FUTURE USE
}
// Get exponent (high 4 bits)
let expo = (full & 0xF000) >> 12
let expoFloat = floatFromTwosComplementUInt16(expo, havingBitsInValueIncludingSign: 4)
// Get mantissa (low 12 bits)
let mantissa = full & 0x0FFF
let mantissaFloat = floatFromTwosComplementUInt16(mantissa, havingBitsInValueIncludingSign: 12)
// Put it together
let finalValue = mantissaFloat * pow(10.0, expoFloat)
return finalValue
}
extraSFloat 方法采用 Uint8 数组和该数组的索引以指示 SFLOAT 的位置。例如,如果数组是两个字节(只是 SFLOAT 的两个字节),那么你会说:
let floatValue = extractSFloat(values: array, startingIndex: 0)
我这样做是因为在处理蓝牙数据时,我总是以包含我需要解码的数据的 UInt8 值数组结束。
这就是我在 Swift 5 (XCode 12.4)
中的做法
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard characteristic.service.uuid == CBUUID(string: "1822"),
characteristic.uuid == CBUUID(string: "2A5F"),
let data = characteristic.value else {
return
}
let numberOfBytes = data.count
var byteArray = [UInt8](repeating: 0, count: numberOfBytes)
(data as NSData).getBytes(&byteArray, length: numberOfBytes)
logger.debug("Data: \(byteArray)")
let oxygenation = byteArray[1]
let heartRate = byteArray[3]
}
摘自我的教程"Reverse Engineering Bluetooth Devices - An Introduction to CoreBluetooth"
我正在开发一个 iOS 应用程序,该应用程序使用 CoreBluetooth[= 从支持 Bluetooth LE 的设备读取脉搏血氧计数据55=] 在 iOS 11.4 在 Swift 4.1.
我有 CBCentralManager 搜索外围设备,我找到了我感兴趣的 CBPeripheral,我验证它有0x1822 脉搏血氧计服务,如蓝牙 SIG here 所述。 (您可能需要向蓝牙 SIG 注册才能访问 link。它是免费的,但需要一两天时间。)
之后,我连接到它,然后发现服务:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.discoverServices(nil)
}
然后在我的peripheral:didDiscoverServices中我发现了GATT特性:
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?){
for service in peripheral.services ?? [] {
if service.uuid.uuidString == "1822" {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
从中我看到以下特征 (CBCharacteristic.uuid) 可用:0x2A5F、0x2A5E、0x2a60 和 0x2A52。然后我订阅 0x2A5F 的更新,即 PLX 连续测量,描述为 here:
if service.uuid.uuidString == "1822" && characteristic.uuid.uuidString == "2A5F" {
// pulseox continuous
print("[SUBSCRIBING TO UPDATES FOR SERVICE 1822 'PulseOx' for Characteristic 2A5F 'PLX Continuous']")
peripheral.setNotifyValue(true, for: characteristic)
}
然后我开始在我的 peripheral:didUpdateValueFor 方法中接收回 20 字节的数据包:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.service.uuid.uuidString == "1822" && characteristic.uuid.uuidString == "2A5F" {
if let data = characteristic.value {
var values = [UInt8](repeating:0, count:data.count)
data.copyBytes(to: &values, count: data.count)
}
}
}
从参考文档中您可以看到第一个字节是一堆位域,描述了数据包中包含哪些可选值。接下来的 2 个字节是 SFLOAT SpO2PR-Normal - SpO2(氧合)读数的值,接下来的 2 个字节是另一个 SFLOAT 的值 SpO2PR-正常 - PR(脉率)值。
蓝牙 SIG 将 SFLOAT 列为 IEEE-11073 16 位 SFLOAT here. IEEE's document on IEEE-11073 is not publicly listed, and is available for purchase,但我更喜欢避免这种情况。
知道如何解码吗?我在 Stack Overflow 上发现了另一个引用正常
这里是:
Xcode 9.4.1 或 Xcode 10.0 测试版 3
iOS 11.4.1 或 iOS 12.0 测试版 3
Swift 4.1.2 或 Swift 4.2
func floatFromTwosComplementUInt16(_ value: UInt16, havingBitsInValueIncludingSign bitsInValueIncludingSign: Int) -> Float {
// calculate a signed float from a two's complement signed value
// represented in the lowest n ("bitsInValueIncludingSign") bits
// of the UInt16 value
let signMask: UInt16 = UInt16(0x1) << (bitsInValueIncludingSign - 1)
let signMultiplier: Float = (value & signMask == 0) ? 1.0 : -1.0
var valuePart = value
if signMultiplier < 0 {
// Undo two's complement if it's negative
var valueMask = UInt16(1)
for _ in 0 ..< bitsInValueIncludingSign - 2 {
valueMask = valueMask << 1
valueMask += 1
}
valuePart = ((~value) & valueMask) &+ 1
}
let floatValue = Float(valuePart) * signMultiplier
return floatValue
}
func extractSFloat(values: [UInt8], startingIndex index: Int) -> Float {
// IEEE-11073 16-bit SFLOAT -> Float
let full = UInt16(values[index+1]) * 256 + UInt16(values[index])
// Check special values defined by SFLOAT first
if full == 0x07FF {
return Float.nan
} else if full == 0x800 {
return Float.nan // This is really NRes, "Not at this Resolution"
} else if full == 0x7FE {
return Float.infinity
} else if full == 0x0802 {
return -Float.infinity // This is really negative infinity
} else if full == 0x801 {
return Float.nan // This is really RESERVED FOR FUTURE USE
}
// Get exponent (high 4 bits)
let expo = (full & 0xF000) >> 12
let expoFloat = floatFromTwosComplementUInt16(expo, havingBitsInValueIncludingSign: 4)
// Get mantissa (low 12 bits)
let mantissa = full & 0x0FFF
let mantissaFloat = floatFromTwosComplementUInt16(mantissa, havingBitsInValueIncludingSign: 12)
// Put it together
let finalValue = mantissaFloat * pow(10.0, expoFloat)
return finalValue
}
extraSFloat 方法采用 Uint8 数组和该数组的索引以指示 SFLOAT 的位置。例如,如果数组是两个字节(只是 SFLOAT 的两个字节),那么你会说:
let floatValue = extractSFloat(values: array, startingIndex: 0)
我这样做是因为在处理蓝牙数据时,我总是以包含我需要解码的数据的 UInt8 值数组结束。
这就是我在 Swift 5 (XCode 12.4)
中的做法func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard characteristic.service.uuid == CBUUID(string: "1822"),
characteristic.uuid == CBUUID(string: "2A5F"),
let data = characteristic.value else {
return
}
let numberOfBytes = data.count
var byteArray = [UInt8](repeating: 0, count: numberOfBytes)
(data as NSData).getBytes(&byteArray, length: numberOfBytes)
logger.debug("Data: \(byteArray)")
let oxygenation = byteArray[1]
let heartRate = byteArray[3]
}
摘自我的教程"Reverse Engineering Bluetooth Devices - An Introduction to CoreBluetooth"