android - 连接到多个 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 设备的数据。
谢谢你的帮助。如果您需要更多代码,请告诉我。我希望我已经包含了足够多的相关部分。
解决方案
推荐阅读
- apache-kafka - 即使在 kafka 机器重新启动后,如何保留 kafka 保留字节和 kafka 保留段
- mysql - 缓慢的 MySQL 查询,第 1 行“使用 where;使用临时;使用文件排序。” 似乎是问题
- java - 格式化负双精度时,DecimalFormat 返回奇怪的字符
- perl - 需要使用 Perl 设置和读取 cookie
- nancy - Topshelf:在 Topshelf 4.2.1 中使用 NancyFX 的问题
- python - Python idle 不允许使用通过子进程调用在 shell 中安装 Telepot
- java - 我的输出中出现随机字符,不确定它们来自哪里
- java - 是否可以在中断后重新进入while循环?
- node.js - npm WARN npm npm 不支持 Node.js v14.6.0
- c# - 来自 OnTokenValidated 事件侦听器的 TokenValidatedContext 的安全令牌缺少最后一个字符串段