【问题标题】:How do I get the value of Heart Rate Measurement from a Polar H10 in iOS using Swift and Core Bluetooth?如何在 iOS 中使用 Swift 和 Core 蓝牙从 Polar H10 获取心率测量值?
【发布时间】:2020-06-06 11:19:04
【问题描述】:

我使用 Polar H10 作为心率监测器,我想使用 Swift 和 Core 蓝牙获取 iOS 中心率测量特性中的值。

下面是 CBPeripheralDelegate 的相应回调方法中的代码:

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {

    print("didUpdateValueFor")

    if let error = error {
        print("error:", error)
    }

    guard let value = characteristic.value else {
        return
    }

    print("value:", value)

    guard let stringValue = String(data: value, encoding: .utf8) else {
        return
    }

    print("string value:", stringValue)

}

这个Heart Rate Measurement的官方蓝牙网页显示了这个未格式化的信息:

强制 8 位 注意:心率测量值字段的格式取决于标志字段的位 0。 C1 uint8 org.bluetooth.unit.period.beats_per_minute 注意:心率测量值字段的格式取决于标志字段的位 0。 C2 uint16 org.bluetooth.unit.period.beats_per_minute 能量消耗字段的存在取决于标志字段的位 3。 C3 uint16 org.bluetooth.unit.energy.joule C4 uint16 org.bluetooth.unit.time.second 分辨率为 1/1024 秒 上表中的字段按 LSO 到 MSO 的顺序排列。其中 LSO = 最低有效八位字节,MSO = 最高有效八位字节。

我无法弄清楚文档的含义。特别是我不明白 C1、C2、C3 和 C4 指的是什么。

character.value 是数据类型。我该如何处理该 Data 对象?如何获得我需要的值?我首先要的是 RR 值。

有类似这样的帖子,但它们对我没有帮助,因为他们使用不同的工具和不同的语言,或者他们没有很好的答案。

更新:

我发现Heart Rate Measurement 的页面是一个xml 文件。使用可以显示该文件的应用程序打开该文件后,我能够从该文件中获取所需的信息。我第一次看到它是在 Safari 中。它不能很好地显示 xml 文件。这是文件:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2011 Bluetooth SIG, Inc. All rights reserved. -->
<Characteristic xsi:noNamespaceSchemaLocation="http://schemas.bluetooth.org/Documents/characteristic.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" type="org.bluetooth.characteristic.heart_rate_measurement" uuid="2A37" name="Heart Rate Measurement">
    <InformativeText>
    </InformativeText>
    <Value>
        <Field name="Flags">
            <Requirement>Mandatory</Requirement>
            <Format>8bit</Format>

            <BitField>
                <Bit index="0" size="1" name="Heart Rate Value Format bit">
                    <Enumerations>
                        <Enumeration key="0" value="Heart Rate Value Format is set to UINT8. Units: beats per minute (bpm)" requires="C1" />
                        <Enumeration key="1" value="Heart Rate Value Format is set to UINT16. Units: beats per minute (bpm)" requires="C2" />
                    </Enumerations>
                </Bit>
                <Bit index="1" size="2" name="Sensor Contact Status bits">
                    <Enumerations>
                        <Enumeration key="0" value="Sensor Contact feature is not supported in the current connection" />
                        <Enumeration key="1" value="Sensor Contact feature is not supported in the current connection" />
                        <Enumeration key="2" value="Sensor Contact feature is supported, but contact is not detected" />
                        <Enumeration key="3" value="Sensor Contact feature is supported and contact is detected" />
                    </Enumerations>
                </Bit>

                <Bit index="3" size="1" name="Energy Expended Status bit">
                    <Enumerations>
                        <Enumeration key="0" value="Energy Expended field is not present" />
                        <Enumeration key="1" value="Energy Expended field is present. Units: kilo Joules" requires="C3"/>
                    </Enumerations>
                </Bit>
                <Bit index="4" size="1" name="RR-Interval bit">
                    <Enumerations>
                        <Enumeration key="0" value="RR-Interval values are not present." />
                        <Enumeration key="1" value="One or more RR-Interval values are present." requires="C4"/>
                        </Enumerations>
                </Bit>
                <ReservedForFutureUse index="5" size="3"></ReservedForFutureUse>
                </BitField>
        </Field>
        <Field name="Heart Rate Measurement Value (uint8)">
              <InformativeText>
                Note: The format of the Heart Rate Measurement Value field is dependent upon bit 0 of the Flags field.
              </InformativeText>
            <Requirement>C1</Requirement>
            <Format>uint8</Format>
            <Unit>org.bluetooth.unit.period.beats_per_minute</Unit>

        </Field>    

         <Field name="Heart Rate Measurement Value (uint16)">
              <InformativeText>
                Note: The format of the Heart Rate Measurement Value field is dependent upon bit 0 of the Flags field.
              </InformativeText>
            <Requirement>C2</Requirement>
            <Format>uint16</Format>
            <Unit>org.bluetooth.unit.period.beats_per_minute</Unit>

        </Field>       

        <Field name="Energy Expended">
            <InformativeText>The presence of the Energy Expended field is dependent upon bit 3 of the Flags field.</InformativeText>
            <Requirement>C3</Requirement>
            <Format>uint16</Format>
            <Unit>org.bluetooth.unit.energy.joule</Unit>

        </Field>
        <Field name="RR-Interval">
            <InformativeText>
               <!-- The presence of the RR-Interval field is dependent upon bit 4 of the Flags field. 
                <p>The RR-Interval value represents the time between two R-Wave detections.</p> 

                <p>Because several RR-Intervals may be measured between transmissions of the HEART RATE MEASUREMENT characteristic, 
                multiple RR-Interval sub-fields may be present in the characteristic. The number of RR-Interval sub-fields present 
                is determined by a combination of the overall length of the characteristic and whether or not the characteristic contains 
                the Energy Expended field.</p>

                <p>Where there are multiple RR-Interval values transmitted in the HEART RATE MEASUREMENT characteristic, the field uses the following format:</p>
                <p>RR-Interval Value 0 (LSO...MSO), RR-Interval Value 1 (LSO...MSO), RR-Interval Value 2 (LSO...MSO), RR-Interval Value n (LSO...MSO).</p>
                <p>Where the RR-Interval Value 0 is older than the RR-Interval Value 1.</p>
                <p>RR-Interval Value 0 is transmitted first followed by the newer measurements.</p>-->

            </InformativeText>
            <Requirement>C4</Requirement>
            <Format>uint16</Format>
            <Unit>org.bluetooth.unit.time.second</Unit>
            <Description>Resolution of 1/1024 second</Description>



        </Field>
    </Value>
   <Note> <p>The fields in the above table are in the order of LSO to MSO. Where LSO = Least Significant Octet and MSO = Most Significant Octet.</p>
   </Note>
