首页 > 解决方案 > 连接到多个 BLE 设备时 onCharacteristicRead 错误 129

问题描述

我正在尝试从多个类似的 BLE 设备(如http://www.blehome.com/sensorbug-PC.html)读取蓝牙低功耗特性。我的应用程序扫描并找到设备,然后创建设备映射来管理每个设备的数据(BluetoothGatt、BluetoothGattService 以及 readCharacteristic() 调用返回的数据值)。该代码适用于它连接到的第一个设备,但在连接到其他设备时,我在 onCharacteristicRead 回调中得到一个 status==129。如果我切换设备的顺序,它适用于第一个但不是第二个设备,所以我怀疑我的代码或 Android BLE 层中的某些内容。我已经在多部手机和平板电脑(Google Pixel 4.2、诺基亚 7.2 和三星 Galaxy TabA)上进行了尝试,结果相似。我'

主要活动:

public class MainActivity extends AppCompatActivity {

    private final String[] targetDevices = {"EC:FE:7E:10:90:7C", "EC:FE:7E:10:93:47", "EC:FE:7E:10:92:F1"};

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
... 
code to scan for devices and populate an arrayadapter to show a list of available devices to the user.  This part is working great.
...
// MyServiceConnection is a thin wrapper around ServiceConnection to handle the binding of  the BleService to the Activity via an Intent
        mBleServiceConnection = new MyServiceConnection();

        // Start the BLE Service
        Log.d(TAG, "Starting BLE Service");
        Intent gattServiceIntent = new Intent(this, BleService.class);
        r=bindService(gattServiceIntent, mBleServiceConnection, BIND_AUTO_CREATE);
        startService(gattServiceIntent);
}

    View.OnClickListener onClickStartBLEService = new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            BleService bleService = mBleServiceConnection.getBleservice();

            // Look for each of my devices
            for (String devAddrTarget : targetDevices) {
                for (BluetoothDevice btDev : btDevArrayAdapter.btDevList) {
                    String deviceAddress = btDev.getAddress();
                    if (deviceAddress.equalsIgnoreCase(devAddrTarget)) {
                        bleService.connectToDevice(deviceAddress);
                    }
                }
            }
        }
    };

    View.OnClickListener onClickGetCharacteristics = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            boolean result;

                BleService bleService = mBleServiceConnection.getBleservice();

                result = bleService.readLightDataCharacteristic(targetDevices[0]);
                if (!result) {
                    Log.e(TAG, "onClickGetCharacteristics readLightDataCharacteristic result==false");
                }
    };

    View.OnClickListener onClickGetCharacteristicD2 = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            boolean result;
                BleService bleService = mBleServiceConnection.getBleservice();

                result = bleService.readLightDataCharacteristic(targetDevices[1]);
                if (!result) {
                    Log.e(TAG, "onClickGetCharacteristics readLightDataCharacteristic result==false");
                }
        }
    };
}

蓝牙服务:

public class BleService extends Service {
    private final static String TAG = BleService.class.getSimpleName();

    private Map<String, BleDeviceInstanceData> connectedDeviceMap;
    BluetoothAdapter mBluetoothAdapter;
    BluetoothManager mBluetoothManager;

    private static int numInstances=0;
    // Bluetooth objects that we need to interact with

    // variables to track values set on SensorBug device

    private boolean mConnected=false;
    private final IBinder mBinder;

    public BleService() {
        Log.i(TAG, "Created a new BleService instance");
        mBinder = new LocalBinder();
    }

    BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {

            Log.d(TAG, "OnConnectionStateChange status="+status+ "deviceAddr="+ gatt.getDevice().getAddress());
            if (status == GATT_SUCCESS) {
                BluetoothDevice device = gatt.getDevice();
                String address = device.getAddress();

                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    Log.i(TAG, "Connected to GATT server");

                    if (!connectedDeviceMap.containsKey(address)) {
                        BleDeviceInstanceData bleDeviceInstanceData = new BleDeviceInstanceData();
                        bleDeviceInstanceData.mBluetoothGatt = gatt;
                        connectedDeviceMap.put(address, bleDeviceInstanceData);
                    }
                    // Broadcast if needed
                    Log.i(TAG, "Attempting to start service discovery:" +
                            gatt.discoverServices());

                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    Log.i(TAG, "Disconnected from GATT server.");
                    if (connectedDeviceMap.containsKey(address)){
                        BluetoothGatt bluetoothGatt = connectedDeviceMap.get(address).mBluetoothGatt;
                        if( bluetoothGatt != null ){
                            bluetoothGatt.close();
                            connectedDeviceMap.get(address).mBluetoothGatt = null;
                        }
                        connectedDeviceMap.remove(address);
                        broadcastUpdate(Constant.ACTION_DISCONNECTED);
                    }

                }

            } else {

                Log.e("BLE", "onConnectionStateChanged error "+status);
                // error
            }
        }

        /**
         * This is called when a service discovery has completed.
         *
         * It gets the characteristics we are interested in and then
         * broadcasts an update to the main activity.
         *
         * @param gatt The GATT database object
         * @param status Status of whether the write was successful.
         */
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            // Get just the service that we are looking for
            BluetoothDevice device = gatt.getDevice();
            String address = device.getAddress();
            BleDeviceInstanceData bleDeviceInstanceData = connectedDeviceMap.get(address);
            bleDeviceInstanceData.mGattLightService = gatt.getService(UUID.fromString(Constant.BR_LightServiceUUID));
            bleDeviceInstanceData.mUuidServiceList = gatt.getServices();

            /* Get characteristics from our desired service */
