import 'dart:async'; import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import '../models/bp_reading.dart'; /// 欧姆龙蓝牙血压计 BLE 服务 class OmronBleService { static const bpServiceUuid = '00001810-0000-1000-8000-00805F9B34FB'; static const bpMeasurementUuid = '00002A35-0000-1000-8000-00805F9B34FB'; static const bpFeatureUuid = '00002A49-0000-1000-8000-00805F9B34FB'; static const dateTimeUuid = '00002A08-0000-1000-8000-00805F9B34FB'; static const intermediateCuffUuid = '00002A36-0000-1000-8000-00805F9B34FB'; static const racpUuid = '00002A52-0000-1000-8000-00805F9B34FB'; BluetoothDevice? _device; StreamSubscription>? _measurementSub; StreamSubscription? _connStateSub; final _readingsController = StreamController.broadcast(); final _connStateController = StreamController.broadcast(); Stream get readings => _readingsController.stream; Stream get connectionState => _connStateController.stream; bool get isConnected => _device?.isConnected ?? false; /// 直接重连已知设备(通过MAC地址) Future reconnectByMac(String mac) async { debugPrint('[BLE] 直连设备: $mac'); final bonded = await FlutterBluePlus.bondedDevices; BluetoothDevice? target; for (final d in bonded) { if (d.remoteId.toString() == mac) { target = d; break; } } if (target == null) { debugPrint('[BLE] 未在已配对设备中找到 $mac'); return false; } try { await connect(target); return true; } catch (e) { debugPrint('[BLE] 直连失败: $e'); return false; } } static bool isBpDevice(ScanResult r) { final name = [ r.advertisementData.advName, r.device.platformName, ].where((n) => n.isNotEmpty).join(' ').toUpperCase(); return name.contains('OMRON') || name.contains('HEM') || name.contains('BLESMART') || name.contains('J735') || name.contains('BPM') || name.contains('BP'); } /// 连接设备并订阅测量数据 /// 设备连接后会自动发送存储的最后一次测量数据 Future connect(BluetoothDevice device) async { _device = device; debugPrint('[BLE] ═══ 连接: "${device.platformName}" ═══'); _connStateSub = device.connectionState.listen((s) { debugPrint('[BLE] 连接状态: $s'); _connStateController.add(s == BluetoothConnectionState.connected); }); // 1. GATT连接 await device.connect(autoConnect: false); debugPrint('[BLE] ① 已连接, MTU=${device.mtuNow}'); try { await device.requestMtu(512); } catch (_) {} // 2. 发现服务 final services = await device.discoverServices(); debugPrint('[BLE] ② 发现${services.length}个服务'); // 遍历所有服务/特征 BluetoothService? bpSvc; BluetoothCharacteristic? measChar, racpChar; for (final s in services) { debugPrint('[BLE] 服务: ${s.uuid.str128}'); for (final c in s.characteristics) { final p = []; if (c.properties.read) p.add('R'); if (c.properties.write) p.add('W'); if (c.properties.notify) p.add('N'); if (c.properties.indicate) p.add('I'); debugPrint('[BLE] ${c.uuid.str128} [$p]'); for (final d in c.descriptors) { debugPrint('[BLE] desc: ${d.uuid.str128}'); } } if (s.uuid.str128.toUpperCase() == bpServiceUuid.toUpperCase()) bpSvc = s; } if (bpSvc == null) throw Exception('未找到血压服务0x1810'); for (final c in bpSvc.characteristics) { final u = c.uuid.str128.toUpperCase(); if (u == bpMeasurementUuid.toUpperCase()) measChar = c; if (u == racpUuid.toUpperCase()) racpChar = c; } if (measChar == null) throw Exception('未找到0x2A35'); debugPrint('[BLE] ③ 0x2A35: indicate=${measChar.properties.indicate} read=${measChar.properties.read}'); debugPrint('[BLE] RACP: ${racpChar != null ? "有" : "无"}'); // ★ 3. 先订阅数据流(在配置CCCD之前!) debugPrint('[BLE] ④ 订阅lastValueStream...'); _measurementSub = measChar.lastValueStream.listen( (raw) { debugPrint('[BLE] ★★★ 收到数据! ${raw.length}字节 ★★★'); debugPrint('[BLE] HEX: ${raw.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}'); _parseReading(raw); }, onError: (e) => debugPrint('[BLE] 流错误: $e'), cancelOnError: false, ); // 4. 配置CCCD final isIndicate = measChar.properties.indicate; final cccdVal = isIndicate ? [0x02, 0x00] : [0x01, 0x00]; debugPrint('[BLE] ⑤ 配置CCCD → ${isIndicate ? "Indicate" : "Notify"}...'); var ok = false; for (final d in measChar.descriptors) { if (d.uuid.str128 == '00002902-0000-1000-8000-00805F9B34FB') { final before = await d.read(); await d.write(cccdVal); final after = await d.read(); ok = after.length >= 2 && after[0] == cccdVal[0] && after[1] == cccdVal[1]; debugPrint('[BLE] ⑤ CCCD: $before → $after ${ok ? "OK" : "FAIL"}'); break; } } if (!ok) { await measChar.setNotifyValue(true, forceIndications: isIndicate); debugPrint('[BLE] ⑤ 降级setNotifyValue, isNotifying=${measChar.isNotifying}'); } // 5. 主动读取缓存数据(设备存储的最后一次测量) debugPrint('[BLE] ⑥ 主动读取0x2A35...'); if (measChar.properties.read) { try { final data = await measChar.read(); if (data.isNotEmpty) { debugPrint('[BLE] ⑥ 读到${data.length}字节 HEX=${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}'); _parseReading(data); } else { debugPrint('[BLE] ⑥ 空数据'); } } catch (e) { debugPrint('[BLE] ⑥ 读取失败: $e'); } } // 6. RACP请求存储记录 if (racpChar != null) { debugPrint('[BLE] ⑦ RACP请求存储记录...'); try { await racpChar.write([0x01, 0x01]); // ReportStoredRecords, All debugPrint('[BLE] ⑦ RACP已发送'); } catch (e) { debugPrint('[BLE] ⑦ RACP失败: $e'); } } debugPrint('[BLE] ═══ 连接完成, 等待数据 ═══'); } Future disconnect() async { _connStateSub?.cancel(); _connStateSub = null; _measurementSub?.cancel(); _measurementSub = null; await _device?.disconnect(); _device = null; debugPrint('[BLE] 已断开'); } void dispose() { disconnect(); _readingsController.close(); _connStateController.close(); } // ── 解析 BLE 测量数据 ── void _parseReading(List raw) { debugPrint('[BLE] 解析: flags=0x${raw[0].toRadixString(16)}, len=${raw.length}'); if (raw.length < 7) { debugPrint('[BLE] 太短,跳过'); return; } final flags = raw[0]; final isKpa = (flags & 0x01) != 0; final hasTs = (flags & 0x02) != 0; final hasPulse = (flags & 0x04) != 0; final hasUid = (flags & 0x08) != 0; final hasStatus = (flags & 0x10) != 0; int sys = _decodeSFloat(raw[1] | (raw[2] << 8)).round(); int dia = _decodeSFloat(raw[3] | (raw[4] << 8)).round(); if (isKpa) { sys = (sys * 7.50062).round(); dia = (dia * 7.50062).round(); } int off = 7; DateTime ts = DateTime.now(); if (hasTs && raw.length >= off + 7) { ts = DateTime(raw[off] | (raw[off + 1] << 8), raw[off + 2], raw[off + 3], raw[off + 4], raw[off + 5], raw[off + 6]); off += 7; } int? pulse; if (hasPulse && raw.length >= off + 2) { pulse = _decodeSFloat(raw[off] | (raw[off + 1] << 8)).round(); off += 2; } String? uid; if (hasUid && raw.length > off) { uid = raw[off].toString(); off++; } bool bm = false, ir = false; if (hasStatus && raw.length >= off + 2) { int s = raw[off] | (raw[off + 1] << 8); bm = (s & 0x0001) != 0; ir = (s & 0x0800) != 0; } final r = BpReading(systolic: sys, diastolic: dia, pulse: pulse, timestamp: ts, userId: uid, isKpa: isKpa, hasBodyMovement: bm, hasIrregularPulse: ir); debugPrint('[BLE] ★ $sys/$dia mmHg, pulse=$pulse ★'); _readingsController.add(r); } static double _decodeSFloat(int raw) { int m = raw & 0x0FFF; if (m & 0x0800 != 0) m |= 0xFFFFF000; int e = (raw >> 12) & 0x0F; if (e & 0x08 != 0) e |= 0xFFFFFFF0; return (m * pow(10, e)).toDouble(); } }