我们在本章回中介绍的通过蓝牙设备发送数据仍然使用flutter_blue_plus包提供的接口,我们在第一百一十九章回中介过通过蓝牙发送数据的方法,不过还有一些
细节问题需要注意,本章回中将详细介绍通过蓝牙发送数据的细节内容。
通过蓝牙发送数据的细节主要包含发现服务(BluetoothService)和特征值(Characteristic),发送数据和接收数据。我们把这些内容分成各个小节来介绍。
发现服务使用包中的discoverServices()方法就可以,不过蓝牙设备的服务比较多,需要进行遍历操作,在遍历过程中找到需要操作的服务,通常是通过服务的uuid
来判断服务是否是我们需要操作的某个服务。此外,蓝牙设备的服务具有读写特性,也可以依据读写特性来区分服务。
发现特征值不需要专门的方法,通过服务的characteristics属性就可以获取到该服务的特征值,该属性是一个列表,包含服务中的多个特征值。我们需要对特征值列表
进行遍历操作,在遍历过程中找到需要操作的特征值,通常是通过特征值的uuid来判断特征值是否是我们需要操作的某个特征值。此外,蓝牙设备的特征值类似服务,也
具有读写特性,也可以依据读写特性来区分不同的特征值。
发送数据有两种方法:读特征值或者写特征值,读取特征值只能从蓝牙设备中读取内容,写特征值可以向蓝牙设备发送特定的内容,蓝牙设备收到具体的内容后可以做出专
门的响应,这种方法在实际项目中比较常用。包中提供了读特征值和写特征值的方法:read()和write()。注意这两个方法是特征值的方法,不是蓝牙设备的方法,在使
用这两个方法前需要判断特征值是否具有读或者写的特性,不具有读写特性的特征值无法调用这两种方法。
接收数据通过Stream来实现,包中提供了名叫onValueReceived的Stream,监听该Stream可以获取到蓝牙设备回复的数据。此外,新版本的包中还提供了event,
里面包含FlutterBluePlus.events.onCharacteristicReceived和FlutterBluePlus.events.onCharacteristicWritten,它们都是Stream,监听
这两个Stream也可以收到蓝牙设备回复的数据。有看官说这些内容比较抽象,我们在后面的小节中将通过具体的示例代码来演示接收数据的方法。此外,还有一点需要特
别注意:监听Stream前一定激活监听,调用特征值的notifysetNotifyValue(true/false)方法就可以激活或者关闭监听。
上面小节中介绍的实现方法比较抽象,接下来我们通过具体的代码来演示如何通过蓝牙设备读写数据;
Future<List<BluetoothService>> discoverServices(BluetoothDevice device) async {
///获取服务
List<BluetoothService> services = await device.discoverServices();
List<BluetoothCharacteristic> characteristics;
Stream<List<int>> readValueChanged;
Stream<List<int>> writeValueChanged;
///查找具有读写特性的特征值同时监听Strem来获取蓝牙设备返回的数值
for (var element in services) {
// log.i("service: ${element.toString()}");
characteristics = element.characteristics;
for(var char in characteristics) {
if(char.properties.read) {
///激活监听
char.setNotifyValue(true);
readValueChanged = char.onValueReceived;
readValueChanged.listen((event) {
log.i('read chara feedback: ${event.toHes()}');
});
readCharacteristics(char);
}
if(char.properties.write) {
///激活监听
char.setNotifyValue(true);
writeValueChanged = char.onValueReceived;
writeValueChanged.listen((event) {
log.i('write chara feedback: ${event.toHex()}');
},
onError:(e){log.i('write chara error: ${e.toString()}');},
onDone: () => log.i('write chara done'),
);
writeCharacteristics(char);
}
}
}
return services;
}
///依据指定的UUID读取特征值
void readCharacteristics (BluetoothCharacteristic characteristic) async{
if(PrivateKey.searchServiceUuid != characteristic.characteristicUuid.toString()) {
return null;
}
List<int> value = await characteristic.read();
log.w('read characteristic: ${value.toString()}');
}
///依据指定的UUID写入特征值
void writeCharacteristics (BluetoothCharacteristic characteristic) async{
if(PrivateKey.writeCharacteristicUuid != characteristic.characteristicUuid.toString()) {
return null;
}
List<int> value = [12,13,14];
await characteristic.write(value,withoutResponse: false);
log.w('write characteristic: ${value.toString()}');
}
上面的代码中把读写特征值的操作封装成了独立的方法,这样方便调用。我们可以依据特征值的读写属性来区分特征值,也可以依据特征值的uuid来区分特征值,不过需
要与蓝牙设备的开发工程师获取特征值的uuid.发起读写操作后,可以在Stream中的Listen()方法中获取到蓝牙设备返回的数据。我们在代码的关键位置都添加了注释,
这样方便大家理解代码。注意:读写操作需要进行异步操作,不然无法接收到数据。
最后,我们对本章回的内容做一个全面的总结:
第一百九十七回 蓝牙综合使用示例一
我们在上一章回中介绍了"通过蓝牙发送数据的细节相关的内容,本章回中将介绍蓝牙综合使用示例.闲话休提,让我们一起Talk Flutter吧。
本章回中的蓝牙综合示例是在前面两个章回中的基础上实现的,主要包含蓝牙的扫描和连接功能。建议大家先阅读博客中关于蓝牙基础知识的介绍,然后是前面两个章回中
的细节内容,这样有助于大家理解内容。此外,示例中仍然使用使用flutter_blue_plus包提供的接口.
扫描蓝牙功能直接使用FlutterBluePlus.startScan()方法就可以,不过扫描前需要申请蓝牙权限,有了蓝牙权限以后才可以进行扫描。此外,还需要进行初始化操
作,这个初始化主要是监听Stream: FlutterBluePlus.scanResults.当扫描到蓝牙设备的话,就可以直接从该Stream中获取蓝牙设备列表。此外,扫描前还需
要打开手机上的蓝牙开关,这个内容比较多,我们将在后面章回中介绍这方面的内容。
连接蓝牙设备使用包中提供的connect()方法就可以,该方法返回的是Future,我们可以通过Future处理连接完成和连接失败的相关操作。注意一下,该方法是异步方
法,因此需要使用async/await关键字来实现异步操作。
介绍完主要的功能后,我们通过具体的示例代码来演示如何实现这些功能,下面是示例代码,请大家参考:
void checkPermission() async {
var status = await Permission.bluetooth.status;
if (status.isDenied) {
requestPermission();
}
}
///这里的蓝牙权限只包含两个,大家可以依据平台版本来请求不同的权限
void requestPermission() async {
Map<Permission, PermissionStatus> permissionMap = await [
// Permission.locationAlways,
Permission.bluetoothScan,
Permission.bluetoothConnect,
].request();
}
///初始化操作,主要是监听Stream来获取扫描到的蓝牙设备列表
void initBLE() {
subscription = FlutterBluePlus.scanResults.listen(
(result) {
scanDeviceList = result;
filterDeviceList = scanDeviceList?.where((element) => element.device.localName.isNotEmpty).toList();
filterDeviceList?.forEach((element) {
_deviceViewModel.setDeviceModel(element.device.localName, "Disconnected");
});
},
onError: (e) => debugPrint('onError ${e.toString()}'),
onDone: () => debugPrint('onDone'),
);
}
///扫描蓝牙设备
void startScan() {
debugPrint('start scan');
FlutterBluePlus.startScan(
// withServices: [Guid(PrivateKey.uuid)],
timeout: Duration(seconds: scanTimeout),
);
}
///连接设备
void connectDevice(BluetoothDevice device) async {
if (device != null) {
await device
.connect()
.onError((error, stackTrace) =>
log.e('connect device: ${error.toString()}'))
.whenComplete(() => log.i('connect finished'));
}
}
我们在上面的示例代码中依据不同的功能把相关的操作封装成了方法,并且添加了相关的注释以方便大家理解代码。程序需要运行在真机上才可以扫描蓝牙,建议大家自己
动手去实践,这样可以看到扫描到的蓝牙设备。代码中没有涉及页面,大家可以把相关的功能添加到按钮的onPress属性上,这样就可以在点击按钮时完成相关的功能,
蓝牙设备列表也可以显示到页面上,或者通过log打印出来。我在这里就不实现这些内容了,只是给大家提一个实现的方向和思路。
最后,我们对本章回中的内容做一个全面的总结: