Flutter蓝牙应用开发求助:实现1米内用户检测并触发震动
嘿,作为Flutter新手想做这个蓝牙近距离检测+震动的功能,其实思路挺清晰的,我给你拆解下具体步骤、代码示例和一些避坑技巧,还有几个创新优化方向:
核心实现逻辑
蓝牙近距离检测的关键是利用**RSSI(接收信号强度指示)**来估算设备间的距离,再结合自定义蓝牙广播识别同应用用户,最后触发震动。
步骤1:依赖与权限配置
首先得把需要的Flutter包和系统权限搞定:
- 在
pubspec.yaml里添加依赖:dependencies: flutter: sdk: flutter flutter_blue_plus: ^1.13.3 # 目前维护较好的蓝牙插件,替代旧的flutter_blue vibration: ^1.7.6+2 # 震动功能插件 - Android权限:在
android/app/src/main/AndroidManifest.xml里添加:<!-- 基础蓝牙权限(适配旧版本) --> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> <!-- Android 12+ 专属蓝牙权限 --> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <!-- 蓝牙扫描需要位置权限 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> - iOS权限:在
ios/Runner/Info.plist里添加:<key>NSBluetoothAlwaysUsageDescription</key> <string>需要蓝牙权限来检测附近的应用用户</string> <key>NSBluetoothPeripheralUsageDescription</key> <string>需要蓝牙权限来检测附近的应用用户</string> <key>NSVibrationUsageDescription</key> <string>检测到近距离用户时震动提醒</string>
步骤2:蓝牙广播与扫描
为了只识别同应用的用户,我们需要让每个设备广播一个自定义的服务UUID,扫描时只过滤这个UUID的设备,避免误判其他蓝牙设备:
- 初始化蓝牙,检查并请求开启蓝牙
- 启动自定义蓝牙广播(让当前设备成为蓝牙外设)
- 持续扫描带有自定义UUID的设备,获取RSSI值
步骤3:距离估算与震动触发
RSSI和距离的关系是近似的,常用的估算公式如下(你可以根据实际环境调整参数):
// txPower:设备蓝牙广播的发射功率(一般默认-59dBm,可根据设备实际值调整) // rssi:扫描到的信号强度 // n:环境衰减系数(室内取2-4,室外取2) double calculateDistance(int txPower, int rssi, double n) { if (rssi == 0) return -1.0; // 无法检测 double ratio = rssi * 1.0 / txPower; if (ratio < 1.0) { return pow(ratio, 10).toDouble(); } else { double distance = (0.89976) * pow(ratio, 7.7095) + 0.111; return distance; } }
当估算距离≤1米时,调用震动插件触发震动,记得加冷却时间避免频繁震动。
完整代码示例
下面是一个简化的可运行示例,包含核心逻辑:
import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:vibration/vibration.dart'; // 自定义服务UUID,用来识别同应用的设备(自己可以换一个唯一的UUID) const String appServiceUuid = "00001234-0000-1000-8000-00805f9b34fb"; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { final FlutterBluePlus _flutterBlue = FlutterBluePlus.instance; List<ScanResult> _scanResults = []; bool _isScanning = false; DateTime? _lastVibrateTime; final int _cooldownSeconds = 5; // 震动冷却时间 final double _thresholdDistance = 1.0; // 1米阈值 @override void initState() { super.initState(); _initBluetooth(); _startAdvertising(); // 启动自定义蓝牙广播 } // 初始化蓝牙,监听状态变化 void _initBluetooth() { _flutterBlue.state.listen((state) { if (state == BluetoothState.on) { _startScan(); } else { // 请求用户开启蓝牙 _flutterBlue.requestEnable(); } }); } // 启动自定义蓝牙广播 void _startAdvertising() { _flutterBlue.startAdvertising( AdvertiseData( serviceUuids: [Guid(appServiceUuid)], name: "MyAppUser", // 可选:广播设备名称,方便识别 ), ); } // 启动蓝牙扫描 void _startScan() { if (_isScanning) return; setState(() => _isScanning = true); // 只扫描带有自定义UUID的设备,减少无效结果 _flutterBlue.startScan( withServices: [Guid(appServiceUuid)], timeout: const Duration(seconds: 4), ); // 监听扫描结果 _flutterBlue.scanResults.listen((results) { setState(() => _scanResults = results); _checkAndVibrate(results); }); // 扫描结束后自动重启,实现持续扫描 _flutterBlue.isScanning.listen((scanning) { if (!scanning && mounted) { Future.delayed(const Duration(seconds: 1), _startScan); } }); } // 检查距离并触发震动 void _checkAndVibrate(List<ScanResult> results) { for (var result in results) { double distance = calculateDistance(-59, result.rssi, 2.5); // 室内环境用2.5作为衰减系数 if (distance <= _thresholdDistance && distance != -1) { // 检查冷却时间,避免频繁震动 bool canVibrate = _lastVibrateTime == null || DateTime.now().difference(_lastVibrateTime!).inSeconds >= _cooldownSeconds; if (canVibrate) { Vibration.vibrate(duration: 500); // 震动500毫秒 setState(() => _lastVibrateTime = DateTime.now()); debugPrint("检测到1米内的应用用户,已触发震动"); } } } } // 距离估算公式 double calculateDistance(int txPower, int rssi, double n) { if (rssi == 0) return -1.0; double ratio = rssi * 1.0 / txPower; if (ratio < 1.0) { return pow(ratio, 10).toDouble(); } else { return (0.89976) * pow(ratio, 7.7095) + 0.111; } } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text("蓝牙近距离检测")), body: ListView.builder( padding: const EdgeInsets.all(16), itemCount: _scanResults.length, itemBuilder: (context, index) { var result = _scanResults[index]; double distance = calculateDistance(-59, result.rssi, 2.5); return Card( child: ListTile( title: Text(result.device.name.isEmpty ? "未知用户" : result.device.name), subtitle: Text( distance == -1 ? "无法估算距离 | RSSI: ${result.rssi}dBm" : "距离: ${distance.toStringAsFixed(2)}米 | RSSI: ${result.rssi}dBm", ), ), ); }, ), ), ); } @override void dispose() { _flutterBlue.stopScan(); _flutterBlue.stopAdvertising(); super.dispose(); } }
优化与创新方案
- RSSI平滑处理:因为RSSI波动很大,可以取最近5-10次的RSSI平均值来计算距离,减少误判
- 双向验证:检测到近距离后,发起短蓝牙连接,交换简单的用户标识(比如随机生成的临时ID),确认对方确实是应用用户,避免被其他设备模仿广播
- 低功耗优化:间隔扫描(比如扫描3秒,休息2秒),或者在后台使用低功耗蓝牙模式,减少电量消耗
- 用户身份标记:让用户设置昵称或头像,广播时携带这些信息,方便识别对方是谁
- 多维度验证:如果设备支持,可以结合蓝牙的角度信息(部分蓝牙5.1+设备支持),进一步提升距离检测的准确性
注意事项
- RSSI估算的距离是近似值,受环境(比如墙壁、障碍物)影响很大,建议在目标使用环境中实际测试调整参数
- 蓝牙权限申请要清晰告知用户用途,避免被拒绝
- iOS后台蓝牙扫描需要开启
Background Modes里的Uses Bluetooth LE accessories选项
内容的提问来源于stack exchange,提问作者shub-naik
相关产品推荐
相关产品推荐

