You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的设备,避免误判其他蓝牙设备:

  1. 初始化蓝牙,检查并请求开启蓝牙
  2. 启动自定义蓝牙广播(让当前设备成为蓝牙外设)
  3. 持续扫描带有自定义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();
  }
}
优化与创新方案
  1. RSSI平滑处理:因为RSSI波动很大,可以取最近5-10次的RSSI平均值来计算距离,减少误判
  2. 双向验证:检测到近距离后,发起短蓝牙连接,交换简单的用户标识(比如随机生成的临时ID),确认对方确实是应用用户,避免被其他设备模仿广播
  3. 低功耗优化:间隔扫描(比如扫描3秒,休息2秒),或者在后台使用低功耗蓝牙模式,减少电量消耗
  4. 用户身份标记:让用户设置昵称或头像,广播时携带这些信息,方便识别对方是谁
  5. 多维度验证:如果设备支持,可以结合蓝牙的角度信息(部分蓝牙5.1+设备支持),进一步提升距离检测的准确性
注意事项
  • RSSI估算的距离是近似值,受环境(比如墙壁、障碍物)影响很大,建议在目标使用环境中实际测试调整参数
  • 蓝牙权限申请要清晰告知用户用途,避免被拒绝
  • iOS后台蓝牙扫描需要开启Background Modes里的Uses Bluetooth LE accessories选项

内容的提问来源于stack exchange,提问作者shub-naik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 15:57:44