Android BLE: onCharacteristicChanged никогда не срабатывает

Я пытаюсь написать приложение для Android, которое имитирует функциональность, уже присутствующую в написанном мной приложении для iOS. Я взаимодействую с двумя разными устройствами BLE:

  1. Манжета для измерения артериального давления
  2. Весовая шкала

На iOS у меня оба устройства работают нормально и предоставляют данные. На Android не могу заставить его работать. После нескольких часов исследований и испытаний я думаю, что основная проблема, которую я пытаюсь решить, заключается в следующем:

В iOS я вызываю следующий код, чтобы позволить устройству BLE уведомлять мое устройство iOS, когда у него есть данные для отчета:

#pragma mark - CBPeripheralDelegate Protocol methods
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    for (CBCharacteristic *characteristic in [service characteristics]) {
        [peripheral setNotifyValue:YES forCharacteristic:characteristic];
    }
}

Вот и все. Примечания для этого метода в iOS говорят следующее:

Если указанная характеристика настроена так, чтобы разрешать и уведомления, и индикации, вызов этого метода разрешает только уведомления.

Исходя из этого (и того факта, что он работает в iOS), я полагаю, что дескриптор конфигурации для характеристики, для которой я хочу получать уведомления, должен быть настроен следующим образом:

descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
gatt.writeDescriptor(descriptor);

Имея это в виду, мой класс BLEDevice выглядит так:

public abstract class BLEDevice {
    protected BluetoothAdapter.LeScanCallback   mLeScanCallback;
    protected BluetoothGattCallback             mBluetoothGattCallback;
    protected byte[]                            mBytes;
    protected Context                           mContext;
    protected GotReadingCallback                mGotReadingCallback;
    protected String                            mDeviceName;
    public final static UUID                    UUID_WEIGHT_SCALE_SERVICE                       
            = UUID.fromString(GattAttributes.WEIGHT_SCALE_SERVICE);
    public final static UUID                    UUID_WEIGHT_SCALE_READING_CHARACTERISTIC        
            = UUID.fromString(GattAttributes.WEIGHT_SCALE_READING_CHARACTERISTIC);
    public final static UUID                    UUID_WEIGHT_SCALE_CONFIGURATION_CHARACTERISTIC  
            = UUID.fromString(GattAttributes.WEIGHT_SCALE_CONFIGURATION_CHARACTERISTIC);
    public final static UUID                    UUID_WEIGHT_SCALE_CONFIGURATION_DESCRIPTOR      
            = UUID.fromString(GattAttributes.WEIGHT_SCALE_CONFIGURATION_DESCRIPTOR);

    abstract void processReading();

    interface GotReadingCallback {
        void gotReading(Object reading);
    }

    public BLEDevice(Context context, String deviceName, GotReadingCallback gotReadingCallback) {
        mContext                    = context;
        BluetoothManager btManager  = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
        final BluetoothAdapter btAdapter  = btManager.getAdapter();
        if (btAdapter != null && !btAdapter.isEnabled()) {
            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            mContext.startActivity(enableIntent);
        }
        mDeviceName = deviceName;
        mBluetoothGattCallback = new BluetoothGattCallback() {
            @Override
            public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
                byte[] data = characteristic.getValue();
                mBytes = data;
                Log.d("BluetoothGattCallback.onCharacteristicChanged", "data: " + data.toString());
            }
            @Override
            public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
                // this will get called when a device connects or disconnects
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    gatt.discoverServices();
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    if (mBytes != null) {
                        processReading();
                    }
                }
            }
            @Override
            public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
                super.onDescriptorWrite(gatt, descriptor, status);
                Log.d("onDescriptorWrite", "descriptor: " + descriptor.getUuid() + ". characteristic: " + descriptor.getCharacteristic().getUuid() + ". status: " + status);
            }
            @Override
            public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
                // this will get called after the client initiates a BluetoothGatt.discoverServices() call
                BluetoothGattService service = gatt.getService(UUID_WEIGHT_SCALE_SERVICE);
                if (service != null) {
                    BluetoothGattCharacteristic characteristic;
                    characteristic                              = service.getCharacteristic(UUID_WEIGHT_SCALE_READING_CHARACTERISTIC);
                    if (characteristic != null) {
                        gatt.setCharacteristicNotification(characteristic, true);
                    }
                    characteristic                              = service.getCharacteristic(UUID_WEIGHT_SCALE_CONFIGURATION_CHARACTERISTIC);
                    if (characteristic != null) {
                        BluetoothGattDescriptor descriptor      = characteristic.getDescriptor(UUID_WEIGHT_SCALE_CONFIGURATION_DESCRIPTOR);
                        if (descriptor != null) {
                            descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
                            gatt.writeDescriptor(descriptor);
                        }
                    }
                }
            }
        };
        mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
            @Override
            public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
                Log.d("LeScanCallback", device.toString());
                if (device.getName().contains("{Device Name}")) {
                    BluetoothGatt bluetoothGatt = device.connectGatt(mContext, false, mBluetoothGattCallback);
                    btAdapter.stopLeScan(mLeScanCallback);
                }
            }
        };
        btAdapter.startLeScan(mLeScanCallback);
    }
}

