如何用flutter_blue_plus检测Apple及Feasycom(0x0A2D)iBeacon?
解决方案
1. 基于flutter_blue_plus的可靠iBeacon检测与解析
你的现有代码仅通过厂商ID过滤,容易误判同厂商的非iBeacon设备。要准确检测,必须验证iBeacon的标准数据结构:
- Apple iBeacon:厂商ID为
0x004C,厂商数据前2字节为0x0215(iBeacon广告帧类型),数据总长度需达23字节(包含类型、UUID、major、minor、发射功率)。 - Feasycom iBeacon:厂商ID为
0x0A2D,多数型号遵循标准iBeacon规范,同样需验证数据长度和类型字段。
修改检测逻辑并添加数据解析:
// 定义Beacon数据模型 class BeaconData { final int manufacturerId; final String uuid; final int major; final int minor; final int txPower; BeaconData({ required this.manufacturerId, required this.uuid, required this.major, required this.minor, required this.txPower, }); } // 解析iBeacon原始数据 BeaconData? parseBeaconData(List<int> data, int manufacturerId) { // 标准iBeacon数据长度要求:2字节类型+16字节UUID+2字节major+2字节minor+1字节txPower = 23字节 if (data.length < 23) return null; // 验证iBeacon类型标识 if (data[0] != 0x02 || data[1] != 0x15) return null; // 解析UUID(小端转大端处理) final uuidBytes = data.sublist(2, 18); final uuidStr = [ ...uuidBytes.sublist(0,4).reversed, ...uuidBytes.sublist(4,6).reversed, ...uuidBytes.sublist(6,8).reversed, ...uuidBytes.sublist(8,16) ].map((b) => b.toRadixString(16).padLeft(2, '0')).join(); final uuid = '$uuidStr.substring(0,8)-${uuidStr.substring(8,12)}-${uuidStr.substring(12,16)}-${uuidStr.substring(16,20)}-${uuidStr.substring(20)}'; // 解析major/minor(大端字节序) final major = (data[18] << 8) | data[19]; final minor = (data[20] << 8) | data[21]; final txPower = data[22]; return BeaconData( manufacturerId: manufacturerId, uuid: uuid, major: major, minor: minor, txPower: txPower, ); } // 修改目标信标检测方法 bool isTargetBeacon(ScanResult result) { final manufacturerData = result.advertisementData.manufacturerData; if (manufacturerData.isEmpty) return false; const feasycomId = 0x0A2D; const appleId = 0x004C; // 检测Apple iBeacon if (manufacturerData.containsKey(appleId)) { final data = manufacturerData[appleId]; if (data != null) { final beaconData = parseBeaconData(data, appleId); if (beaconData != null) { debugPrint('Apple iBeacon: UUID=${beaconData.uuid}, Major=${beaconData.major}, Minor=${beaconData.minor}'); return true; } } } // 检测Feasycom iBeacon if (manufacturerData.containsKey(feasycomId)) { final data = manufacturerData[feasycomId]; if (data != null) { final beaconData = parseBeaconData(data, feasycomId); if (beaconData != null) { debugPrint('Feasycom iBeacon: UUID=${beaconData.uuid}, Major=${beaconData.major}, Minor=${beaconData.minor}'); return true; } } } return false; }
2. 厂商特定数据过滤的正确方式
方式一:扫描阶段直接过滤(推荐)
利用flutterBlue.startScan的withManufacturers参数,只扫描指定厂商ID的设备,减少无关数据干扰:
void startScan() { setState(() { isScanning = true; beaconResults.clear(); }); // 仅扫描Apple和Feasycom厂商的设备 flutterBlue.startScan( withManufacturers: [0x004C, 0x0A2D], timeout: const Duration(seconds: 10), ); // 后续监听逻辑保持不变 }
方式二:回调中二次验证
即使扫描时过滤,仍需在回调中验证数据结构,避免同厂商的非iBeacon设备被误判(即上述修改后的isTargetBeacon方法)。
3. 更适合的跨平台iBeacon检测包:flutter_beacon
如果核心需求是iBeacon检测,推荐使用flutter_beacon包。它封装了Android的AltBeacon库和iOS的CoreLocation原生API,无需手动解析BLE原始数据,直接获取标准化的iBeacon信息,稳定性和兼容性更强。
基本使用示例
- 添加依赖到
pubspec.yaml:
dependencies: flutter_beacon: ^0.5.0
- 核心代码:
import 'package:flutter/material.dart'; import 'package:flutter_beacon/flutter_beacon.dart'; import 'package:permission_handler/permission_handler.dart'; class BeaconScanner extends StatefulWidget { const BeaconScanner({super.key}); @override State<BeaconScanner> createState() => _BeaconScannerState(); } class _BeaconScannerState extends State<BeaconScanner> { final regions = <Region>[ // 监听所有Apple iBeacon(可指定具体UUID缩小范围) Region(identifier: 'Apple Beacons', proximityUUID: '00000000-0000-0000-0000-000000000000'), // 监听Feasycom iBeacon(替换为设备实际UUID) Region(identifier: 'Feasycom Beacons', proximityUUID: '00000000-0000-0000-0000-000000000000'), ]; StreamSubscription<RangingResult>? _rangingStream; List<Beacon> detectedBeacons = []; @override void initState() { super.initState(); _initScanner(); } Future<void> _initScanner() async { // 初始化扫描并请求权限 await flutterBeacon.initializeScanning; await [ Permission.locationWhenInUse, Permission.bluetoothScan, Permission.bluetoothConnect, ].request(); // 开始监听iBeacon _rangingStream = flutterBeacon.ranging(regions).listen((result) { setState(() { detectedBeacons = result.beacons; }); for (var beacon in result.beacons) { debugPrint('Beacon: ${beacon.proximityUUID}, Major=${beacon.major}, Minor=${beacon.minor}, RSSI=${beacon.rssi}'); } }); } @override void dispose() { _rangingStream?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('iBeacon Scanner')), body: detectedBeacons.isEmpty ? const Center(child: Text('Scanning for beacons...')) : ListView.builder( itemCount: detectedBeacons.length, itemBuilder: (context, index) { final beacon = detectedBeacons[index]; return ListTile( title: Text('UUID: ${beacon.proximityUUID}'), subtitle: Text('Major: ${beacon.major}, Minor: ${beacon.minor}, RSSI: ${beacon.rssi}'), ); }, ), ); } }
平台配置注意
- Android:在
AndroidManifest.xml添加权限
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
- iOS:在
Info.plist添加权限描述
<key>NSLocationWhenInUseUsageDescription</key> <string>需要位置权限扫描iBeacon</string> <key>NSBluetoothAlwaysUsageDescription</key> <string>需要蓝牙权限扫描iBeacon</string>
内容的提问来源于stack exchange,提问作者nima alihosseini
相关产品推荐
相关产品推荐