</Characteristic>

【问题讨论】:

  • 有更多信息here 但本质上您的数据是一系列 8 位字节。这些字节中的一些位告诉您如何解释其他字节。您要做的第一件事是将数据转换为UInt8 的数组。 This 可能会有很大帮助。

标签: ios swift bluetooth core-bluetooth gatt


【解决方案1】:

这是我从 BLE(蓝牙低功耗)连接到 iOS 应用程序获取 HR 和 HRV(RR 间隔)的功能。与@yedy 的解决方案不同,我想在进行时“弹出”我的数组。

 private func heartRate2(from characteristic: CBCharacteristic) -> (Int, [Double]) {
    guard let characteristicData = characteristic.value else { return (-1, []) }
    var byteArray = [UInt8](characteristicData)
    var rawRRinterval = 0
    var rrInMilSeconds = [Double]()
    var hrValue = 0
        
    // https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.heart_rate_measurement.xml
    // https://stackoverflow.com/a/62692067/14414215
    // https://stackoverflow.com/a/65458794/14414215
    // The heart rate mesurement is in the 2nd, or in the 2nd and 3rd bytes, i.e. one one or in two bytes
    // The first byte of the first bit specifies the length of the heart rate data, 0 == 1 byte, 1 == 2 bytes
    // the & 0x01 is the bitwise operation on the first value in the array. (the Flag)
    // BIT0: 00000001 - 1   - 0x01 - HR Value 0 = UINT8, 1 = UINT16
    // BIT1: 00000010 - 2   - 0x02 - Sensor Contact Status 1 = Sensor Contact Supported
    // BIT2: 00000100 - 4   - 0x04 - Sensor Contact Status 1 = Good contact with the skin
    // BIT3: 00001000 - 8   - 0x08 - Energy Expended 1 = Present
    // BIT4: 00010000 - 16  - 0x10 - RR-Interval 1 = Present

    let flags = byteArray[0]
    
    // Pop 1st Bits (Flags)
    byteArray.removeFirst(1)

    // Check HR is UINT8(1bit) or UINT16(2bit)
    if flags & 0x01 == 0 {
      hrValue = Int(byteArray[0])
      byteArray.removeFirst(1)
    } else if flags & 0x01 == 1 {
      hrValue = (Int(byteArray[0]) << 8) + Int(byteArray[1])
      byteArray.removeFirst(2)
    }
    
    // check for RR presence
    if (flags & 0x10 )  != 0 {
      while byteArray.count > 0 {
        rawRRinterval = Int(byteArray[0]) + (Int(byteArray[1]) << 8)
        
        //Resolution of 1/1024 second
        rrInMilSeconds.append((Double(rawRRinterval) / 1024.0 ) * 1000.0)
        byteArray.removeFirst(2)
      }
    }
    
    return (hrValue, rrInMilSeconds)
  }