//            mLightDataCharacteristic = mService.get(iCurrent).getCharacteristic(UUID.fromString(BR_LightSrv_DataCharUUID));
//            mLightConfigCharacteristic = mService.get(iCurrent).getCharacteristic(UUID.fromString(BR_LightSrv_ConfigCharUUID));

            /* Get the SensorBug CCCD */
//            mLightDataCccd = mLightDataCharacteristic.getDescriptor(UUID.fromString(CccdUUID));

            // Broadcast that service/characteristic/descriptor discovery is done
            broadcastUpdate(Constant.ACTION_SERVICES_DISCOVERED);
        }

        /**
         * This is called when a read completes
         *
         * @param gatt the GATT database object
         * @param characteristic the GATT characteristic that was read
         * @param status the status of the transaction
         */
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {

            if (status == GATT_SUCCESS) {
                BluetoothDevice device = gatt.getDevice();
                String address = device.getAddress();
                BleDeviceInstanceData bleDeviceInstanceData = connectedDeviceMap.get(address);

                // Verify that the read was the LED state
                String uuid = characteristic.getUuid().toString();
                // In this case, the only read the app does is the LED state.
                // If the application had additional characteristics to read we could
                // use a switch statement here to operate on each one separately.
                if (uuid.equalsIgnoreCase(Constant.BR_LightSrv_DataCharUUID)) {
                    bleDeviceInstanceData.mLightData = characteristic.getValue();
                }

                if (uuid.equalsIgnoreCase(Constant.BR_LightSrv_ConfigCharUUID)) {
                    bleDeviceInstanceData.mLightConfig = characteristic.getValue();
                }

                if (uuid.equalsIgnoreCase(Constant.BR_LightSrv_AlertCharUUID)) {
                    bleDeviceInstanceData.mLightAlert = characteristic.getValue();
                }
                // Notify the main activity that new data is available
                broadcastUpdate(Constant.ACTION_DATA_RECEIVED);
            } else {
                Log.e("BLE", "OnCharacteristicRead error status="+status+" gatt device addr="+gatt.getDevice().getAddress());
            }
        }

        /**
         * This is called when a characteristic with notify set changes.
         * It broadcasts an update to the main activity with the changed data.
         *
         * @param gatt The GATT database object
         * @param characteristic The characteristic that was changed
         */
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {

            BluetoothDevice device = gatt.getDevice();
            String address = device.getAddress();
            BleDeviceInstanceData bleDeviceInstanceData = connectedDeviceMap.get(address);

            String uuid = characteristic.getUuid().toString();

            // In this case, the only notification the apps gets is the CapSense value.
            // If the application had additional notifications we could
            // use a switch statement here to operate on each one separately.
            if (uuid.equalsIgnoreCase(Constant.BR_LightSrv_DataCharUUID)) {
                bleDeviceInstanceData.mLightData = characteristic.getValue();
            }

            // Notify the main activity that new data is available
            broadcastUpdate(Constant.ACTION_DATA_RECEIVED);
        }

        /**
         * Sends a broadcast to the listener in the main activity.
         *
         * @param action The type of action that occurred.
         */
        private void broadcastUpdate(final String action) {
            final Intent intent = new Intent(action);
            sendBroadcast(intent);
        }

    };


    private static int oncreatecount=0;

    @Override
    public void onCreate() {
        connectedDeviceMap = new HashMap<String, BleDeviceInstanceData>();
        mBluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = mBluetoothManager.getAdapter();
        oncreatecount++;
    }

    public class LocalBinder extends Binder {
        BleService getService() {
            return BleService.this;
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        // The BLE close method is called when we unbind the service to free up the resources.

        connectedDeviceMap.forEach(
                (K, V) -> V.mBluetoothGatt.close()
        );
        return super.onUnbind(intent);
    }

    private Handler handler = new Handler();

    public boolean initialize() {
        return true;
    }

    public void connectToDevice(String deviceAddress) {
        try {
            Log.i(TAG,"BleService.connectToDevice devAddr="+deviceAddress);

            BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceAddress);
            int connectionState = mBluetoothManager.getConnectionState(device, BluetoothProfile.GATT);

            if (connectionState == BluetoothProfile.STATE_DISCONNECTED) {
                // connect your device
                device.connectGatt(this, false, mGattCallback);
            } else if (connectionState == BluetoothProfile.STATE_CONNECTED) {
                // already connected . send Broadcast if needed

                Method m = device.getClass().getDeclaredMethod("connectGatt", Context.class, boolean.class, BluetoothGattCallback.class, int.class);
                int transport = device.getClass().getDeclaredField("TRANSPORT_LE").getInt(null);
                m.invoke(device, this, false, mGattCallback, transport);
            }

        } catch(IllegalAccessException e){
                e.printStackTrace();
            } catch(InvocationTargetException e){
                e.printStackTrace();
            } catch(NoSuchMethodException e){
                e.printStackTrace();
            } catch(NoSuchFieldException e){
                e.printStackTrace();
            }
        }

    /**
     * Runs service discovery on the connected device.
     */
    public void discoverServices(String deviceAddress) {
        BleDeviceInstanceData bleDeviceInstanceData=connectedDeviceMap.get(deviceAddress);
        if (bleDeviceInstanceData==null) return;

        if (connectedDeviceMap.get(deviceAddress).mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothGatt not initialized");
            return;
        }
        connectedDeviceMap.get(deviceAddress).mBluetoothGatt.discoverServices();
    }

    /**
     * Disconnects an existing connection or cancel a pending connection. The disconnection result
     * is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public void disconnect(String deviceAddress) {
        BleDeviceInstanceData bleDeviceInstanceData=connectedDeviceMap.get(deviceAddress);
        if (bleDeviceInstanceData==null) return;

        if (connectedDeviceMap.get(deviceAddress).mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        connectedDeviceMap.get(deviceAddress).mBluetoothGatt.disconnect();
    }

    /**
     * After using a given BLE device, the app must call this method to ensure resources are
     * released properly.
     */
    public void close(String deviceAddress) {
        BleDeviceInstanceData bleDeviceInstanceData=connectedDeviceMap.get(deviceAddress);
        if (bleDeviceInstanceData==null) return;

        if (connectedDeviceMap.get(deviceAddress).mBluetoothGatt == null) {
            return;
        }
        connectedDeviceMap.get(deviceAddress).mBluetoothGatt.close();
        connectedDeviceMap.get(deviceAddress).mBluetoothGatt = null;
    }

    /**
     * Implements callback methods for GATT events that the app cares about.  For example,
     * connection change and services discovered.
     */

    public boolean readLightDataCharacteristic(String deviceAddress) {

        BleDeviceInstanceData bleDeviceInstanceData=connectedDeviceMap.get(deviceAddress);
        if (bleDeviceInstanceData==null) {
            Log.e(TAG, "readLightDataCharacteristic DeviceInstanceData==null devAddr="+deviceAddress);
            return false;
        }

        if (bleDeviceInstanceData.mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return false;
        }
        BluetoothGattService btGattService = bleDeviceInstanceData.mBluetoothGatt.getService(UUID.fromString(Constant.BR_LightServiceUUID));

        BluetoothGattCharacteristic btchar = btGattService.getCharacteristic(UUID.fromString(Constant.BR_LightSrv_DataCharUUID));
        boolean result=bleDeviceInstanceData.mBluetoothGatt.readCharacteristic(btchar);
        if (!result) {
            Log.e(TAG, "readCharacteristic returned false deviceAddr="+deviceAddress);
        }
        return result;
    }


    public byte[] getLightData(String deviceAddress) {
        BleDeviceInstanceData bleDeviceInstanceData=connectedDeviceMap.get(deviceAddress);
        if (bleDeviceInstanceData==null) return null;

        return connectedDeviceMap.get(deviceAddress).mLightData;
    }


    public List<BluetoothGattService> getServicelist(String deviceAddress) {
        BleDeviceInstanceData bleDeviceInstanceData=connectedDeviceMap.get(deviceAddress);
        if (bleDeviceInstanceData==null) return null;

        return connectedDeviceMap.get(deviceAddress).mUuidServiceList;
    }





    /**
     * Sends a broadcast to the listener in the main activity.
     *
     * @param action The type of action that occurred.
     */
    private void broadcastUpdate(final String action) {
        final Intent intent = new Intent(action);
        sendBroadcast(intent);
    }
}

