从CBCharacteristic订阅通知无法正常工作

9

首先,我的操作系统是OSX 10.10.4,iOS 4,Xcode 6.3.2,iPhone 6和Swift。

简单来说,我有一个特定的蓝牙LE设备,当Characteristic的值发生变化时,例如用户输入时,我希望能够接收到通知。尝试订阅它并不成功,而是会产生一个错误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在这方面有一个很好的答案https://dev59.com/UV8d5IYBdhLWcg3w1VI1 - Jeffrey Stewart
不,我正在做那个;为了简洁起见,我把它省略了:我已经在发现、连接和已连接状态下使用了外围设备的数组,并确保在设置过程中将外围设备保持在适当的状态。 - mss
1
你的代码看起来没问题,可能是在你的设备或iOS上出现了一些问题。我用于测试的首选工具是LightBlue应用程序。你可以使用它来发现你的外围设备和服务,并查看是否可以在你的特征上设置通知。 - Paulw11
1
很好的提示。有趣的是,LightBlue可以连接到另一个特征进行通知,但对于所讨论的特征,点击“侦听通知”栏位并没有改变文本。看起来问题不在我的代码上,嗯?我会尝试与制造商联系确认。 - mss
对我来说,问题出在外围设备重置了通知标志,所以我不得不每次将通知设置为true。 - david72
3个回答

5

3
虽然这是一个旧问题,而且已经得到解决,但我仍然想提供我的意见和评论。
首先,BLE外设负责定义其特征的行为。外设可以选择定义特征属性。有关Characteristic支持的各种属性在苹果文档中列出,点击此处查看。
现在让我们聚焦于你的问题。您正在尝试监听Characteristic值的更改,但订阅失败。
  • The problem which I see in your code is, a subscription is made regardless of Characteristics support it or not. Subscribe to characteristic only if it's supported. Refer following code block which shows how to identify supported property for characteristic and perform the operation based on it.

    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.
}

如果不起作用,需要检查一些事情

  • 向外围设备制造商查询特征实现以及是否支持必需属性。
  • 检查您的实现,并确保遵循所有必要的步骤。

我已尽可能在各个方面提供答案。希望它有所帮助。愉快编程:)


3
得到了厂商提供的更多信息后,我得知设备在通信特征的通知状态未被通知时仍然连接并发送通知。这意味着即使peripheral(_, didUpdateNotificationStateForCharacteristic, error)提示特征未通知,peripheral(_, didUpdateValueForCharacteristic, error)仍会被调用并在设备发送数据时传递数据。之前我没有实现此回调,因为我不希望在这种情况下发送数据。

因此,我的问题似乎已经解决了。尽管如此,我仍然想知道设备的行为是否符合蓝牙LE规范; 我对这些较低实现级别没有深入了解,但我认为Apple编写他们的文档是有一定原因的,至少强烈暗示特征状态的更改将在接收任何数据之前发生。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接