安卓蓝牙4.0BLE通信之体重称
最近正在做一个关于手机跟蓝牙体重称之间数据交互的工程,因为之前没接触过蓝牙开发,所以浪费了不少时间,但是经过查资料和大神们的文章,终于在一周后完工了,现在总结下这个demo,先来介绍下关于蓝牙BLE有关的知识。
什么是BLE?
BLE是蓝牙4.0的核心Profile,主打功能是快速搜索,快速连接,超低功耗保持连接和传输数据,弱点是数据传输速率低,由于BLE的低功耗特点,因此普遍用于穿戴设备。Android 4.3才开始支持BLE API,所以请各位客官把本文代码运行在蓝牙4.0和Android 4.3及其以上的系统。
BLE分为三部分Service、Characteristic、Descriptor,这三部分都由UUID作为唯一标示符。一个蓝牙4.0的终端可以包含多个Service,一个Service可以包含多个Characteristic,一个Characteristic包含一个Value和多Descriptor,一个Descriptor包含一个Value。一般来说,Characteristic是手机与BLE终端交换数据的关键,两者就是通过改变Characteristic的值来实现数据交互的。Characteristic有较多的跟权限相关的字段,例如PERMISSION和PROPERTY,而其中最常用的是PROPERTY。Characteristic的PROPERTY可以通过位运算符组合来设置读写属性,例如READ|WRITE、READ|WRITE_NO_RESPONSE|NOTIFY,因此读取PROPERTY后要分解成所用的组合。
下面直接贴代码,边贴核心代码边解释。
上面是我的项目目录结构,BluetothLeClass是提取的蓝牙工具类,Conversion与Utils是转码的工具类,ProgressDialogsUtils是对话框工具类,LeDeviceListAdapter是设备ListView的Adapter,activity_main.xml的视图如下:
点击蓝牙搜索按钮后,会查找蓝牙设备,然后将搜索到的设备显示在ListView上,再通过点击列表项,去连接设备,再发数据给设备然后处理设备发回的回调信息,下面粘一下核心代码:
这是按钮点击事件的方法,主要功能是判断手机是否支持蓝牙4.0,开启蓝牙和设置发现设备和数据交互的回调:
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_search_bluetooth:
openBluetooth();
break;
}
}
private void openBluetooth() {
//判断手机是否支持蓝牙4.0
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
finish();
return;
}
//开启蓝牙
mBluetoothAdapter.enable();
mBLE = new BluetoothLeClass(this, mHandler);
if (!mBLE.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth");
finish();
}
//发现BLE终端的Service时回调
mBLE.setOnServiceDiscoverListener(mOnServiceDiscover);
//收到BLE终端数据交互的事件
mBLE.setOnDataAvailableListener(mOnDataAvailable);
mLeDeviceListAdapter = new LeDeviceListAdapter(this);
ls_setup.setAdapter(mLeDeviceListAdapter);
scanLeDevice(true);
}
这是发现设备的回调接口:
private BluetoothLeClass.OnServiceDiscoverListener mOnServiceDiscover = new BluetoothLeClass.OnServiceDiscoverListener() {
@Override
public void onServiceDiscover(BluetoothGatt gatt) {
Log.w(TAG, "onServiceDiscover");
//gattServices=mBLE.getSupportedGattServices();
displayGattServices(mBLE.getSupportedGattServices());
Log.e(TAG, "found");
}
};
这是数据交互的回调接口:
/**
* 收到BLE终端数据交互的事件
*/
private BluetoothLeClass.OnDataAvailableListener mOnDataAvailable = new BluetoothLeClass.OnDataAvailableListener() {
/**
* BLE终端数据被读的事件
*/
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
Log.w(TAG, "onServiceDiscover");
if (status == BluetoothGatt.GATT_SUCCESS)
Log.e(TAG, "onCharRead " + gatt.getDevice().getName()
+ " read "
+ characteristic.getUuid().toString()
+ " -> "
+ Utils.bytesToHexString(characteristic.getValue()))
;
}
/**
* 收到BLE终端写入数据回调
*/
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
mProgressDialogUtils.dismissProgressDialog();
Log.e(TAG, "onCharWrite " + gatt.getDevice().getName()
+ " write "
+ characteristic.getUuid().toString()
+ " -> "
+ "结果是:" + Utils.bytesToHexString(characteristic.getValue())
);
if (gatt.getDevice().getName().equalsIgnoreCase("Chipsea-BLE")) {
getWeight(gatt, Utils.bytesToHexString(characteristic.getValue()).substring(10, 14));
} else {
getWeight(gatt, Utils.bytesToHexString(characteristic.getValue()).substring(4, 8));
}
}
displayGattServices方法主要是输出搜索到的设备中的各种Services,Characteristic,和Descriptor的各种信息,同时设置Charicteristic被写的通知,往BLE设备上写数据,
和读出数据
private void displayGattServices(List<BluetoothGattService> gattServices) {
Log.w(TAG, "displayGattServices");
if (gattServices == null) return;
for (BluetoothGattService gattService : gattServices) {
//-----Service的字段信息-----//
int type = gattService.getType();
Log.e(TAG, "-->service type:" + Utils.getServiceType(type));
Log.e(TAG, "-->includedServices size:" + gattService.getIncludedServices().size());
Log.e(TAG, "-->service uuid:" + gattService.getUuid());
//-----Characteristics的字段信息-----//
List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
Log.e(TAG, "---->char uuid:" + gattCharacteristic.getUuid());
int permission = gattCharacteristic.getPermissions();
Log.e(TAG, "---->char permission:" + Utils.getCharPermission(permission));
int property = gattCharacteristic.getProperties();
Log.e(TAG, "---->char property:" + Utils.getCharPropertie(property));
byte[] data = gattCharacteristic.getValue();
if (data != null && data.length > 0) {
Log.e(TAG, "---->char value:" + Utils.bytesToHexString(data));
}
//UUID_KEY_DATA是可以跟蓝牙模块串口通信的Characteristic
//if (gattCharacteristic.getUuid().toString().equals(UUID_KEY_DATA)) {
//当测试读取前Characteristic数据,会触发mOnDataAvailable.onCharacteristicRead()
//接受Characteristic被写的通知,收到蓝牙模块的数据后会触发mOnDataAvailable.onCharacteristicWrite()
mBLE.setCharacteristicNotification(gattCharacteristic, true);
//设置数据内容
//gattCharacteristic.setValue("A5,00,19,AF,50,5A,19");
//attCharacteristic.setValue(Conversion.HexString2Bytes("A5019AF505A19"));
byte[] b = {(byte) 0xA5, (byte) 0x00, (byte) 0x19, (byte) 0xAF, (byte) 0x50, (byte) 0x5A, (byte) 0x19};
gattCharacteristic.setValue(b);
//往蓝牙模块写入数据
mBLE.writeCharacteristic(gattCharacteristic);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mBLE.readCharacteristic(gattCharacteristic);
}
}, 500);
Log.e(TAG, "转换是C:" + Utils.bytesToHexString(b));
//}
//-----Descriptors的字段信息-----//
List<BluetoothGattDescriptor> gattDescriptors = gattCharacteristic.getDescriptors();
for (BluetoothGattDescriptor gattDescriptor : gattDescriptors) {
Log.e(TAG, "-------->desc uuid:" + gattDescriptor.getUuid());
int descPermission = gattDescriptor.getPermissions();
Log.e(TAG, "-------->desc permission:" + Utils.getDescPermission(descPermission));
byte[] desData = gattDescriptor.getValue();
if (desData != null && desData.length > 0) {
Log.e(TAG, "-------->desc value:" + Utils.bytesToHexString(desData));
}
}
}
}//
}
setValue()为写数据关键,参数为设备上规定的APP到BLE设备上的协议内容。必须设置数据改变通知监听:mBLE.setCharacteristicNotification(gattCharacteristic, true);不然特征值改变也收不到回调信息,具体的通知方法为:
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
List<BluetoothGattDescriptor> gattDescriptors = characteristic.getDescriptors();
for (BluetoothGattDescriptor gattDescriptor : gattDescriptors) {
gattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(gattDescriptor);//写数据回调关键
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
}
}
谨记,只有mBluetoothGatt.writeDescriptor(gattDescriptor);才能保证可准确收到BLE设备往特征值写入数据的监听,具体原理我也不是特别清楚,只知道删除这句话将收不到监听方法。
下面来介绍下当BLE设备往特征值写入数据时的回调方法:
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
mProgressDialogUtils.dismissProgressDialog();
Log.e(TAG, "onCharWrite " + gatt.getDevice().getName()
+ " write "
+ characteristic.getUuid().toString()
+ " -> "
+ "结果是:" + Utils.bytesToHexString(characteristic.getValue())
);
if (gatt.getDevice().getName().equalsIgnoreCase("Chipsea-BLE")) {
getWeight(gatt, Utils.bytesToHexString(characteristic.getValue()).substring(10, 14));
} else {
getWeight(gatt, Utils.bytesToHexString(characteristic.getValue()).substring(4, 8));
}
}
当设备写入数据时,将对话框取消掉,因为我连接的体重秤有两种,协议的内容也不同,第一张设备中是去回调值的第10到14位,而第二种是取4到8位,所以我根据设备名称做了个判断,getWeight()方法主要是转码和计算体重值:
private void getWeight(BluetoothGatt gatt, String s) {
int a = Integer.parseInt(s, 16);
if (mt == null) {
mt = new MyThread();
mt.start();
Log.e(TAG, "当前线程为current:" + mt.currentThread() + cout++);
}
if (gatt.getDevice().getName().equalsIgnoreCase("Chipsea-BLE")) {
double w = a;
weight = w / 10;
} else {
double w = a;
weight = w / 100;
}
}
};
这里我通过handle与Thead的方法,当体重秤的数据改变时,我不断地刷新界面上的体重值,具体代码为:
class MyThread extends Thread {
@Override
public void run() {
super.run();
while (finish) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
Bundle bundle = new Bundle();
bundle.putDouble("weight", weight);
msg.setData(bundle);
msg.what = 1;
mHandler.sendMessage(msg);
}
}
}
private void intHandle() {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
Bundle b = msg.getData();
setWeight(b.getDouble("weight"));
if (weight == 0.0) {
mHandler.removeCallbacks(mt);
mt = null;
}
break;
case 2:
Log.e(TAG, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~handlemessage" + i++);
mProgressDialogUtils.dismissProgressDialog();
mProgressDialogUtils.showProgressDialog(MainActivity.this, "连接成功,正常测量您的体重,请勿离开称体!");
break;
case 3:
mProgressDialogUtils.dismissProgressDialog();
mProgressDialogUtils.showProgressDialogWithButton(MainActivity.this, "连接失败!", "确定");
break;
}
}
};
}
最后的结果为:
时间比较赶,所以逻辑有点混乱,思路不是很清晰,所以提供源码给各位小伙伴参考,谢谢!
源码地址:点击打开链接