Flutter蓝牙通信插件flutter_ble_peripheral_central的使用
Flutter蓝牙通信插件flutter_ble_peripheral_central的使用
1. 引言
该项目是探索跨平台应用间数据交换方法的一部分,通过蓝牙低功耗(BLE)实现。项目旨在使iOS和Android平台上的功能在Flutter中可用,通过参考alexanderlavrushko的BLEProof-collection(https://github.com/alexanderlavrushko/BLEProof-collection),创建了flutter_ble_peripheral_central插件项目。
该项目适用于最新的iOS和Android版本。对于简单的应用间数据交换,该插件已经足够。然而,对于更复杂的设备控制,我们建议使用经过验证和广泛使用的库。
请注意,UUIDs和功能列表直接采用了BLEProof-collection的内容。
2. 截图
主页 | 中央模式 | 外围模式 |
---|---|---|
![]() |
![]() |
![]() |
3. 设置
Android
- 在
AndroidManifest.xml
中添加权限。
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
- 注册外围服务到
AndroidManifest.xml
。
<service android:name="com.novice.flutter_ble_peripheral_central.ble.BlePeripheralService"
android:exported="true"
android:enabled="true"
android:permission="android.permission.BLUETOOTH">
</service>
- 注册中央服务到
AndroidManifest.xml
。
<service android:name="com.novice.flutter_ble_peripheral_central.ble.BleCentralService"
android:exported="true"
android:enabled="true"
android:permission="android.permission.BLUETOOTH_ADMIN">
<intent-filter>
<action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
</intent-filter>
</service>
iOS
- 对于iOS,必须在应用程序的Info.plist文件中添加以下条目。否则无法访问Core Bluetooth。
<key>NSBluetoothAlwaysUsageDescription</key>
<string>我们使用蓝牙来展示中央与外围之间的基本通信</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>我们使用蓝牙来展示中央与外围之间的基本通信</string>
4. 使用
import 'package:flutter_ble_peripheral_central/flutter_ble_peripheral_central.dart';
// 插件实例
final _flutterBlePeripheralCentralPlugin = FlutterBlePeripheralCentral();
// 获取平台版本
var platformVersion = await _flutterBlePeripheralCentralPlugin.getPlatformVersion();
// 外围模式
StreamSubscription<dynamic>? _eventSubscription;
var advertisingText = "广告数据"; // 只有iOS
var readableText = "可读数据";
// 开始广告
_eventSubscription = await _flutterBlePeripheralCentralPlugin.startBlePeripheralService(advertisingText, readableText).listen((event) {
// 处理事件
// ...
});
// 编辑可读文本
await _flutterBlePeripheralCentralPlugin.editTextCharForRead(readableText);
// 发送指示
var sendData = '发送数据';
var result = await _flutterBlePeripheralCentralPlugin.sendIndicate(sendData);
// 停止广告
await _flutterBlePeripheralCentralPlugin.stopBlePeripheralService();
// 中央模式
// 扫描并自动连接
_eventSubscription = await _flutterBlePeripheralCentralPlugin.scanAndConnect().listen((event) {
// 处理事件
// ...
});
// 断开连接
await _flutterBlePeripheralCentralPlugin.bleDisconnect();
// 读取特征值
var result = await _flutterBlePeripheralCentralPlugin.bleReadCharacteristic();
// 写入特征值
var sendData = '发送数据';
await _flutterBlePeripheralCentralPlugin.bleWriteCharacteristic(sendData);
示例代码
// example/lib/main.dart
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_ble_peripheral_central/flutter_ble_peripheral_central.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(MaterialApp(home: HomeScreen()));
}
// 主页面
class HomeScreen extends StatelessWidget {
HomeScreen({Key? key}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'BLE 外围 & 中央',
style: TextStyle(fontSize: 27, fontWeight: FontWeight.bold,),
)
),
body: Column(
children: [
Text(
'示例',
style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold,),
),
SizedBox(height: MediaQuery.of(context).size.height * 0.4),
Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return BLEPeripheralWidget();
}),
);
},
icon: Icon(
Icons.login_sharp,
size: MediaQuery.of(context).size.width * 0.07,
color: Colors.white
),
label: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 50),
style: TextStyle(
color: Colors.white,
fontSize: MediaQuery.of(context).size.width * 0.05,
fontWeight: FontWeight.bold,
),
child: Text('BLE 外围视图'),
),
style: ElevatedButton.styleFrom(
primary: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.symmetric(
vertical: MediaQuery.of(context).size.height * 0.02,
horizontal: MediaQuery.of(context).size.width * 0.08,
),
),
),
SizedBox(height: 10,),
ElevatedButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return BLECentralWidget();
}),
);
},
icon: Icon(
Icons.login_sharp,
size: MediaQuery.of(context).size.width * 0.07,
color: Colors.white
),
label: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 50),
style: TextStyle(
color: Colors.white,
fontSize: MediaQuery.of(context).size.width * 0.05,
fontWeight: FontWeight.bold,
),
child: Text('BLE 中央视图 '),
),
style: ElevatedButton.styleFrom(
primary: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.symmetric(
vertical: MediaQuery.of(context).size.height * 0.02,
horizontal: MediaQuery.of(context).size.width * 0.08,
),
),
),
]
),
),
),
],
),
);
}
}
// BLE 外围视图
class BLEPeripheralWidget extends StatefulWidget {
[@override](/user/override)
_BLEPeripheralWidgetState createState() => _BLEPeripheralWidgetState();
}
class _BLEPeripheralWidgetState extends State<BLEPeripheralWidget> {
final _flutterBlePeripheralCentralPlugin = FlutterBlePeripheralCentral();
List<String> _events = [];
final _eventStreamController = StreamController<String>();
final _bluetoothState = TextEditingController();
final _advertisingText = TextEditingController();
final _readableText = TextEditingController();
final _indicateText = TextEditingController();
final _writeableText = TextEditingController();
bool _isSwitchOn = false;
final ScrollController _scrollController = ScrollController();
[@override](/user/override)
void initState() {
super.initState();
_permissionCheck();
}
[@override](/user/override)
void dispose() {
_eventStreamController.close();
super.dispose();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'BLE 外围视图',
style: TextStyle(fontSize: 27, fontWeight: FontWeight.bold),
)
),
body: SingleChildScrollView(child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10,),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"蓝牙状态",
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
),
SizedBox(height: 5),
SizedBox(
width: double.infinity,
child: Container(
width: MediaQuery.of(context).size.width * 0.6,
height: 45,
child: TextField(
controller: _bluetoothState,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 12,),
),
enabled: false,
),
),
),
],
)
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10,),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"广告数据",
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
),
SizedBox(height: 5),
SizedBox(
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.75,
height: 45,
child: TextField(
controller: _advertisingText,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 12,),
),
enabled: Platform.isIOS ? true : false,
),
),
SizedBox(width: 10),
Container(
width: MediaQuery.of(context).size.width * 0.17,
height: 45,
child: Transform.scale(
scale: 1.3,
child: Switch(
value: _isSwitchOn,
activeColor: CupertinoColors.activeBlue,
onChanged: (value) {
setState(() {
_isSwitchOn = value;
});
if (_isSwitchOn) {
_bleStartAdvertising(_advertisingText.text, _readableText.text);
} else {
_bleStopAdvertising();
}
},
),
)
),
]),
),
],
)
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10,),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"可读特性",
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
),
SizedBox(height: 5),
SizedBox(
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.67,
height: 45,
child: TextField(
controller: _readableText,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 12,),
),
),
),
SizedBox(width: 10,),
Container(
width: MediaQuery.of(context).size.width * 0.25,
height: 45,
child:
ElevatedButton(
onPressed: () async {
_bleEditTextCharForRead(_readableText.text);
},
child: Text('编辑'),
),
),
]
),
),
],
)
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10,),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"指示",
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
),
SizedBox(height: 5),
SizedBox(
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.67,
height: 45,
child: TextField(
controller: _indicateText,
decoration: InputDecoration(
hintText: '输入指示值',
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 12,),
),
),
),
SizedBox(width: 10,),
Container(
width: MediaQuery.of(context).size.width * 0.25,
height: 45,
child:
ElevatedButton(
onPressed: () async {
_bleIndicate(_indicateText.text);
},
child: Text('发送'),
),
),
]
),
),
],
)
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10,),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"可写特性",
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
),
SizedBox(height: 5),
SizedBox(
width: double.infinity,
child: Container(
width: MediaQuery.of(context).size.width * 0.6,
height: 45,
child: TextField(
controller: _writeableText,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 12,),
),
enabled: false,
),
),
),
],
)
),
Container(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10,),
child: Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * 0.32,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
),
child:
ListView.builder(
controller: _scrollController,
itemCount: _events.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
_events[index],
style: TextStyle(fontSize: 15,),
)
);
},
),
),
),
),
],
),
),
);
}
StreamSubscription<dynamic>? _eventSubscription;
void _bleStartAdvertising(String advertisingText, String readableText) async {
_clearLog();
_eventStreamController.sink.add('Starting...');
_eventSubscription = await _flutterBlePeripheralCentralPlugin
.startBlePeripheralService(advertisingText, readableText)
.listen((event) {
_eventStreamController.sink.add('-> ' + event);
_addEvent(event);
print('----------------------- > event: ' + event);
});
}
void _bleStopAdvertising() async {
await _flutterBlePeripheralCentralPlugin.stopBlePeripheralService();
}
void _bleEditTextCharForRead(String readableText) async {
await _flutterBlePeripheralCentralPlugin.editTextCharForRead(readableText);
}
void _bleIndicate(String sendData) async {
await _flutterBlePeripheralCentralPlugin.sendIndicate(sendData);
}
// 添加事件
void _addEvent(String event) {
setState(() {
_events.add(event);
});
Map<String, dynamic> responseMap = jsonDecode(event);
if (responseMap.containsKey('message')) {
String message = responseMap['message'];
print('Message: $message');
} else if (responseMap.containsKey('state')) {
setState(() {
_bluetoothState.text = responseMap['state'];
});
if (event == 'disconnected') {
_eventSubscription?.cancel();
}
} else if (responseMap.containsKey('onCharacteristicWriteRequest')) {
setState(() {
_writeableText.text = responseMap['onCharacteristicWriteRequest'];
});
} else {
print('Message key not found in the JSON response.');
}
// 滚动到底部
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
// 清空日志
void _clearLog() {
setState(() {
_events.clear();
});
}
void _permissionCheck() async {
if (Platform.isAndroid) {
var permission = await Permission.location.request();
var bleScan = await Permission.bluetoothScan.request();
var bleConnect = await Permission.bluetoothConnect.request();
var bleAdvertise = await Permission.bluetoothAdvertise.request();
var locationWhenInUse = await Permission.locationWhenInUse.request();
print('location permission: ${permission.isGranted}');
print('bleScan permission: ${bleScan.isGranted}');
print('bleConnect permission: ${bleConnect.isGranted}');
print('bleAdvertise permission: ${bleAdvertise.isGranted}');
print('location locationWhenInUse: ${locationWhenInUse.isGranted}');
}
}
}
// BLE 中央视图
class BLECentralWidget extends StatefulWidget {
[@override](/user/override)
_BLECentralWidgetState createState() => _BLECentralWidgetState();
}
class _BLECentralWidgetState extends State<BLECentralWidget> {
final _flutterBlePeripheralCentralPlugin = FlutterBlePeripheralCentral();
List<String> _events = [];
final _eventStreamController = StreamController<String>();
var _lifecycleState = TextEditingController();
var _readableText = TextEditingController();
var _writeableText = TextEditingController();
var _indicateText = TextEditingController();
bool _isSwitchOn = false;
final ScrollController _scrollController = ScrollController();
[@override](/user/override)
void initState() {
super.initState();
_permissionCheck();
}
[@override](/user/override)
void dispose() {
_eventStreamController.close();
super.dispose();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'BLE 中央视图',
style: TextStyle(fontSize: 27, fontWeight: FontWeight.bold),
),
),
body: SingleChildScrollView(child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10,),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"生命周期状态",
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
),
SizedBox(height: 5),
SizedBox(
width: double.infinity,
child: Container(
width: MediaQuery.of(context).size.width * 0.6,
height: 45,
child: TextField(
controller: _lifecycleState,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 12,),
),
enabled: false,
),
),
),
],
)
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10,),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.75,
height: 45,
child: Padding(padding: EdgeInsets.symmetric(vertical: 8,),
child: Text(
'扫描 & 自动连接',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),),
),
SizedBox(width: 10),
Container(
width: MediaQuery.of(context).size.width * 0.17,
height: 45,
child: Transform.scale(
scale: 1.3,
child: Switch(
value: _isSwitchOn,
activeColor: CupertinoColors.activeBlue,
onChanged: (value) {
setState(() {
_isSwitchOn = value;
});
if (_isSwitchOn) {
_bleScanAndConnect();
} else {
_bleDisconnect();
}
},
),
)
),
]),
),
],
)
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10,),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"可读特性",
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
),
SizedBox(height: 5),
SizedBox(
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.67,
height: 45,
child: TextField(
controller: _readableText,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 12,),
),
enabled: false,
),
),
SizedBox(width: 10,),
Container(
width: MediaQuery.of(context).size.width * 0.25,
height: 45,
child:
ElevatedButton(
onPressed: () async {
_bleReadCharacteristic();
},
child: Text('读取'),
),
),
]
),
),
],
)
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10,),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"可写特性",
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
),
SizedBox(height: 5),
SizedBox(
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.67,
height: 45,
child: TextField(
controller: _writeableText,
decoration: InputDecoration(
hintText: '写入发送数据',
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 12,),
),
),
),
SizedBox(width: 10,),
Container(
width: MediaQuery.of(context).size.width * 0.25,
height: 45,
child:
ElevatedButton(
onPressed: () async {
_bleWriteCharacteristic(_writeableText.text);
},
child: Text('写入'),
),
),
]
),
),
],
)
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10,),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"指示",
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
),
SizedBox(height: 5),
SizedBox(
width: double.infinity,
child: Container(
width: MediaQuery.of(context).size.width * 0.6,
height: 45,
child: TextField(
controller: _indicateText,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 12,),
),
enabled: false,
),
),
),
],
)
),
Container(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10,),
child: Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * 0.32,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
),
child:
ListView.builder(
controller: _scrollController,
itemCount: _events.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
_events[index],
style: TextStyle(fontSize: 15,),
)
);
},
),
),
),
),
],
),
),
);
}
StreamSubscription<dynamic>? _eventSubscription;
void _bleScanAndConnect() async {
_clearLog();
_eventStreamController.sink.add('Starting...');
_eventSubscription = await _flutterBlePeripheralCentralPlugin
.scanAndConnect()
.listen((event) {
_eventStreamController.sink.add('-> ' + event);
_addEvent(event);
print('----------------------- > event: ' + event);
});
}
void _bleReadCharacteristic() async {
var result = await _flutterBlePeripheralCentralPlugin.bleReadCharacteristic();
setState(() {
_readableText.text = result!;
});
}
void _bleWriteCharacteristic(String sendData) async {
await _flutterBlePeripheralCentralPlugin.bleWriteCharacteristic(sendData);
}
void _bleDisconnect() async {
await _flutterBlePeripheralCentralPlugin.bleDisconnect();
}
// 添加事件
void _addEvent(String event) {
setState(() {
_events.add(event);
});
Map<String, dynamic> responseMap = jsonDecode(event);
if (responseMap.containsKey('message')) {
String message = responseMap['message'];
print('Message: $message');
} else if (responseMap.containsKey('state')) {
setState(() {
_lifecycleState.text = responseMap['state'];
});
if (event == 'disconnected') {
_eventSubscription?.cancel();
}
} else if (responseMap.containsKey('onCharacteristicChanged')) {
setState(() {
_indicateText.text = responseMap['onCharacteristicChanged'];
});
} else {
print('Message key not found in the JSON response.');
}
// 滚动到底部
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
// 清空日志
void _clearLog() {
setState(() {
_events.clear();
});
}
void _permissionCheck() async {
if (Platform.isAndroid) {
var permission = await Permission.location.request();
var bleScan = await Permission.bluetoothScan.request();
var bleConnect = await Permission.bluetoothConnect.request();
var bleAdvertise = await Permission.bluetoothAdvertise.request();
var locationWhenInUse = await Permission.locationWhenInUse.request();
print('location permission: ${permission.isGranted}');
print('bleScan permission: ${bleScan.isGranted}');
print('bleConnect permission: ${bleConnect.isGranted}');
print('bleAdvertise permission: ${bleAdvertise.isGranted}');
print('location locationWhenInUse: ${locationWhenInUse.isGranted}');
}
}
}
更多关于Flutter蓝牙通信插件flutter_ble_peripheral_central的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter蓝牙通信插件flutter_ble_peripheral_central的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter应用中使用flutter_ble_peripheral_central
插件进行蓝牙通信的示例代码。这个插件允许你的Flutter应用同时作为蓝牙外设(Peripheral)和中心设备(Central)进行操作。
添加依赖
首先,你需要在你的pubspec.yaml
文件中添加依赖:
dependencies:
flutter:
sdk: flutter
flutter_ble_peripheral_central: ^x.y.z # 请替换为最新版本号
然后运行flutter pub get
来安装依赖。
配置权限
在Android上,你需要在AndroidManifest.xml
中添加必要的权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.yourapp">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- 其他权限 -->
<!-- ... -->
</manifest>
在iOS上,你需要在Info.plist
中添加蓝牙使用描述:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>App needs bluetooth to connect to devices</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>App needs bluetooth to connect to devices</string>
外设(Peripheral)模式示例
下面是一个简单的外设模式示例,它启动一个服务并广播一个特征值:
import 'package:flutter/material.dart';
import 'package:flutter_ble_peripheral_central/flutter_ble_peripheral.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
FlutterBlePeripheral _peripheral = FlutterBlePeripheral();
@override
void initState() {
super.initState();
_initPeripheral();
}
Future<void> _initPeripheral() async {
// 定义服务
Uuid serviceUuid = Uuid.parse("0000180d-0000-1000-8000-00805f9b34fb");
Uuid characteristicUuid = Uuid.parse("00002a37-0000-1000-8000-00805f9b34fb");
// 创建服务和特征值
FlutterBlePeripheralService service = FlutterBlePeripheralService(
serviceUuid: serviceUuid,
characteristics: [
FlutterBlePeripheralCharacteristic(
characteristicUuid: characteristicUuid,
properties: [
CharacteristicProperty.read,
CharacteristicProperty.notify,
],
value: Uint8List.fromList([0x00]),
),
],
);
// 添加服务并开始广播
await _peripheral.addService(service);
await _peripheral.startAdvertising();
await _peripheral.setNotifyValue(characteristicUuid, Uint8List.fromList([0x01]));
print("Peripheral started and advertising");
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter BLE Peripheral Example'),
),
body: Center(
child: Text('BLE Peripheral is running'),
),
),
);
}
}
中心(Central)模式示例
下面是一个简单的中心模式示例,它扫描设备并连接到第一个找到的设备:
import 'package:flutter/material.dart';
import 'package:flutter_ble_peripheral_central/flutter_ble_central.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
FlutterBleCentral _central = FlutterBleCentral();
@override
void initState() {
super.initState();
_scanForDevices();
}
Future<void> _scanForDevices() async {
_central.scanForDevices(withServices: []).listen((scanResult) async {
print("Device found: ${scanResult.device.id}");
// 连接第一个找到的设备
if (scanResult.device.name != null) {
await _central.connectToDevice(scanResult.device.id);
// 列出服务
List<FlutterBleCentralService> services = await _central.discoverServices(scanResult.device.id);
services.forEach((service) {
print("Service found: ${service.uuid}");
// 列出特征值
_central.discoverCharacteristics(scanResult.device.id, service.uuid).then((characteristics) {
characteristics.forEach((characteristic) {
print("Characteristic found: ${characteristic.uuid}");
});
});
});
// 停止扫描
_central.stopScan();
}
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter BLE Central Example'),
),
body: Center(
child: Text('Scanning for BLE devices...'),
),
),
);
}
}
注意事项
- 权限处理:在实际应用中,你需要处理权限请求,尤其是在iOS上,蓝牙权限和位置权限是紧密相关的。
- 错误处理:上述代码未包含错误处理逻辑,实际应用中应添加适当的错误处理。
- 平台差异:不同平台(Android和iOS)在蓝牙处理上可能有细微差异,请根据实际需要进行调整。
这段代码提供了一个基本的框架,展示了如何使用flutter_ble_peripheral_central
插件进行蓝牙通信。根据具体需求,你可以进一步扩展和优化这些代码。