ios - 如何使用 CoreBluetooth 从 1822 PulseOximeter 的蓝牙 LE 数据中提取 SFLOAT 值

标签 ios swift bluetooth-lowenergy core-bluetooth gatt

我正在开发一个 iOS 应用程序,该应用程序在 iOS 11.4 上使用 CoreBluetooth 从支持Bluetooth LE 的设备读取脉搏血氧计数据> 在 Swift 4.1 中。

我有 CBCentralManager 搜索外围设备,我找到了我感兴趣的 CBPeripheral,我验证它有 0x1822 脉搏血氧仪服务,如蓝牙 SIG here 所述. (您可能需要注册蓝牙 SIG 才能访问该链接。它是免费的,但需要一两天时间。)

之后,我连接到它,然后发现服务:

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 个字节是 SpO2PR-Normal - SpO2(氧合)读数的 SFLOAT 值,接下来的 2 个字节是另一个 SFLOAT 值为了 SpO2PR-正常 - PR(脉率)值。

蓝牙 SIG 将 SFLOAT 列为 IEEE-11073 16 位 SFLOAT here . IEEE document on IEEE-11073未公开上市,并且是 available for purchase ,但我宁愿避免这种情况。

知道如何解码吗?我在 Stack Overflow 上发现了另一个问题,引用了正常的 32-bit Float ,但该问题针对的是不同类型的 Float,其答案不适用。

最佳答案

这里是:

Xcode 9.4.1Xcode 10.0 beta 3

iOS 11.4.1iOS 12.0 beta 3

Swift 4.1.2Swift 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 值数组结束。

关于ios - 如何使用 CoreBluetooth 从 1822 PulseOximeter 的蓝牙 LE 数据中提取 SFLOAT 值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51249335/

相关文章:

swift - URL.standardized 和 URL.absoluteURL 有什么区别?

c# - 低功耗蓝牙扫描仪 Xamarin Android

java - 应用程序位于前台时锁定屏幕可防止后台执行 (Android)

Swift- 在 Cocoa 中播放系统默认声音的最简单方法(macOS、OS X)

android - Android 中的 BLE 广告

ios - Swift:如何将 Action 链接到 tableViewCell 内的 View ?

iphone - native iOS Rss Feed 阅读器在 safari 中打开完整故事

ios - AudioKit 停止和启动会产生奇怪的声音

android - 你能从应用商店下载的 Cordova 应用程序中提取 www 文件夹吗

Swift-使用完成处理程序更新闭包外部的全局变量