BleDeviceInstanceData:

public class BleDeviceInstanceData {
    public BluetoothGatt mBluetoothGatt;
    public BluetoothGattService mGattLightService;
    public List<BluetoothGattService> mUuidServiceList;

    public byte[] mLightData;

    //  private static BluetoothGattCharacteristic mLightConfigCharacteristic;
    public byte[] mLightConfig;

    //    private static BluetoothGattCharacteristic mLightAlertCharacteristic;
    public byte[] mLightAlert;
}

当我运行应用程序时,我会扫描设备,然后遍历我的目标设备地址列表并调用 BleService.connect 到设备以填充我的 BLE 设备地图。然后我尝试从其中一个设备中读取特征数据。我总是能够从我连接的第一个设备读取数据,无论它是否是第一个读取特征调用。当我从第二个连接到设备调用设备上的读取特性时,读取特性成功,但我在 onCharacteristicRead 回调中得到了不太有用的 status==129 错误。

每次我想向 BLE 设备读/写数据时,是否需要通过 device.connectGatt 重新建立连接?我希望不会因为我计划在多个设备上启用特征通知,以使我的应用程序能够同时观看和查看来自多个 BLE 设备的数据。

谢谢你的帮助。如果您需要更多代码,请告诉我。我希望我已经包含了足够多的相关部分。

标签: androidservicebluetooth-lowenergy

解决方案


推荐阅读