import 'dart:async'; import 'dart:developer' as developer; import 'dart:typed_data'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'bytes_util.dart'; import 'protocol_parser.dart'; class BleManager { static final BleManager instance = BleManager._internal(); BleManager._internal(); BluetoothDevice? _connectedDevice; BluetoothDevice? _connectingDevice; BluetoothCharacteristic? _writeCharacteristic; BluetoothCharacteristic? _readCharacteristic; /// 有状态的协议解析器(支持 BLE 分片) final ProtocolParser _parser = ProtocolParser(); final Map _scanResults = {}; final StreamController> _scanController = StreamController.broadcast(); final StreamController _packetController = StreamController.broadcast(); final StreamController _connectionController = StreamController.broadcast(); final StreamController _connectingController = StreamController.broadcast(); StreamSubscription>? _scanSubscription; StreamSubscription>? _readSubscription; StreamSubscription? _adapterStateSubscription; StreamSubscription? _deviceConnectionSubscription; /// 防止异步竞态:stopScan() 后 in-flight 的 startScan() 不应生效 int _scanGen = 0; /// 协商后的 MTU 值,用于分片写入 int _negotiatedMtu = 23; /// 已收到的数据包存储(跨页面持久化) final List receivedPacketStore = []; static const int _maxStoredPackets = 100; void clearReceivedPackets() { receivedPacketStore.clear(); } bool get isConnected => _connectedDevice != null; BluetoothDevice? get connectedDevice => _connectedDevice; BluetoothDevice? get connectingDevice => _connectingDevice; Stream> get scanResults => _scanController.stream; Stream get packetStream => _packetController.stream; Stream get adapterState => FlutterBluePlus.adapterState; /// 连接状态变化流:连接成功时发出 device,断开时发出 null Stream get connectionStream => _connectionController.stream; /// 连接中状态流:开始连接时发出 device,连接完成/失败时发出 null Stream get connectingStream => _connectingController.stream; Future checkBluetooth() async { final state = await FlutterBluePlus.adapterState.first; return state == BluetoothAdapterState.on; } Future requestPermissions() async { final connStatus = await Permission.bluetoothConnect.status; developer.log( '[BLE] bluetoothConnect status: $connStatus', name: 'BleManager', ); if (connStatus.isDenied) { developer.log('[BLE] requesting bluetoothConnect...', name: 'BleManager'); await Permission.bluetoothConnect.request(); } final scanStatus = await Permission.bluetoothScan.status; developer.log( '[BLE] bluetoothScan status: $scanStatus', name: 'BleManager', ); if (scanStatus.isDenied) { developer.log('[BLE] requesting bluetoothScan...', name: 'BleManager'); await Permission.bluetoothScan.request(); } final locStatus = await Permission.locationWhenInUse.status; developer.log( '[BLE] locationWhenInUse status: $locStatus', name: 'BleManager', ); if (locStatus.isDenied) { developer.log( '[BLE] requesting locationWhenInUse...', name: 'BleManager', ); await Permission.locationWhenInUse.request(); } final allGranted = await Permission.bluetoothConnect.isGranted && await Permission.bluetoothScan.isGranted; developer.log( '[BLE] all permissions granted: $allGranted', name: 'BleManager', ); return allGranted; } Future openBluetooth() async { try { await FlutterBluePlus.turnOn(); } catch (e) { await _goToSystemSettings(); } } Future _goToSystemSettings() async { await openAppSettings(); } Future _openLocationSettings() async { await openAppSettings(); } Future _checkLocationService() async { final locStatus = await Permission.location.serviceStatus; developer.log( '[BLE] location service status: $locStatus', name: 'BleManager', ); return locStatus == ServiceStatus.enabled; } Future startScan({bool continuous = true}) async { final int myGen = ++_scanGen; developer.log( '[BLE] startScan called, gen=$myGen, continuous=$continuous', name: 'BleManager', ); // 先停止任何残留扫描,确保干净启动 try { await FlutterBluePlus.stopScan(); } catch (_) {} _scanResults.clear(); _scanController.add([]); final isOn = await checkBluetooth(); if (myGen != _scanGen) return; developer.log('[BLE] Bluetooth adapter on: $isOn', name: 'BleManager'); if (!isOn) { developer.log( '[BLE] Bluetooth is OFF, aborting scan', name: 'BleManager', ); return; } final granted = await requestPermissions(); if (myGen != _scanGen) return; developer.log('[BLE] Permissions granted: $granted', name: 'BleManager'); if (!granted) { developer.log( '[BLE] Permissions not granted, aborting scan', name: 'BleManager', ); return; } final locEnabled = await _checkLocationService(); if (myGen != _scanGen) return; if (!locEnabled) { developer.log( '[BLE] Location service OFF, directing to settings', name: 'BleManager', ); await _openLocationSettings(); return; } _scanSubscription?.cancel(); _scanSubscription = FlutterBluePlus.scanResults.listen( (results) { for (final result in results) { _scanResults[result.device.remoteId] = result; } developer.log( '[BLE] 📡 扫描到 ${results.length} 个设备: ${results.map((r) => '${r.device.platformName.isNotEmpty ? r.device.platformName : r.device.advName.isNotEmpty ? r.device.advName : r.device.remoteId} (${r.rssi}dBm)').join(', ')}', name: 'BleManager', ); _scanController.add(_scanResults.values.toList()); }, onError: (e) { developer.log('[BLE] scanResults error: $e', name: 'BleManager'); }, ); try { if (continuous) { developer.log('[BLE] Starting continuous scan...', name: 'BleManager'); await FlutterBluePlus.startScan( timeout: const Duration(days: 1), androidUsesFineLocation: true, androidScanMode: AndroidScanMode.lowLatency, androidLegacy: true, ); } else { developer.log('[BLE] Starting 10s scan...', name: 'BleManager'); await FlutterBluePlus.startScan( timeout: const Duration(seconds: 10), androidUsesFineLocation: true, androidScanMode: AndroidScanMode.lowLatency, androidLegacy: true, ); } developer.log('[BLE] startScan completed', name: 'BleManager'); } catch (e, stackTrace) { developer.log( '[BLE] startScan error: $e\n$stackTrace', name: 'BleManager', ); } } Future refreshScan() async { await stopScan(); await Future.delayed(const Duration(milliseconds: 200)); await startScan(continuous: true); } Future stopScan() async { _scanGen++; developer.log('[BLE] stopScan, gen=$_scanGen', name: 'BleManager'); _scanSubscription?.cancel(); await FlutterBluePlus.stopScan(); } /// 连接设备,返回 null 表示成功,返回错误信息字符串表示失败 Future connect(BluetoothDevice device) async { // 标记正在连接,通知所有监听者 _connectingDevice = device; _connectingController.add(device); try { await device .connect(license: License.nonprofit, mtu: 512) .timeout(const Duration(seconds: 15)); _connectedDevice = device; // 连接成功,立即停止扫描(避免扫描射频干扰导致连接断开) await stopScan(); // 清除连接中状态 _connectingDevice = null; _connectingController.add(null); // 监听设备连接状态,任何一方断开都能感知 _deviceConnectionSubscription?.cancel(); _deviceConnectionSubscription = device.connectionState.listen((state) { developer.log( '[BLE] device connectionState: $state', name: 'BleManager', ); if (state == BluetoothConnectionState.disconnected) { _onDeviceDisconnected(); } }); _connectionController.add(device); await _discoverServices(device); // 确认协商后的 MTU try { final mtu = await device.requestMtu(512); _negotiatedMtu = mtu; developer.log('[BLE] MTU: $mtu', name: 'BleManager'); } catch (e) { developer.log('[BLE] requestMtu failed: $e', name: 'BleManager'); } return null; // 成功 } catch (e) { // 连接失败,清除连接中状态 _connectingDevice = null; _connectingController.add(null); // 返回具体的错误原因 if (e is TimeoutException) { return '连接超时(15秒),请确认设备在附近且已开启'; } return '连接失败: $e'; } } void _onDeviceDisconnected() { _connectedDevice = null; _connectingDevice = null; _writeCharacteristic = null; _readCharacteristic = null; _negotiatedMtu = 23; _readSubscription?.cancel(); _deviceConnectionSubscription?.cancel(); _connectionController.add(null); _connectingController.add(null); // 清空协议解析器缓冲区 _parser.clear(); } Future disconnect() async { final device = _connectedDevice; if (device == null) return; developer.log('[BLE] 发起断开连接...', name: 'BleManager'); await device.disconnect(); // 等待连接状态流确认已断开,设置超时防止无限等待 try { await device.connectionState .firstWhere((s) => s == BluetoothConnectionState.disconnected) .timeout(const Duration(seconds: 3)); developer.log('[BLE] 连接已确认断开', name: 'BleManager'); } catch (_) { developer.log('[BLE] 等待断开超时,强制清理', name: 'BleManager'); } _onDeviceDisconnected(); } Future _discoverServices(BluetoothDevice device) async { final services = await device.discoverServices(); for (final service in services) { for (final characteristic in service.characteristics) { if (characteristic.properties.write) { _writeCharacteristic = characteristic; } if (characteristic.properties.notify) { _readCharacteristic = characteristic; await characteristic.setNotifyValue(true); _readSubscription?.cancel(); _readSubscription = characteristic.lastValueStream.listen((value) { _onDataReceived(value); }); } } } } void _onDataReceived(List data) { developer.log( '[BLE] 📩 收: ${data.length}B ${_bytesToHex(data)}', name: 'BleManager', ); _parser.append(data); final packets = _parser.parse(); developer.log( '[BLE] 📦 解析 ${packets.length}包 (buf ${_parser.bufferLength}B)', name: 'BleManager', ); if (packets.isEmpty && data.isNotEmpty) { developer.log( '[BLE] ⚠️ 非标准帧 raw(${data.length}B)', name: 'BleManager', ); final packet = BlePacket( command: 0x00, payload: Uint8List.fromList(data), ); _storePacket(packet); _packetController.add(packet); return; } for (final packet in packets) { developer.log( '[BLE] cmd=0x${packet.command.toRadixString(16).toUpperCase().padLeft(2, '0')} ' 'payload(${packet.payload.length}B): ${_bytesToHex(packet.payload)}', name: 'BleManager', ); _storePacket(packet); _packetController.add(packet); } } void _storePacket(BlePacket packet) { receivedPacketStore.insert(0, packet); if (receivedPacketStore.length > _maxStoredPackets) { receivedPacketStore.removeLast(); } } String _bytesToHex(List bytes) { if (bytes.isEmpty) return ''; return bytes .map((b) => b.toRadixString(16).toUpperCase().padLeft(2, '0')) .join(' '); } Future sendCommand(int command, List payload) async { final char = _writeCharacteristic; if (char == null) { developer.log('[BLE] ❌ 写特征为空,无法发送', name: 'BleManager'); return; } final frame = ProtocolParser.pack(command, payload); final crc = frame.length >= 5 ? 'CRC16=0x${frame[frame.length - 4].toRadixString(16).padLeft(2, '0')}${frame[frame.length - 3].toRadixString(16).padLeft(2, '0')}' : ''; developer.log( '[BLE] 📤 发送: 0x${command.toRadixString(16).toUpperCase().padLeft(2, '0')}, ' '帧(${frame.length}B) $crc\n' ' ${_bytesToHex(frame)}', name: 'BleManager', ); // 分片写入:每次最多写 (MTU - 3) 字节,全部走 withResponse 确保可靠性 final maxWriteLen = _negotiatedMtu - 3; final totalChunks = (frame.length + maxWriteLen - 1) ~/ maxWriteLen; if (totalChunks > 1) { developer.log( '[BLE] 🔀 分${totalChunks}片写入 (MTU=$_negotiatedMtu, 每片≤${maxWriteLen}B)', name: 'BleManager', ); } for (int offset = 0; offset < frame.length; offset += maxWriteLen) { final end = (offset + maxWriteLen <= frame.length) ? offset + maxWriteLen : frame.length; final chunk = frame.sublist(offset, end); if (totalChunks > 1) { final chunkIdx = offset ~/ maxWriteLen + 1; developer.log( '[BLE] 片$chunkIdx/$totalChunks: offset=$offset len=${chunk.length}B ${_bytesToHex(chunk)}', name: 'BleManager', ); } await char.write(chunk, withoutResponse: false); } developer.log('[BLE] ✅ 写入完成', name: 'BleManager'); } Future sendRawBytes(List bytes) async { if (_writeCharacteristic == null) return; await _writeCharacteristic!.write(bytes, withoutResponse: false); } Future sendHeartbeat() async { await sendCommand(0xFF, []); } void dispose() { _scanSubscription?.cancel(); _readSubscription?.cancel(); _adapterStateSubscription?.cancel(); _deviceConnectionSubscription?.cancel(); _scanController.close(); _packetController.close(); _connectionController.close(); _connectingController.close(); } }