微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何将十六进制数据分解为来自 BLE 设备的可用数据? 速度和节奏

如何解决如何将十六进制数据分解为来自 BLE 设备的可用数据? 速度和节奏

我正在构建一个显示速度和节奏的 iOS 应用。我已成功连接到我的 BLE 设备并接收到数据。我根本不知道从这里开始做什么。我如何理解这些数据?

这是收到的数据

central.state is .poweredOn
<CBPeripheral: 0x2838f48c0,identifier = A7DBA197-EF45-A8E5-17FB-DF8505493179,name = DuoTrap S,state = disconnected>
Peripheral(id: 0,name: "DuoTrap S",RSSi: -70)
Connected!
<CBService: 0x281cbd380,isPrimary = YES,UUID = Cycling Speed and Cadence>
<CBCharacteristic: 0x282de4420,UUID = 2A5B,properties = 0x10,value = {length = 11,bytes = 0x03030000005d1601008212},notifying = NO>
2A5B: properties contains .notify
<CBCharacteristic: 0x282df8660,UUID = 2A5C,properties = 0x2,value = {length = 2,bytes = 0x0700},notifying = NO>
2A5C: properties contains .read
<CBCharacteristic: 0x282df8420,UUID = 2A5D,value = {length = 1,bytes = 0x04},notifying = NO>
2A5D: properties contains .read
<CBCharacteristic: 0x282df8660,notifying = NO>
Unhandled Characteristic UUID: 2A5D
<CBCharacteristic: 0x282de4420,bytes = 0x0307000000442c0500af25},notifying = YES>
<CBCharacteristic: 0x282de4420,bytes = 0x0308000000304506002e43},bytes = 0x0309000000664c07006a4b},bytes = 0x030a000000cf500800f14f},bytes = 0x030b0000005a540900a953},bytes = 0x030c00000075570b00b459},bytes = 0x030e0000000f5d0c00815c},bytes = 0x030f000000a25f0d00265f},notifying = YES>

据我所知,每次收到通知时,它代表来自 BLE 设备的最新数据。我假设在 UUID 为 2A5B 的重复行中表示以“字节”表示的原始数据。

<CBCharacteristic: 0x282de4420,notifying = YES>

我还假设这个十六进制数据 0x0307000000442c0500af25 是最重要的,因为它包含数据。

在这里找到了规范。

enter image description here

我只是看着这个十六进制数据和这个规格表,感觉好像我在看胡言乱语。这个规格表与数据有什么关系? 十六进制数据的每一部分是否都被分配了一个特定的值,还是整个十六进制是一个奇异值?我从哪里开始?感谢您的帮助!

解决方法

首先,不要将其视为“十六进制数据”。这只是一个字节序列。它恰好以十六进制显示,只是因为这通常很有用。但是来自设备的数据不是“十六进制”。它只是一堆字节,您需要按照规范指示对这些字节进行解码。 IMO 解码字节的最佳方法是在进行过程中使用它们。下标数据很危险,因为第一个索引不承诺为 0。我使用以下方法来做到这一点:

extension Data {
    // Based on Martin R's work: https://stackoverflow.com/a/38024025/97337
    mutating func consume<T>(type: T.Type) -> T? where T: ExpressibleByIntegerLiteral {
        let valueSize = MemoryLayout<T>.size
        guard count >= valueSize else { return nil }
        var value: T = 0
        _ = Swift.withUnsafeMutableBytes(of: &value,{ copyBytes(to: $0)} )
        removeFirst(valueSize)
        return value
    }
}

这是主要的解码器,它创建一个 CSCData 结构(使用 throws 可能会更好一些,但它增加了示例的复杂性):

struct CSCData {
    var wheelRevolutions: RevolutionData?
    var crankRevolutions: RevolutionData?

    init?(data: Data) {

        var data = data // Make mutable so we can consume it

        // First pull off the flags
        guard let flags = Flags(consuming: &data) else { return nil }

        // If wheel revolution is present,decode it
        if flags.contains(.wheelRevolutionPresent) {
            guard let value = RevolutionData(consuming: &data,countType: UInt32.self) else {
                return nil
            }
            self.wheelRevolutions = value
        }

        // If crank revolution is present,countType: UInt16.self) else {
                return nil
            }
            self.crankRevolutions = value
        }

        // You may or may not want this. Left-over data suggests that there was an error
        if !data.isEmpty {
            return nil
        }
    }
}

