Flutter蓝牙通信插件flutter_bluex的使用
Flutter蓝牙通信插件flutter_bluex的使用
这是一个 fork 版本,使用前请注意!
由于原先的 flutter_blue
不再维护,我 fork 了原仓库并针对新版本 Flutter 继续维护:
- 增加了获取已绑定设备(已配对设备)的方法,已配对设备可以不需要扫描就能连接(仅安卓)。
- 增加了更直观的直接获取设备连接状态和 MTU 的函数,而不是从流获得第一个状态。
- 修复了在高版本安卓上由于一个无用的 UUID 类导致的闪退问题。
- 修复了原来的
connect
方法中的timeout
设置并不会在当前的await
流程中抛出异常的问题。 - 修复了示例在高版本安卓上运行的问题。
Introduction
flutter_blue
是一个为 Flutter 设计的蓝牙插件,帮助开发者构建现代跨平台应用。
Alpha version
此库正在与生产应用一起积极开发,API 将随着我们向版本 1.0 的发展而演变。
请充分准备应对可能的破坏性更改。 此包必须在真实设备上进行测试。
如果遇到适应最新 API 的困难,请联系我,我很乐意听取您的用例。
Cross-Platform Bluetooth LE
flutter_blue
目标是提供两个平台(iOS 和 Android)的最佳功能。
使用 FlutterBlue
实例,您可以扫描并连接附近的设备 (BluetoothDevice
)。
一旦连接到设备,BluetoothDevice
对象可以发现服务 (BluetoothService
)、特征 (BluetoothCharacteristic
) 和描述符 (BluetoothDescriptor
)。
然后可以使用 BluetoothDevice
对象直接与特征和描述符交互。
Usage
Obtain an instance
FlutterBlue flutterBlue = FlutterBlue.instance;
Scan for devices
// 开始扫描
flutterBlue.startScan(timeout: Duration(seconds: 4));
// 监听扫描结果
var subscription = flutterBlue.scanResults.listen((results) {
// 处理扫描结果
for (ScanResult r in results) {
print('${r.device.name} found! rssi: ${r.rssi}');
}
});
// 停止扫描
flutterBlue.stopScan();
Connect to a device
// 连接到设备
await device.connect();
// 断开设备连接
device.disconnect();
Discover services
List<BluetoothService> services = await device.discoverServices();
services.forEach((service) {
// 处理服务
});
Read and write characteristics
// 读取所有特征
var characteristics = service.characteristics;
for(BluetoothCharacteristic c in characteristics) {
List<int> value = await c.read();
print(value);
}
// 向特征写入数据
await c.write([0x12, 0x34])
Read and write descriptors
// 读取所有描述符
var descriptors = characteristic.descriptors;
for(BluetoothDescriptor d in descriptors) {
List<int> value = await d.read();
print(value);
}
// 向描述符写入数据
await d.write([0x12, 0x34])
Set notifications and listen to changes
await characteristic.setNotifyValue(true);
characteristic.value.listen((value) {
// 处理新值
});
Read the MTU and request larger size
final mtu = await device.mtu.first;
await device.requestMtu(512);
注意:iOS 不允许请求 MTU 大小,并且总是尝试协商最大的可能 MTU(iOS 支持高达 MTU 大小 185)
Getting Started
Change the minSdkVersion for Android
flutter_blue
仅从 Android SDK 版本 19 兼容,因此您应该在 android/app/build.gradle
中更改此设置:
Android {
defaultConfig {
minSdkVersion: 19
Add permissions for Bluetooth
我们需要添加使用蓝牙和访问位置的权限:
Android
在 android/app/src/main/AndroidManifest.xml
中添加:
<!-- Request legacy Bluetooth permissions on older devices. -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<!-- Needed only if your app looks for Bluetooth devices.
You must add an attribute to this permission, or declare the
ACCESS_FINE_LOCATION permission, depending on the results when you
check location usage in your app. -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- Needed only if your app makes the device discoverable to Bluetooth
devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- Needed only if your app communicates with already-paired Bluetooth
devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
iOS
在 ios/Runner/Info.plist
中添加:
<dict>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Need BLE permission</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Need BLE permission</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Need Location permission</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Need Location permission</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Need Location permission</string>
对于 iOS 的位置权限,请参阅:https://developer.apple.com/documentation/corelocation/requesting_authorization_for_location_services
Reference
FlutterBlue API
功能 | Android | iOS | 描述 |
---|---|---|---|
扫描 | ✅ | ✅ | 开始扫描低功耗蓝牙设备 |
状态 | ✅ | ✅ | 蓝牙适配器状态变化的流 |
是否可用 | ✅ | ✅ | 检查设备是否支持蓝牙 |
是否开启 | ✅ | ✅ | 检查蓝牙功能是否打开 |
BluetoothDevice API
功能 | Android | iOS | 描述 |
---|---|---|---|
连接 | ✅ | ✅ | 与设备建立连接 |
断开连接 | ✅ | ✅ | 取消设备的活动或待定连接 |
发现服务 | ✅ | ✅ | 发现远程设备提供的服务及其特征和描述符 |
服务 | ✅ | ✅ | 获取服务列表。需要调用 discoverServices() 完成 |
状态 | ✅ | ✅ | 蓝牙设备状态变化的流 |
MTU | ✅ | ✅ | MTU 大小变化的流 |
请求 MTU | ✅ | ✅ | 请求更改设备的 MTU |
BluetoothCharacteristic API
功能 | Android | iOS | 描述 |
---|---|---|---|
读取 | ✅ | ✅ | 获取特征的值 |
写入 | ✅ | ✅ | 写入特征的值 |
设置通知 | ✅ | ✅ | 在特征上设置通知或指示 |
值 | ✅ | ✅ | 当值改变时的特征值流 |
BluetoothDescriptor API
功能 | Android | iOS | 描述 |
---|---|---|---|
读取 | ✅ | ✅ | 获取描述符的值 |
写入 | ✅ | ✅ | 写入描述符的值 |
Troubleshooting
When I scan using a service UUID filter, it doesn’t find any devices.
确保设备正在广播它支持的服务 UUID。这可以在广告包中找到作为“UUID 16 位完整列表”或“UUID 128 位完整列表”。
示例代码
// Copyright 2017, Paul DeMarco.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_bluex/flutter_blue.dart';
import 'package:flutter_blue_example/widgets.dart';
void main() {
runApp(FlutterBlueApp());
}
class FlutterBlueApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
color: Colors.lightBlue,
// home: BondedTestPage(),
home: StreamBuilder<BluetoothState>(
stream: FlutterBlue.instance.state,
initialData: BluetoothState.unknown,
builder: (c, snapshot) {
final state = snapshot.data;
if (state == BluetoothState.on) {
return FindDevicesScreen();
}
return BluetoothOffScreen(state: state);
},
),
);
}
}
/// 用于测试绑定设备的连接断开
class BondedTestPage extends StatefulWidget {
const BondedTestPage({Key? key}) : super(key: key);
[@override](/user/override)
State<BondedTestPage> createState() => _BondedTestPageState();
}
class _BondedTestPageState extends State<BondedTestPage> {
List<BluetoothDevice> deviceList = [];
Map<String, BluetoothDeviceState> stateMap = {};
/// 加载已绑定设备
reloadBondedDevices() async {
final res = await FlutterBlue.instance.bondedDevices;
for (var eachDevice in res) {
final state = await eachDevice.currentState();
stateMap[eachDevice.id.id] = state;
}
setState(() {
deviceList = res;
});
}
[@override](/user/override)
void initState() {
super.initState();
reloadBondedDevices();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Bonded Device Test'),
actions: [
IconButton(
onPressed: reloadBondedDevices,
icon: Icon(Icons.refresh),
)
],
),
body: ListView(
children: [
for (final device in deviceList)
Container(
padding: EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${device.name} ${device.id.id}'),
Text(
stateMap[device.id.id]?.toString() ?? "--",
style: TextStyle(
color: Colors.black.withOpacity(0.5),
),
),
],
),
Container(
child: Row(
children: [
TextButton(
child: Text('连接'),
onPressed: () async {
final time = DateTime.now();
await device.connect();
print(
'Connect Time Cost:'
'${DateTime.now().difference(time).inMilliseconds}ms',
);
await device.discoverServices();
print(
'DiscoverServices Time Cost:'
'${DateTime.now().difference(time).inMilliseconds}ms',
);
await reloadBondedDevices();
},
),
TextButton(
child: Text('断开'),
onPressed: () async {
await device.disconnect();
await reloadBondedDevices();
},
),
],
),
),
],
),
)
],
),
);
}
}
class BluetoothOffScreen extends StatelessWidget {
const BluetoothOffScreen({Key? key, this.state}) : super(key: key);
final BluetoothState? state;
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.lightBlue,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.bluetooth_disabled,
size: 200.0,
color: Colors.white54,
),
Text(
'Bluetooth Adapter is ${state != null ? state.toString().substring(15) : 'not available'}.',
),
],
),
),
);
}
}
class FindDevicesScreen extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Find Devices'),
),
body: RefreshIndicator(
onRefresh: () => FlutterBlue.instance.startScan(timeout: Duration(seconds: 4)),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
StreamBuilder<List<BluetoothDevice>>(
stream: Stream.periodic(Duration(seconds: 2)).asyncMap((_) => FlutterBlue.instance.connectedDevices),
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data!
.map((d) => ListTile(
title: Text(d.name),
subtitle: Text(d.id.toString()),
trailing: StreamBuilder<BluetoothDeviceState>(
stream: d.state,
initialData: BluetoothDeviceState.disconnected,
builder: (c, snapshot) {
if (snapshot.data == BluetoothDeviceState.connected) {
return TextButton(
child: Text('OPEN'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (context) => DeviceScreen(device: d))),
);
}
return Text(snapshot.data.toString());
},
),
))
.toList(),
),
),
StreamBuilder<List<ScanResult>>(
stream: FlutterBlue.instance.scanResults,
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data!
.map(
(r) => ScanResultTile(
result: r,
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
r.device.connect();
return DeviceScreen(device: r.device);
})),
),
)
.toList(),
),
),
],
),
),
),
floatingActionButton: StreamBuilder<bool>(
stream: FlutterBlue.instance.isScanning,
initialData: false,
builder: (c, snapshot) {
if (snapshot.data!) {
return FloatingActionButton(
child: Icon(Icons.stop),
onPressed: () => FlutterBlue.instance.stopScan(),
backgroundColor: Colors.red,
);
} else {
return FloatingActionButton(
child: Icon(Icons.search),
onPressed: () => FlutterBlue.instance
.startScan(timeout: Duration(seconds: 4)));
}
},
),
);
}
}
class DeviceScreen extends StatelessWidget {
const DeviceScreen({Key? key, required this.device}) : super(key: key);
final BluetoothDevice device;
List<int> _getRandomBytes() {
final math = Random();
return [
math.nextInt(255),
math.nextInt(255),
math.nextInt(255),
math.nextInt(255)
];
}
List<Widget> _buildServiceTiles(List<BluetoothService> services) {
return services
.map(
(s) => ServiceTile(
service: s,
characteristicTiles: s.characteristics
.map(
(c) => CharacteristicTile(
characteristic: c,
onReadPressed: () => c.read(),
onWritePressed: () async {
await c.write(_getRandomBytes(), withoutResponse: true);
await c.read();
},
onNotificationPressed: () async {
await c.setNotifyValue(!c.isNotifying);
await c.read();
},
descriptorTiles: c.descriptors
.map(
(d) => DescriptorTile(
descriptor: d,
onReadPressed: () => d.read(),
onWritePressed: () => d.write(_getRandomBytes()),
),
)
.toList(),
),
)
.toList(),
),
)
.toList();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(device.name),
actions: <Widget>[
StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (c, snapshot) {
VoidCallback? onPressed;
String text;
switch (snapshot.data) {
case BluetoothDeviceState.connected:
onPressed = () => device.disconnect();
text = 'DISCONNECT';
break;
case BluetoothDeviceState.disconnected:
onPressed = () => device.connect();
text = 'CONNECT';
break;
default:
onPressed = null;
text = snapshot.data.toString().substring(21).toUpperCase();
break;
}
return TextButton(
onPressed: onPressed,
child: Text(
text,
));
},
)
],
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (c, snapshot) => ListTile(
leading: (snapshot.data == BluetoothDeviceState.connected)
? Icon(Icons.bluetooth_connected)
: Icon(Icons.bluetooth_disabled),
title: Text(
'Device is ${snapshot.data.toString().split('.')[1]}.'),
subtitle: Text('${device.id}'),
trailing: StreamBuilder<bool>(
stream: device.isDiscoveringServices,
initialData: false,
builder: (c, snapshot) => IndexedStack(
index: snapshot.data! ? 1 : 0,
children: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () => device.discoverServices(),
),
IconButton(
icon: SizedBox(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.grey),
),
width: 18.0,
height: 18.0,
),
onPressed: null,
)
],
),
),
),
),
StreamBuilder<int>(
stream: device.mtu,
initialData: 0,
builder: (c, snapshot) => ListTile(
title: Text('MTU Size'),
subtitle: Text('${snapshot.data} bytes'),
trailing: IconButton(
icon: Icon(Icons.edit),
onPressed: () => device.requestMtu(223),
),
),
),
StreamBuilder<List<BluetoothService>>(
stream: device.services,
initialData: [],
builder: (c, snapshot) {
return Column(
children: _buildServiceTiles(snapshot.data!),
);
},
),
],
),
),
);
}
}
更多关于Flutter蓝牙通信插件flutter_bluex的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter蓝牙通信插件flutter_bluex的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
flutter_blue
是一个用于 Flutter 应用的蓝牙通信插件,支持 Android 和 iOS 平台。它提供了丰富的 API 来发现、连接、发送和接收数据到蓝牙设备。以下是如何使用 flutter_blue
插件的基本步骤。
1. 添加依赖
首先,在 pubspec.yaml
文件中添加 flutter_blue
依赖:
dependencies:
flutter:
sdk: flutter
flutter_blue: ^0.8.0
然后运行 flutter pub get
来安装依赖。
2. 导入包
在你的 Dart 文件中导入 flutter_blue
包:
import 'package:flutter_blue/flutter_blue.dart';
3. 初始化 FlutterBlue
在应用启动时,初始化 FlutterBlue
:
FlutterBlue flutterBlue = FlutterBlue.instance;
4. 扫描蓝牙设备
你可以使用 startScan
方法来扫描附近的蓝牙设备:
flutterBlue.startScan(timeout: Duration(seconds: 4));
flutterBlue.scanResults.listen((results) {
for (ScanResult result in results) {
print('Found device: ${result.device.name} (${result.device.id})');
}
});
5. 停止扫描
扫描完成后,调用 stopScan
方法停止扫描:
flutterBlue.stopScan();
6. 连接设备
选择你要连接的设备,并调用 connect
方法:
BluetoothDevice device = ...; // 从扫描结果中获取设备
device.connect().then((_) {
print('Connected to device: ${device.name}');
});
7. 发现服务
连接成功后,你可以发现设备的服务:
device.discoverServices().then((services) {
for (BluetoothService service in services) {
print('Service: ${service.uuid}');
}
});
8. 读取特征值
你可以通过服务的特征值来读取或写入数据:
BluetoothCharacteristic characteristic = ...; // 从服务中获取特征值
characteristic.read().then((value) {
print('Read value: $value');
});
9. 写入特征值
你也可以向特征值写入数据:
List<int> data = [0x01, 0x02, 0x03];
characteristic.write(data).then((_) {
print('Data written');
});
10. 断开连接
使用 disconnect
方法断开与设备的连接:
device.disconnect().then((_) {
print('Disconnected from device');
});
权限配置
在 Android 和 iOS 上使用蓝牙功能时,需要配置相应的权限。
Android
在 AndroidManifest.xml
中添加以下权限:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
iOS
在 Info.plist
中添加以下权限:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>We need Bluetooth access to connect to devices</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>We need Bluetooth access to connect to devices</string>
注意事项
- 蓝牙功能在某些设备上可能需要用户授权,特别是位置权限。
- 不同蓝牙设备的服务和特征值可能不同,需要根据具体设备的BLE协议来进行操作。
示例代码
以下是一个简单的示例,展示了如何使用 flutter_blue
进行蓝牙通信:
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: BluetoothApp(),
);
}
}
class BluetoothApp extends StatefulWidget {
[@override](/user/override)
_BluetoothAppState createState() => _BluetoothAppState();
}
class _BluetoothAppState extends State<BluetoothApp> {
FlutterBlue flutterBlue = FlutterBlue.instance;
BluetoothDevice? connectedDevice;
List<BluetoothService> services = [];
[@override](/user/override)
void initState() {
super.initState();
_startScan();
}
void _startScan() {
flutterBlue.startScan(timeout: Duration(seconds: 4));
flutterBlue.scanResults.listen((results) {
for (ScanResult result in results) {
print('Found device: ${result.device.name} (${result.device.id})');
if (result.device.name == 'YourDeviceName') {
_connectToDevice(result.device);
break;
}
}
});
}
void _connectToDevice(BluetoothDevice device) async {
await device.connect();
setState(() {
connectedDevice = device;
});
_discoverServices(device);
}
void _discoverServices(BluetoothDevice device) async {
List<BluetoothService> services = await device.discoverServices();
setState(() {
this.services = services;
});
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Blue Example'),
),
body: ListView(
children: services.map((service) {
return ListTile(
title: Text('Service: ${service.uuid}'),
subtitle: Text('Characteristics: ${service.characteristics.length}'),
);
}).toList(),
),
);
}
}