ПРИМЕЧАНИЕ.. Возможно, важно знать, что эти 2 устройства работают следующим образом:

  1. Устройство BLE включается, и на устройстве запускается измерение.
  2. После выполнения измерения устройство BLE пытается инициировать соединение BLE.
  3. Как только соединение BLE установлено, устройство практически сразу же отправляет данные, иногда отправляя пару пакетов данных. (Если предыдущие измерения данных не были успешно отправлены через BLE, он сохраняет их в памяти и отправляет их все, поэтому меня действительно волнует только окончательный пакет данных.)
  4. После отправки последнего пакета данных устройство BLE быстро отключается.
  5. Если устройству BLE не удается отправить данные (как это сейчас происходит в приложении Android), устройство BLE отключается довольно быстро.

В моем LogCat я вижу много результатов, которые соответствуют моим ожиданиям.

  1. Я вижу список ожидаемых служб, включая нужную мне службу передачи данных.
  2. Я вижу список ожидаемых характеристик, включая те характеристики данных, которые мне нужны.
  3. Я вижу список дескрипторов, как и ожидал, включая дескриптор «конфигурации» (0x2902).

Самая последняя ошибка, с которой я столкнулся, - это статус "128", сообщаемый в onCharacteristicWrite. Комментарии к вопросу № 3 (ниже), похоже, указывают на то, что это проблема с ресурсами.

Я рассмотрел следующие вопросы:

  1. Android BLE onCharacteristicChanged не вызывается
  2. Android BLE, характеристики чтения и записи
  3. Android 4.3 onDescriptorWrite возвращает статус 128

Вот почему мне не дают то, что мне нужно:

  1. Ответ на этот вопрос заключался в том, чтобы не читать значение дескриптора. Я этого не делаю, так что это не может быть то, что мешает.
  2. Это в основном обзор различных доступных методов, которые, как мне кажется, я теперь понимаю. Главный ключ в этом вопросе / ответе - не писать несколько раз в разные дескрипторы, но я тоже этого не делаю. Меня волнует только одна характеристика.
  3. Этот вопрос / ответ, похоже, связан с ограничениями ресурсов BLE, но я не думаю, что это применимо. Я подключаю только это одно устройство и пытаюсь выполнить очень и очень простую передачу данных. Я не думаю, что достигаю потолка ресурсов.

Я перепробовал кучу примеров и руководств, включая образец кода Google для Android. Ни один из них, похоже, не позволяет устройству BLE уведомлять мое устройство Android об обновлениях данных. Дело явно не в девайсе, так как версия для iOS работает. Итак, что код iOS делает в фоновом режиме, чтобы уведомления работали, и какой код на стороне Android имитирует эту функциональность?


ИЗМЕНИТЬ / ОБНОВИТЬ

Основываясь на комментариях @yonran, я обновил свой код, изменив реализацию onServicesDiscovered на это:

@Override
public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
    // this will get called after the client initiates a BluetoothGatt.discoverServices() call
    BluetoothGattService service = gatt.getService(UUID_WEIGHT_SCALE_SERVICE);
    if (service != null) {
        BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID_WEIGHT_SCALE_READING_CHARACTERISTIC);
        if (characteristic != null) {
            if (gatt.setCharacteristicNotification(characteristic, true) == true) {
                Log.d("gatt.setCharacteristicNotification", "SUCCESS!");
            } else {
                Log.d("gatt.setCharacteristicNotification", "FAILURE!");
            }
            BluetoothGattDescriptor descriptor = characteristic.getDescriptors().get(0);
            if (0 != (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE)) {
                // It's an indicate characteristic
                Log.d("onServicesDiscovered", "Characteristic (" + characteristic.getUuid() + ") is INDICATE");
                if (descriptor != null) {
                    descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
                    gatt.writeDescriptor(descriptor);
                }
            } else {
                // It's a notify characteristic
                Log.d("onServicesDiscovered", "Characteristic (" + characteristic.getUuid() + ") is NOTIFY");
                if (descriptor != null) {
                    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                    gatt.writeDescriptor(descriptor);
                }
            }
        }
    }
}

Это действительно, похоже, немного изменило некоторые вещи. Вот текущий Logcat после этого изменения кода:

D/BluetoothGatt﹕ setCharacteristicNotification() - uuid: <UUID> enable: true
D/gatt.setCharacteristicNotification﹕ SUCCESS!
D/onServicesDiscovered﹕ Characteristic (<UUID>) is INDICATE
D/BluetoothGatt﹕ writeDescriptor() - uuid: 00002902-0000-1000-8000-00805f9b34fb
D/BluetoothGatt﹕ onDescriptorWrite() - Device=D0:5F:B8:01:6C:9E UUID=<UUID>
D/onDescriptorWrite﹕ descriptor: 00002902-0000-1000-8000-00805f9b34fb. characteristic: <UUID>. status: 0
D/BluetoothGatt﹕ onClientConnectionState() - status=0 clientIf=6 device=D0:5F:B8:01:6C:9E

Итак, похоже, что теперь я настраиваю все правильно (поскольку setCharacteristicNotification возвращает true, а статус onDescriptorWrite равен 0). Однако onCharacteristicChanged по-прежнему не срабатывает.