Flags 是一个 OptionSet 并以这种方式解码:

struct Flags : OptionSet {
    let rawValue: UInt8

    static let wheelRevolutionPresent = Flags(rawValue: 1 << 0)
    static let crankRevolutionPresent = Flags(rawValue: 1 << 1)
}

extension Flags {
    init?(consuming data: inout Data) {
        guard let byte = data.consume(type: UInt8.self) else { return nil }
        self.init(rawValue: byte)
    }
}

RevolutionData 就是这样解码的。注意 .littleEndian 的使用;即使您认为自己永远不会在大端平台上运行,解码时也要精确:

struct RevolutionData {
    var revolutions: Int
    var eventTime: TimeInterval

    init?<RevolutionCount>(consuming data: inout Data,countType: RevolutionCount.Type)
    where RevolutionCount: FixedWidthInteger
    {
        guard let count = data.consume(type: RevolutionCount.self)?.littleEndian,let time = data.consume(type: UInt16.self)?.littleEndian
        else {
            return nil
        }

        self.revolutions = Int(clamping: count)
        self.eventTime = TimeInterval(time) / 1024.0    // Unit is 1/1024 second
    }
}

注意 Int(clamping:) 的使用。这对于您的特定用途不是绝对必要的,但在 32 位平台上使用 UInt32(或更大)调用此代码是合法的。这可能会溢出并崩溃。决定在这种情况下做什么是一个重要的选择,但如果坏数据不会是灾难性的并且您不想崩溃,那么 init(clamping:) 是一个很好的默认值。 TimeInterval 不需要它,因为它肯定大于 UInt16。

更深层次的一点是,在解码从蓝牙获得的数据时,您应该始终保持警惕。您可能误解了规范,或者设备可能存在错误。他们可能会向您发送意外数据,您应该能够从中恢复。

并对此进行测试:

let data = Data([0x03,0x07,0x00,0x44,0x2c,0x05,0xaf,0x25])
let result = CSCData(data: data)!
// CSCData(wheelRevolutions: Optional(RevolutionData(revolutions: 7,eventTime: 11.06640625)),//         crankRevolutions: Optional(RevolutionData(revolutions: 5,eventTime: 9.4208984375)))
,

首先,我不得不提一下,我对 BLE 以及您具体尝试做的事情没有任何经验。但由于目前还没有答案,我会告诉你我对需求的理解。

所以你的设备的十六进制结果看起来像这样:0x0307000000442c0500af25

它有 11 个字节的布局,如下所示:

  • 第一个字节是标志字节;
  • 接下来的4个字节是车轮转数数据;
  • 下 2 个最后一个轮子事件时间;
  • 接下来的 2 次累积曲柄转数;
  • 最后 2 个是最后一次启动事件时间。

现在这些值中的每一个都有意义,具体取决于标志值。 因此,如果 Flags 字节的第一位为 0,则表示不存在车轮旋转数据,如果为 1,则表示存在。因此对于第一个标志位为 0 的情况,代表车轮转数数据的 4 个字节不包含相关数据。

如果 Flags 字节的第二位是 0,则没有曲柄转数数据,因此我们不关心表示此类数据的 2 个字节。

Flags 字节的最后六位保留供将来使用。所以我们根本不关心他们。

通过这种方式,您需要分析您收到的十六进制数据并对它们执行按位运算以获得您需要的数据。

在 0x0307000000442c0500af25 的例子中,第一位是 0x03。在二进制中,即 00000011。我们从右到左计算位,因此在这种情况下,车轮和曲柄数据都存在。

这意味着接下来的 4 个字节 0x07000000 代表转数数据。我不知道这个数字应该是多少。

接下来的 2 个字节 0x442c 代表最后一个车轮事件时间。依此类推,直到结束。

所以这就是您的问题的主要要点。以前要解决它,您必须学习如何在 Swift 中使用按位运算,以便您可以从数据中提取您需要的部分。

您可以从文档 https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html 开始。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。