【讨论】:

  • “BLE”代表什么?
  • @DanielBrower BLE = Bluetooth Low Energy,也有人称其为BTLE - Bluetooth Low Energy
【解决方案2】:

这是我获得 RR 间隔的工作解决方案:

guard let characteristicData = characteristic.value else { return nil }
        let byteArray = [UInt8](characteristicData)
        var rawRRinterval = 0
        //if fifth bit (index 4) is set -> RR-Inteval present (00010000 = 16)
        if (byteArray[0] & 16)  != 0{
            print("One or more RR-Interval values are present.")
            //00000000 (0) = heart rate 8 bit in [1]
        //00000001 (1) = heart rate 16 bit in [1] + [2]
        //00000110 () = sensor contact flags - status decoded in flags no other field needed
        //00001000 (8) = energy status 16 bit in [2] + [3]
        //00001001 (9) = energy status 16 bit in [3] + [4]
        
        //00010000 (16) = RR-value 16 bit in [2] + [3]
        //00010010 (18) = RR-value 16 bit in [2] + [3]
        //00010100 (20) = RR-value 16 bit in [2] + [3]
        //00010110 (22) = RR-value 16 bit in [2] + [3]
        
        //00010001 (17) = RR-value 16 bit in [3] + [4]
        //00010011 (19) = RR-value 16 bit in [3] + [4]
        //00010101 (21) = RR-value 16 bit in [3] + [4]
        //00010111 (23) = RR-value 16 bit in [3] + [4]
        
        //00011000 (24) = RR-value 16 bit in [4] + [5]
        //00011010 (26) = RR-value 16 bit in [4] + [5]
        //00011100 (28) = RR-value 16 bit in [4] + [5]
        //00011110 (30) = RR-value 16 bit in [4] + [5]
        
        //00011001 (25) = RR-value 16 bit in [5] + [6]
        //00011011 (27) = RR-value 16 bit in [5] + [6]
        //00011101 (29) = RR-value 16 bit in [5] + [6]
        //00011111 (31) = RR-value 16 bit in [5] + [6]
        
        switch byteArray[0] {
        case 16,18,20,22:
            //rr-value in [2] und [3]
            rawRRinterval = Int(byteArray[2]) + (Int(byteArray[3]) << 8)
        case 17,19,21,23:
            //rr-value in [3] und [4]
            rawRRinterval = Int(byteArray[3]) + (Int(byteArray[4]) << 8)
        case 24,26,28,30:
            //rr-value in [4] und [5]
            rawRRinterval = Int(byteArray[4]) + (Int(byteArray[5]) << 8)
        case 25,27,29,31:
            //rr-value in [5] und [6]
            rawRRinterval = Int(byteArray[4]) + (Int(byteArray[5]) << 8)
        }else{
             print("RR-Interval values are not present.")
        }
        
        //Resolution of 1/1024 second
        let rrInSeconds: Double = Double(rawRRinterval)/1024
        print("? rrInSeconds: \(rrInSeconds)")
        let rrInMilSeconds: Double = Double(rrInSeconds) * 1000
        print("? rrInMilSeconds: \(rrInMilSeconds)")
        
        let value = (Double(rawRRinterval) / 1024.0 ) * 1000.0
        print("? value: \(value)")
        
        return rrInMilSeconds

【讨论】:

    【解决方案3】:

    首先,您需要将特征值数据转换为UInt8 数组,然后您需要检查您的心率传感器在哪个索引处提供值。我在我的项目中使用以下代码,它工作正常。希望对你也有帮助。

    var heartRate: Int = 0
    guard let characteristicData = characteristic.value else { return }
    let byteArray = [UInt8](characteristicData)
    let firstBitValue = byteArray[0] & 0x01
    if firstBitValue == 0 {
        // Heart Rate Value Format is in the 2nd byte
        heartRate = Int(byteArray[1])
    } else {
        // Heart Rate Value Format is in the 2nd and 3rd bytes
        heartRate = (Int(byteArray[2]) << 8) + Int(byteArray[1])
    }
    print("heart rate", heartRate)
    

    将上述代码放入CBPeripheralDelegatedidUpdateValueFor方法中,尝试从你的心率传感器中获取值。

    【讨论】:

    • 你怎么知道第一个字节的第一个比特值为0表示第二个字节是心率值?您是否能够从我在帖子中包含的心率测量特性的官方 bluetooth.com 网页中找出这一点?我无法理解它。
    • 是的,你是对的。它来自您发布的 XML。请注意,上面写着Bit Index=0,这意味着查看标志字段byteArray[0] 中的第一位,然后您有enumerations keys,如果(第一位)为0,则为8 位整数,否则为16 位整数。 @yedy 上面的回答显示了如何解码它
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-28
    • 1970-01-01
    • 2020-08-16
    • 2011-08-22
    • 1970-01-01
    • 2013-05-11
    • 2014-02-20
    相关资源
    最近更新 更多