greenpepper 2019-06-21
公司开发任务是,将医疗设备通过蓝牙集成到app中,在这开发中遇到了数不尽的坑.在此记录一下做一个记录,如果其他开发人员看见或许能提供一些帮助,如有不对,尽情指正,不胜感激!
刚开始接触的时候,被各种超长的API吓到了,像:BluetoothGatt , BluetoothGattCharacteristic , BluetoothGattDescriptor 等等.而且还要做多连接,上位机一对多下位机.网上例子也是杂七杂八.看的头晕.后来在老大的帮助下,渐渐明白许多,在此感谢老大.废话到此结束,下面进入正题.
1. 判断当前设备是否支持蓝牙
BluetoothManager mBluetoothManager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter adapter = mBluetoothManager.getAdapter(); if(adapter==null){ //系统不支持蓝牙。 }
2. 判断当前设备是否支持低功耗蓝牙BLE
boolean isSupportBle = activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
3. 判断蓝牙是否开启,开启蓝牙!
BluetoothManager mBluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter adapter = mBluetoothManager.getAdapter(); if(!adapter.isEnable){ //未开启蓝牙 //申请开启蓝牙 Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(intent , request); }
BluetoothAdapter adapter = mBluetoothManager.getAdapter(); adapter.startLeScan(callback); //扫描需要一个回调。
注意,扫描周围蓝牙是一个很耗电的过程,最好加上一个扫描时间。自动停止。
handler.postDelayed(new Runnable() { @Override public void run() { adapter.stopLeScan(callback); //停止扫描 } },10000);//设置10秒钟结束扫描
public BluetoothAdapter.LeScanCallback scanCallBack = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { //这里注意,本人在开发中遇到的是 经常有的蓝牙设备是没有名字的, (device.getName == null) //不知道这是什么原因引起的,后来跟很多蓝牙高手讨论的是结果初步怀疑应该是芯片的问题 //尤其是MTK的芯片经常出现这种问题,换了搭载高通和华为的芯片的设备就没问题了。 } };
BluetoothDevice remoteDevice = adapter.getRemoteDevice(address); remoteDevice.connectGatt(context, true, mGattCallback);//参数1:上下文。 //参数2:是否自动连接(当设备可以用时) //参数3:连接回调。
这里可能有些疑问就是,明明已经扫描到了,在回调中已经有了 BluetoothDevice 为何还要去 getRemoteDevice(address)?
那是因为,很多低功耗的设备开机时间是很少的,就拿我们公司开发的那个血压计,他是开机才开启蓝牙,而测量完了之后过一段时间就会自动关闭。所以防止去连接设备的时候设备已经关机的情况。
连接回调
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { //连接成功 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { //连接断开 } changeStatus(newState);//改变当前状态 } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { //当服务发现之后回调这里 } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { } };
一个低功耗蓝牙设备是有很多种服务的,就比如该设备的电量信息,设备的当前状态(比如血压计,是正在测量还是在等待测量)
有的设备支持历史数据等等。这些都是在蓝牙的服务当中。我们要去发现蓝牙的服务!
这里很简单就是一句话,在连接成功的回调中调用:
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { //连接成功 gatt.discoverServices();//开始发现设备的服务 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { //连接断开 } changeStatus(newState);//改变当前状态 }
调用了之后会在 另一个回调中 回调回来。
@Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { //当服务发现之后回调这里 }
这里是比较重要的地方,注意,每一个蓝牙的通讯协议不通,有的设备是连接了之后不需要任何操作就等待蓝牙设备上传数据的,而有的设备是需要手动打开数据通道!或者发送指令给蓝牙设备,每一个Gatt协议中有多个BluetoothGattService,而每个BluetoothGattService中又有多个BluetoothGattCharacteristic (我把它看做一个数据通道-_-!),而每一个BluetoothGattCharacteristic 的属性是不同的,有的是可读,有的是可写,有的是可订阅,所以一定不要搞混了,可以用UUID区分他们,这里大多数设备厂家都会给一份设备的通讯协议其中就有 哪一个UUID 代表什么。都会有说明。通过UUID 获取到了对应的BluetoothGattCharacteristic 之后就可以判断他的属性是什么。
开启数据通道
@Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { //服务发现方法回调。 if (status == BluetoothGatt.GATT_SUCCESS) { BluetoothGattService service = gatt.getService(SERVICE_UUID); //通过厂家给的UUID获取BluetoothGattService if (service != null) { BluetoothGattCharacteristic characteristic = service.getCharacteristic(CHARACTERISTIC_UUID);//同上 if (characteristic != null && (characteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { //通过判断,打开Notification 通知,提醒。一般是设备测量完成了之后会发送对应的数据上来。 gatt.setCharacteristicNotification(characteristic, true); //在通过上面的设置返回为true之后还要进行下面的操作,才能订阅到数据的上传。下面是完整的订阅数据代码! if(gatt.setCharacteristicNotification(characteristic, true)){ for(BluetoothGattDescriptor dp: characteristic.getDescriptors()){ if (dp != null) { if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) { dp.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); } else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) { dp.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); } gatt.writeDescriptor(dp); } } } } } } }
像设备发送指令
一般向设备发送什么指令在通讯协议上面也是有的,都是发送一个byte[]数组,每一位代表什么协议里面都是不同的。例如:一个测量温度的设备,他当前是华氏度的单位,我们可以给他发送一个指令让他把单位更换成摄氏度:
private void changeMonitorMod(BluetoothGatt gatt, byte[] buffer) { if (gatt != null && gatt != null) { BluetoothGattService writeService = gatt.getService(MYUUID); if (writeService == null) { return; } } BluetoothGattCharacteristic writeCharacteristic = writeService.getCharacteristic(MYWRITECHARACTERISTIC); if (writeCharacteristic == null) { return; } writeCharacteristic.setValue(buffer); //上面的buffer数组中装的就是指令,多长? 每一位上面的数字代表什么意思在协议中查看! gatt.writeCharacteristic(writeCharacteristic);//像设备写入指令。 }
不要忘了,要在清单文件中AndroidManifest.xml 声明权限哦。
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
关于一些坑:
很多厂家很坑爹,给的文档水的要命,第一时间要看看文档详细不详细,如果没有文档至少也要给个Demo.
注意设备的开机时间,自动关机时间,对状态的保存。
很多设备在自动关机之后的回调是很慢的,甚至设备关机10秒之后才会回调到连接状态的回调方法中。
关于手动设置断开 gatt.disConnect() 这个方法,我试过了,调用之后确实会立即回调到对应的状态方法中,但是实际上物理上的连接是还没有断开的。物理上的连接断开之后还会再次回调到方法中。这是一个比较漫长的回调,区别与设备,不通设备的机制不一样,有的快,有的慢。
好了,差不多就这么多,写的匆忙,如果有哪里不对,轻喷,还请大佬们指正。谢啦!