谈谈蓝牙4.0(BLE)模块与安卓的数据交互


开发蓝牙4.0也快两个月了,给我的感受还是颇多的。
我开发的是与TI的蓝牙模块CC2540交互的安卓端蓝牙软件,对于安卓都不是很熟悉的我,是一个不小的挑战。
我用的是google官方的源码,它本身把很多基本框架都搭好了。直接就能运行,当然前提是在Android Studio上,如果是Eclipse上还需要稍作改动。
我们先来看下代码。它主要有两个Avtivity和一个Service组成,顾名思义,两个Activity的用途分别为设备扫描与设备管理。
这里我不准备介绍基本的BLE知识(自行百度即可),我只是说一下一些需要注意的地方,特别是我百度不到的好多东西,希望跟大家分享一下。
一开始的软件已经可以查看Service和Characteristic的UUID,不过只能接收消息,原因大概是这个Sample当初设计的时候就只负责接收。当然要发送也简单,只要在BluetoothLeService中加writeCharacteristic方法。

1
2
3
4
5
6
7
8
9
10
public boolean writeCharacteristic(BluetoothGattCharacteristic charac,String message){
// check if mBluetoothGatt is available
if (mBluetoothGatt == null) {
Log.e(TAG, "lost connection");
return false;
}
charac.setValue(message.getBytes());
boolean status = mBluetoothGatt.writeCharacteristic(charac);
return status;
}

写数据比较简单,直接调用这个方法即可。当然,这里也有个陷阱,我们发现,如果你在cc2540中定义了Byte字节的长度,那么在charac.setValue()中的数组长度要跟其相同,不然模块是收不到的。
我们发现在很多地方都用到了BluetoothGattCharacteristic这个类,即特性,BLE就是靠改变特性值来传递数据的,那么,这些BluetoothGattCharacteristic是从哪里得到的呢?我们知道,BluetoothGattCharacteristic是BluetoothGattService中的属性,我们要得到BluetoothGattCharacteristic,就要先得到BluetoothGattService。那要怎么样得到BluetoothGattService呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
Log.i(TAG, "Connected to GATT server.");
// Attempts to discover services after successful connection.
Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
}
}

这里先简单见一下回调,开发过安卓的朋友们应该了解,在安卓开发中,某个动作可能会出发后续的动作,这个被触发的动作就是回调。这里onConnectionStateChange函数就是在连接状态改变后会触发的回调,我们看到mBluetoothGatt.discoverServices()这里执行了发现服务的方法,那么在发现完服务呢?没错,它同样触发回调。

1
2
3
4
5
6
7
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}

它会调用onServicesDiscovered(BluetoothGatt gatt, int status)方法,在这里它会发出一个广播broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED)。值得注意的是,在这里,BluetoothLeService与DeviceControlActivity交互的方式是广播。我们看到broadcastUpdate方法,在BluetoothLeService中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for(byte byteChar : data) {
stringBuilder.append(String.format("%02X ", byteChar));
}
intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());
}
sendBroadcast(intent);
}

这里有两个broadcastUpdate,但效果是相似的,他们都调用sendBroadcast(intent),至于sendBroadcast方法,就是底层的一些实现了,这里不用管他。我们知道了,在发现了服务之后,BluetoothLeService就sendBroadcast。那么,这个Broadcast传到哪里呢?答案是这里。

1
2
3
4
5
6
7
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
mBluetoothLeService.getSupportedGattServices();
}
}
}

在DeviceControlActivity中有个onReceive方法,就是接受广播的地方。他通过参数intent的值判断广播的内容。ACTION_GATT_SERVICES_DISCOVERED这个参数表示发现服务。这里再调用getSupportedGattServices就可以获得服务啦。然后特性什么的就都知道了。
目前为止我们解决了往特性里写值的问题,那么,如何读取模块发来的消息呢?我们来说说具体的接收流程。
我们首先要明确的是,安卓端接收消息的被动的,它只能被动地告知要接收消息,而告诉他的人就是模块。这里我们说一下特性,特性有自身的一些属性,其实一个服务的每个特性都有自己独立的作用,这个可以自己定义,主要有可写,可读,还有专门用来通知的,即Notification,在模块改变了特性值,他得让手机知道,那么他就通过有Notification功能的特性发出通知。那么,如果模块的Service有好多特性,他怎么知道往哪个特性中发通知呢?所以,我们必须先告知模块我们通过那个特性接收通知,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(DeviceControlActivity.NotificationCharacDescripter));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
}

这里设置了BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE,相当于告知模块,我们从这个特性接受通知。然后,如果模块改变了其他特性值,他就会通过这个特性通知我们去读取。
在我们接收到通知后,安卓触发回调onCharacteristicChanged,并广播通知DeviceControlActivity去接收消息。
这里我们再看一下broadcastUpdate函数,这里调用的是private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic)。

1
2
3
4
5
6
7
8
9
10
11
12
private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for(byte byteChar : data) {
stringBuilder.append(String.format("%02X ", byteChar));
}
intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());
}
sendBroadcast(intent);
}

上面的代码中BluetoothLeService把接收的数据保存到了EXTRA_DATA里面。

1
2
3
if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
intent.getStringExtra(BluetoothLeService.EXTRA_DATA);
}

DeviceControlActivity再通过这里接收广播并取出数据,完成数据接收。


坚持原创技术分享,您的支持将鼓励我继续创作!