Commit 266ba06d authored by akari's avatar akari

feat: 控制温湿度

parent dc8a1afb
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:laki_icu_app/models/bo/device_control_bo.dart'; import 'package:laki_icu_app/models/bo/device_control_bo.dart';
import 'package:laki_icu_app/utils/bluetooth/index.dart';
/// 用途:设备控制事件基类 /// 用途:设备控制事件基类
abstract class DeviceControlEvent extends Equatable { abstract class DeviceControlEvent extends Equatable {
...@@ -26,6 +27,16 @@ class DeviceControlDataUpdated extends DeviceControlEvent { ...@@ -26,6 +27,16 @@ class DeviceControlDataUpdated extends DeviceControlEvent {
List<Object?> get props => [data]; List<Object?> get props => [data];
} }
/// MCU上报数据同步到设备控制
class DeviceControlMcuReportFetched extends DeviceControlEvent {
final McuReport report;
const DeviceControlMcuReportFetched(this.report);
@override
List<Object?> get props => [report];
}
// ==================== 设备SN事件 ==================== // ==================== 设备SN事件 ====================
/// 获取设备SN(从MCU获取) /// 获取设备SN(从MCU获取)
...@@ -70,6 +81,16 @@ class DeviceControlCabinTempSetValueChanged extends DeviceControlEvent { ...@@ -70,6 +81,16 @@ class DeviceControlCabinTempSetValueChanged extends DeviceControlEvent {
List<Object?> get props => [setValue]; List<Object?> get props => [setValue];
} }
/// 调整舱内温度设定值(用户点击 -/+,发送给MCU)
class DeviceControlCabinTempSetValueAdjusted extends DeviceControlEvent {
final int step;
const DeviceControlCabinTempSetValueAdjusted(this.step);
@override
List<Object?> get props => [step];
}
/// 设置舱内温度开关 /// 设置舱内温度开关
class DeviceControlCabinTempSwitchChanged extends DeviceControlEvent { class DeviceControlCabinTempSwitchChanged extends DeviceControlEvent {
final bool isOn; final bool isOn;
...@@ -102,6 +123,16 @@ class DeviceControlCabinHumiditySetValueChanged extends DeviceControlEvent { ...@@ -102,6 +123,16 @@ class DeviceControlCabinHumiditySetValueChanged extends DeviceControlEvent {
List<Object?> get props => [setValue]; List<Object?> get props => [setValue];
} }
/// 调整舱内湿度设定值(用户点击 -/+,发送给MCU)
class DeviceControlCabinHumiditySetValueAdjusted extends DeviceControlEvent {
final int step;
const DeviceControlCabinHumiditySetValueAdjusted(this.step);
@override
List<Object?> get props => [step];
}
/// 设置舱内湿度开关 /// 设置舱内湿度开关
class DeviceControlCabinHumiditySwitchChanged extends DeviceControlEvent { class DeviceControlCabinHumiditySwitchChanged extends DeviceControlEvent {
final bool isOn; final bool isOn;
...@@ -115,7 +146,8 @@ class DeviceControlCabinHumiditySwitchChanged extends DeviceControlEvent { ...@@ -115,7 +146,8 @@ class DeviceControlCabinHumiditySwitchChanged extends DeviceControlEvent {
// ==================== 氧气浓度控制事件 ==================== // ==================== 氧气浓度控制事件 ====================
/// 获取氧气浓度当前值(从MCU获取) /// 获取氧气浓度当前值(从MCU获取)
class DeviceControlOxygenConcentrationCurrentValueFetched extends DeviceControlEvent { class DeviceControlOxygenConcentrationCurrentValueFetched
extends DeviceControlEvent {
final String currentValue; final String currentValue;
const DeviceControlOxygenConcentrationCurrentValueFetched(this.currentValue); const DeviceControlOxygenConcentrationCurrentValueFetched(this.currentValue);
...@@ -125,7 +157,8 @@ class DeviceControlOxygenConcentrationCurrentValueFetched extends DeviceControlE ...@@ -125,7 +157,8 @@ class DeviceControlOxygenConcentrationCurrentValueFetched extends DeviceControlE
} }
/// 设置氧气浓度设定值(用户配置,发送给MCU) /// 设置氧气浓度设定值(用户配置,发送给MCU)
class DeviceControlOxygenConcentrationSetValueChanged extends DeviceControlEvent { class DeviceControlOxygenConcentrationSetValueChanged
extends DeviceControlEvent {
final String setValue; final String setValue;
const DeviceControlOxygenConcentrationSetValueChanged(this.setValue); const DeviceControlOxygenConcentrationSetValueChanged(this.setValue);
...@@ -134,6 +167,17 @@ class DeviceControlOxygenConcentrationSetValueChanged extends DeviceControlEvent ...@@ -134,6 +167,17 @@ class DeviceControlOxygenConcentrationSetValueChanged extends DeviceControlEvent
List<Object?> get props => [setValue]; List<Object?> get props => [setValue];
} }
/// 调整氧气浓度设定值(用户点击 -/+,发送给MCU)
class DeviceControlOxygenConcentrationSetValueAdjusted
extends DeviceControlEvent {
final int step;
const DeviceControlOxygenConcentrationSetValueAdjusted(this.step);
@override
List<Object?> get props => [step];
}
/// 设置氧气浓度开关 /// 设置氧气浓度开关
class DeviceControlOxygenConcentrationSwitchChanged extends DeviceControlEvent { class DeviceControlOxygenConcentrationSwitchChanged extends DeviceControlEvent {
final bool isOn; final bool isOn;
...@@ -147,7 +191,8 @@ class DeviceControlOxygenConcentrationSwitchChanged extends DeviceControlEvent { ...@@ -147,7 +191,8 @@ class DeviceControlOxygenConcentrationSwitchChanged extends DeviceControlEvent {
// ==================== 二氧化碳浓度控制事件 ==================== // ==================== 二氧化碳浓度控制事件 ====================
/// 获取二氧化碳浓度当前值(从MCU获取) /// 获取二氧化碳浓度当前值(从MCU获取)
class DeviceControlCO2ConcentrationCurrentValueFetched extends DeviceControlEvent { class DeviceControlCO2ConcentrationCurrentValueFetched
extends DeviceControlEvent {
final String currentValue; final String currentValue;
const DeviceControlCO2ConcentrationCurrentValueFetched(this.currentValue); const DeviceControlCO2ConcentrationCurrentValueFetched(this.currentValue);
...@@ -157,7 +202,8 @@ class DeviceControlCO2ConcentrationCurrentValueFetched extends DeviceControlEven ...@@ -157,7 +202,8 @@ class DeviceControlCO2ConcentrationCurrentValueFetched extends DeviceControlEven
} }
/// 设置二氧化碳浓度告警阈值(用户配置,发送给MCU) /// 设置二氧化碳浓度告警阈值(用户配置,发送给MCU)
class DeviceControlCO2ConcentrationAlarmThresholdChanged extends DeviceControlEvent { class DeviceControlCO2ConcentrationAlarmThresholdChanged
extends DeviceControlEvent {
final String alarmThreshold; final String alarmThreshold;
const DeviceControlCO2ConcentrationAlarmThresholdChanged(this.alarmThreshold); const DeviceControlCO2ConcentrationAlarmThresholdChanged(this.alarmThreshold);
...@@ -166,6 +212,17 @@ class DeviceControlCO2ConcentrationAlarmThresholdChanged extends DeviceControlEv ...@@ -166,6 +212,17 @@ class DeviceControlCO2ConcentrationAlarmThresholdChanged extends DeviceControlEv
List<Object?> get props => [alarmThreshold]; List<Object?> get props => [alarmThreshold];
} }
/// 调整二氧化碳浓度设定值(用户点击 -/+,发送给MCU)
class DeviceControlCO2ConcentrationAlarmThresholdAdjusted
extends DeviceControlEvent {
final int step;
const DeviceControlCO2ConcentrationAlarmThresholdAdjusted(this.step);
@override
List<Object?> get props => [step];
}
// ==================== 护理等级控制事件 ==================== // ==================== 护理等级控制事件 ====================
/// 设置护理等级(用户配置,发送给MCU) /// 设置护理等级(用户配置,发送给MCU)
......
...@@ -8,25 +8,25 @@ const DeviceControlBO _defaultDeviceControlBO = DeviceControlBO( ...@@ -8,25 +8,25 @@ const DeviceControlBO _defaultDeviceControlBO = DeviceControlBO(
environmentTemp: '25.0℃', environmentTemp: '25.0℃',
totalOxygenTime: '0h', totalOxygenTime: '0h',
cabinTemp: TemperatureControlBO( cabinTemp: TemperatureControlBO(
currentValue: '24', currentValue: '--',
setValue: '24', setValue: '24',
unit: '℃', unit: '℃',
isOn: false, isOn: false,
), ),
cabinHumidity: HumidityControlBO( cabinHumidity: HumidityControlBO(
currentValue: '24', currentValue: '--',
setValue: '50', setValue: '50',
unit: '%', unit: '%',
isOn: false, isOn: false,
), ),
oxygenConcentration: OxygenConcentrationBO( oxygenConcentration: OxygenConcentrationBO(
currentValue: '24', currentValue: '--',
setValue: '90', setValue: '90',
unit: '%', unit: '%',
isOn: true, isOn: true,
), ),
co2Concentration: CO2ConcentrationBO( co2Concentration: CO2ConcentrationBO(
currentValue: '2400', currentValue: '--',
alarmThreshold: '4000', alarmThreshold: '4000',
unit: 'PPM', unit: 'PPM',
), ),
...@@ -106,7 +106,15 @@ class DeviceControlState extends Equatable { ...@@ -106,7 +106,15 @@ class DeviceControlState extends Equatable {
/// 从持久化存储中恢复状态(仅恢复用户设置,MCU值使用默认值) /// 从持久化存储中恢复状态(仅恢复用户设置,MCU值使用默认值)
factory DeviceControlState.fromJson(Map<String, dynamic> json) { factory DeviceControlState.fromJson(Map<String, dynamic> json) {
final userSettings = DeviceControlUserSettings.fromJson(json); final userSettings = DeviceControlUserSettings.fromJson(json);
final data = userSettings.applyTo(_defaultDeviceControlBO); final restoredData = userSettings.applyTo(_defaultDeviceControlBO);
final data = restoredData.copyWith(
cabinTemp: restoredData.cabinTemp.copyWith(currentValue: '--'),
cabinHumidity: restoredData.cabinHumidity.copyWith(currentValue: '--'),
oxygenConcentration:
restoredData.oxygenConcentration.copyWith(currentValue: '--'),
co2Concentration:
restoredData.co2Concentration.copyWith(currentValue: '--'),
);
return DeviceControlState( return DeviceControlState(
data: data, data: data,
userSettings: userSettings, userSettings: userSettings,
......
...@@ -23,10 +23,12 @@ class BluetoothReadModel extends Equatable { ...@@ -23,10 +23,12 @@ class BluetoothReadModel extends Equatable {
factory BluetoothReadModel.fromMcuReport(McuReport report) { factory BluetoothReadModel.fromMcuReport(McuReport report) {
return BluetoothReadModel( return BluetoothReadModel(
sn: report.sn.trim().isEmpty ? null : report.sn.trim(), sn: report.sn.trim().isEmpty ? null : report.sn.trim(),
mainTemperature: report.mainTemperature, mainTemperature:
humidity: report.humidity, report.mainTemperature.isNaN ? null : report.mainTemperature,
oxygenConcentration: report.oxygenConcentration, humidity: report.humidity < 0 ? null : report.humidity,
co2Measurement: report.co2Measurement, oxygenConcentration:
report.oxygenConcentration < 0 ? null : report.oxygenConcentration,
co2Measurement: report.co2Measurement < 0 ? null : report.co2Measurement,
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
); );
} }
......
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
import 'package:laki_icu_app/utils/logger.dart';
typedef BluetoothPayloadParser<T> = T? Function(List<int> bytes, String rawHex); typedef BluetoothPayloadParser<T> = T? Function(List<int> bytes, String rawHex);
final BleBluetoothManager appBluetoothManager = BleBluetoothManager();
enum BluetoothConnectionStatus { enum BluetoothConnectionStatus {
disconnected, disconnected,
connecting, connecting,
...@@ -125,6 +129,7 @@ class BleBluetoothManager<T> { ...@@ -125,6 +129,7 @@ class BleBluetoothManager<T> {
await _ble.initialize(); await _ble.initialize();
final status = _ble.status; final status = _ble.status;
debugPrint('🔥🔥🔥 BLE_CONNECT_DEBUG ensure_ready status=$status');
if (status == BleStatus.ready) { if (status == BleStatus.ready) {
return true; return true;
} }
...@@ -135,10 +140,14 @@ class BleBluetoothManager<T> { ...@@ -135,10 +140,14 @@ class BleBluetoothManager<T> {
.timeout(const Duration(seconds: 2), onTimeout: () => status); .timeout(const Duration(seconds: 2), onTimeout: () => status);
if (nextStatus != BleStatus.ready) { if (nextStatus != BleStatus.ready) {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG ensure_ready_failed nextStatus=$nextStatus');
_notifyMessage('蓝牙未就绪或未授权: $nextStatus'); _notifyMessage('蓝牙未就绪或未授权: $nextStatus');
return false; return false;
} }
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG ensure_ready_success nextStatus=$nextStatus');
return true; return true;
} }
...@@ -243,12 +252,18 @@ class BleBluetoothManager<T> { ...@@ -243,12 +252,18 @@ class BleBluetoothManager<T> {
} }
Future<void> connectToDeviceId(String deviceId) async { Future<void> connectToDeviceId(String deviceId) async {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_to_device_id start deviceId=$deviceId');
if (deviceId.isEmpty) { if (deviceId.isEmpty) {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_to_device_id empty_device_id');
_notifyMessage('无效的蓝牙设备地址'); _notifyMessage('无效的蓝牙设备地址');
return; return;
} }
if (!await ensureBluetoothReady()) { if (!await ensureBluetoothReady()) {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_to_device_id bluetooth_not_ready');
return; return;
} }
...@@ -267,6 +282,7 @@ class BleBluetoothManager<T> { ...@@ -267,6 +282,7 @@ class BleBluetoothManager<T> {
_setStatus(BluetoothConnectionStatus.connecting); _setStatus(BluetoothConnectionStatus.connecting);
_notifyMessage('正在连接蓝牙设备: $deviceId'); _notifyMessage('正在连接蓝牙设备: $deviceId');
debugPrint('🔥🔥🔥 BLE_CONNECT_DEBUG connecting deviceId=$deviceId');
final completer = Completer<void>(); final completer = Completer<void>();
_connectionSubscription = _ble _connectionSubscription = _ble
...@@ -282,10 +298,14 @@ class BleBluetoothManager<T> { ...@@ -282,10 +298,14 @@ class BleBluetoothManager<T> {
switch (update.connectionState) { switch (update.connectionState) {
case DeviceConnectionState.connecting: case DeviceConnectionState.connecting:
_setStatus(BluetoothConnectionStatus.connecting); _setStatus(BluetoothConnectionStatus.connecting);
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG state_connecting deviceId=$deviceId');
break; break;
case DeviceConnectionState.connected: case DeviceConnectionState.connected:
_setStatus(BluetoothConnectionStatus.connectedReady); _setStatus(BluetoothConnectionStatus.connectedReady);
_notifyMessage('蓝牙连接成功: $deviceId'); _notifyMessage('蓝牙连接成功: $deviceId');
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG state_connected deviceId=$deviceId');
await _enableNotifications(deviceId); await _enableNotifications(deviceId);
activateDataSource(); activateDataSource();
if (!completer.isCompleted) { if (!completer.isCompleted) {
...@@ -294,10 +314,16 @@ class BleBluetoothManager<T> { ...@@ -294,10 +314,16 @@ class BleBluetoothManager<T> {
break; break;
case DeviceConnectionState.disconnecting: case DeviceConnectionState.disconnecting:
_setStatus(BluetoothConnectionStatus.disconnected); _setStatus(BluetoothConnectionStatus.disconnected);
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG state_disconnecting deviceId=$deviceId');
break; break;
case DeviceConnectionState.disconnected: case DeviceConnectionState.disconnected:
_setStatus(BluetoothConnectionStatus.disconnected); _setStatus(BluetoothConnectionStatus.disconnected);
_notifyMessage('蓝牙连接断开: $deviceId'); _notifyMessage('蓝牙连接断开: $deviceId');
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG state_disconnected deviceId=$deviceId '
'failure=${update.failure}',
);
_clearCharacteristics(); _clearCharacteristics();
if (!completer.isCompleted && update.failure != null) { if (!completer.isCompleted && update.failure != null) {
completer.completeError(update.failure!); completer.completeError(update.failure!);
...@@ -308,6 +334,8 @@ class BleBluetoothManager<T> { ...@@ -308,6 +334,8 @@ class BleBluetoothManager<T> {
onError: (Object error) { onError: (Object error) {
_setStatus(BluetoothConnectionStatus.disconnected); _setStatus(BluetoothConnectionStatus.disconnected);
_notifyMessage('蓝牙连接失败: $error'); _notifyMessage('蓝牙连接失败: $error');
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_error deviceId=$deviceId error=$error');
if (!completer.isCompleted) { if (!completer.isCompleted) {
completer.completeError(error); completer.completeError(error);
} }
...@@ -317,6 +345,8 @@ class BleBluetoothManager<T> { ...@@ -317,6 +345,8 @@ class BleBluetoothManager<T> {
try { try {
await completer.future.timeout(connectionTimeout); await completer.future.timeout(connectionTimeout);
} catch (_) { } catch (_) {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_timeout_or_error deviceId=$deviceId');
await disconnect(); await disconnect();
rethrow; rethrow;
} }
...@@ -327,6 +357,8 @@ class BleBluetoothManager<T> { ...@@ -327,6 +357,8 @@ class BleBluetoothManager<T> {
_status == BluetoothConnectionStatus.active) { _status == BluetoothConnectionStatus.active) {
_setStatus(BluetoothConnectionStatus.active); _setStatus(BluetoothConnectionStatus.active);
_notifyMessage('蓝牙数据源已激活'); _notifyMessage('蓝牙数据源已激活');
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG data_source_active deviceId=$_connectedDeviceId');
} }
} }
...@@ -395,10 +427,16 @@ class BleBluetoothManager<T> { ...@@ -395,10 +427,16 @@ class BleBluetoothManager<T> {
Future<void> _enableNotifications(String deviceId) async { Future<void> _enableNotifications(String deviceId) async {
final characteristic = _notifyCharacteristic; final characteristic = _notifyCharacteristic;
if (characteristic == null) { if (characteristic == null) {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG notify_characteristic_null deviceId=$deviceId');
throw StateError('接收特征值为空'); throw StateError('接收特征值为空');
} }
await _notifySubscription?.cancel(); await _notifySubscription?.cancel();
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG notify_subscribe_start deviceId=$deviceId '
'service=$serviceUuid characteristic=$notifyCharacteristicUuid',
);
_notifySubscription = _ble.subscribeToCharacteristic(characteristic).listen( _notifySubscription = _ble.subscribeToCharacteristic(characteristic).listen(
(bytes) { (bytes) {
if (!isActive || bytes.isEmpty) { if (!isActive || bytes.isEmpty) {
...@@ -406,6 +444,10 @@ class BleBluetoothManager<T> { ...@@ -406,6 +444,10 @@ class BleBluetoothManager<T> {
} }
final rawHex = bytesToHex(bytes); final rawHex = bytesToHex(bytes);
debugPrint(
'🔥🔥🔥 BLE_READ_DEBUG manager_notify deviceId=$deviceId len=${bytes.length} hex=$rawHex');
Log.warn('🔥🔥🔥 BLE_READ_DEBUG manager_notify deviceId=$deviceId '
'len=${bytes.length} hex=$rawHex');
final parsedData = _parser?.call(bytes, rawHex); final parsedData = _parser?.call(bytes, rawHex);
_dataController.add( _dataController.add(
BluetoothDataPacket<T>( BluetoothDataPacket<T>(
...@@ -416,8 +458,14 @@ class BleBluetoothManager<T> { ...@@ -416,8 +458,14 @@ class BleBluetoothManager<T> {
), ),
); );
}, },
onError: (Object error) => _notifyMessage('接收蓝牙数据失败: $error'), onError: (Object error) {
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG notify_error deviceId=$deviceId error=$error');
_notifyMessage('接收蓝牙数据失败: $error');
},
); );
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG notify_subscribe_done deviceId=$deviceId');
} }
void _clearCharacteristics() { void _clearCharacteristics() {
...@@ -427,6 +475,10 @@ class BleBluetoothManager<T> { ...@@ -427,6 +475,10 @@ class BleBluetoothManager<T> {
void _setStatus(BluetoothConnectionStatus status) { void _setStatus(BluetoothConnectionStatus status) {
_status = status; _status = status;
debugPrint(
'🔥🔥🔥 BLE_CONNECT_DEBUG manager_status status=$status '
'deviceId=$_connectedDeviceId',
);
if (!_stateController.isClosed) { if (!_stateController.isClosed) {
_stateController.add(status); _stateController.add(status);
} }
......
import 'dart:convert'; import 'dart:convert';
const int mcuReportBytes = 145; const int mcuReportBytes = 145;
const int mcuReportHeaderBytes = 5;
const int mcuReportTailBytes = 3;
class HexReader { class HexReader {
HexReader(String hexData) { HexReader(String hexData) {
...@@ -37,10 +39,20 @@ class HexReader { ...@@ -37,10 +39,20 @@ class HexReader {
return int.parse(readHex(2), radix: 16); return int.parse(readHex(2), radix: 16);
} }
int readUInt16LE() {
final low = readUInt8();
final high = readUInt8();
return low | (high << 8);
}
double readTenths() { double readTenths() {
return readUInt16() / 10; return readUInt16() / 10;
} }
double readTenthsLE() {
return readUInt16LE() / 10;
}
int readHighByte() { int readHighByte() {
return readUInt16() >> 8; return readUInt16() >> 8;
} }
...@@ -330,85 +342,104 @@ class McuReport { ...@@ -330,85 +342,104 @@ class McuReport {
class McuReportParser { class McuReportParser {
static McuReport parseHex(String hexData) { static McuReport parseHex(String hexData) {
final data = HexReader(hexData); final data = HexReader(hexData);
if (data.remainingBytes < mcuReportBytes) { if (data.remainingBytes < mcuReportHeaderBytes) {
throw RangeError( throw RangeError(
'完整 MCU 上报需要 $mcuReportBytes 字节,当前只有 ${data.remainingBytes} 字节', 'MCU 上报至少需要 $mcuReportHeaderBytes 字节头,当前只有 ${data.remainingBytes} 字节',
); );
} }
final head = data.readHex(2);
final direct = data.readHex(1);
final serviceNum = data.readHex(1);
final length = data.readUInt8();
final payloadBytes = data.remainingBytes >= length + mcuReportTailBytes
? length
: data.remainingBytes;
final payload =
payloadBytes > 0 ? HexReader(data.readHex(payloadBytes)) : null;
int remainingPayloadBytes() => payload?.remainingBytes ?? 0;
int u16() => remainingPayloadBytes() >= 2 ? payload!.readUInt16LE() : -1;
double tenths() =>
remainingPayloadBytes() >= 2 ? payload!.readTenthsLE() : double.nan;
int highByte() =>
remainingPayloadBytes() >= 2 ? payload!.readUInt16LE() >> 8 : -1;
String hex2() => remainingPayloadBytes() >= 2 ? payload!.readHex(2) : '';
String ascii(int bytes) =>
remainingPayloadBytes() >= bytes ? payload!.readAscii(bytes) : '';
return McuReport( return McuReport(
head: data.readHex(2), head: head,
direct: data.readHex(1), direct: direct,
serviceNum: data.readHex(1), serviceNum: serviceNum,
length: data.readUInt8(), length: length,
mainTemperature: data.readTenths(), mainTemperature: tenths(),
versionNumber: data.readTenths(), versionNumber: tenths(),
ambientTemperature: data.readTenths(), ambientTemperature: tenths(),
upperTemperature: data.readTenths(), upperTemperature: tenths(),
humidity: data.readUInt16(), humidity: u16(),
oxygenConcentration: data.readUInt16(), oxygenConcentration: u16(),
temperatureSetting: data.readTenths(), temperatureSetting: tenths(),
lowerTemperature: data.readTenths(), lowerTemperature: tenths(),
humiditySetting: data.readUInt16(), humiditySetting: u16(),
oxygenSetting: data.readUInt16(), oxygenSetting: u16(),
internalExternalCycleState: data.readUInt16(), internalExternalCycleState: u16(),
negativeIonSwitch: data.readUInt16(), negativeIonSwitch: u16(),
uvLamp: data.readUInt16(), uvLamp: u16(),
inspectionLamp: data.readUInt16(), inspectionLamp: u16(),
floodLight: data.readUInt16(), floodLight: u16(),
o2ChangeSlope: data.readUInt16(), o2ChangeSlope: u16(),
rightAtomizerTreatmentTime: data.readUInt16(), rightAtomizerTreatmentTime: u16(),
rightInfraredPhysiotherapyTime: data.readUInt16(), rightInfraredPhysiotherapyTime: u16(),
tempS1: data.readHighByte() / 10, tempS1: highByte() / 10,
tempH1: data.readHighByte(), tempH1: highByte(),
atomizerTreatmentTime: data.readUInt16(), atomizerTreatmentTime: u16(),
infraredPhysiotherapyTime: data.readHighByte(), infraredPhysiotherapyTime: highByte(),
mainTempCorrect: data.readHighByte(), mainTempCorrect: highByte(),
auxiliaryTemperatureCorrect: data.readHighByte(), auxiliaryTemperatureCorrect: highByte(),
o2CorrectStart: data.readHighByte(), o2CorrectStart: highByte(),
o2CorrectDiffer: data.readHighByte(), o2CorrectDiffer: highByte(),
humidityCorrect: data.readHighByte(), humidityCorrect: highByte(),
oxygenCorrect: data.readHighByte(), oxygenCorrect: highByte(),
eTempCorrect: data.readHighByte(), eTempCorrect: highByte(),
xyyModel: data.readHex(2), xyyModel: hex2(),
unknown: data.readHex(2), unknown: hex2(),
wifible: data.readHighByte(), wifible: highByte(),
levelLight: data.readHighByte(), levelLight: highByte(),
openO2: data.readHighByte(), openO2: highByte(),
airConditioner: data.readHighByte(), airConditioner: highByte(),
co2Correct: data.readHighByte(), co2Correct: highByte(),
co2Measurement: data.readUInt16(), co2Measurement: u16(),
co2Setting: data.readUInt16(), co2Setting: u16(),
co2FastClearSwitch: data.readHighByte(), co2FastClearSwitch: highByte(),
openO2SettingMax: data.readHighByte(), openO2SettingMax: highByte(),
noOpenO2SettingMax: data.readHighByte(), noOpenO2SettingMax: highByte(),
windSet: data.readHighByte(), windSet: highByte(),
rangeData1: data.readHex(2), rangeData1: hex2(),
rangeData2: data.readHex(2), rangeData2: hex2(),
rangeData3: data.readHex(2), rangeData3: hex2(),
co2Safety: data.readUInt16(), co2Safety: u16(),
co2SettingMin: data.readUInt16(), co2SettingMin: u16(),
blueLight: data.readHighByte(), blueLight: highByte(),
redLight: data.readHighByte(), redLight: highByte(),
fcSwitch: data.readHighByte(), fcSwitch: highByte(),
o2SupplyTime: data.readUInt16(), o2SupplyTime: u16(),
totalTime: data.readUInt16(), totalTime: u16(),
ratioTemp2: data.readUInt8(), ratioTemp2: remainingPayloadBytes() >= 1 ? payload!.readUInt8() : -1,
ratioTemp1: data.readUInt8(), ratioTemp1: remainingPayloadBytes() >= 1 ? payload!.readUInt8() : -1,
sp: data.readHighByte(), sp: highByte(),
dp: data.readHighByte(), dp: highByte(),
mean: data.readHighByte(), mean: highByte(),
pulseRate: data.readHighByte(), pulseRate: highByte(),
xyyStatus: data.readHighByte(), xyyStatus: highByte(),
xyyError1: data.readHighByte(), xyyError1: highByte(),
sn: data.readAscii(10), sn: ascii(10),
topTempCorrect: data.readHighByte(), topTempCorrect: highByte(),
midTempCorrect: data.readHighByte(), midTempCorrect: highByte(),
o2OpenCorrectStart: data.readHighByte(), o2OpenCorrectStart: highByte(),
o2OpenCorrectStep: data.readHighByte(), o2OpenCorrectStep: highByte(),
o2CorrectMax: data.readHighByte(), o2CorrectMax: highByte(),
o2OpenCorrectMax: data.readHighByte(), o2OpenCorrectMax: highByte(),
parsedBytes: data.byteIndex, parsedBytes: mcuReportHeaderBytes + (payload?.byteIndex ?? 0),
); );
} }
...@@ -427,6 +458,14 @@ class McuReportParser { ...@@ -427,6 +458,14 @@ class McuReportParser {
class McuReportFrameDecoder { class McuReportFrameDecoder {
final List<int> _buffer = []; final List<int> _buffer = [];
int get bufferedBytes => _buffer.length;
String get bufferedHexPreview => _buffer
.take(24)
.map((item) => item.toRadixString(16).padLeft(2, '0'))
.join()
.toUpperCase();
List<McuReport> addBytes(List<int> bytes) { List<McuReport> addBytes(List<int> bytes) {
if (bytes.isEmpty) { if (bytes.isEmpty) {
return const []; return const [];
...@@ -435,10 +474,12 @@ class McuReportFrameDecoder { ...@@ -435,10 +474,12 @@ class McuReportFrameDecoder {
_buffer.addAll(bytes); _buffer.addAll(bytes);
final reports = <McuReport>[]; final reports = <McuReport>[];
while (_buffer.length >= mcuReportBytes) { while (_buffer.length >= mcuReportHeaderBytes) {
final headIndex = _buffer.indexOf(0x68); final headIndex = _findReportHeadIndex();
if (headIndex < 0) { if (headIndex < 0) {
_buffer.clear(); if (_buffer.length > 3) {
_buffer.removeRange(0, _buffer.length - 3);
}
break; break;
} }
...@@ -446,23 +487,43 @@ class McuReportFrameDecoder { ...@@ -446,23 +487,43 @@ class McuReportFrameDecoder {
_buffer.removeRange(0, headIndex); _buffer.removeRange(0, headIndex);
} }
if (_buffer.length < mcuReportBytes) { if (_buffer.length < mcuReportHeaderBytes) {
break; break;
} }
final frame = List<int>.from(_buffer.take(mcuReportBytes)); final payloadLength = _buffer[4];
final frameBytes =
mcuReportHeaderBytes + payloadLength + mcuReportTailBytes;
if (_buffer.length < frameBytes) {
reports.add(McuReportParser.parseBytes(List<int>.from(_buffer)));
break;
}
final frame = List<int>.from(_buffer.take(frameBytes));
if (frame.last != 0x16) { if (frame.last != 0x16) {
_buffer.removeAt(0); _buffer.removeAt(0);
continue; continue;
} }
reports.add(McuReportParser.parseBytes(frame)); reports.add(McuReportParser.parseBytes(frame));
_buffer.removeRange(0, mcuReportBytes); _buffer.removeRange(0, frameBytes);
} }
return reports; return reports;
} }
int _findReportHeadIndex() {
for (var index = 0; index <= _buffer.length - 4; index++) {
if (_buffer[index] == 0x68 &&
_buffer[index + 1] == 0x02 &&
_buffer[index + 2] == 0x01 &&
_buffer[index + 3] == 0x01) {
return index;
}
}
return -1;
}
void clear() { void clear() {
_buffer.clear(); _buffer.clear();
} }
......
import 'dart:async'; import 'dart:async';
import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart'; import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart';
import 'package:laki_icu_app/utils/bluetooth/index.dart';
class EventBus { class EventBus {
final StreamController<dynamic> _controller = StreamController.broadcast(); final StreamController<dynamic> _controller = StreamController.broadcast();
...@@ -41,3 +42,10 @@ class BluetoothReadInfoChangedEvent { ...@@ -41,3 +42,10 @@ class BluetoothReadInfoChangedEvent {
class BluetoothReadInfoClearedEvent { class BluetoothReadInfoClearedEvent {
const BluetoothReadInfoClearedEvent(); const BluetoothReadInfoClearedEvent();
} }
// MCU上报数据更新事件
class McuReportChangedEvent {
final McuReport report;
const McuReportChangedEvent(this.report);
}
...@@ -3,6 +3,7 @@ import 'package:laki_icu_app/enums/video_stream_mode_enum.dart'; ...@@ -3,6 +3,7 @@ import 'package:laki_icu_app/enums/video_stream_mode_enum.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart'; import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/services/webrtc_service.dart'; import 'package:laki_icu_app/services/webrtc_service.dart';
import 'package:laki_icu_app/utils/bluetooth/index.dart'; import 'package:laki_icu_app/utils/bluetooth/index.dart';
import 'package:laki_icu_app/utils/mqtt/index.dart';
enum MonitoringIndexStatus { enum MonitoringIndexStatus {
initial, initial,
...@@ -61,6 +62,15 @@ class MonitoringIndexState extends Equatable { ...@@ -61,6 +62,15 @@ class MonitoringIndexState extends Equatable {
/// 最近解析出的 MCU 上报数据 /// 最近解析出的 MCU 上报数据
final McuReport? latestMcuReport; final McuReport? latestMcuReport;
/// MQTT 连接状态
final LakiMqttConnectionState mqttConnectionState;
/// MQTT 状态提示
final String? mqttMessage;
/// 当前 MQTT 连接使用的设备 SN
final String? mqttDeviceSn;
const MonitoringIndexState({ const MonitoringIndexState({
this.status = MonitoringIndexStatus.initial, this.status = MonitoringIndexStatus.initial,
this.isLoading = false, this.isLoading = false,
...@@ -82,6 +92,9 @@ class MonitoringIndexState extends Equatable { ...@@ -82,6 +92,9 @@ class MonitoringIndexState extends Equatable {
this.bluetoothMessage, this.bluetoothMessage,
this.latestBluetoothRawHex, this.latestBluetoothRawHex,
this.latestMcuReport, this.latestMcuReport,
this.mqttConnectionState = LakiMqttConnectionState.disconnected,
this.mqttMessage,
this.mqttDeviceSn,
}); });
MonitoringIndexState copyWith({ MonitoringIndexState copyWith({
...@@ -105,9 +118,13 @@ class MonitoringIndexState extends Equatable { ...@@ -105,9 +118,13 @@ class MonitoringIndexState extends Equatable {
String? bluetoothMessage, String? bluetoothMessage,
String? latestBluetoothRawHex, String? latestBluetoothRawHex,
McuReport? latestMcuReport, McuReport? latestMcuReport,
LakiMqttConnectionState? mqttConnectionState,
String? mqttMessage,
String? mqttDeviceSn,
bool clearBoundBluetoothDevice = false, bool clearBoundBluetoothDevice = false,
bool clearLatestMcuReport = false, bool clearLatestMcuReport = false,
bool clearPatientInfo = false, bool clearPatientInfo = false,
bool clearMqttDeviceSn = false,
}) { }) {
return MonitoringIndexState( return MonitoringIndexState(
status: status ?? this.status, status: status ?? this.status,
...@@ -138,6 +155,10 @@ class MonitoringIndexState extends Equatable { ...@@ -138,6 +155,10 @@ class MonitoringIndexState extends Equatable {
latestBluetoothRawHex ?? this.latestBluetoothRawHex, latestBluetoothRawHex ?? this.latestBluetoothRawHex,
latestMcuReport: latestMcuReport:
clearLatestMcuReport ? null : latestMcuReport ?? this.latestMcuReport, clearLatestMcuReport ? null : latestMcuReport ?? this.latestMcuReport,
mqttConnectionState: mqttConnectionState ?? this.mqttConnectionState,
mqttMessage: mqttMessage ?? this.mqttMessage,
mqttDeviceSn:
clearMqttDeviceSn ? null : mqttDeviceSn ?? this.mqttDeviceSn,
); );
} }
...@@ -163,5 +184,8 @@ class MonitoringIndexState extends Equatable { ...@@ -163,5 +184,8 @@ class MonitoringIndexState extends Equatable {
bluetoothMessage, bluetoothMessage,
latestBluetoothRawHex, latestBluetoothRawHex,
latestMcuReport, latestMcuReport,
mqttConnectionState,
mqttMessage,
mqttDeviceSn,
]; ];
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment