Commit 5dc83081 authored by akari's avatar akari

fix: 修复部分蓝牙逻辑

parent 6d1704c6
......@@ -15,6 +15,8 @@ import 'package:laki_icu_app/utils/logger.dart';
import 'package:laki_icu_app/utils/mqtt/index.dart';
import 'package:laki_icu_app/utils/storage/storage_service.dart';
enum _LightTarget { check, illumination, blue, red }
/// 用途:设备控制Bloc
/// 涉及页面:设备控制页面
/// 说明:提供各个子模块的获取和设置方法,方便MCU单元调用
......@@ -228,24 +230,30 @@ class DeviceControlBloc
sn: report.sn.isEmpty ? state.data.sn : report.sn,
cabinTemp: state.data.cabinTemp.copyWith(
currentValue: _formatTemperature(report.mainTemperature),
setValue: _formatTemperatureOrKeep(
report.temperatureSetting,
state.data.cabinTemp.setValue,
),
setValue: state.data.cabinTemp.isOn
? _formatTemperatureOrKeep(
report.temperatureSetting,
state.data.cabinTemp.setValue,
)
: _preservedCabinTempSetValue,
),
cabinHumidity: state.data.cabinHumidity.copyWith(
currentValue: _formatInt(report.humidity),
setValue: _formatIntOrKeep(
report.humiditySetting,
state.data.cabinHumidity.setValue,
),
setValue: state.data.cabinHumidity.isOn
? _formatIntOrKeep(
report.humiditySetting,
state.data.cabinHumidity.setValue,
)
: _preservedCabinHumiditySetValue,
),
oxygenConcentration: state.data.oxygenConcentration.copyWith(
currentValue: _formatInt(report.oxygenConcentration),
setValue: _formatIntOrKeep(
report.oxygenSetting,
state.data.oxygenConcentration.setValue,
),
setValue: state.data.oxygenConcentration.isOn
? _formatIntOrKeep(
report.oxygenSetting,
state.data.oxygenConcentration.setValue,
)
: _preservedOxygenConcentrationSetValue,
),
co2Concentration: state.data.co2Concentration.copyWith(
currentValue: _formatInt(report.co2Measurement),
......@@ -302,8 +310,12 @@ class DeviceControlBloc
final humiLower = humidity != null && humidity < config.humidityMin;
final oxyUpper = oxygen != null && oxygen > config.oxygenMax;
final oxyLower = oxygen != null && oxygen < config.oxygenMin;
final hasAnyAlarm =
tempUpper || tempLower || humiUpper || humiLower || oxyUpper || oxyLower;
final hasAnyAlarm = tempUpper ||
tempLower ||
humiUpper ||
humiLower ||
oxyUpper ||
oxyLower;
// MQTT 上行(仅存在告警时才发送)
final mqttClient = globalMqttClient;
......@@ -439,10 +451,10 @@ class DeviceControlBloc
}
/// [用户操作] 设置舱内温度开关(发送给MCU)—— 持久化
void _onCabinTempSwitchChanged(
Future<void> _onCabinTempSwitchChanged(
DeviceControlCabinTempSwitchChanged event,
Emitter<DeviceControlState> emit,
) {
) async {
final newData = state.data.copyWith(
cabinTemp: state.data.cabinTemp.copyWith(
isOn: event.isOn,
......@@ -453,7 +465,13 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(cabinTempIsOn: event.isOn),
clearError: true,
));
// TODO: 发送设置到MCU
await _writeControlValueToBluetooth(
identifier: 0x02,
setValue: event.isOn ? _preservedCabinTempSetValue : '10',
errorPrefix: '舱内温度${event.isOn ? '开启' : '关闭'}',
emit: emit,
scale: event.isOn ? 10 : 1,
);
}
// ==================== 舱内湿度控制事件处理 ====================
......@@ -520,10 +538,10 @@ class DeviceControlBloc
}
/// [用户操作] 设置舱内湿度开关(发送给MCU)—— 持久化
void _onCabinHumiditySwitchChanged(
Future<void> _onCabinHumiditySwitchChanged(
DeviceControlCabinHumiditySwitchChanged event,
Emitter<DeviceControlState> emit,
) {
) async {
final newData = state.data.copyWith(
cabinHumidity: state.data.cabinHumidity.copyWith(
isOn: event.isOn,
......@@ -534,7 +552,12 @@ class DeviceControlBloc
userSettings: state.userSettings.copyWith(cabinHumidityIsOn: event.isOn),
clearError: true,
));
// TODO: 发送设置到MCU
await _writeControlValueToBluetooth(
identifier: 0x04,
setValue: event.isOn ? _preservedCabinHumiditySetValue : '100',
errorPrefix: '舱内湿度${event.isOn ? '开启' : '关闭'}',
emit: emit,
);
}
// ==================== 氧气浓度控制事件处理 ====================
......@@ -567,7 +590,7 @@ class DeviceControlBloc
) async {
final currentValue =
int.tryParse(state.data.oxygenConcentration.setValue) ?? 0;
final nextValue = (currentValue + event.step).clamp(0, 100);
final nextValue = (currentValue + event.step).clamp(0, _oxygenSetValueMax);
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG oxygen_concentration_adjust '
'current=$currentValue step=${event.step} next=$nextValue',
......@@ -579,45 +602,55 @@ class DeviceControlBloc
String setValue,
Emitter<DeviceControlState> emit,
) async {
final safeSetValue = _clampIntString(setValue, 0, _oxygenSetValueMax);
final newData = state.data.copyWith(
oxygenConcentration: state.data.oxygenConcentration.copyWith(
setValue: setValue,
setValue: safeSetValue,
),
);
emit(state.copyWith(
data: newData,
userSettings:
state.userSettings.copyWith(oxygenConcentrationSetValue: setValue),
userSettings: state.userSettings
.copyWith(oxygenConcentrationSetValue: safeSetValue),
clearError: true,
));
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG oxygen_concentration_set_value value=$setValue',
'🔥🔥🔥 BLE_WRITE_DEBUG oxygen_concentration_set_value value=$safeSetValue',
);
await _writeControlValueToBluetooth(
identifier: 0x05,
setValue: setValue,
setValue: safeSetValue,
errorPrefix: '氧气浓度设定',
emit: emit,
);
}
/// [用户操作] 设置氧气浓度开关(发送给MCU)—— 持久化
void _onOxygenConcentrationSwitchChanged(
Future<void> _onOxygenConcentrationSwitchChanged(
DeviceControlOxygenConcentrationSwitchChanged event,
Emitter<DeviceControlState> emit,
) {
) async {
final safeSetValue = _preservedOxygenConcentrationSetValue;
final newData = state.data.copyWith(
oxygenConcentration: state.data.oxygenConcentration.copyWith(
isOn: event.isOn,
setValue: safeSetValue,
),
);
emit(state.copyWith(
data: newData,
userSettings:
state.userSettings.copyWith(oxygenConcentrationIsOn: event.isOn),
userSettings: state.userSettings.copyWith(
oxygenConcentrationIsOn: event.isOn,
oxygenConcentrationSetValue: safeSetValue,
),
clearError: true,
));
// TODO: 发送设置到MCU
await _writeControlValueToBluetooth(
identifier: 0x05,
setValue: event.isOn ? _preservedOxygenConcentrationSetValue : '20',
errorPrefix: '氧气浓度${event.isOn ? '开启' : '关闭'}',
emit: emit,
);
}
// ==================== 二氧化碳浓度控制事件处理 ====================
......@@ -650,7 +683,7 @@ class DeviceControlBloc
) async {
final currentValue =
int.tryParse(state.data.co2Concentration.alarmThreshold) ?? 0;
final nextValue = (currentValue + event.step).clamp(0, 9999);
final nextValue = (currentValue + event.step).clamp(4000, 5000);
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG co2_alarm_threshold_adjust '
'current=$currentValue step=${event.step} next=$nextValue',
......@@ -662,24 +695,25 @@ class DeviceControlBloc
String alarmThreshold,
Emitter<DeviceControlState> emit,
) async {
final safeAlarmThreshold = _clampIntString(alarmThreshold, 4000, 5000);
final newData = state.data.copyWith(
co2Concentration: state.data.co2Concentration.copyWith(
alarmThreshold: alarmThreshold,
alarmThreshold: safeAlarmThreshold,
),
);
emit(state.copyWith(
data: newData,
userSettings:
state.userSettings.copyWith(co2AlarmThreshold: alarmThreshold),
state.userSettings.copyWith(co2AlarmThreshold: safeAlarmThreshold),
clearError: true,
));
Log.warn(
'🔥🔥🔥 BLE_WRITE_DEBUG co2_alarm_threshold_set_value '
'value=$alarmThreshold',
'value=$safeAlarmThreshold',
);
await _writeControlValueToBluetooth(
identifier: 0x31,
setValue: alarmThreshold,
setValue: safeAlarmThreshold,
errorPrefix: '二氧化碳浓度设定',
emit: emit,
);
......@@ -740,6 +774,10 @@ class DeviceControlBloc
DeviceControlWindSpeedModeChanged event,
Emitter<DeviceControlState> emit,
) async {
if (event.mode == WindSpeedMode.off && state.data.cabinTemp.isOn) {
emit(state.copyWith(error: '舱内温度开启时风速不能关闭'));
return;
}
final newData = state.data.copyWith(
windSpeed: state.data.windSpeed.copyWith(
currentMode: event.mode,
......@@ -817,14 +855,37 @@ class DeviceControlBloc
DeviceControlOpenOxygenSwitchChanged event,
Emitter<DeviceControlState> emit,
) async {
final oxygenSetValue = event.isOn
? state.data.oxygenConcentration.setValue
: _clampIntString(state.data.oxygenConcentration.setValue, 0, 60);
final newData = state.data.copyWith(
openOxygen: state.data.openOxygen.copyWith(
isOn: event.isOn,
),
oxygenConcentration: state.data.oxygenConcentration.copyWith(
setValue: oxygenSetValue,
),
circulationMode: event.isOn
? state.data.circulationMode.copyWith(
currentMode: CirculationMode.external,
)
: null,
windSpeed: event.isOn
? state.data.windSpeed.copyWith(currentMode: WindSpeedMode.off)
: null,
);
emit(state.copyWith(
data: newData,
userSettings: state.userSettings.copyWith(openOxygenIsOn: event.isOn),
userSettings: state.userSettings.copyWith(
openOxygenIsOn: event.isOn,
oxygenConcentrationSetValue: oxygenSetValue,
circulationModeValue: event.isOn
? CirculationMode.external
: state.userSettings.circulationModeValue,
windSpeedModeValue: event.isOn
? WindSpeedMode.off
: state.userSettings.windSpeedModeValue,
),
clearError: true,
));
await _writeSwitchValueToBluetooth(
......@@ -906,20 +967,9 @@ class DeviceControlBloc
DeviceControlCheckLightSwitchChanged event,
Emitter<DeviceControlState> emit,
) async {
final newData = state.data.copyWith(
lightControl: state.data.lightControl.copyWith(
checkLightOn: event.isOn,
),
);
emit(state.copyWith(
data: newData,
userSettings: state.userSettings.copyWith(checkLightOn: event.isOn),
clearError: true,
));
await _writeSwitchValueToBluetooth(
identifier: 0x0A,
await _setExclusiveLight(
target: _LightTarget.check,
isOn: event.isOn,
errorPrefix: '检查灯',
emit: emit,
);
}
......@@ -929,20 +979,9 @@ class DeviceControlBloc
DeviceControlIlluminationLightSwitchChanged event,
Emitter<DeviceControlState> emit,
) async {
final newData = state.data.copyWith(
lightControl: state.data.lightControl.copyWith(
illuminationOn: event.isOn,
),
);
emit(state.copyWith(
data: newData,
userSettings: state.userSettings.copyWith(illuminationOn: event.isOn),
clearError: true,
));
await _writeSwitchValueToBluetooth(
identifier: 0x09,
await _setExclusiveLight(
target: _LightTarget.illumination,
isOn: event.isOn,
errorPrefix: '照明灯',
emit: emit,
);
}
......@@ -952,20 +991,9 @@ class DeviceControlBloc
DeviceControlBlueLightSwitchChanged event,
Emitter<DeviceControlState> emit,
) async {
final newData = state.data.copyWith(
lightControl: state.data.lightControl.copyWith(
blueLightOn: event.isOn,
),
);
emit(state.copyWith(
data: newData,
userSettings: state.userSettings.copyWith(blueLightOn: event.isOn),
clearError: true,
));
await _writeSwitchValueToBluetooth(
identifier: 0x1E,
await _setExclusiveLight(
target: _LightTarget.blue,
isOn: event.isOn,
errorPrefix: '蓝光',
emit: emit,
);
}
......@@ -975,20 +1003,9 @@ class DeviceControlBloc
DeviceControlRedLightSwitchChanged event,
Emitter<DeviceControlState> emit,
) async {
final newData = state.data.copyWith(
lightControl: state.data.lightControl.copyWith(
redLightOn: event.isOn,
),
);
emit(state.copyWith(
data: newData,
userSettings: state.userSettings.copyWith(redLightOn: event.isOn),
clearError: true,
));
await _writeSwitchValueToBluetooth(
identifier: 0x1F,
await _setExclusiveLight(
target: _LightTarget.red,
isOn: event.isOn,
errorPrefix: '红光',
emit: emit,
);
}
......@@ -1037,6 +1054,199 @@ class DeviceControlBloc
// TODO: 发送重置指令到MCU
}
int get _oxygenSetValueMax => state.data.openOxygen.isOn ? 80 : 60;
String get _preservedCabinTempSetValue {
final savedValue = state.userSettings.cabinTempSetValue;
if (savedValue != null) return savedValue;
final value = state.data.cabinTemp.setValue;
if (!state.data.cabinTemp.isOn && value == '1') {
return '24';
}
return value;
}
String get _preservedCabinHumiditySetValue {
final savedValue = state.userSettings.cabinHumiditySetValue;
if (savedValue != null) return savedValue;
final value = state.data.cabinHumidity.setValue;
if (!state.data.cabinHumidity.isOn && value == '100') {
return '50';
}
return value;
}
String get _preservedOxygenConcentrationSetValue {
final savedValue = state.userSettings.oxygenConcentrationSetValue;
final value = savedValue ?? state.data.oxygenConcentration.setValue;
if (savedValue == null &&
!state.data.oxygenConcentration.isOn &&
value == '20') {
return '60';
}
return _clampIntString(
value,
0,
_oxygenSetValueMax,
);
}
String _clampIntString(String value, int min, int max) {
final parsed = int.tryParse(value);
if (parsed == null) return '$min';
return '${parsed.clamp(min, max)}';
}
Future<void> _setExclusiveLight({
required _LightTarget target,
required bool isOn,
required Emitter<DeviceControlState> emit,
}) async {
final current = state.data.lightControl;
final nextLightControl = isOn
? _lightControlForTurnOn(current, target)
: current.copyWith(
checkLightOn: target == _LightTarget.check ? false : null,
illuminationOn: target == _LightTarget.illumination ? false : null,
blueLightOn: target == _LightTarget.blue ? false : null,
redLightOn: target == _LightTarget.red ? false : null,
);
final newData = state.data.copyWith(lightControl: nextLightControl);
emit(state.copyWith(
data: newData,
userSettings: state.userSettings.copyWith(
checkLightOn: nextLightControl.checkLightOn,
illuminationOn: nextLightControl.illuminationOn,
blueLightOn: nextLightControl.blueLightOn,
redLightOn: nextLightControl.redLightOn,
),
clearError: true,
));
final List<_LightTarget> lightsToClose = [];
if (isOn) {
for (final light in _lightsToCloseBeforeTurnOn(target)) {
if (_isLightOn(current, light)) lightsToClose.add(light);
}
}
for (final light in lightsToClose) {
await _writeLightToBluetooth(light, false, emit);
await Future<void>.delayed(const Duration(milliseconds: 300));
}
await _writeLightToBluetooth(target, isOn, emit);
}
LightControlBO _lightControlForTurnOn(
LightControlBO current,
_LightTarget target,
) {
switch (target) {
case _LightTarget.check:
return current.copyWith(
checkLightOn: true,
blueLightOn: false,
redLightOn: false,
);
case _LightTarget.illumination:
return current.copyWith(
illuminationOn: true,
blueLightOn: false,
redLightOn: false,
);
case _LightTarget.blue:
return const LightControlBO(
checkLightOn: false,
illuminationOn: false,
blueLightOn: true,
redLightOn: false,
);
case _LightTarget.red:
return const LightControlBO(
checkLightOn: false,
illuminationOn: false,
blueLightOn: false,
redLightOn: true,
);
}
}
List<_LightTarget> _lightsToCloseBeforeTurnOn(_LightTarget target) {
switch (target) {
case _LightTarget.check:
case _LightTarget.illumination:
return const [_LightTarget.blue, _LightTarget.red];
case _LightTarget.blue:
return const [
_LightTarget.check,
_LightTarget.illumination,
_LightTarget.red,
];
case _LightTarget.red:
return const [
_LightTarget.check,
_LightTarget.illumination,
_LightTarget.blue,
];
}
}
bool _isLightOn(LightControlBO lightControl, _LightTarget target) {
switch (target) {
case _LightTarget.check:
return lightControl.checkLightOn;
case _LightTarget.illumination:
return lightControl.illuminationOn;
case _LightTarget.blue:
return lightControl.blueLightOn;
case _LightTarget.red:
return lightControl.redLightOn;
}
}
Future<void> _writeLightToBluetooth(
_LightTarget target,
bool isOn,
Emitter<DeviceControlState> emit,
) {
return _writeSwitchValueToBluetooth(
identifier: _lightIdentifier(target),
isOn: isOn,
errorPrefix: _lightName(target),
emit: emit,
);
}
int _lightIdentifier(_LightTarget target) {
switch (target) {
case _LightTarget.check:
return 0x0A;
case _LightTarget.illumination:
return 0x09;
case _LightTarget.blue:
return 0x1E;
case _LightTarget.red:
return 0x1F;
}
}
String _lightName(_LightTarget target) {
switch (target) {
case _LightTarget.check:
return '检查灯';
case _LightTarget.illumination:
return '照明灯';
case _LightTarget.blue:
return '蓝光';
case _LightTarget.red:
return '红光';
}
}
Future<void> _writeControlValueToBluetooth({
required int identifier,
required String setValue,
......@@ -1098,6 +1308,8 @@ class DeviceControlBloc
int _windSpeedProtocolValue(WindSpeedMode mode) {
switch (mode) {
case WindSpeedMode.off:
return 3;
case WindSpeedMode.comfort:
return 0;
case WindSpeedMode.strong:
......
......@@ -21,7 +21,7 @@ const DeviceControlBO _defaultDeviceControlBO = DeviceControlBO(
),
oxygenConcentration: OxygenConcentrationBO(
currentValue: '--',
setValue: '90',
setValue: '60',
unit: '%',
isOn: true,
),
......@@ -41,10 +41,10 @@ const DeviceControlBO _defaultDeviceControlBO = DeviceControlBO(
currentMode: WindSpeedMode.sleep,
),
nebulizationTime: NebulizationTimeBO(
setMinutes: '00',
setMinutes: '30',
),
disinfectionTime: DisinfectionTimeBO(
setMinutes: '14',
setMinutes: '30',
),
openOxygen: OpenOxygenControlBO(
isOn: false,
......
......@@ -121,7 +121,8 @@ class DeviceControlBO extends Equatable {
environmentTemp: json['environmentTemp'] as String? ?? '25.0℃',
totalOxygenTime: json['totalOxygenTime'] as String? ?? '0h',
cabinTemp: json['cabinTemp'] != null
? TemperatureControlBO.fromJson(json['cabinTemp'] as Map<String, dynamic>)
? TemperatureControlBO.fromJson(
json['cabinTemp'] as Map<String, dynamic>)
: const TemperatureControlBO(
currentValue: '24',
setValue: '24',
......@@ -129,7 +130,8 @@ class DeviceControlBO extends Equatable {
isOn: false,
),
cabinHumidity: json['cabinHumidity'] != null
? HumidityControlBO.fromJson(json['cabinHumidity'] as Map<String, dynamic>)
? HumidityControlBO.fromJson(
json['cabinHumidity'] as Map<String, dynamic>)
: const HumidityControlBO(
currentValue: '24',
setValue: '50',
......@@ -137,22 +139,25 @@ class DeviceControlBO extends Equatable {
isOn: false,
),
oxygenConcentration: json['oxygenConcentration'] != null
? OxygenConcentrationBO.fromJson(json['oxygenConcentration'] as Map<String, dynamic>)
? OxygenConcentrationBO.fromJson(
json['oxygenConcentration'] as Map<String, dynamic>)
: const OxygenConcentrationBO(
currentValue: '24',
setValue: '90',
setValue: '60',
unit: '%',
isOn: true,
),
co2Concentration: json['co2Concentration'] != null
? CO2ConcentrationBO.fromJson(json['co2Concentration'] as Map<String, dynamic>)
? CO2ConcentrationBO.fromJson(
json['co2Concentration'] as Map<String, dynamic>)
: const CO2ConcentrationBO(
currentValue: '2400',
alarmThreshold: '4000',
unit: 'PPM',
),
careLevel: json['careLevel'] != null
? CareLevelControlBO.fromJson(json['careLevel'] as Map<String, dynamic>)
? CareLevelControlBO.fromJson(
json['careLevel'] as Map<String, dynamic>)
: const CareLevelControlBO(
currentLevel: CareLevel.special,
),
......@@ -163,22 +168,26 @@ class DeviceControlBO extends Equatable {
description: 'CO₂超设定值时自动启动外循环净化,当降至2000ppm后,本功能自动关闭',
),
windSpeed: json['windSpeed'] != null
? WindSpeedControlBO.fromJson(json['windSpeed'] as Map<String, dynamic>)
? WindSpeedControlBO.fromJson(
json['windSpeed'] as Map<String, dynamic>)
: const WindSpeedControlBO(
currentMode: WindSpeedMode.sleep,
),
nebulizationTime: json['nebulizationTime'] != null
? NebulizationTimeBO.fromJson(json['nebulizationTime'] as Map<String, dynamic>)
? NebulizationTimeBO.fromJson(
json['nebulizationTime'] as Map<String, dynamic>)
: const NebulizationTimeBO(
setMinutes: '00',
setMinutes: '30',
),
disinfectionTime: json['disinfectionTime'] != null
? DisinfectionTimeBO.fromJson(json['disinfectionTime'] as Map<String, dynamic>)
? DisinfectionTimeBO.fromJson(
json['disinfectionTime'] as Map<String, dynamic>)
: const DisinfectionTimeBO(
setMinutes: '14',
setMinutes: '30',
),
openOxygen: json['openOxygen'] != null
? OpenOxygenControlBO.fromJson(json['openOxygen'] as Map<String, dynamic>)
? OpenOxygenControlBO.fromJson(
json['openOxygen'] as Map<String, dynamic>)
: const OpenOxygenControlBO(
isOn: false,
description: '开启后风扇打开,制冷、加热和循环等设备暂停。\n*保持设备周围空气相对禁止,严禁烟火',
......@@ -191,12 +200,14 @@ class DeviceControlBO extends Equatable {
seconds: '00',
),
circulationMode: json['circulationMode'] != null
? CirculationModeBO.fromJson(json['circulationMode'] as Map<String, dynamic>)
? CirculationModeBO.fromJson(
json['circulationMode'] as Map<String, dynamic>)
: const CirculationModeBO(
currentMode: CirculationMode.external,
),
lightControl: json['lightControl'] != null
? LightControlBO.fromJson(json['lightControl'] as Map<String, dynamic>)
? LightControlBO.fromJson(
json['lightControl'] as Map<String, dynamic>)
: const LightControlBO(
checkLightOn: false,
illuminationOn: false,
......@@ -365,7 +376,7 @@ class OxygenConcentrationBO extends Equatable {
factory OxygenConcentrationBO.fromJson(Map<String, dynamic> json) {
return OxygenConcentrationBO(
currentValue: json['currentValue'] as String? ?? '24',
setValue: json['setValue'] as String? ?? '90',
setValue: json['setValue'] as String? ?? '60',
unit: json['unit'] as String? ?? '%',
isOn: json['isOn'] as bool? ?? true,
);
......@@ -495,7 +506,7 @@ class FreshAirControlBO extends Equatable {
}
/// 风速模式枚举
enum WindSpeedMode { sleep, comfort, strong }
enum WindSpeedMode { off, sleep, comfort, strong }
/// 用途:风速调节控制数据
/// 涉及页面:设备控制页面
......@@ -548,7 +559,7 @@ class NebulizationTimeBO extends Equatable {
factory NebulizationTimeBO.fromJson(Map<String, dynamic> json) {
return NebulizationTimeBO(
setMinutes: json['setMinutes'] as String? ?? '00',
setMinutes: json['setMinutes'] as String? ?? '30',
);
}
......@@ -576,7 +587,7 @@ class DisinfectionTimeBO extends Equatable {
factory DisinfectionTimeBO.fromJson(Map<String, dynamic> json) {
return DisinfectionTimeBO(
setMinutes: json['setMinutes'] as String? ?? '14',
setMinutes: json['setMinutes'] as String? ?? '30',
);
}
......@@ -741,7 +752,8 @@ class LightControlBO extends Equatable {
}
@override
List<Object?> get props => [checkLightOn, illuminationOn, blueLightOn, redLightOn];
List<Object?> get props =>
[checkLightOn, illuminationOn, blueLightOn, redLightOn];
}
/// 用途:设备控制用户设置数据(仅包含用户可配置的字段,不包含MCU获取的值)
......@@ -851,16 +863,21 @@ class DeviceControlUserSettings extends Equatable {
sn: sn ?? this.sn,
cabinTempSetValue: cabinTempSetValue ?? this.cabinTempSetValue,
cabinTempIsOn: cabinTempIsOn ?? this.cabinTempIsOn,
cabinHumiditySetValue: cabinHumiditySetValue ?? this.cabinHumiditySetValue,
cabinHumiditySetValue:
cabinHumiditySetValue ?? this.cabinHumiditySetValue,
cabinHumidityIsOn: cabinHumidityIsOn ?? this.cabinHumidityIsOn,
oxygenConcentrationSetValue: oxygenConcentrationSetValue ?? this.oxygenConcentrationSetValue,
oxygenConcentrationIsOn: oxygenConcentrationIsOn ?? this.oxygenConcentrationIsOn,
oxygenConcentrationSetValue:
oxygenConcentrationSetValue ?? this.oxygenConcentrationSetValue,
oxygenConcentrationIsOn:
oxygenConcentrationIsOn ?? this.oxygenConcentrationIsOn,
co2AlarmThreshold: co2AlarmThreshold ?? this.co2AlarmThreshold,
careLevelValue: careLevelValue ?? this.careLevelValue,
freshAirModeValue: freshAirModeValue ?? this.freshAirModeValue,
windSpeedModeValue: windSpeedModeValue ?? this.windSpeedModeValue,
nebulizationTimeMinutes: nebulizationTimeMinutes ?? this.nebulizationTimeMinutes,
disinfectionTimeMinutes: disinfectionTimeMinutes ?? this.disinfectionTimeMinutes,
nebulizationTimeMinutes:
nebulizationTimeMinutes ?? this.nebulizationTimeMinutes,
disinfectionTimeMinutes:
disinfectionTimeMinutes ?? this.disinfectionTimeMinutes,
openOxygenIsOn: openOxygenIsOn ?? this.openOxygenIsOn,
circulationModeValue: circulationModeValue ?? this.circulationModeValue,
checkLightOn: checkLightOn ?? this.checkLightOn,
......@@ -884,7 +901,8 @@ class DeviceControlUserSettings extends Equatable {
isOn: cabinHumidityIsOn ?? bo.cabinHumidity.isOn,
),
oxygenConcentration: bo.oxygenConcentration.copyWith(
setValue: oxygenConcentrationSetValue ?? bo.oxygenConcentration.setValue,
setValue:
oxygenConcentrationSetValue ?? bo.oxygenConcentration.setValue,
isOn: oxygenConcentrationIsOn ?? bo.oxygenConcentration.isOn,
),
co2Concentration: bo.co2Concentration.copyWith(
......@@ -927,7 +945,8 @@ class DeviceControlUserSettings extends Equatable {
cabinTempIsOn: json['cabinTempIsOn'] as bool?,
cabinHumiditySetValue: json['cabinHumiditySetValue'] as String?,
cabinHumidityIsOn: json['cabinHumidityIsOn'] as bool?,
oxygenConcentrationSetValue: json['oxygenConcentrationSetValue'] as String?,
oxygenConcentrationSetValue:
json['oxygenConcentrationSetValue'] as String?,
oxygenConcentrationIsOn: json['oxygenConcentrationIsOn'] as bool?,
co2AlarmThreshold: json['co2AlarmThreshold'] as String?,
careLevelValue: json['careLevelValue'] != null
......@@ -969,18 +988,26 @@ class DeviceControlUserSettings extends Equatable {
if (sn != null) 'sn': sn,
if (cabinTempSetValue != null) 'cabinTempSetValue': cabinTempSetValue,
if (cabinTempIsOn != null) 'cabinTempIsOn': cabinTempIsOn,
if (cabinHumiditySetValue != null) 'cabinHumiditySetValue': cabinHumiditySetValue,
if (cabinHumiditySetValue != null)
'cabinHumiditySetValue': cabinHumiditySetValue,
if (cabinHumidityIsOn != null) 'cabinHumidityIsOn': cabinHumidityIsOn,
if (oxygenConcentrationSetValue != null) 'oxygenConcentrationSetValue': oxygenConcentrationSetValue,
if (oxygenConcentrationIsOn != null) 'oxygenConcentrationIsOn': oxygenConcentrationIsOn,
if (oxygenConcentrationSetValue != null)
'oxygenConcentrationSetValue': oxygenConcentrationSetValue,
if (oxygenConcentrationIsOn != null)
'oxygenConcentrationIsOn': oxygenConcentrationIsOn,
if (co2AlarmThreshold != null) 'co2AlarmThreshold': co2AlarmThreshold,
if (careLevelValue != null) 'careLevelValue': careLevelValue!.name,
if (freshAirModeValue != null) 'freshAirModeValue': freshAirModeValue!.name,
if (windSpeedModeValue != null) 'windSpeedModeValue': windSpeedModeValue!.name,
if (nebulizationTimeMinutes != null) 'nebulizationTimeMinutes': nebulizationTimeMinutes,
if (disinfectionTimeMinutes != null) 'disinfectionTimeMinutes': disinfectionTimeMinutes,
if (freshAirModeValue != null)
'freshAirModeValue': freshAirModeValue!.name,
if (windSpeedModeValue != null)
'windSpeedModeValue': windSpeedModeValue!.name,
if (nebulizationTimeMinutes != null)
'nebulizationTimeMinutes': nebulizationTimeMinutes,
if (disinfectionTimeMinutes != null)
'disinfectionTimeMinutes': disinfectionTimeMinutes,
if (openOxygenIsOn != null) 'openOxygenIsOn': openOxygenIsOn,
if (circulationModeValue != null) 'circulationModeValue': circulationModeValue!.name,
if (circulationModeValue != null)
'circulationModeValue': circulationModeValue!.name,
if (checkLightOn != null) 'checkLightOn': checkLightOn,
if (illuminationOn != null) 'illuminationOn': illuminationOn,
if (blueLightOn != null) 'blueLightOn': blueLightOn,
......
......@@ -747,7 +747,10 @@ class BleBluetoothManager<T> {
}
static bool _startsWithAny(String value, List<String> prefixes) {
return prefixes.any(value.startsWith);
final normalizedValue = value.trim().toUpperCase();
return prefixes.any(
(prefix) => normalizedValue.startsWith(prefix.trim().toUpperCase()),
);
}
static String _normalizeRemoteIdForSort(String remoteId) {
......
......@@ -10,7 +10,6 @@ import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'cubit/device_control_index_cubit.dart';
import 'cubit/device_control_index_state.dart';
import 'widgets/device_control_care_panel.dart';
import 'widgets/device_control_confirm_dialog.dart';
import 'widgets/device_control_info_panel.dart';
import 'widgets/device_control_metric_card.dart';
import 'widgets/device_control_mode_panel.dart';
......@@ -163,14 +162,9 @@ class _DeviceControlLayout extends StatelessWidget {
icon: Icons.air,
body: '开启后风扇打开、制冷、加热和\n循环等设备暂停。',
warning: '*保持设备周围空气相对禁止,严禁烟火',
onTap: () {
confirmDeviceControlChange(
context,
() => context.read<DeviceControlBloc>().add(
DeviceControlOpenOxygenSwitchChanged(!isOn),
),
);
},
onTap: () => context.read<DeviceControlBloc>().add(
DeviceControlOpenOxygenSwitchChanged(!isOn),
),
);
},
),
......
......@@ -7,7 +7,6 @@ import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/device_control_bo.dart';
import 'device_control_common.dart';
import 'device_control_confirm_dialog.dart';
/// 护理等级面板
class DeviceControlCarePanel extends StatelessWidget {
......@@ -37,14 +36,9 @@ class DeviceControlCarePanel extends StatelessWidget {
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
confirmDeviceControlChange(
context,
() => context.read<DeviceControlBloc>().add(
DeviceControlCareLevelChanged(item.level),
),
);
},
onTap: () => context.read<DeviceControlBloc>().add(
DeviceControlCareLevelChanged(item.level),
),
child: Text(
item.label,
textAlign: TextAlign.center,
......@@ -107,16 +101,11 @@ class _CareLevelBar extends StatelessWidget {
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
confirmDeviceControlChange(
context,
() => context.read<DeviceControlBloc>().add(
DeviceControlCareLevelChanged(
_careLevelItems[index].level,
),
),
);
},
onTap: () => context.read<DeviceControlBloc>().add(
DeviceControlCareLevelChanged(
_careLevelItems[index].level,
),
),
child: Container(
height: 20.h,
decoration: BoxDecoration(
......
......@@ -94,31 +94,31 @@ class DeviceControlPill extends StatelessWidget {
super.key,
required this.text,
this.active = false,
this.onTap,
});
final String text;
final bool active;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return Container(
height: 40.h,
constraints: BoxConstraints(minWidth: 90.w),
padding: EdgeInsets.symmetric(horizontal: 18.w),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: active ? 0.14 : 0.06),
border: Border.all(
color: active ? const Color(0xFF52FFBB) : const Color(0xFF75C1DD),
width: 1.w,
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
height: 40.h,
constraints: BoxConstraints(minWidth: 90.w),
padding: EdgeInsets.symmetric(horizontal: 18.w),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: active ? 0.14 : 0.06),
border: Border.all(
color: active ? const Color(0xFF52FFBB) : const Color(0xFF75C1DD),
width: 1.w,
),
borderRadius: BorderRadius.circular(20.r),
),
borderRadius: BorderRadius.circular(20.r),
),
child: GestureDetector(
onTap: () {
var key = super.key;
debugPrint("test $key");
},
child: Text(
text,
style: TextStyle(
......
......@@ -13,8 +13,9 @@ class DeviceControlConfirmConfig {
Future<void> confirmDeviceControlChange(
BuildContext context,
VoidCallback onConfirmed,
) async {
VoidCallback onConfirmed, {
String message = '确认设置当前修改参数',
}) async {
if (!DeviceControlConfirmConfig.enabled) {
onConfirmed();
return;
......@@ -23,7 +24,7 @@ Future<void> confirmDeviceControlChange(
final confirmed = await showDialog<bool>(
context: context,
barrierColor: Colors.black.withValues(alpha: 0.44),
builder: (_) => const _DeviceControlConfirmDialog(),
builder: (_) => _DeviceControlConfirmDialog(message: message),
);
if (!context.mounted || confirmed != true) return;
......@@ -31,7 +32,9 @@ Future<void> confirmDeviceControlChange(
}
class _DeviceControlConfirmDialog extends StatelessWidget {
const _DeviceControlConfirmDialog();
const _DeviceControlConfirmDialog({required this.message});
final String message;
@override
Widget build(BuildContext context) {
......@@ -83,8 +86,9 @@ class _DeviceControlConfirmDialog extends StatelessWidget {
),
const Spacer(),
Text(
'确认设置当前修改参数',
maxLines: 1,
message,
maxLines: 3,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
......
......@@ -40,7 +40,12 @@ class DeviceControlMetricCard extends StatelessWidget {
previous.data.oxygenConcentration.currentValue !=
current.data.oxygenConcentration.currentValue ||
previous.data.co2Concentration.currentValue !=
current.data.co2Concentration.currentValue,
current.data.co2Concentration.currentValue ||
previous.data.cabinTemp.isOn != current.data.cabinTemp.isOn ||
previous.data.cabinHumidity.isOn != current.data.cabinHumidity.isOn ||
previous.data.oxygenConcentration.isOn !=
current.data.oxygenConcentration.isOn ||
previous.data.openOxygen.isOn != current.data.openOxygen.isOn,
builder: (context, state) {
final currentValue = isTemperature
? state.data.cabinTemp.currentValue
......@@ -72,6 +77,19 @@ class DeviceControlMetricCard extends StatelessWidget {
isOxygen: isOxygen,
isCo2: isCo2,
);
final isSwitchOn = isTemperature
? state.data.cabinTemp.isOn
: isHumidity
? state.data.cabinHumidity.isOn
: isOxygen
? state.data.oxygenConcentration.isOn
: false;
final switchEventBuilder = _switchEventBuilder(
isTemperature: isTemperature,
isHumidity: isHumidity,
isOxygen: isOxygen,
);
final displaySetValue = !isCo2 && !isSwitchOn ? '--' : setValue;
return DeviceControlPanelFrame(
active: isOxygen,
......@@ -86,12 +104,18 @@ class DeviceControlMetricCard extends StatelessWidget {
child: DeviceControlPanelTitle(_title(metric.label)),
),
DeviceControlPill(
text: isOxygen
? 'ON'
: isCo2
? 'PPM'
: 'Off',
active: isOxygen,
text: isCo2 ? 'PPM' : (isSwitchOn ? 'ON' : 'Off'),
active: !isCo2 && isSwitchOn,
onTap: switchEventBuilder == null
? null
: () {
confirmDeviceControlChange(
context,
() => context.read<DeviceControlBloc>().add(
switchEventBuilder(!isSwitchOn),
),
);
},
),
],
),
......@@ -166,7 +190,6 @@ class DeviceControlMetricCard extends StatelessWidget {
? null
: () => _showSetValueKeyboard(
context,
initialValue: setValue,
decimalEnabled: isTemperature,
eventBuilder: setValueEventBuilder,
),
......@@ -174,7 +197,7 @@ class DeviceControlMetricCard extends StatelessWidget {
padding:
EdgeInsets.symmetric(horizontal: 18.w, vertical: 8.h),
child: Text(
setValue,
displaySetValue,
style: TextStyle(
color: isCo2
? const Color(0xFFFFC400)
......@@ -262,15 +285,31 @@ class DeviceControlMetricCard extends StatelessWidget {
return null;
}
static DeviceControlEvent Function(bool isOn)? _switchEventBuilder({
required bool isTemperature,
required bool isHumidity,
required bool isOxygen,
}) {
if (isTemperature) {
return (isOn) => DeviceControlCabinTempSwitchChanged(isOn);
}
if (isHumidity) {
return (isOn) => DeviceControlCabinHumiditySwitchChanged(isOn);
}
if (isOxygen) {
return (isOn) => DeviceControlOxygenConcentrationSwitchChanged(isOn);
}
return null;
}
static Future<void> _showSetValueKeyboard(
BuildContext context, {
required String initialValue,
required bool decimalEnabled,
required DeviceControlEvent Function(String value) eventBuilder,
}) async {
final result = await NumericKeyboard.show(
context,
initialValue: initialValue == '--' ? '' : initialValue,
initialValue: '',
decimalEnabled: decimalEnabled,
);
if (!context.mounted || result == null) return;
......
......@@ -7,7 +7,6 @@ import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/device_control_bo.dart';
import 'device_control_common.dart';
import 'device_control_confirm_dialog.dart';
/// 模式切换面板(外循环/检查灯/照明灯/内循环/蓝光/红光)
class DeviceControlModePanel extends StatelessWidget {
......@@ -95,14 +94,9 @@ class DeviceControlModePanel extends StatelessWidget {
label: item.label,
icon: item.icon,
active: item.active,
onTap: () {
confirmDeviceControlChange(
context,
() => context.read<DeviceControlBloc>().add(
item.event,
),
);
},
onTap: () => context.read<DeviceControlBloc>().add(
item.event,
),
),
),
],
......
......@@ -7,7 +7,6 @@ import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/utils/numeric_keyboard.dart';
import 'device_control_common.dart';
import 'device_control_confirm_dialog.dart';
/// 设置雾化/消毒时间面板
class DeviceControlTimePanel extends StatelessWidget {
......@@ -82,10 +81,7 @@ class DeviceControlTimePanel extends StatelessWidget {
final value = result.trim();
if (value.isEmpty) return;
await confirmDeviceControlChange(
context,
() => context.read<DeviceControlBloc>().add(eventBuilder(value)),
);
context.read<DeviceControlBloc>().add(eventBuilder(value));
}
}
......
......@@ -7,7 +7,6 @@ import 'package:laki_icu_app/blocs/device_control/device_control_state.dart';
import 'package:laki_icu_app/models/bo/device_control_bo.dart';
import 'device_control_common.dart';
import 'device_control_confirm_dialog.dart';
/// 风速调节面板
class DeviceControlWindPanel extends StatelessWidget {
......@@ -34,16 +33,11 @@ class DeviceControlWindPanel extends StatelessWidget {
label: '睡眠模式',
icon: Icons.nightlight_round,
active: currentMode == WindSpeedMode.sleep,
onTap: () {
confirmDeviceControlChange(
context,
() => context.read<DeviceControlBloc>().add(
const DeviceControlWindSpeedModeChanged(
WindSpeedMode.sleep,
),
),
);
},
onTap: () => context.read<DeviceControlBloc>().add(
const DeviceControlWindSpeedModeChanged(
WindSpeedMode.sleep,
),
),
),
),
SizedBox(height: 18.h),
......@@ -52,16 +46,11 @@ class DeviceControlWindPanel extends StatelessWidget {
label: '舒适模式',
icon: Icons.favorite_border,
active: currentMode == WindSpeedMode.comfort,
onTap: () {
confirmDeviceControlChange(
context,
() => context.read<DeviceControlBloc>().add(
const DeviceControlWindSpeedModeChanged(
WindSpeedMode.comfort,
),
),
);
},
onTap: () => context.read<DeviceControlBloc>().add(
const DeviceControlWindSpeedModeChanged(
WindSpeedMode.comfort,
),
),
),
),
SizedBox(height: 18.h),
......@@ -70,16 +59,11 @@ class DeviceControlWindPanel extends StatelessWidget {
label: '强劲模式',
icon: Icons.air,
active: currentMode == WindSpeedMode.strong,
onTap: () {
confirmDeviceControlChange(
context,
() => context.read<DeviceControlBloc>().add(
const DeviceControlWindSpeedModeChanged(
WindSpeedMode.strong,
),
),
);
},
onTap: () => context.read<DeviceControlBloc>().add(
const DeviceControlWindSpeedModeChanged(
WindSpeedMode.strong,
),
),
),
),
],
......
......@@ -612,7 +612,7 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
try {
await _bluetoothManager.scanForDevices(
namePrefixes: const [],
namePrefixes: const ['CNA'],
timeout: const Duration(seconds: 10),
);
} catch (e) {
......@@ -916,7 +916,8 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
);
// 按告警标题去重:相同标题 → 原地覆盖;不同标题 → 插入头部
final existingIndex = state.cameraAlerts.indexWhere((a) => a.title == title);
final existingIndex =
state.cameraAlerts.indexWhere((a) => a.title == title);
if (existingIndex >= 0) {
// 已存在相同标题 → 原地替换
final updatedAlerts = List<AlertInfoBO>.from(state.cameraAlerts);
......@@ -1361,8 +1362,12 @@ class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
if (client == null ||
!client.isConnected ||
deviceSn == null ||
deviceSn.isEmpty) return;
if (alert.alarmId == null) return;
deviceSn.isEmpty) {
return;
}
if (alert.alarmId == null) {
return;
}
try {
client.publishCameraAlarmResult(
......
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