Подписка на уведомления от 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 по какой-то причине написала свои документы, чтобы хотя бы сильно предположить, что изменение состояния Характеристики будет предшествовать приему любых данных.

Другие вопросы по тегам