【问题标题】:Android BLE: onCharacteristicChanged never firesAndroid BLE:onCharacteristicChanged 永远不会触发
【发布时间】:2015-04-19 14:26:14
【问题描述】:

我正在尝试编写一个模仿我编写的 iOS 应用程序中已有功能的 Android 应用程序。我正在与 2 个不同的 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);
    }
}

注意:了解这两种设备以下列方式运行可能很重要:

  1. BLE 设备已开启并在设备上启动测量。
  2. 进行测量后,BLE 设备会尝试启动 BLE 连接。
  3. 一旦建立 BLE 连接,设备几乎会立即发送数据,有时会发送几个数据包。 (如果之前的数据测量没有通过 BLE 成功发送,它会将它们保存在内存中并全部发送,所以我只关心最终的数据包。)
  4. 一旦发送最终数据包,BLE 设备会迅速断开连接。
  5. 如果 BLE 设备无法发送数据(目前在 Android 应用上发生的情况),BLE 设备会很快断开连接。

在我的 LogCat 中,我看到了很多与我预期完全一样的输出。

  1. 我看到了我期望的服务列表,包括我想要的数据服务。
  2. 我看到了我期望的特征列表,包括我想要的数据特征。
  3. 我看到了一个符合预期的描述符列表,包括“配置”(0x2902) 描述符。

我最近遇到的故障是onCharacteristicWrite 中报告的“128”状态。问题 #3(下)的 cmets 似乎表明这是一个资源问题。

我查看了以下问题:

  1. Android BLE onCharacteristicChanged not called
  2. Android BLE, read and write characteristics
  3. Android 4.3 onDescriptorWrite returns status 128

这就是为什么他们没有给我我需要的东西:

  1. 这个问题的答案不是读取描述符的值。我不这样做,所以这不会是阻碍。
  2. 这基本上是对可用的各种方法的概述,我想我现在已经理解了。这个问题/答案的关键不是多次写入不同的描述符,但我也没有这样做。我只关心一个特征。
  3. 这个问题/答案似乎与 BLE 资源限制有关,但我认为这并不适用。我只连接这一个设备,我正在尝试进行非常非常简单的数据传输。我认为我没有达到资源上限。

我尝试了很多示例和教程,包括 Google 的 Android 示例代码。它们似乎都无法使 BLE 设备通知我的 Android 设备数据更新。这显然不是设备,因为 iOS 版本可以工作。那么,iOS 代码在后台做什么以使通知工作,以及 Android 端的哪些代码会模仿该功能?


编辑/更新

基于@yonran 的cmets,我通过将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 仍然不会触发。


【问题讨论】:

  • 您是否介意检查特征属性以查看支持的属性(通知与指示)? 0 != (characteristic.getProperties() &amp; BluetoothGattCharacteristic.PROPERTY_INDICATE)? iOS 会自动为您执行此操作。
  • @yonran 我愿意!当我回到我的办公室时会这样做。谢谢你给我一个想法。你认为我可能需要以不同的方式启用它?
  • @yonran 以下代码返回 true(执行进入“if”语句):if (0 != (characteristic.getProperties() &amp; BluetoothGattCharacteristic.PROPERTY_INDICATE)) {
  • 等等,看起来您正在对不同的特征调用 setCharacteristicNotification,而不是您启用指示的客户端特征配置。使用相同的特征。
  • @yonran 是的,我发现并更改了它。我即将发布更新。

标签: android bluetooth-lowenergy android-bluetooth


【解决方案1】:

我已经能够通过以下方式成功捕获具有多种服务和特征的 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;
    }
    }
    

【讨论】:

  • 我刚刚处理了一个类似的问题。我在onServicesDiscovered(...) 里面有readCharacteristic(...)writeDescriptor(...) 背靠背。 writeDescriptor(...) 将启动写入并返回 true,但它不会通过,并且它的回调 onDescriptorWrite(...) 不会触发。我删除了对readCharacteristic(...) 的调用,所以现在它们不是背靠背的。现在writeDescriptor(...) 通过了,它的回调也触发了。
【解决方案2】:

我也遇到了同样的问题。 那是因为当设备发送指示值时,您的应用程序会在另一个进程中收费,这就是为什么您永远不会获得使 onCharacteristicChanged 永远不会触发的指示值的原因。 要解决您的问题,请尝试将所有特征放在服务中。并从您的活动中调用函数。

【讨论】:

    猜你喜欢
    • 2016-09-03
    • 2014-05-15
    • 2021-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多