import 'dart:async'; import 'dart:math'; 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'; BluetoothDevice? _device; StreamSubscription>? _subscription; final _readingsController = StreamController.broadcast(); Stream get readings => _readingsController.stream; bool get isConnected => _device?.isConnected ?? false; /// 扫描血压计设备 Stream scan({Duration timeout = const Duration(seconds: 15)}) async* { await FlutterBluePlus.startScan(timeout: timeout); await for (final list in FlutterBluePlus.scanResults) { for (final r in list) { yield r; } } } /// 判断是否为血压计设备 static bool isBpDevice(ScanResult r) { final name = [ r.advertisementData.localName, 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; await device.connect(autoConnect: false); await device.discoverServices(); final services = await device.discoverServices(); final bpService = services.firstWhere( (s) => s.uuid.str128.toUpperCase() == bpServiceUuid.toUpperCase(), ); final measurementChar = bpService.characteristics.firstWhere( (c) => c.uuid.str128.toUpperCase() == bpMeasurementUuid.toUpperCase(), ); await measurementChar.setNotifyValue(true); _subscription = measurementChar.lastValueStream.listen(_parseReading); await _syncTime(bpService); } /// 断开连接 Future disconnect() async { _subscription?.cancel(); _subscription = null; await _device?.disconnect(); _device = null; } void dispose() { disconnect(); _readingsController.close(); } // ── 解析 BLE 测量数据(Bluetooth SIG BP Service 1.1) ── void _parseReading(List raw) { if (raw.length < 7) return; final flags = raw[0]; final isKpa = (flags & 0x01) != 0; final hasTimestamp = (flags & 0x02) != 0; final hasPulse = (flags & 0x04) != 0; final hasUserId = (flags & 0x08) != 0; final hasStatus = (flags & 0x10) != 0; int systolic = _decodeSFloat(raw[1] | (raw[2] << 8)).round(); int diastolic = _decodeSFloat(raw[3] | (raw[4] << 8)).round(); if (isKpa) { systolic = (systolic * 7.50062).round(); diastolic = (diastolic * 7.50062).round(); } int offset = 7; DateTime timestamp = DateTime.now(); if (hasTimestamp && raw.length >= offset + 7) { int year = raw[offset] | (raw[offset + 1] << 8); int month = raw[offset + 2]; int day = raw[offset + 3]; int hour = raw[offset + 4]; int minute = raw[offset + 5]; int second = raw[offset + 6]; timestamp = DateTime(year, month, day, hour, minute, second); offset += 7; } int? pulse; if (hasPulse && raw.length >= offset + 2) { pulse = _decodeSFloat(raw[offset] | (raw[offset + 1] << 8)).round(); offset += 2; } String? userId; if (hasUserId && raw.length > offset) { userId = raw[offset].toString(); offset += 1; } bool bodyMovement = false, irregularPulse = false; if (hasStatus && raw.length >= offset + 2) { int status = raw[offset] | (raw[offset + 1] << 8); bodyMovement = (status & 0x0001) != 0; irregularPulse = (status & 0x0800) != 0; } _readingsController.add(BpReading( systolic: systolic, diastolic: diastolic, pulse: pulse, timestamp: timestamp, userId: userId, isKpa: isKpa, hasBodyMovement: bodyMovement, hasIrregularPulse: irregularPulse, )); } /// SFLOAT IEEE-11073 16-bit 解码 static double _decodeSFloat(int raw) { int mantissa = raw & 0x0FFF; if (mantissa & 0x0800 != 0) mantissa |= 0xFFFFF000; int exponent = (raw >> 12) & 0x0F; if (exponent & 0x08 != 0) exponent |= 0xFFFFFFF0; return (mantissa * pow(10, exponent)).toDouble(); } /// 同步当前时间到设备 Future _syncTime(BluetoothService service) async { try { final dtChar = service.characteristics.firstWhere( (c) => c.uuid.str128.toUpperCase() == dateTimeUuid.toUpperCase(), ); final now = DateTime.now(); await dtChar.write([ now.year & 0xFF, now.year >> 8, now.month, now.day, now.hour, now.minute, now.second, ]); } catch (e) { print('[BLE]读取数据失败: $e'); } } }