Подписка на уведомления от CBCharacteristic не работает
Перво-наперво: запуск OSX 10.10.4, iOS 4, Xcode 6.3.2, iPhone 6, Swift
Короткая история: у меня есть определенное устройство Bluetooth 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
Привет! Это говорит updateNotification
является false
, Откуда это? Почему это мой обратный звонок для 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 кажется возможным подписаться на уведомления, так что, думаю, я могу исключить проблемы с устройством... или я могу? Любые подсказки относительно этого действительно ценятся!
2 ответа
Для меня проблема заключалась в том, что я использовал другое устройство Android в качестве периферийного устройства и мне нужно было реализовать дескриптор конфигурации. Смотрите здесь: /questions/11682802/lyuboj-sposob-realizovat-uvedomleniya-ble-v-predvaritelnom-prosmotre-android-l/11682809#11682809
Хотя это старый вопрос и он все же решен, я хотел бы высказать свое мнение и комментарии.
Во-первых, BLE Peripheral отвечает за определение поведения своих характеристик. Периферийное устройство имеет возможность определить свойство Характеристики. Различные поддерживаемые свойства для характеристики перечислены в документации Apple здесь.
Теперь давайте сосредоточимся на вашем вопросе. Вы пытаетесь прослушать изменение значения характеристики, но подписка не проходит.
Проблема, которую я вижу в вашем коде, заключается в том, что подписка оформляется независимо от того, поддерживают ее характеристики или нет. Подпишитесь на характеристику, только если она поддерживается. Обратитесь к следующему блоку кода, в котором показано, как определить поддерживаемое свойство для характеристики и выполнить операцию на его основе.
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 } } }
Если Характеристика поддерживает 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.
}
Несколько вещей, чтобы проверить, не работает ли он
- Узнайте у производителя периферийных устройств о характерных реализациях и поддержке необходимых свойств.
- Проверьте свою реализацию и убедитесь, что вы выполняете все необходимые шаги.
Я постарался ответить как можно больше по всем аспектам. Надеюсь, это поможет. Удачного кодирования:)
Получив больше информации от производителя, я обнаружил, что устройство подключено и отправляет уведомления независимо от состояния уведомления характеристики, о котором сообщалось. Что означает, что даже если peripheral(_, didUpdateNotificationStateForCharacteristic, error)
говорит мне, что характеристика не уведомляет, peripheral(_, didUpdateValueForCharacteristic, error)
вызывается и доставляет данные, когда устройство отправляет его. Я не реализовывал этот обратный вызов ранее, так как не ожидал, что данные будут отправлены в этом состоянии.
В общем, моя проблема, похоже, решилась сама собой. Тем не менее, мне все еще интересно, соответствует ли поведение устройства спецификациям Bluetooth LE или нет; У меня нет понимания этих более низких уровней реализации, но я полагаю, что Apple по какой-то причине написала свои документы, чтобы хотя бы сильно предположить, что изменение состояния Характеристики будет предшествовать приему любых данных.