Flutter蓝牙通信插件bluetooth_plus的使用
Flutter蓝牙通信插件bluetooth_plus的使用
引言
FlutterBlue是一个为Flutter开发的蓝牙插件,帮助开发者构建现代多平台应用。
Alpha版本
该库正在与生产应用一起积极开发,API将在我们达到1.0版本之前不断演变。
- 请充分准备处理破坏性更改。
- 此插件必须在真实设备上进行测试。
如果您在适应最新API时遇到困难,请联系我,我很乐意听取您的用例。
跨平台蓝牙低功耗(BLE)
FlutterBlue旨在为iOS和Android提供最佳功能。
- 使用FlutterBlue实例,您可以扫描并连接到附近的设备(
BluetoothDevice
)。 - 连接后,
BluetoothDevice
对象可以发现服务(BluetoothService
)、特征(BluetoothCharacteristic
)和描述符(BluetoothDescriptor
)。 BluetoothDevice
对象用于直接与特征和描述符交互。
使用方法
获取一个实例
FlutterBlue flutterBlue = FlutterBlue.instance;
扫描设备
// 开始扫描
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();
连接到设备
// 连接到设备
await device.connect();
// 断开设备连接
device.disconnect();
发现服务
List<BluetoothService> services = await device.discoverServices();
services.forEach((service) {
// 对服务进行操作
});
读取和写入特征
// 读取所有特征
var characteristics = service.characteristics;
for(BluetoothCharacteristic c in characteristics) {
List<int> value = await c.read();
print(value);
}
// 向特征写入数据
await c.write([0x12, 0x34]);
读取和写入描述符
// 读取所有描述符
var descriptors = characteristic.descriptors;
for(BluetoothDescriptor d in descriptors) {
List<int> value = await d.read();
print(value);
}
// 向描述符写入数据
await d.write([0x12, 0x34]);
设置通知并监听变化
await characteristic.setNotifyValue(true);
characteristic.value.listen((value) {
// 对新值进行操作
});
读取MTU并请求更大的尺寸
final mtu = await device.mtu.first;
await device.requestMtu(512);
注意:iOS不允许请求MTU大小,它会尝试协商最高的可能MTU(iOS支持高达185字节)。
入门指南
更改Android的minSdkVersion
Flutter_blue仅从Android SDK版本19开始兼容,因此您应该在android/app/build.gradle
中更改此设置:
Android {
defaultConfig {
minSdkVersion: 19
}
添加蓝牙权限
我们需要添加使用蓝牙和访问位置的权限:
Android
在android/app/src/main/AndroidManifest.xml
中添加以下内容:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
iOS
在ios/Runner/Info.plist
中添加以下内容:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>需要BLE权限</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>需要BLE权限</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>需要位置权限</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>需要位置权限</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要位置权限</string>
关于iOS的位置权限更多信息,请参阅:苹果官方文档
示例代码
// 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_bluetooth_plus/flutter_bluetooth_plus.dart';
import 'package:flutter_bluetooth_plus_example/widgets.dart';
void main() {
runApp(FlutterBlueApp());
}
class FlutterBlueApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
color: Colors.lightBlue,
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 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'}.',
style: Theme.of(context)
.primaryTextTheme
.titleMedium
?.copyWith(color: Colors.white),
),
],
),
),
);
}
}
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,
style: Theme.of(context)
.primaryTextTheme
.labelLarge
?.copyWith(color: Colors.white),
));
},
)
],
),
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蓝牙通信插件bluetooth_plus的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter蓝牙通信插件bluetooth_plus的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
bluetooth_plus
是一个用于在 Flutter 应用中实现蓝牙通信的插件。它提供了与蓝牙设备连接、扫描、读写数据等功能。以下是使用 bluetooth_plus
插件的基本步骤和示例代码。
1. 添加依赖
首先,在 pubspec.yaml
文件中添加 bluetooth_plus
插件的依赖:
dependencies:
flutter:
sdk: flutter
bluetooth_plus: ^1.1.0 # 请根据最新版本进行更新
然后运行 flutter pub get
来获取依赖。
2. 导入插件
在你的 Dart 文件中导入 bluetooth_plus
插件:
import 'package:bluetooth_plus/bluetooth_plus.dart';
3. 初始化蓝牙
在使用蓝牙功能之前,需要初始化蓝牙适配器:
BluetoothPlus bluetooth = BluetoothPlus.instance;
4. 检查蓝牙状态
在开始扫描或连接设备之前,检查蓝牙是否已启用:
bool isEnabled = await bluetooth.isEnabled();
if (!isEnabled) {
await bluetooth.requestEnable();
}
5. 扫描蓝牙设备
使用 startScan
方法来扫描附近的蓝牙设备:
bluetooth.startScan().listen((BluetoothDevice device) {
print('Found device: ${device.name}, ${device.address}');
});
6. 停止扫描
当你不再需要扫描设备时,可以停止扫描:
bluetooth.stopScan();
7. 连接蓝牙设备
使用 connect
方法来连接指定的蓝牙设备:
BluetoothDevice device = ...; // 从扫描结果中获取设备
await bluetooth.connect(device.address);
8. 读取和写入数据
连接成功后,你可以通过 read
和 write
方法与设备进行数据交互:
// 读取数据
List<int> data = await bluetooth.read();
// 写入数据
await bluetooth.write([0x01, 0x02, 0x03]);
9. 断开连接
当你不再需要与设备通信时,可以断开连接:
await bluetooth.disconnect();
10. 处理权限
在 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>我们需要访问蓝牙来连接设备</string>
完整示例
以下是一个完整的示例,展示了如何使用 bluetooth_plus
插件进行蓝牙设备的扫描、连接和数据传输:
import 'package:flutter/material.dart';
import 'package:bluetooth_plus/bluetooth_plus.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: BluetoothScreen(),
);
}
}
class BluetoothScreen extends StatefulWidget {
[@override](/user/override)
_BluetoothScreenState createState() => _BluetoothScreenState();
}
class _BluetoothScreenState extends State<BluetoothScreen> {
BluetoothPlus bluetooth = BluetoothPlus.instance;
List<BluetoothDevice> devices = [];
bool isConnected = false;
[@override](/user/override)
void initState() {
super.initState();
_checkBluetooth();
}
Future<void> _checkBluetooth() async {
bool isEnabled = await bluetooth.isEnabled();
if (!isEnabled) {
await bluetooth.requestEnable();
}
}
Future<void> _scanDevices() async {
devices.clear();
bluetooth.startScan().listen((device) {
setState(() {
devices.add(device);
});
});
}
Future<void> _connectDevice(BluetoothDevice device) async {
await bluetooth.connect(device.address);
setState(() {
isConnected = true;
});
}
Future<void> _sendData() async {
await bluetooth.write([0x01, 0x02, 0x03]);
}
Future<void> _disconnect() async {
await bluetooth.disconnect();
setState(() {
isConnected = false;
});
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Bluetooth Plus Example'),
),
body: Column(
children: [
ElevatedButton(
onPressed: _scanDevices,
child: Text('Scan Devices'),
),
Expanded(
child: ListView.builder(
itemCount: devices.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(devices[index].name),
subtitle: Text(devices[index].address),
onTap: () => _connectDevice(devices[index]),
);
},
),
),
if (isConnected) ...[
ElevatedButton(
onPressed: _sendData,
child: Text('Send Data'),
),
ElevatedButton(
onPressed: _disconnect,
child: Text('Disconnect'),
),
],
],
),
);
}
}