person mbm29414    schedule 18.02.2015    source источник
comment
Не могли бы вы проверить характерные свойства, чтобы узнать, какие из них поддерживаются (уведомлять или указывать)? 0 != (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE)? iOS автоматически сделает это за вас.   -  person yonran    schedule 18.02.2015
comment
@yonran Я бы с удовольствием! Сделаю, когда вернусь в свой офис. Спасибо, что поделились идеей. Вы думаете, мне нужно включить его по-другому?   -  person mbm29414    schedule 18.02.2015
comment
@yonran Следующий код возвращает истину (выполнение переходит в оператор if): if (0 != (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE)) {   -  person mbm29414    schedule 19.02.2015
comment
Подождите, похоже, вы вызываете setCharacteristicNotification для характеристики, отличной от той, для которой вы включаете индикацию клиентской конфигурации. Используйте ту же характеристику.   -  person yonran    schedule 19.02.2015
comment
@yonran Да, я это поймал и изменил. Я собираюсь опубликовать обновление.   -  person mbm29414    schedule 19.02.2015
comment
@yonran Хорошо. Опубликовано обновление кода. Спасибо за вашу помощь. Думаю, сейчас я ближе к решению. Но все еще не там.   -  person mbm29414    schedule 19.02.2015
comment
Я не знаю прямо сейчас. Возможно, захват пакета покажет, действительно ли устройство давало какие-либо указания? См. nowsecure.com/blog/2014/02/ 07 / bluetooth-packet-capture-android   -  person yonran    schedule 19.02.2015
comment
@yonran Я получил файл btsnoop со своего устройства и анализирую его в Wireshark. Я вижу большой трафик между моим телефоном и весами (устройством BLE), но в основном это разговор, который происходит во время discoverServices(). Есть какие-нибудь мысли о том, на что я должен обратить внимание? Как распознать уведомление / указание?   -  person mbm29414    schedule 19.02.2015
comment
Давайте продолжим это обсуждение в чате.   -  person yonran    schedule 19.02.2015
comment
@ mbm29414 у тебя есть решение. У меня точно такая же проблема   -  person user1767260    schedule 02.04.2015
comment
@yonran К сожалению, решение было получить более новое устройство. Я получил LG G3 и обновил его до Android Lollipop 5.0.1. После этого все заработало. Согласно моим исследованиям, стек BLE был очень нестабильным до Android 4.4.4 (мой старый LG Optimus G Pro работал под управлением 4.4.3; посмотрите сами). На моем LG G3, работающем под управлением 5.0.1, написанный мной код работает как шарм! Я понимаю, что это плохое решение, но на самом деле оно работает надежно, так что, возможно, это что-то полезное.   -  person mbm29414    schedule 02.04.2015


Ответы (2)


Мне удалось успешно поймать onCharacteristicChanged () с несколькими службами и характеристиками:

  1. Запись значений дескриптора в broadcastReceiver () в основном цикле после завершения обнаружения службы.

    private final BroadcastReceiver UARTStatusChangeReceiver = new BroadcastReceiver() {
    //more code...
    if (action.equals(uartservice.ACTION_GATT_SERVICES_DISCOVERED)) {
             mService.enableTXNotification();
        }
    

а также

  1. Добавляя задержку между настройками значения дескриптора

    public void enableTXNotification(){ 
    /*
    if (mBluetoothGatt == null) {
        showMessage("mBluetoothGatt null" + mBluetoothGatt);
        broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
        return;
    }
        */
    /**
     * Enable Notifications for the IO service and characteristic
     *
     */
    BluetoothGattService IOService = mBluetoothGatt.getService(IO_SERVICE_UUID);
    if (IOService == null) {
        showMessage("IO service not found!");
        broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_IO);
        return;
    }
    BluetoothGattCharacteristic IOChar = IOService.getCharacteristic(IO_CHAR_UUID);
    if (IOChar == null) {
        showMessage("IO charateristic not found!");
        broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_IO);
        return;
    }
    mBluetoothGatt.setCharacteristicNotification(IOChar,true);
    BluetoothGattDescriptor descriptorIO = IOChar.getDescriptor(CCCD);
    descriptorIO.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    mBluetoothGatt.writeDescriptor(descriptorIO);
    
    /**
     * For some reason android (or the device) can't handle
     * writing one descriptor after another properly.  Without
     * the delay only the first characteristic can be caught in
     * onCharacteristicChanged() method.
     */
    try {
        Thread.sleep(200);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    /**
     * Enable Indications for the RXTX service and characteristic
     */
    BluetoothGattService RxService = mBluetoothGatt.getService(RXTX_SERVICE_UUID);
    if (RxService == null) {
        showMessage("Rx service not found!");
        broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
        return;
    }
    BluetoothGattCharacteristic RxChar = RxService.getCharacteristic(RXTX_CHAR_UUID);
    if (RxChar == null) {
        showMessage("Tx charateristic not found!");
        broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
        return;
    }
    mBluetoothGatt.setCharacteristicNotification(RxChar,true);
    BluetoothGattDescriptor descriptor = RxChar.getDescriptor(CCCD);
    descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE );
    mBluetoothGatt.writeDescriptor(descriptor);
    
    try {
        Thread.sleep(200);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    /**
     * Enable Notifications for the Battery service and Characteristic?
     */
    BluetoothGattService batteryService = mBluetoothGatt.getService(BATTERY_SERVICE_UUID);
    if (batteryService == null) {
        showMessage("Battery service not found!");
        broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_BATTERY);
        return;
    }
    BluetoothGattCharacteristic batteryChar = batteryService.getCharacteristic(BATTERY_CHAR_UUID);
    if (batteryChar == null) {
        showMessage("Battery charateristic not found!");
        broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_BATTERY);
        return;
    }
    }
    
person madcow37    schedule 16.04.2015
comment
Мне только что пришлось столкнуться с подобной проблемой. У меня были readCharacteristic(...) и writeDescriptor(...) подряд внутри onServicesDiscovered(...). writeDescriptor(...) инициирует запись и возвращает true, но не проходит, и его обратный вызов onDescriptorWrite(...) не запускается. Я удалил вызов readCharacteristic(...), так что теперь они не повторяются. Теперь writeDescriptor(...) проходит, и его обратный вызов тоже срабатывает. - person Nick Alexeev; 01.03.2016

Я столкнулся с той же проблемой. это потому, что, когда устройство отправляет указанное значение, ваше приложение заряжается в другом процессе, и поэтому вы никогда не получаете указанное значение, из-за которого onCharacteristicChanged никогда не срабатывает. чтобы решить вашу проблему, постарайтесь вложить все свои силы в услугу. и просто вызывайте функции из своей деятельности.

person Fakher    schedule 10.09.2015