Yot*_*jon 2 ios core-bluetooth bluetooth-lowenergy swift
我正在构建一个仅显示速度和节奏的 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, properties = 0x2, value = {length = 1, bytes = 0x04}, notifying = NO>
2A5D: properties contains .read
<CBCharacteristic: 0x282df8660, UUID = 2A5C, properties = 0x2, value = {length = 2, bytes = 0x0700}, notifying = NO>
Unhandled Characteristic UUID: 2A5D
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x0307000000442c0500af25}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x0307000000442c0500af25}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x0308000000304506002e43}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x0308000000304506002e43}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x0309000000664c07006a4b}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x030a000000cf500800f14f}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x030b0000005a540900a953}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x030c00000075570b00b459}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x030e0000000f5d0c00815c}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x030f000000a25f0d00265f}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x030f000000a25f0d00265f}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x030f000000a25f0d00265f}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x030f000000a25f0d00265f}, notifying = YES>
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x030f000000a25f0d00265f}, notifying = YES>
Run Code Online (Sandbox Code Playgroud)
据我了解,每次收到通知时,它代表来自 BLE 设备的最新数据。我假设在重复行中 UUID 为 2A5B 表示以“字节”表示的原始数据。
<CBCharacteristic: 0x282de4420, UUID = 2A5B, properties = 0x10, value = {length = 11, bytes = 0x0307000000442c0500af25}, notifying = YES>
Run Code Online (Sandbox Code Playgroud)
我还假设这个十六进制数据0x0307000000442c0500af25最重要,因为它包含数据。
我只是看看这个十六进制数据和这个规格表,感觉好像我在看胡言乱语。该规格表与数据有什么关系?十六进制数据的每个部分是否被分配了一个特定值,或者整个十六进制是一个奇异值?我从哪说起呢?感谢您的帮助!
首先,不要将其视为“十六进制数据”。这只是一个字节序列。它恰好以十六进制显示,只是因为这通常很有用。但来自设备的数据不是“十六进制”。它只是一堆字节,您需要按照规范指示对这些字节进行解码。在我看来,解码字节的最佳方法是在进行过程中消耗它们。下标数据是危险的,因为第一个索引不承诺为 0。我使用以下方法来做到这一点:
extension Data {
// Based on Martin R's work: /sf/answers/2661681781/
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
}
}
Run Code Online (Sandbox Code Playgroud)
这是主解码器,它创建一个 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, decode it
if flags.contains(.wheelRevolutionPresent) {
guard let value = RevolutionData(consuming: &data, 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
}
}
}
Run Code Online (Sandbox Code Playgroud)
标志是一个选项集,并以这种方式解码:
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)
}
}
Run Code Online (Sandbox Code Playgroud)
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
}
}
Run Code Online (Sandbox Code Playgroud)
注意使用Int(clamping:). UInt32这对于您的特定用途来说并不是严格需要的,但在 32 位平台上使用(或更大的)调用此代码是合法的。这可能会溢出并崩溃。决定在这种情况下做什么是一个重要的选择,但init(clamping:)如果坏数据不会造成灾难性的后果并且您不想崩溃,那么 a 是一个很好的默认值。TimeInterval 不需要这样做,因为它肯定大于 UInt16。
更深层的一点是,当解码从蓝牙获得的数据时,您应该始终保持高度防御性。您可能误解了规范,或者设备可能存在错误。他们可能会向您发送意外的数据,您应该能够从中恢复。
并测试这个:
let data = Data([0x03,0x07,0x00,0x00,0x00,0x44,0x2c,0x05,0x00,0xaf,0x25])
let result = CSCData(data: data)!
// CSCData(wheelRevolutions: Optional(RevolutionData(revolutions: 7, eventTime: 11.06640625)),
// crankRevolutions: Optional(RevolutionData(revolutions: 5, eventTime: 9.4208984375)))
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1427 次 |
| 最近记录: |