订阅来自 CBCharacteristic 的通知不起作用

Posted

技术标签:

【中文标题】订阅来自 CBCharacteristic 的通知不起作用【英文标题】:Subscribing for notifications from a CBCharacteristic does not work 【发布时间】:2015-09-25 07:42:54 【问题描述】:

首要任务:运行 OSX 10.10.4、ios 4、Xcode 6.3.2、iPhone 6、Swift

简短的故事:我这里有一个蓝牙 LE 设备,我想在特性值发生变化时从该设备接收通知,例如通过用户输入。尝试订阅它不会成功,而是会产生错误Error Domain=CBATTErrorDomain Code=10 "The attribute could not be found."

长话短说:所以,我有一个 BluetoothManager 类,一旦我的$CBCentralManager.state.PoweredOn,我就开始扫描外围设备。这很容易,我什至是一个好公民,并且专门为那些拥有我想要的服务的人扫描

centralManager.scanForPeripheralsWithServices([ServiceUUID], options: nil)

希望这会成功,我实现了以下委托方法:

func centralManager(central: CBCentralManager!, didDiscoverPeripheral peripheral: CBPeripheral!, advertisementData: [NSObject : AnyObject]!, RSSI: NSNumber!) 

    if *this is a known device* 
        connectToPeripheral(peripheral)
        return
    

    [...] // various stuff to make something a known device, this works

所以继续前进,我们得到:

func connectToPeripheral(peripheral: CBPeripheral) 
    println("connecting to \(peripheral.identifier)")

    [...] // saving the peripheral in an array along the way so it is being retained

    centralManager.connectPeripheral(peripheral, options: nil)

是的,这成功了,所以我得到确认并开始发现服务:

func centralManager(central: CBCentralManager!, didConnectPeripheral peripheral: CBPeripheral!)         
    println("Connected \(peripheral.name)")

    peripheral.delegate = self

    println("connected to \(peripheral)")
    peripheral.discoverServices([BluetoothConstants.MY_SERVICE_UUID])

这也有效,因为该委托方法也被调用:

func peripheral(peripheral: CBPeripheral!, didDiscoverServices error: NSError!) 

    if peripheral.services != nil 
        for service in peripheral.services 
            println("discovered service \(service)")
            let serviceObject = service as! CBService

            [...] // Discover the Characteristic to send controls to, this works
            peripheral.discoverCharacteristics([BluetoothConstants.MY_CHARACTERISTIC_NOTIFICATION_UUID], forService: serviceObject)

           [...] // Some unneccessary stuff about command caches
        
    

你知道什么:特征发现了!

func peripheral(peripheral: CBPeripheral!, didDiscoverCharacteristicsForService service: CBService!, error: NSError!) 
    for characteristic in service.characteristics 
        let castCharacteristic = characteristic as! CBCharacteristic

        characteristics.append(castCharacteristic) // Retaining the characteristic in an Array as well, not sure if I need to do this

        println("discovered characteristic \(castCharacteristic)")
        if *this is the control characteristic* 
            println("control")
         else if castCharacteristic.UUID.UUIDString == BluetoothConstants.MY_CHARACTERISTIC_NOTIFICATION_UUID.UUIDString 
            println("notification")
            peripheral.setNotifyValue(true, forCharacteristic: castCharacteristic)
         else 
            println(castCharacteristic.UUID.UUIDString) // Just in case
        
        println("following properties:")

        // Just to see what we are dealing with
        if (castCharacteristic.properties & CBCharacteristicProperties.Broadcast) != nil 
            println("broadcast")
        
        if (castCharacteristic.properties & CBCharacteristicProperties.Read) != nil 
            println("read")
        
        if (castCharacteristic.properties & CBCharacteristicProperties.WriteWithoutResponse) != nil 
            println("write without response")
        
        if (castCharacteristic.properties & CBCharacteristicProperties.Write) != nil 
            println("write")
        
        if (castCharacteristic.properties & CBCharacteristicProperties.Notify) != nil 
            println("notify")
        
        if (castCharacteristic.properties & CBCharacteristicProperties.Indicate) != nil 
            println("indicate")
        
        if (castCharacteristic.properties & CBCharacteristicProperties.AuthenticatedSignedWrites) != nil 
            println("authenticated signed writes ")
        
        if (castCharacteristic.properties & CBCharacteristicProperties.ExtendedProperties) != nil 
            println("indicate")
        
        if (castCharacteristic.properties & CBCharacteristicProperties.NotifyEncryptionRequired) != nil 
            println("notify encryption required")
        
        if (castCharacteristic.properties & CBCharacteristicProperties.IndicateEncryptionRequired) != nil 
            println("indicate encryption required")
        

        peripheral.discoverDescriptorsForCharacteristic(castCharacteristic) // Do I need this?
    

现在到这里的控制台输出如下所示:

connected to <CBPeripheral: 0x1740fc780, identifier = $FOO, name = $SomeName, state = connected>
discovered service <CBService: 0x170272c80, isPrimary = YES, UUID = $BAR>
[...]
discovered characteristic <CBCharacteristic: 0x17009f220, UUID = $BARBAR properties = 0xA, value = (null), notifying = NO>
control
following properties:
read
write
[...]
discovered characteristic <CBCharacteristic: 0x17409d0b0, UUID = $BAZBAZ, properties = 0x1A, value = (null), notifying = NO>
notification
following properties:
read
write
notify
[...]
discovered DescriptorsForCharacteristic
[]
updateNotification: false

嘿!它说updateNotificationfalse。那是从哪里来的?为什么,这是我对setNotify... 的回调:

func peripheral(peripheral: CBPeripheral!, didUpdateNotificationStateForCharacteristic characteristic: CBCharacteristic!, error: NSError!) 

    println("updateNotification: \(characteristic.isNotifying)")

什么给了?我告诉它要通知!为什么不通知?让我们在 println 的行中设置一个断点并检查错误对象:

(lldb) po error
Error Domain=CBATTErrorDomain Code=10 "The attribute could not be found." UserInfo=0x17026eac0 NSLocalizedDescription=The attribute could not be found.

好的,所以这让我没有想法。我无法找到有关该错误代码的相关线索。描述本身我无法理解,因为我试图为我之前发现的特征设置通知,因此它必须存在,对吗?此外,在 android 上似乎可以订阅通知,所以我想我可以排除设备问题......或者我可以吗?任何有关这方面的线索都非常感谢!

【问题讨论】:

看起来您可能没有保留对外围设备的引用。 Paulw11 有一个很好的答案***.com/questions/26377470/… 不,我正在这样做;为简洁起见,我将其省略:我的外设数组处于已发现、连接和已连接状态,并确保在设置期间将外设保持在适当的状态。 您的代码看起来不错,它在您的设备或 iOS 上可能很重要。我的首选测试工具是 LightBlue 应用程序。您可以使用它来发现您的外围设备和服务,并查看它是否可以为您的特征设置通知。 很好的提示。有趣的是,LightBlue 可以连接到另一个通知特征,但对于有问题的特征,点击 Listen for notifications 根本不会改变文本。看起来问题不在于我的代码,嗯?我会尝试与制造商核实。 对我来说,重置通知标志的外围设备有问题,所以我每次都必须将通知设置为 true。 【参考方案1】:

对我来说,问题是我正在使用另一个 Android 设备作为外围设备并且需要实现配置描述符。看这里: https://***.com/a/25508053/599743

【讨论】:

【参考方案2】:

从制造商那里收到更多信息后,我收集到设备已连接并发送通知,而不管特性的通信通知状态如何。这意味着即使peripheral(_, didUpdateNotificationStateForCharacteristic, error) 告诉我特征没有通知,peripheral(_, didUpdateValueForCharacteristic, error) 也会在设备发送数据时被调用并传递数据。我之前没有实现那个回调,因为我没想到会在这种状态下发送数据。

所以基本上,我的问题似乎已经解决了。尽管如此,我仍然对设备的行为是否符合蓝牙 LE 规范感兴趣;我不了解那些较低级别的实现,但假设 Apple 出于某种原因编写了他们的文档,至少强烈暗示 Characteristic 状态的变化将在接收任何数据之前发生。

【讨论】:

【参考方案3】:

虽然这是一个老问题并且已经解决了,但我想提供我的输入和 cmets。

首先,BLE 外设负责定义其特征的行为。外设可以选择定义特性属性。 Apple 文档here 中列出了 Characteristic 的各种受支持属性。

现在让我们专注于您的问题,您正在尝试收听特征值更改但订阅失败。

我在您的代码中看到的问题是,无论特征是否支持它,都会进行订阅。仅在支持的情况下订阅特征。请参阅以下代码块,该代码块显示了如何识别特征支持的属性并基于它执行操作。

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) 

    // Check if characteristics found for service.
    guard let characteristics = service.characteristics, error == nil else 
        print("error: \(String(describing: error))")
        return
    

    // Loop over all the characteristics found.
    for characteristic in characteristics 
        if characteristic.properties.contains([.notify, .notifyEncryptionRequired]) 
            peripheral.setNotifyValue(true, for: characteristic)
         else if characteristic.properties.contains([.read]) 
            peripheral.readValue(for: characteristic)
         else if characteristic.properties.contains([.write]) 
            // Perform write operation
        
    

如果 Characteristic 支持 notify 属性并且您订阅了它。订阅成功后,将调用以下委托,您可以在其中验证订阅状态。

func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?)  
    // Check subscription here
    print("isNotifying: \(characteristic.isNotifying)")

如果订阅成功,每当更新特征时,将使用新值调用以下委托方法。

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) 
    // read updated value for characteristic.

检查它是否不起作用

请与外围设备制造商核实特征实现以及它是否支持必要的属性。 检查您的实施并确保您遵循所有必要的步骤。

我已经尝试在各个方面尽可能地回答。希望能帮助到你。快乐编码:)

【讨论】:

以上是关于订阅来自 CBCharacteristic 的通知不起作用的主要内容,如果未能解决你的问题,请参考以下文章

未收到来自 CloudKit 订阅的推送通知

Firebase 消息 - 无法接收来自订阅的通知

iOS 蓝牙 LE 无法以编程方式获取通知,但可以在其他应用程序中

iOS 未收到来自 CloudKit 的推送通知

处理来自 superfeedr 的重复通知

识别正在通知的 BLE 设备