Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
L
laki_icu_app
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张宏
laki_icu_app
Commits
266ba06d
Commit
266ba06d
authored
Jun 24, 2026
by
akari
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 控制温湿度
parent
dc8a1afb
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
1128 additions
and
249 deletions
+1128
-249
device_control_bloc.dart
lib/blocs/device_control/device_control_bloc.dart
+391
-64
device_control_event.dart
lib/blocs/device_control/device_control_event.dart
+61
-4
device_control_state.dart
lib/blocs/device_control/device_control_state.dart
+13
-5
bluetooth_read_model.dart
lib/models/bo/bluetooth_read_model.dart
+6
-4
bluetooth_manager.dart
lib/utils/bluetooth/bluetooth_manager.dart
+53
-1
mcu_report.dart
lib/utils/bluetooth/mcu_report.dart
+141
-80
event_bus.dart
lib/utils/event_bus.dart
+8
-0
device_control_metric_card.dart
...ng/device_control/widgets/device_control_metric_card.dart
+194
-83
monitoring_index_cubit.dart
lib/views/monitoring/index/cubit/monitoring_index_cubit.dart
+237
-8
monitoring_index_state.dart
lib/views/monitoring/index/cubit/monitoring_index_state.dart
+24
-0
No files found.
lib/blocs/device_control/device_control_bloc.dart
View file @
266ba06d
import
'dart:async'
;
import
'package:hydrated_bloc/hydrated_bloc.dart'
;
import
'device_control_event.dart'
;
import
'device_control_state.dart'
;
import
'package:laki_icu_app/models/bo/device_control_bo.dart'
;
import
'package:laki_icu_app/utils/bluetooth/index.dart'
;
import
'package:laki_icu_app/utils/event_bus.dart'
;
import
'package:laki_icu_app/utils/logger.dart'
;
import
'package:laki_icu_app/utils/storage/storage_service.dart'
;
/// 用途:设备控制Bloc
/// 涉及页面:设备控制页面
/// 说明:提供各个子模块的获取和设置方法,方便MCU单元调用
/// 使用 HydratedBloc 持久化用户设置,MCU获取的值不做持久化
class
DeviceControlBloc
extends
HydratedBloc
<
DeviceControlEvent
,
DeviceControlState
>
{
DeviceControlBloc
()
:
super
(
const
DeviceControlState
())
{
class
DeviceControlBloc
extends
HydratedBloc
<
DeviceControlEvent
,
DeviceControlState
>
{
DeviceControlBloc
({
BleBluetoothManager
?
bluetoothManager
,
StorageService
?
storageService
,
})
:
_bluetoothManager
=
bluetoothManager
??
appBluetoothManager
,
_storageService
=
storageService
??
StorageService
(),
_mcuReportDecoder
=
McuReportFrameDecoder
(),
super
(
const
DeviceControlState
())
{
// ==================== 基础事件 ====================
on
<
DeviceControlLoadRequested
>(
_onLoadRequested
);
on
<
DeviceControlDataUpdated
>(
_onDataUpdated
);
on
<
DeviceControlMcuReportFetched
>(
_onMcuReportFetched
);
// ==================== 设备SN事件 ====================
on
<
DeviceControlSnFetched
>(
_onSnFetched
);
on
<
DeviceControlSnChanged
>(
_onSnChanged
);
// ==================== 舱内温度控制事件 ====================
on
<
DeviceControlCabinTempCurrentValueFetched
>(
_onCabinTempCurrentValueFetched
);
on
<
DeviceControlCabinTempCurrentValueFetched
>(
_onCabinTempCurrentValueFetched
);
on
<
DeviceControlCabinTempSetValueChanged
>(
_onCabinTempSetValueChanged
);
on
<
DeviceControlCabinTempSetValueAdjusted
>(
_onCabinTempSetValueAdjusted
);
on
<
DeviceControlCabinTempSwitchChanged
>(
_onCabinTempSwitchChanged
);
// ==================== 舱内湿度控制事件 ====================
on
<
DeviceControlCabinHumidityCurrentValueFetched
>(
_onCabinHumidityCurrentValueFetched
);
on
<
DeviceControlCabinHumiditySetValueChanged
>(
_onCabinHumiditySetValueChanged
);
on
<
DeviceControlCabinHumidityCurrentValueFetched
>(
_onCabinHumidityCurrentValueFetched
);
on
<
DeviceControlCabinHumiditySetValueChanged
>(
_onCabinHumiditySetValueChanged
);
on
<
DeviceControlCabinHumiditySetValueAdjusted
>(
_onCabinHumiditySetValueAdjusted
);
on
<
DeviceControlCabinHumiditySwitchChanged
>(
_onCabinHumiditySwitchChanged
);
// ==================== 氧气浓度控制事件 ====================
on
<
DeviceControlOxygenConcentrationCurrentValueFetched
>(
_onOxygenConcentrationCurrentValueFetched
);
on
<
DeviceControlOxygenConcentrationSetValueChanged
>(
_onOxygenConcentrationSetValueChanged
);
on
<
DeviceControlOxygenConcentrationSwitchChanged
>(
_onOxygenConcentrationSwitchChanged
);
on
<
DeviceControlOxygenConcentrationCurrentValueFetched
>(
_onOxygenConcentrationCurrentValueFetched
);
on
<
DeviceControlOxygenConcentrationSetValueChanged
>(
_onOxygenConcentrationSetValueChanged
);
on
<
DeviceControlOxygenConcentrationSetValueAdjusted
>(
_onOxygenConcentrationSetValueAdjusted
);
on
<
DeviceControlOxygenConcentrationSwitchChanged
>(
_onOxygenConcentrationSwitchChanged
);
// ==================== 二氧化碳浓度控制事件 ====================
on
<
DeviceControlCO2ConcentrationCurrentValueFetched
>(
_onCO2ConcentrationCurrentValueFetched
);
on
<
DeviceControlCO2ConcentrationAlarmThresholdChanged
>(
_onCO2ConcentrationAlarmThresholdChanged
);
on
<
DeviceControlCO2ConcentrationCurrentValueFetched
>(
_onCO2ConcentrationCurrentValueFetched
);
on
<
DeviceControlCO2ConcentrationAlarmThresholdChanged
>(
_onCO2ConcentrationAlarmThresholdChanged
);
on
<
DeviceControlCO2ConcentrationAlarmThresholdAdjusted
>(
_onCO2ConcentrationAlarmThresholdAdjusted
);
// ==================== 护理等级控制事件 ====================
on
<
DeviceControlCareLevelChanged
>(
_onCareLevelChanged
);
...
...
@@ -64,7 +93,8 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
// ==================== 灯光控制事件 ====================
on
<
DeviceControlCheckLightSwitchChanged
>(
_onCheckLightSwitchChanged
);
on
<
DeviceControlIlluminationLightSwitchChanged
>(
_onIlluminationLightSwitchChanged
);
on
<
DeviceControlIlluminationLightSwitchChanged
>(
_onIlluminationLightSwitchChanged
);
on
<
DeviceControlBlueLightSwitchChanged
>(
_onBlueLightSwitchChanged
);
on
<
DeviceControlRedLightSwitchChanged
>(
_onRedLightSwitchChanged
);
...
...
@@ -75,9 +105,21 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
on
<
DeviceControlResetSettings
>(
_onResetSettings
);
// 加载初始化数据
_mcuReportSub
=
eventBus
.
on
<
McuReportChangedEvent
>().
listen
(
(
event
)
=>
add
(
DeviceControlMcuReportFetched
(
event
.
report
)),
);
_bluetoothDataSub
=
_bluetoothManager
.
dataStream
.
listen
(
_onBluetoothDataPacket
,
);
add
(
const
DeviceControlLoadRequested
());
}
final
BleBluetoothManager
_bluetoothManager
;
final
StorageService
_storageService
;
final
McuReportFrameDecoder
_mcuReportDecoder
;
late
final
StreamSubscription
<
McuReportChangedEvent
>
_mcuReportSub
;
late
final
StreamSubscription
<
BluetoothDataPacket
>
_bluetoothDataSub
;
// ==================== HydratedBloc 持久化 ====================
@override
...
...
@@ -98,9 +140,11 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
)
async
{
emit
(
state
.
copyWith
(
isLoading:
true
,
clearError:
true
));
try
{
// 从存储中加载设备控制数据
// TODO: 实现从存储加载逻辑
emit
(
state
.
copyWith
(
isLoading:
false
,
clearError:
true
));
emit
(
state
.
copyWith
(
data:
_clearMcuReadValues
(
state
.
data
),
isLoading:
false
,
clearError:
true
,
));
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
isLoading:
false
,
error:
e
.
toString
()));
}
...
...
@@ -113,6 +157,77 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
emit
(
state
.
copyWith
(
data:
event
.
data
,
clearError:
true
));
}
DeviceControlBO
_clearMcuReadValues
(
DeviceControlBO
data
)
{
return
data
.
copyWith
(
cabinTemp:
data
.
cabinTemp
.
copyWith
(
currentValue:
'--'
),
cabinHumidity:
data
.
cabinHumidity
.
copyWith
(
currentValue:
'--'
),
oxygenConcentration:
data
.
oxygenConcentration
.
copyWith
(
currentValue:
'--'
),
co2Concentration:
data
.
co2Concentration
.
copyWith
(
currentValue:
'--'
),
);
}
void
_onBluetoothDataPacket
(
BluetoothDataPacket
packet
)
{
try
{
Log
.
warn
(
'🔥🔥🔥 BLE_READ_DEBUG device_control_packet len=
${packet.bytes.length}
'
'hex=
${packet.rawHex}
'
,
);
final
reports
=
_mcuReportDecoder
.
addBytes
(
packet
.
bytes
);
Log
.
warn
(
'🔥🔥🔥 BLE_READ_DEBUG device_control_parse reports=
${reports.length}
'
'buffered=
${_mcuReportDecoder.bufferedBytes}
'
'bufferHex=
${_mcuReportDecoder.bufferedHexPreview}
'
,
);
if
(
reports
.
isEmpty
||
isClosed
)
return
;
final
latestReport
=
reports
.
last
;
eventBus
.
emit
(
McuReportChangedEvent
(
latestReport
));
}
catch
(
e
)
{
Log
.
warn
(
'🔥🔥🔥 BLE_READ_DEBUG device_control_parse_error error=
$e
'
);
// 蓝牙原始数据可能分包或夹杂非MCU帧,这里只忽略控制页同步失败。
}
}
void
_onMcuReportFetched
(
DeviceControlMcuReportFetched
event
,
Emitter
<
DeviceControlState
>
emit
,
)
{
final
report
=
event
.
report
;
Log
.
warn
(
'🔥🔥🔥 BLE_READ_DEBUG device_control_sync '
'frameLength=
${report.length}
'
'temp=
${report.mainTemperature}
'
'tempSet=
${report.temperatureSetting}
'
'humidity=
${report.humidity}
'
'humiditySet=
${report.humiditySetting}
'
'o2=
${report.oxygenConcentration}
'
'o2Set=
${report.oxygenSetting}
'
'co2=
${report.co2Measurement}
'
'co2Set=
${report.co2Setting}
'
'sn=
${report.sn.isEmpty ? '<EMPTY>' : report.sn}
'
,
);
final
newData
=
state
.
data
.
copyWith
(
sn:
report
.
sn
.
isEmpty
?
state
.
data
.
sn
:
report
.
sn
,
cabinTemp:
state
.
data
.
cabinTemp
.
copyWith
(
currentValue:
_formatTemperature
(
report
.
mainTemperature
),
setValue:
_formatTemperature
(
report
.
temperatureSetting
),
),
cabinHumidity:
state
.
data
.
cabinHumidity
.
copyWith
(
currentValue:
_formatInt
(
report
.
humidity
),
setValue:
_formatInt
(
report
.
humiditySetting
),
),
oxygenConcentration:
state
.
data
.
oxygenConcentration
.
copyWith
(
currentValue:
_formatInt
(
report
.
oxygenConcentration
),
setValue:
_formatInt
(
report
.
oxygenSetting
),
),
co2Concentration:
state
.
data
.
co2Concentration
.
copyWith
(
currentValue:
_formatInt
(
report
.
co2Measurement
),
alarmThreshold:
_formatInt
(
report
.
co2Setting
),
),
);
emit
(
state
.
copyWith
(
data:
newData
,
clearError:
true
));
}
// ==================== 设备SN事件处理 ====================
/// [MCU调用] 更新设备SN(从MCU获取的真实数据)
...
...
@@ -158,21 +273,44 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
}
/// [用户操作] 设置舱内温度设定值(发送给MCU)—— 持久化
void
_onCabinTempSetValueChanged
(
Future
<
void
>
_onCabinTempSetValueChanged
(
DeviceControlCabinTempSetValueChanged
event
,
Emitter
<
DeviceControlState
>
emit
,
)
{
)
async
{
await
_setCabinTempSetValue
(
event
.
setValue
,
emit
);
}
/// [用户操作] 点击舱内温度设定值 -/+(发送给MCU)—— 持久化
Future
<
void
>
_onCabinTempSetValueAdjusted
(
DeviceControlCabinTempSetValueAdjusted
event
,
Emitter
<
DeviceControlState
>
emit
,
)
async
{
final
currentValue
=
int
.
tryParse
(
state
.
data
.
cabinTemp
.
setValue
)
??
0
;
final
nextValue
=
(
currentValue
+
event
.
step
).
clamp
(
0
,
99
);
await
_setCabinTempSetValue
(
'
$nextValue
'
,
emit
);
}
Future
<
void
>
_setCabinTempSetValue
(
String
setValue
,
Emitter
<
DeviceControlState
>
emit
,
)
async
{
final
newData
=
state
.
data
.
copyWith
(
cabinTemp:
state
.
data
.
cabinTemp
.
copyWith
(
setValue:
event
.
setValue
,
setValue:
setValue
,
),
);
emit
(
state
.
copyWith
(
data:
newData
,
userSettings:
state
.
userSettings
.
copyWith
(
cabinTempSetValue:
event
.
setValue
),
userSettings:
state
.
userSettings
.
copyWith
(
cabinTempSetValue:
setValue
),
clearError:
true
,
));
// TODO: 发送设置到MCU
await
_writeControlValueToBluetooth
(
identifier:
0x02
,
setValue:
setValue
,
errorPrefix:
'舱内温度设定'
,
emit:
emit
,
scale:
10
,
);
}
/// [用户操作] 设置舱内温度开关(发送给MCU)—— 持久化
...
...
@@ -209,21 +347,44 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
}
/// [用户操作] 设置舱内湿度设定值(发送给MCU)—— 持久化
void
_onCabinHumiditySetValueChanged
(
Future
<
void
>
_onCabinHumiditySetValueChanged
(
DeviceControlCabinHumiditySetValueChanged
event
,
Emitter
<
DeviceControlState
>
emit
,
)
{
)
async
{
await
_setCabinHumiditySetValue
(
event
.
setValue
,
emit
);
}
/// [用户操作] 点击舱内湿度设定值 -/+(发送给MCU)—— 持久化
Future
<
void
>
_onCabinHumiditySetValueAdjusted
(
DeviceControlCabinHumiditySetValueAdjusted
event
,
Emitter
<
DeviceControlState
>
emit
,
)
async
{
final
currentValue
=
int
.
tryParse
(
state
.
data
.
cabinHumidity
.
setValue
)
??
0
;
final
nextValue
=
(
currentValue
+
event
.
step
).
clamp
(
0
,
100
);
await
_setCabinHumiditySetValue
(
'
$nextValue
'
,
emit
);
}
Future
<
void
>
_setCabinHumiditySetValue
(
String
setValue
,
Emitter
<
DeviceControlState
>
emit
,
)
async
{
final
newData
=
state
.
data
.
copyWith
(
cabinHumidity:
state
.
data
.
cabinHumidity
.
copyWith
(
setValue:
event
.
setValue
,
setValue:
setValue
,
),
);
emit
(
state
.
copyWith
(
data:
newData
,
userSettings:
state
.
userSettings
.
copyWith
(
cabinHumiditySetValue:
event
.
setValue
),
userSettings:
state
.
userSettings
.
copyWith
(
cabinHumiditySetValue:
setValue
),
clearError:
true
,
));
// TODO: 发送设置到MCU
await
_writeControlValueToBluetooth
(
identifier:
0x04
,
setValue:
setValue
,
errorPrefix:
'舱内湿度设定'
,
emit:
emit
,
);
}
/// [用户操作] 设置舱内湿度开关(发送给MCU)—— 持久化
...
...
@@ -260,21 +421,45 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
}
/// [用户操作] 设置氧气浓度设定值(发送给MCU)—— 持久化
void
_onOxygenConcentrationSetValueChanged
(
Future
<
void
>
_onOxygenConcentrationSetValueChanged
(
DeviceControlOxygenConcentrationSetValueChanged
event
,
Emitter
<
DeviceControlState
>
emit
,
)
{
)
async
{
await
_setOxygenConcentrationSetValue
(
event
.
setValue
,
emit
);
}
/// [用户操作] 点击氧气浓度设定值 -/+(发送给MCU)—— 持久化
Future
<
void
>
_onOxygenConcentrationSetValueAdjusted
(
DeviceControlOxygenConcentrationSetValueAdjusted
event
,
Emitter
<
DeviceControlState
>
emit
,
)
async
{
final
currentValue
=
int
.
tryParse
(
state
.
data
.
oxygenConcentration
.
setValue
)
??
0
;
final
nextValue
=
(
currentValue
+
event
.
step
).
clamp
(
0
,
100
);
await
_setOxygenConcentrationSetValue
(
'
$nextValue
'
,
emit
);
}
Future
<
void
>
_setOxygenConcentrationSetValue
(
String
setValue
,
Emitter
<
DeviceControlState
>
emit
,
)
async
{
final
newData
=
state
.
data
.
copyWith
(
oxygenConcentration:
state
.
data
.
oxygenConcentration
.
copyWith
(
setValue:
event
.
setValue
,
setValue:
setValue
,
),
);
emit
(
state
.
copyWith
(
data:
newData
,
userSettings:
state
.
userSettings
.
copyWith
(
oxygenConcentrationSetValue:
event
.
setValue
),
userSettings:
state
.
userSettings
.
copyWith
(
oxygenConcentrationSetValue:
setValue
),
clearError:
true
,
));
// TODO: 发送设置到MCU
await
_writeControlValueToBluetooth
(
identifier:
0x05
,
setValue:
setValue
,
errorPrefix:
'氧气浓度设定'
,
emit:
emit
,
);
}
/// [用户操作] 设置氧气浓度开关(发送给MCU)—— 持久化
...
...
@@ -289,7 +474,8 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
);
emit
(
state
.
copyWith
(
data:
newData
,
userSettings:
state
.
userSettings
.
copyWith
(
oxygenConcentrationIsOn:
event
.
isOn
),
userSettings:
state
.
userSettings
.
copyWith
(
oxygenConcentrationIsOn:
event
.
isOn
),
clearError:
true
,
));
// TODO: 发送设置到MCU
...
...
@@ -311,21 +497,45 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
}
/// [用户操作] 设置二氧化碳浓度告警阈值(发送给MCU)—— 持久化
void
_onCO2ConcentrationAlarmThresholdChanged
(
Future
<
void
>
_onCO2ConcentrationAlarmThresholdChanged
(
DeviceControlCO2ConcentrationAlarmThresholdChanged
event
,
Emitter
<
DeviceControlState
>
emit
,
)
{
)
async
{
await
_setCO2ConcentrationAlarmThreshold
(
event
.
alarmThreshold
,
emit
);
}
/// [用户操作] 点击二氧化碳浓度设定值 -/+(发送给MCU)—— 持久化
Future
<
void
>
_onCO2ConcentrationAlarmThresholdAdjusted
(
DeviceControlCO2ConcentrationAlarmThresholdAdjusted
event
,
Emitter
<
DeviceControlState
>
emit
,
)
async
{
final
currentValue
=
int
.
tryParse
(
state
.
data
.
co2Concentration
.
alarmThreshold
)
??
0
;
final
nextValue
=
(
currentValue
+
event
.
step
).
clamp
(
0
,
9999
);
await
_setCO2ConcentrationAlarmThreshold
(
'
$nextValue
'
,
emit
);
}
Future
<
void
>
_setCO2ConcentrationAlarmThreshold
(
String
alarmThreshold
,
Emitter
<
DeviceControlState
>
emit
,
)
async
{
final
newData
=
state
.
data
.
copyWith
(
co2Concentration:
state
.
data
.
co2Concentration
.
copyWith
(
alarmThreshold:
event
.
alarmThreshold
,
alarmThreshold:
alarmThreshold
,
),
);
emit
(
state
.
copyWith
(
data:
newData
,
userSettings:
state
.
userSettings
.
copyWith
(
co2AlarmThreshold:
event
.
alarmThreshold
),
userSettings:
state
.
userSettings
.
copyWith
(
co2AlarmThreshold:
alarmThreshold
),
clearError:
true
,
));
// TODO: 发送设置到MCU
await
_writeControlValueToBluetooth
(
identifier:
0x31
,
setValue:
alarmThreshold
,
errorPrefix:
'二氧化碳浓度设定'
,
emit:
emit
,
);
}
// ==================== 护理等级控制事件处理 ====================
...
...
@@ -402,7 +612,8 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
);
emit
(
state
.
copyWith
(
data:
newData
,
userSettings:
state
.
userSettings
.
copyWith
(
nebulizationTimeMinutes:
event
.
minutes
),
userSettings:
state
.
userSettings
.
copyWith
(
nebulizationTimeMinutes:
event
.
minutes
),
clearError:
true
,
));
// TODO: 发送设置到MCU
...
...
@@ -422,7 +633,8 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
);
emit
(
state
.
copyWith
(
data:
newData
,
userSettings:
state
.
userSettings
.
copyWith
(
disinfectionTimeMinutes:
event
.
minutes
),
userSettings:
state
.
userSettings
.
copyWith
(
disinfectionTimeMinutes:
event
.
minutes
),
clearError:
true
,
));
// TODO: 发送设置到MCU
...
...
@@ -495,7 +707,8 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
);
emit
(
state
.
copyWith
(
data:
newData
,
userSettings:
state
.
userSettings
.
copyWith
(
circulationModeValue:
event
.
mode
),
userSettings:
state
.
userSettings
.
copyWith
(
circulationModeValue:
event
.
mode
),
clearError:
true
,
));
// TODO: 发送设置到MCU
...
...
@@ -619,6 +832,78 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
// TODO: 发送重置指令到MCU
}
Future
<
void
>
_writeControlValueToBluetooth
({
required
int
identifier
,
required
String
setValue
,
required
String
errorPrefix
,
required
Emitter
<
DeviceControlState
>
emit
,
int
scale
=
1
,
})
async
{
final
command
=
_buildControlValueCommand
(
identifier:
identifier
,
setValue:
setValue
,
scale:
scale
,
);
if
(
command
==
null
)
{
emit
(
state
.
copyWith
(
error:
'
$errorPrefix
值无效:
$setValue
'
));
return
;
}
try
{
if
(!
_bluetoothManager
.
isConnected
)
{
final
boundDevice
=
await
_storageService
.
getBoundBluetoothDevice
();
final
deviceId
=
boundDevice
[
'deviceId'
];
if
(
deviceId
==
null
||
deviceId
.
isEmpty
)
{
emit
(
state
.
copyWith
(
error:
'未绑定蓝牙设备,无法发送
$errorPrefix
'
));
return
;
}
await
_bluetoothManager
.
connectToDeviceId
(
deviceId
);
}
await
_bluetoothManager
.
writeData
(
command
);
}
catch
(
e
)
{
emit
(
state
.
copyWith
(
error:
'
$errorPrefix
发送失败:
$e
'
));
}
}
List
<
int
>?
_buildControlValueCommand
({
required
int
identifier
,
required
String
setValue
,
int
scale
=
1
,
})
{
final
value
=
double
.
tryParse
(
setValue
);
if
(
value
==
null
)
return
null
;
final
payloadValue
=
(
value
*
scale
).
round
();
return
[
0x68
,
0x02
,
0x02
,
0x01
,
identifier
,
payloadValue
&
0xFF
,
(
payloadValue
>>
8
)
&
0xFF
,
0x16
,
];
}
String
_formatTemperature
(
double
value
)
{
if
(
value
.
isNaN
)
return
'--'
;
final
fixed
=
value
.
toStringAsFixed
(
1
);
return
fixed
.
endsWith
(
'.0'
)
?
fixed
.
substring
(
0
,
fixed
.
length
-
2
)
:
fixed
;
}
String
_formatInt
(
int
value
)
{
return
value
<
0
?
'--'
:
'
$value
'
;
}
@override
Future
<
void
>
close
()
async
{
await
_mcuReportSub
.
cancel
();
await
_bluetoothDataSub
.
cancel
();
return
super
.
close
();
}
// ==================== 便捷方法 - 供外部调用 ====================
/// [MCU调用] 更新设备SN
...
...
@@ -628,85 +913,127 @@ class DeviceControlBloc extends HydratedBloc<DeviceControlEvent, DeviceControlSt
void
setSn
(
String
?
sn
)
=>
add
(
DeviceControlSnChanged
(
sn
));
/// [MCU调用] 更新舱内温度当前值
void
updateCabinTempCurrentValue
(
String
value
)
=>
add
(
DeviceControlCabinTempCurrentValueFetched
(
value
));
void
updateCabinTempCurrentValue
(
String
value
)
=>
add
(
DeviceControlCabinTempCurrentValueFetched
(
value
));
/// [用户调用] 设置舱内温度设定值
void
setCabinTempSetValue
(
String
value
)
=>
add
(
DeviceControlCabinTempSetValueChanged
(
value
));
void
setCabinTempSetValue
(
String
value
)
=>
add
(
DeviceControlCabinTempSetValueChanged
(
value
));
/// [用户调用] 调整舱内温度设定值
void
adjustCabinTempSetValue
(
int
step
)
=>
add
(
DeviceControlCabinTempSetValueAdjusted
(
step
));
/// [用户调用] 设置舱内温度开关
void
setCabinTempSwitch
(
bool
isOn
)
=>
add
(
DeviceControlCabinTempSwitchChanged
(
isOn
));
void
setCabinTempSwitch
(
bool
isOn
)
=>
add
(
DeviceControlCabinTempSwitchChanged
(
isOn
));
/// [MCU调用] 更新舱内湿度当前值
void
updateCabinHumidityCurrentValue
(
String
value
)
=>
add
(
DeviceControlCabinHumidityCurrentValueFetched
(
value
));
void
updateCabinHumidityCurrentValue
(
String
value
)
=>
add
(
DeviceControlCabinHumidityCurrentValueFetched
(
value
));
/// [用户调用] 设置舱内湿度设定值
void
setCabinHumiditySetValue
(
String
value
)
=>
add
(
DeviceControlCabinHumiditySetValueChanged
(
value
));
void
setCabinHumiditySetValue
(
String
value
)
=>
add
(
DeviceControlCabinHumiditySetValueChanged
(
value
));
/// [用户调用] 调整舱内湿度设定值
void
adjustCabinHumiditySetValue
(
int
step
)
=>
add
(
DeviceControlCabinHumiditySetValueAdjusted
(
step
));
/// [用户调用] 设置舱内湿度开关
void
setCabinHumiditySwitch
(
bool
isOn
)
=>
add
(
DeviceControlCabinHumiditySwitchChanged
(
isOn
));
void
setCabinHumiditySwitch
(
bool
isOn
)
=>
add
(
DeviceControlCabinHumiditySwitchChanged
(
isOn
));
/// [MCU调用] 更新氧气浓度当前值
void
updateOxygenConcentrationCurrentValue
(
String
value
)
=>
add
(
DeviceControlOxygenConcentrationCurrentValueFetched
(
value
));
void
updateOxygenConcentrationCurrentValue
(
String
value
)
=>
add
(
DeviceControlOxygenConcentrationCurrentValueFetched
(
value
));
/// [用户调用] 设置氧气浓度设定值
void
setOxygenConcentrationSetValue
(
String
value
)
=>
add
(
DeviceControlOxygenConcentrationSetValueChanged
(
value
));
void
setOxygenConcentrationSetValue
(
String
value
)
=>
add
(
DeviceControlOxygenConcentrationSetValueChanged
(
value
));
/// [用户调用] 调整氧气浓度设定值
void
adjustOxygenConcentrationSetValue
(
int
step
)
=>
add
(
DeviceControlOxygenConcentrationSetValueAdjusted
(
step
));
/// [用户调用] 设置氧气浓度开关
void
setOxygenConcentrationSwitch
(
bool
isOn
)
=>
add
(
DeviceControlOxygenConcentrationSwitchChanged
(
isOn
));
void
setOxygenConcentrationSwitch
(
bool
isOn
)
=>
add
(
DeviceControlOxygenConcentrationSwitchChanged
(
isOn
));
/// [MCU调用] 更新二氧化碳浓度当前值
void
updateCO2ConcentrationCurrentValue
(
String
value
)
=>
add
(
DeviceControlCO2ConcentrationCurrentValueFetched
(
value
));
void
updateCO2ConcentrationCurrentValue
(
String
value
)
=>
add
(
DeviceControlCO2ConcentrationCurrentValueFetched
(
value
));
/// [用户调用] 设置二氧化碳浓度告警阈值
void
setCO2ConcentrationAlarmThreshold
(
String
value
)
=>
add
(
DeviceControlCO2ConcentrationAlarmThresholdChanged
(
value
));
void
setCO2ConcentrationAlarmThreshold
(
String
value
)
=>
add
(
DeviceControlCO2ConcentrationAlarmThresholdChanged
(
value
));
/// [用户调用] 调整二氧化碳浓度告警阈值
void
adjustCO2ConcentrationAlarmThreshold
(
int
step
)
=>
add
(
DeviceControlCO2ConcentrationAlarmThresholdAdjusted
(
step
));
/// [用户调用] 设置护理等级
void
setCareLevel
(
CareLevel
level
)
=>
add
(
DeviceControlCareLevelChanged
(
level
));
void
setCareLevel
(
CareLevel
level
)
=>
add
(
DeviceControlCareLevelChanged
(
level
));
/// [用户调用] 设置新风模式
void
setFreshAirMode
(
FreshAirMode
mode
)
=>
add
(
DeviceControlFreshAirModeChanged
(
mode
));
void
setFreshAirMode
(
FreshAirMode
mode
)
=>
add
(
DeviceControlFreshAirModeChanged
(
mode
));
/// [用户调用] 设置风速模式
void
setWindSpeedMode
(
WindSpeedMode
mode
)
=>
add
(
DeviceControlWindSpeedModeChanged
(
mode
));
void
setWindSpeedMode
(
WindSpeedMode
mode
)
=>
add
(
DeviceControlWindSpeedModeChanged
(
mode
));
/// [用户调用] 设置雾化时间
void
setNebulizationTime
(
String
minutes
)
=>
add
(
DeviceControlNebulizationTimeChanged
(
minutes
));
void
setNebulizationTime
(
String
minutes
)
=>
add
(
DeviceControlNebulizationTimeChanged
(
minutes
));
/// [用户调用] 设置消毒时间
void
setDisinfectionTime
(
String
minutes
)
=>
add
(
DeviceControlDisinfectionTimeChanged
(
minutes
));
void
setDisinfectionTime
(
String
minutes
)
=>
add
(
DeviceControlDisinfectionTimeChanged
(
minutes
));
/// [用户调用] 设置开放供氧开关
void
setOpenOxygenSwitch
(
bool
isOn
)
=>
add
(
DeviceControlOpenOxygenSwitchChanged
(
isOn
));
void
setOpenOxygenSwitch
(
bool
isOn
)
=>
add
(
DeviceControlOpenOxygenSwitchChanged
(
isOn
));
/// [MCU调用] 更新供氧计时
void
updateOxygenTimer
(
String
hours
,
String
minutes
,
String
seconds
)
=>
add
(
DeviceControlOxygenTimerUpdated
(
hours
,
minutes
,
seconds
));
void
updateOxygenTimer
(
String
hours
,
String
minutes
,
String
seconds
)
=>
add
(
DeviceControlOxygenTimerUpdated
(
hours
,
minutes
,
seconds
));
/// [用户调用] 清零供氧计时
void
clearOxygenTimer
()
=>
add
(
const
DeviceControlOxygenTimerCleared
());
/// [用户调用] 设置循环模式
void
setCirculationMode
(
CirculationMode
mode
)
=>
add
(
DeviceControlCirculationModeChanged
(
mode
));
void
setCirculationMode
(
CirculationMode
mode
)
=>
add
(
DeviceControlCirculationModeChanged
(
mode
));
/// [用户调用] 设置检查灯开关
void
setCheckLightSwitch
(
bool
isOn
)
=>
add
(
DeviceControlCheckLightSwitchChanged
(
isOn
));
void
setCheckLightSwitch
(
bool
isOn
)
=>
add
(
DeviceControlCheckLightSwitchChanged
(
isOn
));
/// [用户调用] 设置照明灯开关
void
setIlluminationLightSwitch
(
bool
isOn
)
=>
add
(
DeviceControlIlluminationLightSwitchChanged
(
isOn
));
void
setIlluminationLightSwitch
(
bool
isOn
)
=>
add
(
DeviceControlIlluminationLightSwitchChanged
(
isOn
));
/// [用户调用] 设置蓝光灯开关
void
setBlueLightSwitch
(
bool
isOn
)
=>
add
(
DeviceControlBlueLightSwitchChanged
(
isOn
));
void
setBlueLightSwitch
(
bool
isOn
)
=>
add
(
DeviceControlBlueLightSwitchChanged
(
isOn
));
/// [用户调用] 设置红光灯开关
void
setRedLightSwitch
(
bool
isOn
)
=>
add
(
DeviceControlRedLightSwitchChanged
(
isOn
));
void
setRedLightSwitch
(
bool
isOn
)
=>
add
(
DeviceControlRedLightSwitchChanged
(
isOn
));
/// [MCU调用] 更新总运行时长
void
updateTotalRunTime
(
String
time
)
=>
add
(
DeviceControlTotalRunTimeUpdated
(
time
));
void
updateTotalRunTime
(
String
time
)
=>
add
(
DeviceControlTotalRunTimeUpdated
(
time
));
/// [MCU调用] 更新环境温度
void
updateEnvironmentTemp
(
String
temp
)
=>
add
(
DeviceControlEnvironmentTempUpdated
(
temp
));
void
updateEnvironmentTemp
(
String
temp
)
=>
add
(
DeviceControlEnvironmentTempUpdated
(
temp
));
/// [MCU调用] 更新总制氧时长
void
updateTotalOxygenTime
(
String
time
)
=>
add
(
DeviceControlTotalOxygenTimeUpdated
(
time
));
void
updateTotalOxygenTime
(
String
time
)
=>
add
(
DeviceControlTotalOxygenTimeUpdated
(
time
));
/// [用户调用] 重置设备设置
void
resetSettings
()
=>
add
(
const
DeviceControlResetSettings
());
...
...
lib/blocs/device_control/device_control_event.dart
View file @
266ba06d
import
'package:equatable/equatable.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
{
...
...
@@ -26,6 +27,16 @@ class DeviceControlDataUpdated extends DeviceControlEvent {
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(从MCU获取)
...
...
@@ -70,6 +81,16 @@ class DeviceControlCabinTempSetValueChanged extends DeviceControlEvent {
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
{
final
bool
isOn
;
...
...
@@ -102,6 +123,16 @@ class DeviceControlCabinHumiditySetValueChanged extends DeviceControlEvent {
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
{
final
bool
isOn
;
...
...
@@ -115,7 +146,8 @@ class DeviceControlCabinHumiditySwitchChanged extends DeviceControlEvent {
// ==================== 氧气浓度控制事件 ====================
/// 获取氧气浓度当前值(从MCU获取)
class
DeviceControlOxygenConcentrationCurrentValueFetched
extends
DeviceControlEvent
{
class
DeviceControlOxygenConcentrationCurrentValueFetched
extends
DeviceControlEvent
{
final
String
currentValue
;
const
DeviceControlOxygenConcentrationCurrentValueFetched
(
this
.
currentValue
);
...
...
@@ -125,7 +157,8 @@ class DeviceControlOxygenConcentrationCurrentValueFetched extends DeviceControlE
}
/// 设置氧气浓度设定值(用户配置,发送给MCU)
class
DeviceControlOxygenConcentrationSetValueChanged
extends
DeviceControlEvent
{
class
DeviceControlOxygenConcentrationSetValueChanged
extends
DeviceControlEvent
{
final
String
setValue
;
const
DeviceControlOxygenConcentrationSetValueChanged
(
this
.
setValue
);
...
...
@@ -134,6 +167,17 @@ class DeviceControlOxygenConcentrationSetValueChanged extends DeviceControlEvent
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
{
final
bool
isOn
;
...
...
@@ -147,7 +191,8 @@ class DeviceControlOxygenConcentrationSwitchChanged extends DeviceControlEvent {
// ==================== 二氧化碳浓度控制事件 ====================
/// 获取二氧化碳浓度当前值(从MCU获取)
class
DeviceControlCO2ConcentrationCurrentValueFetched
extends
DeviceControlEvent
{
class
DeviceControlCO2ConcentrationCurrentValueFetched
extends
DeviceControlEvent
{
final
String
currentValue
;
const
DeviceControlCO2ConcentrationCurrentValueFetched
(
this
.
currentValue
);
...
...
@@ -157,7 +202,8 @@ class DeviceControlCO2ConcentrationCurrentValueFetched extends DeviceControlEven
}
/// 设置二氧化碳浓度告警阈值(用户配置,发送给MCU)
class
DeviceControlCO2ConcentrationAlarmThresholdChanged
extends
DeviceControlEvent
{
class
DeviceControlCO2ConcentrationAlarmThresholdChanged
extends
DeviceControlEvent
{
final
String
alarmThreshold
;
const
DeviceControlCO2ConcentrationAlarmThresholdChanged
(
this
.
alarmThreshold
);
...
...
@@ -166,6 +212,17 @@ class DeviceControlCO2ConcentrationAlarmThresholdChanged extends DeviceControlEv
List
<
Object
?>
get
props
=>
[
alarmThreshold
];
}
/// 调整二氧化碳浓度设定值(用户点击 -/+,发送给MCU)
class
DeviceControlCO2ConcentrationAlarmThresholdAdjusted
extends
DeviceControlEvent
{
final
int
step
;
const
DeviceControlCO2ConcentrationAlarmThresholdAdjusted
(
this
.
step
);
@override
List
<
Object
?>
get
props
=>
[
step
];
}
// ==================== 护理等级控制事件 ====================
/// 设置护理等级(用户配置,发送给MCU)
...
...
lib/blocs/device_control/device_control_state.dart
View file @
266ba06d
...
...
@@ -8,25 +8,25 @@ const DeviceControlBO _defaultDeviceControlBO = DeviceControlBO(
environmentTemp:
'25.0℃'
,
totalOxygenTime:
'0h'
,
cabinTemp:
TemperatureControlBO
(
currentValue:
'
24
'
,
currentValue:
'
--
'
,
setValue:
'24'
,
unit:
'℃'
,
isOn:
false
,
),
cabinHumidity:
HumidityControlBO
(
currentValue:
'
24
'
,
currentValue:
'
--
'
,
setValue:
'50'
,
unit:
'%'
,
isOn:
false
,
),
oxygenConcentration:
OxygenConcentrationBO
(
currentValue:
'
24
'
,
currentValue:
'
--
'
,
setValue:
'90'
,
unit:
'%'
,
isOn:
true
,
),
co2Concentration:
CO2ConcentrationBO
(
currentValue:
'
2400
'
,
currentValue:
'
--
'
,
alarmThreshold:
'4000'
,
unit:
'PPM'
,
),
...
...
@@ -106,7 +106,15 @@ class DeviceControlState extends Equatable {
/// 从持久化存储中恢复状态(仅恢复用户设置,MCU值使用默认值)
factory
DeviceControlState
.
fromJson
(
Map
<
String
,
dynamic
>
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
(
data:
data
,
userSettings:
userSettings
,
...
...
lib/models/bo/bluetooth_read_model.dart
View file @
266ba06d
...
...
@@ -23,10 +23,12 @@ class BluetoothReadModel extends Equatable {
factory
BluetoothReadModel
.
fromMcuReport
(
McuReport
report
)
{
return
BluetoothReadModel
(
sn:
report
.
sn
.
trim
().
isEmpty
?
null
:
report
.
sn
.
trim
(),
mainTemperature:
report
.
mainTemperature
,
humidity:
report
.
humidity
,
oxygenConcentration:
report
.
oxygenConcentration
,
co2Measurement:
report
.
co2Measurement
,
mainTemperature:
report
.
mainTemperature
.
isNaN
?
null
:
report
.
mainTemperature
,
humidity:
report
.
humidity
<
0
?
null
:
report
.
humidity
,
oxygenConcentration:
report
.
oxygenConcentration
<
0
?
null
:
report
.
oxygenConcentration
,
co2Measurement:
report
.
co2Measurement
<
0
?
null
:
report
.
co2Measurement
,
updatedAt:
DateTime
.
now
(),
);
}
...
...
lib/utils/bluetooth/bluetooth_manager.dart
View file @
266ba06d
import
'dart:async'
;
import
'package:flutter/foundation.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
);
final
BleBluetoothManager
appBluetoothManager
=
BleBluetoothManager
();
enum
BluetoothConnectionStatus
{
disconnected
,
connecting
,
...
...
@@ -125,6 +129,7 @@ class BleBluetoothManager<T> {
await
_ble
.
initialize
();
final
status
=
_ble
.
status
;
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG ensure_ready status=
$status
'
);
if
(
status
==
BleStatus
.
ready
)
{
return
true
;
}
...
...
@@ -135,10 +140,14 @@ class BleBluetoothManager<T> {
.
timeout
(
const
Duration
(
seconds:
2
),
onTimeout:
()
=>
status
);
if
(
nextStatus
!=
BleStatus
.
ready
)
{
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG ensure_ready_failed nextStatus=
$nextStatus
'
);
_notifyMessage
(
'蓝牙未就绪或未授权:
$nextStatus
'
);
return
false
;
}
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG ensure_ready_success nextStatus=
$nextStatus
'
);
return
true
;
}
...
...
@@ -243,12 +252,18 @@ class BleBluetoothManager<T> {
}
Future
<
void
>
connectToDeviceId
(
String
deviceId
)
async
{
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_to_device_id start deviceId=
$deviceId
'
);
if
(
deviceId
.
isEmpty
)
{
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_to_device_id empty_device_id'
);
_notifyMessage
(
'无效的蓝牙设备地址'
);
return
;
}
if
(!
await
ensureBluetoothReady
())
{
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_to_device_id bluetooth_not_ready'
);
return
;
}
...
...
@@ -267,6 +282,7 @@ class BleBluetoothManager<T> {
_setStatus
(
BluetoothConnectionStatus
.
connecting
);
_notifyMessage
(
'正在连接蓝牙设备:
$deviceId
'
);
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG connecting deviceId=
$deviceId
'
);
final
completer
=
Completer
<
void
>();
_connectionSubscription
=
_ble
...
...
@@ -282,10 +298,14 @@ class BleBluetoothManager<T> {
switch
(
update
.
connectionState
)
{
case
DeviceConnectionState
.
connecting
:
_setStatus
(
BluetoothConnectionStatus
.
connecting
);
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG state_connecting deviceId=
$deviceId
'
);
break
;
case
DeviceConnectionState
.
connected
:
_setStatus
(
BluetoothConnectionStatus
.
connectedReady
);
_notifyMessage
(
'蓝牙连接成功:
$deviceId
'
);
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG state_connected deviceId=
$deviceId
'
);
await
_enableNotifications
(
deviceId
);
activateDataSource
();
if
(!
completer
.
isCompleted
)
{
...
...
@@ -294,10 +314,16 @@ class BleBluetoothManager<T> {
break
;
case
DeviceConnectionState
.
disconnecting
:
_setStatus
(
BluetoothConnectionStatus
.
disconnected
);
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG state_disconnecting deviceId=
$deviceId
'
);
break
;
case
DeviceConnectionState
.
disconnected
:
_setStatus
(
BluetoothConnectionStatus
.
disconnected
);
_notifyMessage
(
'蓝牙连接断开:
$deviceId
'
);
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG state_disconnected deviceId=
$deviceId
'
'failure=
${update.failure}
'
,
);
_clearCharacteristics
();
if
(!
completer
.
isCompleted
&&
update
.
failure
!=
null
)
{
completer
.
completeError
(
update
.
failure
!);
...
...
@@ -308,6 +334,8 @@ class BleBluetoothManager<T> {
onError:
(
Object
error
)
{
_setStatus
(
BluetoothConnectionStatus
.
disconnected
);
_notifyMessage
(
'蓝牙连接失败:
$error
'
);
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_error deviceId=
$deviceId
error=
$error
'
);
if
(!
completer
.
isCompleted
)
{
completer
.
completeError
(
error
);
}
...
...
@@ -317,6 +345,8 @@ class BleBluetoothManager<T> {
try
{
await
completer
.
future
.
timeout
(
connectionTimeout
);
}
catch
(
_
)
{
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG connect_timeout_or_error deviceId=
$deviceId
'
);
await
disconnect
();
rethrow
;
}
...
...
@@ -327,6 +357,8 @@ class BleBluetoothManager<T> {
_status
==
BluetoothConnectionStatus
.
active
)
{
_setStatus
(
BluetoothConnectionStatus
.
active
);
_notifyMessage
(
'蓝牙数据源已激活'
);
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG data_source_active deviceId=
$_connectedDeviceId
'
);
}
}
...
...
@@ -395,10 +427,16 @@ class BleBluetoothManager<T> {
Future
<
void
>
_enableNotifications
(
String
deviceId
)
async
{
final
characteristic
=
_notifyCharacteristic
;
if
(
characteristic
==
null
)
{
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG notify_characteristic_null deviceId=
$deviceId
'
);
throw
StateError
(
'接收特征值为空'
);
}
await
_notifySubscription
?.
cancel
();
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG notify_subscribe_start deviceId=
$deviceId
'
'service=
$serviceUuid
characteristic=
$notifyCharacteristicUuid
'
,
);
_notifySubscription
=
_ble
.
subscribeToCharacteristic
(
characteristic
).
listen
(
(
bytes
)
{
if
(!
isActive
||
bytes
.
isEmpty
)
{
...
...
@@ -406,6 +444,10 @@ class BleBluetoothManager<T> {
}
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
);
_dataController
.
add
(
BluetoothDataPacket
<
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
()
{
...
...
@@ -427,6 +475,10 @@ class BleBluetoothManager<T> {
void
_setStatus
(
BluetoothConnectionStatus
status
)
{
_status
=
status
;
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG manager_status status=
$status
'
'deviceId=
$_connectedDeviceId
'
,
);
if
(!
_stateController
.
isClosed
)
{
_stateController
.
add
(
status
);
}
...
...
lib/utils/bluetooth/mcu_report.dart
View file @
266ba06d
import
'dart:convert'
;
const
int
mcuReportBytes
=
145
;
const
int
mcuReportHeaderBytes
=
5
;
const
int
mcuReportTailBytes
=
3
;
class
HexReader
{
HexReader
(
String
hexData
)
{
...
...
@@ -37,10 +39,20 @@ class HexReader {
return
int
.
parse
(
readHex
(
2
),
radix:
16
);
}
int
readUInt16LE
()
{
final
low
=
readUInt8
();
final
high
=
readUInt8
();
return
low
|
(
high
<<
8
);
}
double
readTenths
()
{
return
readUInt16
()
/
10
;
}
double
readTenthsLE
()
{
return
readUInt16LE
()
/
10
;
}
int
readHighByte
()
{
return
readUInt16
()
>>
8
;
}
...
...
@@ -330,85 +342,104 @@ class McuReport {
class
McuReportParser
{
static
McuReport
parseHex
(
String
hexData
)
{
final
data
=
HexReader
(
hexData
);
if
(
data
.
remainingBytes
<
mcuReportBytes
)
{
if
(
data
.
remainingBytes
<
mcuReport
Header
Bytes
)
{
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
(
head:
data
.
readHex
(
2
)
,
direct:
d
ata
.
readHex
(
1
)
,
serviceNum:
data
.
readHex
(
1
)
,
length:
data
.
readUInt8
()
,
mainTemperature:
data
.
readT
enths
(),
versionNumber:
data
.
readT
enths
(),
ambientTemperature:
data
.
readT
enths
(),
upperTemperature:
data
.
readT
enths
(),
humidity:
data
.
readUInt
16
(),
oxygenConcentration:
data
.
readUInt
16
(),
temperatureSetting:
data
.
readT
enths
(),
lowerTemperature:
data
.
readT
enths
(),
humiditySetting:
data
.
readUInt
16
(),
oxygenSetting:
data
.
readUInt
16
(),
internalExternalCycleState:
data
.
readUInt
16
(),
negativeIonSwitch:
data
.
readUInt
16
(),
uvLamp:
data
.
readUInt
16
(),
inspectionLamp:
data
.
readUInt
16
(),
floodLight:
data
.
readUInt
16
(),
o2ChangeSlope:
data
.
readUInt
16
(),
rightAtomizerTreatmentTime:
data
.
readUInt
16
(),
rightInfraredPhysiotherapyTime:
data
.
readUInt
16
(),
tempS1:
data
.
readH
ighByte
()
/
10
,
tempH1:
data
.
readH
ighByte
(),
atomizerTreatmentTime:
data
.
readUInt
16
(),
infraredPhysiotherapyTime:
data
.
readH
ighByte
(),
mainTempCorrect:
data
.
readH
ighByte
(),
auxiliaryTemperatureCorrect:
data
.
readH
ighByte
(),
o2CorrectStart:
data
.
readH
ighByte
(),
o2CorrectDiffer:
data
.
readH
ighByte
(),
humidityCorrect:
data
.
readH
ighByte
(),
oxygenCorrect:
data
.
readH
ighByte
(),
eTempCorrect:
data
.
readH
ighByte
(),
xyyModel:
data
.
readHex
(
2
),
unknown:
data
.
readHex
(
2
),
wifible:
data
.
readH
ighByte
(),
levelLight:
data
.
readH
ighByte
(),
openO2:
data
.
readH
ighByte
(),
airConditioner:
data
.
readH
ighByte
(),
co2Correct:
data
.
readH
ighByte
(),
co2Measurement:
data
.
readUInt
16
(),
co2Setting:
data
.
readUInt
16
(),
co2FastClearSwitch:
data
.
readH
ighByte
(),
openO2SettingMax:
data
.
readH
ighByte
(),
noOpenO2SettingMax:
data
.
readH
ighByte
(),
windSet:
data
.
readH
ighByte
(),
rangeData1:
data
.
readHex
(
2
),
rangeData2:
data
.
readHex
(
2
),
rangeData3:
data
.
readHex
(
2
),
co2Safety:
data
.
readUInt
16
(),
co2SettingMin:
data
.
readUInt
16
(),
blueLight:
data
.
readH
ighByte
(),
redLight:
data
.
readH
ighByte
(),
fcSwitch:
data
.
readH
ighByte
(),
o2SupplyTime:
data
.
readUInt
16
(),
totalTime:
data
.
readUInt
16
(),
ratioTemp2:
data
.
readUInt8
()
,
ratioTemp1:
data
.
readUInt8
()
,
sp:
data
.
readH
ighByte
(),
dp:
data
.
readH
ighByte
(),
mean:
data
.
readH
ighByte
(),
pulseRate:
data
.
readH
ighByte
(),
xyyStatus:
data
.
readH
ighByte
(),
xyyError1:
data
.
readH
ighByte
(),
sn:
data
.
readA
scii
(
10
),
topTempCorrect:
data
.
readH
ighByte
(),
midTempCorrect:
data
.
readH
ighByte
(),
o2OpenCorrectStart:
data
.
readH
ighByte
(),
o2OpenCorrectStep:
data
.
readH
ighByte
(),
o2CorrectMax:
data
.
readH
ighByte
(),
o2OpenCorrectMax:
data
.
readH
ighByte
(),
parsedBytes:
data
.
byteIndex
,
head:
head
,
direct:
d
irect
,
serviceNum:
serviceNum
,
length:
length
,
mainTemperature:
t
enths
(),
versionNumber:
t
enths
(),
ambientTemperature:
t
enths
(),
upperTemperature:
t
enths
(),
humidity:
u
16
(),
oxygenConcentration:
u
16
(),
temperatureSetting:
t
enths
(),
lowerTemperature:
t
enths
(),
humiditySetting:
u
16
(),
oxygenSetting:
u
16
(),
internalExternalCycleState:
u
16
(),
negativeIonSwitch:
u
16
(),
uvLamp:
u
16
(),
inspectionLamp:
u
16
(),
floodLight:
u
16
(),
o2ChangeSlope:
u
16
(),
rightAtomizerTreatmentTime:
u
16
(),
rightInfraredPhysiotherapyTime:
u
16
(),
tempS1:
h
ighByte
()
/
10
,
tempH1:
h
ighByte
(),
atomizerTreatmentTime:
u
16
(),
infraredPhysiotherapyTime:
h
ighByte
(),
mainTempCorrect:
h
ighByte
(),
auxiliaryTemperatureCorrect:
h
ighByte
(),
o2CorrectStart:
h
ighByte
(),
o2CorrectDiffer:
h
ighByte
(),
humidityCorrect:
h
ighByte
(),
oxygenCorrect:
h
ighByte
(),
eTempCorrect:
h
ighByte
(),
xyyModel:
hex2
(
),
unknown:
hex2
(
),
wifible:
h
ighByte
(),
levelLight:
h
ighByte
(),
openO2:
h
ighByte
(),
airConditioner:
h
ighByte
(),
co2Correct:
h
ighByte
(),
co2Measurement:
u
16
(),
co2Setting:
u
16
(),
co2FastClearSwitch:
h
ighByte
(),
openO2SettingMax:
h
ighByte
(),
noOpenO2SettingMax:
h
ighByte
(),
windSet:
h
ighByte
(),
rangeData1:
hex2
(
),
rangeData2:
hex2
(
),
rangeData3:
hex2
(
),
co2Safety:
u
16
(),
co2SettingMin:
u
16
(),
blueLight:
h
ighByte
(),
redLight:
h
ighByte
(),
fcSwitch:
h
ighByte
(),
o2SupplyTime:
u
16
(),
totalTime:
u
16
(),
ratioTemp2:
remainingPayloadBytes
()
>=
1
?
payload
!.
readUInt8
()
:
-
1
,
ratioTemp1:
remainingPayloadBytes
()
>=
1
?
payload
!.
readUInt8
()
:
-
1
,
sp:
h
ighByte
(),
dp:
h
ighByte
(),
mean:
h
ighByte
(),
pulseRate:
h
ighByte
(),
xyyStatus:
h
ighByte
(),
xyyError1:
h
ighByte
(),
sn:
a
scii
(
10
),
topTempCorrect:
h
ighByte
(),
midTempCorrect:
h
ighByte
(),
o2OpenCorrectStart:
h
ighByte
(),
o2OpenCorrectStep:
h
ighByte
(),
o2CorrectMax:
h
ighByte
(),
o2OpenCorrectMax:
h
ighByte
(),
parsedBytes:
mcuReportHeaderBytes
+
(
payload
?.
byteIndex
??
0
)
,
);
}
...
...
@@ -427,6 +458,14 @@ class McuReportParser {
class
McuReportFrameDecoder
{
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
)
{
if
(
bytes
.
isEmpty
)
{
return
const
[];
...
...
@@ -435,10 +474,12 @@ class McuReportFrameDecoder {
_buffer
.
addAll
(
bytes
);
final
reports
=
<
McuReport
>[];
while
(
_buffer
.
length
>=
mcuReportBytes
)
{
final
headIndex
=
_
buffer
.
indexOf
(
0x68
);
while
(
_buffer
.
length
>=
mcuReport
Header
Bytes
)
{
final
headIndex
=
_
findReportHeadIndex
(
);
if
(
headIndex
<
0
)
{
_buffer
.
clear
();
if
(
_buffer
.
length
>
3
)
{
_buffer
.
removeRange
(
0
,
_buffer
.
length
-
3
);
}
break
;
}
...
...
@@ -446,23 +487,43 @@ class McuReportFrameDecoder {
_buffer
.
removeRange
(
0
,
headIndex
);
}
if
(
_buffer
.
length
<
mcuReportBytes
)
{
if
(
_buffer
.
length
<
mcuReport
Header
Bytes
)
{
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
)
{
_buffer
.
removeAt
(
0
);
continue
;
}
reports
.
add
(
McuReportParser
.
parseBytes
(
frame
));
_buffer
.
removeRange
(
0
,
mcuReport
Bytes
);
_buffer
.
removeRange
(
0
,
frame
Bytes
);
}
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
()
{
_buffer
.
clear
();
}
...
...
lib/utils/event_bus.dart
View file @
266ba06d
import
'dart:async'
;
import
'package:laki_icu_app/models/bo/bluetooth_read_model.dart'
;
import
'package:laki_icu_app/utils/bluetooth/index.dart'
;
class
EventBus
{
final
StreamController
<
dynamic
>
_controller
=
StreamController
.
broadcast
();
...
...
@@ -41,3 +42,10 @@ class BluetoothReadInfoChangedEvent {
class
BluetoothReadInfoClearedEvent
{
const
BluetoothReadInfoClearedEvent
();
}
// MCU上报数据更新事件
class
McuReportChangedEvent
{
final
McuReport
report
;
const
McuReportChangedEvent
(
this
.
report
);
}
lib/views/monitoring/device_control/widgets/device_control_metric_card.dart
View file @
266ba06d
import
'package:flutter/material.dart'
;
import
'package:flutter_bloc/flutter_bloc.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
import
'package:laki_icu_app/blocs/device_control/device_control_bloc.dart'
;
import
'package:laki_icu_app/blocs/device_control/device_control_event.dart'
;
import
'package:laki_icu_app/blocs/device_control/device_control_state.dart'
;
import
'package:laki_icu_app/models/bo/monitoring_bo.dart'
;
import
'device_control_common.dart'
;
...
...
@@ -12,106 +16,164 @@ class DeviceControlMetricCard extends StatelessWidget {
@override
Widget
build
(
BuildContext
context
)
{
final
isTemperature
=
metric
.
label
.
contains
(
'温度'
);
final
isHumidity
=
metric
.
label
.
contains
(
'湿度'
);
final
isOxygen
=
metric
.
label
.
contains
(
'氧气'
);
final
isCo2
=
metric
.
label
.
contains
(
'二氧化碳'
)
||
metric
.
label
.
toUpperCase
().
contains
(
'CO'
);
return
DeviceControlPanelFrame
(
active:
isOxygen
,
backgroundAsset:
isOxygen
?
assetMetricGreen
:
assetMetricBlue
,
padding:
EdgeInsets
.
fromLTRB
(
32
.
w
,
26
.
h
,
32
.
w
,
28
.
h
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
children:
[
Expanded
(
child:
DeviceControlPanelTitle
(
_title
(
metric
.
label
)),
),
DeviceControlPill
(
text:
isOxygen
?
'ON'
:
isCo2
?
'PPM'
:
'Off'
,
active:
isOxygen
,
),
],
),
const
Spacer
(),
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
end
,
return
BlocBuilder
<
DeviceControlBloc
,
DeviceControlState
>(
buildWhen:
(
previous
,
current
)
=>
previous
.
data
.
cabinTemp
.
setValue
!=
current
.
data
.
cabinTemp
.
setValue
||
previous
.
data
.
cabinHumidity
.
setValue
!=
current
.
data
.
cabinHumidity
.
setValue
||
previous
.
data
.
oxygenConcentration
.
setValue
!=
current
.
data
.
oxygenConcentration
.
setValue
||
previous
.
data
.
co2Concentration
.
alarmThreshold
!=
current
.
data
.
co2Concentration
.
alarmThreshold
||
previous
.
data
.
cabinTemp
.
currentValue
!=
current
.
data
.
cabinTemp
.
currentValue
||
previous
.
data
.
cabinHumidity
.
currentValue
!=
current
.
data
.
cabinHumidity
.
currentValue
||
previous
.
data
.
oxygenConcentration
.
currentValue
!=
current
.
data
.
oxygenConcentration
.
currentValue
||
previous
.
data
.
co2Concentration
.
currentValue
!=
current
.
data
.
co2Concentration
.
currentValue
,
builder:
(
context
,
state
)
{
final
currentValue
=
isTemperature
?
state
.
data
.
cabinTemp
.
currentValue
:
isCo2
?
state
.
data
.
co2Concentration
.
currentValue
:
isOxygen
?
state
.
data
.
oxygenConcentration
.
currentValue
:
isHumidity
?
state
.
data
.
cabinHumidity
.
currentValue
:
metric
.
value
;
final
setValue
=
isTemperature
?
state
.
data
.
cabinTemp
.
setValue
:
isCo2
?
state
.
data
.
co2Concentration
.
alarmThreshold
:
isOxygen
?
state
.
data
.
oxygenConcentration
.
setValue
:
isHumidity
?
state
.
data
.
cabinHumidity
.
setValue
:
'24'
;
final
adjustEventBuilder
=
_adjustEventBuilder
(
isTemperature:
isTemperature
,
isHumidity:
isHumidity
,
isOxygen:
isOxygen
,
isCo2:
isCo2
,
);
return
DeviceControlPanelFrame
(
active:
isOxygen
,
backgroundAsset:
isOxygen
?
assetMetricGreen
:
assetMetricBlue
,
padding:
EdgeInsets
.
fromLTRB
(
32
.
w
,
26
.
h
,
32
.
w
,
28
.
h
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
metric
.
value
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
isCo2
?
60
.
sp
:
82
.
sp
,
height:
0.9
,
fontWeight:
FontWeight
.
bold
,
),
),
SizedBox
(
width:
12
.
w
),
Padding
(
padding:
EdgeInsets
.
only
(
bottom:
8
.
h
),
child:
Text
(
isCo2
?
''
:
metric
.
unit
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
w600
,
Row
(
children:
[
Expanded
(
child:
DeviceControlPanelTitle
(
_title
(
metric
.
label
)),
),
),
DeviceControlPill
(
text:
isOxygen
?
'ON'
:
isCo2
?
'PPM'
:
'Off'
,
active:
isOxygen
,
),
],
),
const
Spacer
(),
Image
.
asset
(
deviceControlIconAsset
(
metric
.
label
),
width:
72
.
w
,
height:
72
.
w
,
fit:
BoxFit
.
contain
,
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
end
,
children:
[
Text
(
currentValue
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
isCo2
?
60
.
sp
:
82
.
sp
,
height:
0.9
,
fontWeight:
FontWeight
.
bold
,
),
),
SizedBox
(
width:
12
.
w
),
Padding
(
padding:
EdgeInsets
.
only
(
bottom:
8
.
h
),
child:
Text
(
isCo2
?
''
:
metric
.
unit
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
28
.
sp
,
fontWeight:
FontWeight
.
w600
,
),
),
),
const
Spacer
(),
Image
.
asset
(
deviceControlIconAsset
(
metric
.
label
),
width:
72
.
w
,
height:
72
.
w
,
fit:
BoxFit
.
contain
,
),
],
),
],
),
Divider
(
color:
Colors
.
white
.
withValues
(
alpha:
0.18
),
height:
28
.
h
),
Text
(
isCo2
?
'告警阈值设定'
:
metric
.
label
.
contains
(
'温度'
)
?
'温度设定'
:
'湿度设定'
,
style:
TextStyle
(
color:
Colors
.
white
.
withValues
(
alpha:
0.78
),
fontSize:
26
.
sp
,
),
),
const
Spacer
(),
Row
(
children:
[
DeviceControlAdjustText
(
'-'
,
active:
isOxygen
||
!
isCo2
),
const
Spacer
(),
Divider
(
color:
Colors
.
white
.
withValues
(
alpha:
0.18
),
height:
28
.
h
),
Text
(
isCo2
?
'4000'
:
isOxygen
?
'90'
:
metric
.
label
.
contains
(
'湿度'
)
?
'10'
:
'24'
,
?
'告警阈值设定'
:
metric
.
label
.
contains
(
'温度'
)
?
'温度设定'
:
'湿度设定'
,
style:
TextStyle
(
color:
isCo2
?
const
Color
(
0xFFFFC400
)
:
const
Color
(
0xFF00B7F4
),
fontSize:
64
.
sp
,
height:
1
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
white
.
withValues
(
alpha:
0.78
),
fontSize:
26
.
sp
,
),
),
const
Spacer
(),
const
DeviceControlAdjustText
(
'+'
,
active:
true
),
Row
(
children:
[
_MetricAdjustButton
(
text:
'-'
,
active:
adjustEventBuilder
!=
null
,
onTap:
adjustEventBuilder
==
null
?
null
:
()
=>
context
.
read
<
DeviceControlBloc
>().
add
(
adjustEventBuilder
(
false
),
),
),
const
Spacer
(),
Text
(
setValue
,
style:
TextStyle
(
color:
isCo2
?
const
Color
(
0xFFFFC400
)
:
const
Color
(
0xFF00B7F4
),
fontSize:
64
.
sp
,
height:
1
,
fontWeight:
FontWeight
.
bold
,
),
),
const
Spacer
(),
_MetricAdjustButton
(
text:
'+'
,
active:
adjustEventBuilder
!=
null
,
onTap:
adjustEventBuilder
==
null
?
null
:
()
=>
context
.
read
<
DeviceControlBloc
>().
add
(
adjustEventBuilder
(
true
),
),
),
],
),
],
),
],
)
,
);
}
,
);
}
...
...
@@ -119,4 +181,53 @@ class DeviceControlMetricCard extends StatelessWidget {
if
(
label
.
contains
(
'二氧化碳'
))
return
'二氧化碳浓度'
;
return
label
;
}
static
DeviceControlEvent
Function
(
bool
increase
)?
_adjustEventBuilder
({
required
bool
isTemperature
,
required
bool
isHumidity
,
required
bool
isOxygen
,
required
bool
isCo2
,
})
{
if
(
isTemperature
)
{
return
(
increase
)
=>
DeviceControlCabinTempSetValueAdjusted
(
increase
?
1
:
-
1
);
}
if
(
isHumidity
)
{
return
(
increase
)
=>
DeviceControlCabinHumiditySetValueAdjusted
(
increase
?
1
:
-
1
);
}
if
(
isOxygen
)
{
return
(
increase
)
=>
DeviceControlOxygenConcentrationSetValueAdjusted
(
increase
?
1
:
-
1
);
}
if
(
isCo2
)
{
return
(
increase
)
=>
DeviceControlCO2ConcentrationAlarmThresholdAdjusted
(
increase
?
100
:
-
100
);
}
return
null
;
}
}
class
_MetricAdjustButton
extends
StatelessWidget
{
const
_MetricAdjustButton
({
required
this
.
text
,
required
this
.
active
,
this
.
onTap
,
});
final
String
text
;
final
bool
active
;
final
VoidCallback
?
onTap
;
@override
Widget
build
(
BuildContext
context
)
{
return
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onTap:
onTap
,
child:
Padding
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
12
.
w
,
vertical:
8
.
h
),
child:
DeviceControlAdjustText
(
text
,
active:
active
),
),
);
}
}
lib/views/monitoring/index/cubit/monitoring_index_cubit.dart
View file @
266ba06d
...
...
@@ -10,6 +10,8 @@ import 'package:laki_icu_app/services/webrtc_service.dart';
import
'package:laki_icu_app/models/bo/bluetooth_read_model.dart'
;
import
'package:laki_icu_app/utils/bluetooth/index.dart'
;
import
'package:laki_icu_app/utils/event_bus.dart'
;
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'
;
import
'monitoring_index_state.dart'
;
...
...
@@ -24,6 +26,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
final
StorageService
_storageService
;
final
BleBluetoothManager
_bluetoothManager
;
final
McuReportFrameDecoder
_mcuReportDecoder
;
LakiMqttClient
?
_mqttClient
;
StreamSubscription
<
WebrtcConnectionState
>?
_webrtcStateSub
;
StreamSubscription
<
P2pServiceState
>?
_p2pStateSub
;
...
...
@@ -31,10 +34,18 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
StreamSubscription
<
BluetoothConnectionStatus
>?
_bluetoothStateSub
;
StreamSubscription
<
String
>?
_bluetoothMessageSub
;
StreamSubscription
<
BluetoothDataPacket
>?
_bluetoothDataSub
;
StreamSubscription
<
McuReportChangedEvent
>?
_mcuReportChangedSub
;
StreamSubscription
<
LakiMqttConnectionState
>?
_mqttStateSub
;
StreamSubscription
<
LakiMqttMessage
>?
_mqttMessageSub
;
StreamSubscription
<
String
>?
_mqttErrorSub
;
Timer
?
_bluetoothReconnectTimer
;
Timer
?
_mqttReconnectTimer
;
bool
_isAutoReconnectEnabled
=
false
;
bool
_isAutoConnectingBluetooth
=
false
;
bool
_isManualUnbindingBluetooth
=
false
;
bool
_isMqttReconnectEnabled
=
false
;
bool
_isMqttConnecting
=
false
;
String
?
_mqttDeviceSn
;
/// 连接代际计数器 —— 每次切换模式时 +1,
/// 防止旧连接的异步结果污染当前模式的状态。
...
...
@@ -45,12 +56,13 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_webrtcService
=
WebrtcService
(),
_p2pVideoService
=
P2pVideoService
(),
_storageService
=
StorageService
(),
_bluetoothManager
=
BleBluetoothManager
()
,
_bluetoothManager
=
appBluetoothManager
,
_mcuReportDecoder
=
McuReportFrameDecoder
(),
super
(
const
MonitoringIndexState
())
{
_listenWebrtcState
();
_listenP2pState
();
_listenBluetoothState
();
_listenMcuReportChanged
();
_loadBoundBluetoothDevice
();
loadData
();
}
...
...
@@ -96,17 +108,42 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_bluetoothDataSub
=
_bluetoothManager
.
dataStream
.
listen
((
packet
)
async
{
if
(
isClosed
)
return
;
try
{
Log
.
warn
(
'🔥🔥🔥 BLE_READ_DEBUG index_packet len=
${packet.bytes.length}
'
'hex=
${packet.rawHex}
'
,
);
final
reports
=
_mcuReportDecoder
.
addBytes
(
packet
.
bytes
);
final
latestReport
=
reports
.
isEmpty
?
null
:
reports
.
last
;
Log
.
warn
(
'🔥🔥🔥 BLE_READ_DEBUG index_parse reports=
${reports.length}
'
'buffered=
${_mcuReportDecoder.bufferedBytes}
'
'bufferHex=
${_mcuReportDecoder.bufferedHexPreview}
'
'hasLatest=
${latestReport != null}
'
,
);
if
(
latestReport
!=
null
)
{
Log
.
warn
(
'🔥🔥🔥 BLE_READ_DEBUG index_report '
'frameLength=
${latestReport.length}
'
'temp=
${latestReport.mainTemperature}
'
'tempSet=
${latestReport.temperatureSetting}
'
'humidity=
${latestReport.humidity}
'
'humiditySet=
${latestReport.humiditySetting}
'
'o2=
${latestReport.oxygenConcentration}
'
'o2Set=
${latestReport.oxygenSetting}
'
'co2=
${latestReport.co2Measurement}
'
'co2Set=
${latestReport.co2Setting}
'
'sn=
${latestReport.sn.isEmpty ? '<EMPTY>' : latestReport.sn}
'
,
);
final
bluetoothReadInfo
=
BluetoothReadModel
.
fromMcuReport
(
latestReport
);
if
(
bluetoothReadInfo
.
hasSn
)
{
await
_storageService
.
saveBluetoothReadSn
(
bluetoothReadInfo
.
sn
!);
await
_connectMqttForDeviceSn
(
bluetoothReadInfo
.
sn
!);
}
eventBus
.
emit
(
BluetoothReadInfoChangedEvent
(
bluetoothReadInfo
),
);
_publishMqttMonitorReport
(
latestReport
);
if
(
isClosed
)
return
;
}
emit
(
state
.
copyWith
(
...
...
@@ -128,17 +165,35 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
});
}
void
_listenMcuReportChanged
()
{
_mcuReportChangedSub
=
eventBus
.
on
<
McuReportChangedEvent
>().
listen
((
event
)
{
if
(
isClosed
)
return
;
emit
(
state
.
copyWith
(
latestMcuReport:
event
.
report
,
metrics:
_metricsFromBluetoothReport
(
event
.
report
),
));
});
}
Future
<
void
>
_loadBoundBluetoothDevice
()
async
{
final
boundDevice
=
await
_storageService
.
getBoundBluetoothDevice
();
if
(
isClosed
)
return
;
final
deviceId
=
boundDevice
[
'deviceId'
];
await
_storageService
.
getBluetoothReadInfo
();
final
bluetoothReadInfo
=
await
_storageService
.
getBluetoothReadInfo
();
if
(
isClosed
)
return
;
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG load_bound_device '
'deviceId=
$deviceId
deviceName=
${boundDevice['deviceName']}
'
'hasSn=
${bluetoothReadInfo.hasSn}
'
,
);
emit
(
state
.
copyWith
(
boundBluetoothDeviceId:
deviceId
==
null
||
deviceId
.
isEmpty
?
null
:
deviceId
,
boundBluetoothDeviceName:
boundDevice
[
'deviceName'
],
));
if
(
bluetoothReadInfo
.
hasSn
)
{
_connectMqttForDeviceSn
(
bluetoothReadInfo
.
sn
!);
}
if
(
deviceId
!=
null
&&
deviceId
.
isNotEmpty
)
{
_isAutoReconnectEnabled
=
true
;
_connectBoundBluetoothDevice
();
...
...
@@ -359,12 +414,18 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
await
_bluetoothManager
.
disconnect
();
await
_storageService
.
deleteBoundBluetoothDevice
();
await
_storageService
.
deleteBluetoothReadInfo
();
_isMqttReconnectEnabled
=
false
;
_mqttDeviceSn
=
null
;
await
_disposeMqttClient
();
eventBus
.
emit
(
const
BluetoothReadInfoClearedEvent
());
_mcuReportDecoder
.
clear
();
if
(
isClosed
)
return
;
emit
(
state
.
copyWith
(
clearBoundBluetoothDevice:
true
,
clearLatestMcuReport:
true
,
clearMqttDeviceSn:
true
,
mqttConnectionState:
LakiMqttConnectionState
.
disconnected
,
mqttMessage:
'MQTT 已断开'
,
bluetoothMessage:
'蓝牙设备已解绑'
,
latestBluetoothRawHex:
''
,
));
...
...
@@ -406,13 +467,23 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
if
(!
isClosed
)
{
emit
(
state
.
copyWith
(
bluetoothMessage:
'正在自动连接蓝牙设备'
));
}
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG auto_connect_start '
'deviceId=
$deviceId
isConnected=
${_bluetoothManager.isConnected}
'
,
);
try
{
await
_bluetoothManager
.
connectToDeviceId
(
deviceId
);
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG auto_connect_success '
'deviceId=
$deviceId
isConnected=
${_bluetoothManager.isConnected}
'
,
);
if
(!
isClosed
)
{
emit
(
state
.
copyWith
(
bluetoothMessage:
'蓝牙设备已自动连接'
));
}
}
catch
(
e
)
{
debugPrint
(
'🔥🔥🔥 BLE_CONNECT_DEBUG auto_connect_error deviceId=
$deviceId
error=
$e
'
);
if
(!
isClosed
)
{
emit
(
state
.
copyWith
(
bluetoothMessage:
'蓝牙自动连接失败:
$e
'
));
}
...
...
@@ -429,26 +500,168 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_bluetoothReconnectTimer
=
null
;
}
// ==================== MQTT 自动连接/上报 ====================
Future
<
void
>
_connectMqttForDeviceSn
(
String
deviceSn
)
async
{
if
(
deviceSn
.
isEmpty
||
isClosed
)
return
;
if
(
_isMqttConnecting
)
return
;
if
(
_mqttDeviceSn
==
deviceSn
&&
(
_mqttClient
?.
isConnected
??
false
))
{
return
;
}
_isMqttReconnectEnabled
=
true
;
_isMqttConnecting
=
true
;
_cancelMqttReconnect
();
var
shouldScheduleReconnect
=
false
;
try
{
if
(
_mqttDeviceSn
!=
deviceSn
)
{
await
_disposeMqttClient
();
_mqttDeviceSn
=
deviceSn
;
_mqttClient
=
LakiMqttClient
(
config:
LakiMqttConfig
.
tencentTdmq
(
deviceSn:
deviceSn
,
logging:
kDebugMode
,
),
);
_listenMqttState
(
_mqttClient
!);
}
if
(!
isClosed
)
{
emit
(
state
.
copyWith
(
mqttConnectionState:
LakiMqttConnectionState
.
connecting
,
mqttMessage:
'正在连接 MQTT:
$deviceSn
'
,
mqttDeviceSn:
deviceSn
,
));
}
await
_mqttClient
!.
connect
();
_mqttClient
!
..
subscribeIcuCommands
(
deviceSn
)
..
subscribePetBind
(
deviceSn
);
if
(!
isClosed
)
{
emit
(
state
.
copyWith
(
mqttConnectionState:
LakiMqttConnectionState
.
connected
,
mqttMessage:
'MQTT 已连接并订阅:
$deviceSn
'
,
mqttDeviceSn:
deviceSn
,
));
}
}
catch
(
e
)
{
shouldScheduleReconnect
=
true
;
if
(!
isClosed
)
{
emit
(
state
.
copyWith
(
mqttConnectionState:
LakiMqttConnectionState
.
failed
,
mqttMessage:
'MQTT 自动连接失败:
$e
'
,
mqttDeviceSn:
deviceSn
,
));
}
}
finally
{
_isMqttConnecting
=
false
;
if
(
shouldScheduleReconnect
)
{
_scheduleMqttReconnect
();
}
}
}
void
_listenMqttState
(
LakiMqttClient
client
)
{
_mqttStateSub
=
client
.
stateStream
.
listen
((
status
)
{
if
(
isClosed
)
return
;
emit
(
state
.
copyWith
(
mqttConnectionState:
status
));
if
(
status
==
LakiMqttConnectionState
.
disconnected
&&
_isMqttReconnectEnabled
)
{
_scheduleMqttReconnect
();
}
});
_mqttMessageSub
=
client
.
messageStream
.
listen
((
message
)
{
if
(
isClosed
)
return
;
emit
(
state
.
copyWith
(
mqttMessage:
'MQTT 收到
${message.topic}
:
${message.payload}
'
,
));
});
_mqttErrorSub
=
client
.
errorStream
.
listen
((
message
)
{
if
(
isClosed
)
return
;
emit
(
state
.
copyWith
(
mqttMessage:
message
));
});
}
void
_publishMqttMonitorReport
(
McuReport
report
)
{
final
client
=
_mqttClient
;
if
(
client
==
null
||
!
client
.
isConnected
||
report
.
sn
.
isEmpty
)
{
return
;
}
try
{
client
.
publishMonitorReport
(
report
);
if
(!
isClosed
)
{
emit
(
state
.
copyWith
(
mqttMessage:
'MQTT 已上报:
${report.sn}
'
));
}
}
catch
(
e
)
{
if
(!
isClosed
)
{
emit
(
state
.
copyWith
(
mqttMessage:
'MQTT 上报失败:
$e
'
));
}
}
}
void
_scheduleMqttReconnect
()
{
if
(!
_isMqttReconnectEnabled
||
_isMqttConnecting
||
_mqttDeviceSn
==
null
||
_mqttDeviceSn
!.
isEmpty
)
{
return
;
}
_mqttReconnectTimer
??=
Timer
(
const
Duration
(
seconds:
5
),
()
{
_mqttReconnectTimer
=
null
;
final
deviceSn
=
_mqttDeviceSn
;
if
(
deviceSn
!=
null
&&
deviceSn
.
isNotEmpty
)
{
_connectMqttForDeviceSn
(
deviceSn
);
}
},
);
}
void
_cancelMqttReconnect
()
{
_mqttReconnectTimer
?.
cancel
();
_mqttReconnectTimer
=
null
;
}
Future
<
void
>
_disposeMqttClient
()
async
{
_cancelMqttReconnect
();
await
_mqttStateSub
?.
cancel
();
await
_mqttMessageSub
?.
cancel
();
await
_mqttErrorSub
?.
cancel
();
_mqttStateSub
=
null
;
_mqttMessageSub
=
null
;
_mqttErrorSub
=
null
;
await
_mqttClient
?.
dispose
();
_mqttClient
=
null
;
}
List
<
MonitoringMetricBO
>
_metricsFromBluetoothReport
(
McuReport
report
)
{
final
replacements
=
<
String
,
MonitoringMetricBO
>{
'舱内温度'
:
_replaceMetricValue
(
'舱内温度'
,
report
.
mainTemperature
.
toStringAsFixed
(
1
),
_formatReportTemperature
(
report
.
mainTemperature
),
fallbackUnit:
'℃'
,
),
'舱内湿度'
:
_replaceMetricValue
(
'舱内湿度'
,
'
${report.humidity}
'
,
_formatReportInt
(
report
.
humidity
)
,
fallbackUnit:
'RH'
,
),
'氧气浓度'
:
_replaceMetricValue
(
'氧气浓度'
,
'
${report.oxygenConcentration}
'
,
_formatReportInt
(
report
.
oxygenConcentration
)
,
fallbackUnit:
'%'
,
),
'二氧化碳'
:
_replaceMetricValue
(
'二氧化碳'
,
'
${report.co2Measurement}
'
,
_formatReportInt
(
report
.
co2Measurement
)
,
fallbackUnit:
'ppm'
,
),
};
...
...
@@ -490,6 +703,16 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
);
}
String
_formatReportTemperature
(
double
value
)
{
if
(
value
.
isNaN
)
return
'--'
;
final
fixed
=
value
.
toStringAsFixed
(
1
);
return
fixed
.
endsWith
(
'.0'
)
?
fixed
.
substring
(
0
,
fixed
.
length
-
2
)
:
fixed
;
}
String
_formatReportInt
(
int
value
)
{
return
value
<
0
?
'--'
:
'
$value
'
;
}
/// 清空宠物信息(出舱)
void
clearPatientInfo
()
{
if
(
isClosed
)
return
;
...
...
@@ -499,7 +722,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
/// 设置测试宠物信息(绑定宠物)
void
setTestPatientInfo
()
{
if
(
isClosed
)
return
;
final
testPatientInfo
=
PatientInfoBO
(
const
testPatientInfo
=
PatientInfoBO
(
name:
'妮蔻'
,
type:
'猫'
,
phone:
'130-1111-3333'
,
...
...
@@ -522,14 +745,20 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
@override
Future
<
void
>
close
()
{
_cancelBluetoothReconnect
();
_isMqttReconnectEnabled
=
false
;
_cancelMqttReconnect
();
_webrtcStateSub
?.
cancel
();
_p2pStateSub
?.
cancel
();
_bluetoothScanSub
?.
cancel
();
_bluetoothStateSub
?.
cancel
();
_bluetoothMessageSub
?.
cancel
();
_bluetoothDataSub
?.
cancel
();
_mcuReportChangedSub
?.
cancel
();
_webrtcService
.
dispose
();
_p2pVideoService
.
dispose
();
return
_bluetoothManager
.
release
().
then
((
_
)
=>
super
.
close
());
return
Future
.
wait
([
_disposeMqttClient
(),
_bluetoothManager
.
release
(),
]).
then
((
_
)
=>
super
.
close
());
}
}
lib/views/monitoring/index/cubit/monitoring_index_state.dart
View file @
266ba06d
...
...
@@ -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/services/webrtc_service.dart'
;
import
'package:laki_icu_app/utils/bluetooth/index.dart'
;
import
'package:laki_icu_app/utils/mqtt/index.dart'
;
enum
MonitoringIndexStatus
{
initial
,
...
...
@@ -61,6 +62,15 @@ class MonitoringIndexState extends Equatable {
/// 最近解析出的 MCU 上报数据
final
McuReport
?
latestMcuReport
;
/// MQTT 连接状态
final
LakiMqttConnectionState
mqttConnectionState
;
/// MQTT 状态提示
final
String
?
mqttMessage
;
/// 当前 MQTT 连接使用的设备 SN
final
String
?
mqttDeviceSn
;
const
MonitoringIndexState
({
this
.
status
=
MonitoringIndexStatus
.
initial
,
this
.
isLoading
=
false
,
...
...
@@ -82,6 +92,9 @@ class MonitoringIndexState extends Equatable {
this
.
bluetoothMessage
,
this
.
latestBluetoothRawHex
,
this
.
latestMcuReport
,
this
.
mqttConnectionState
=
LakiMqttConnectionState
.
disconnected
,
this
.
mqttMessage
,
this
.
mqttDeviceSn
,
});
MonitoringIndexState
copyWith
({
...
...
@@ -105,9 +118,13 @@ class MonitoringIndexState extends Equatable {
String
?
bluetoothMessage
,
String
?
latestBluetoothRawHex
,
McuReport
?
latestMcuReport
,
LakiMqttConnectionState
?
mqttConnectionState
,
String
?
mqttMessage
,
String
?
mqttDeviceSn
,
bool
clearBoundBluetoothDevice
=
false
,
bool
clearLatestMcuReport
=
false
,
bool
clearPatientInfo
=
false
,
bool
clearMqttDeviceSn
=
false
,
})
{
return
MonitoringIndexState
(
status:
status
??
this
.
status
,
...
...
@@ -138,6 +155,10 @@ class MonitoringIndexState extends Equatable {
latestBluetoothRawHex
??
this
.
latestBluetoothRawHex
,
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 {
bluetoothMessage
,
latestBluetoothRawHex
,
latestMcuReport
,
mqttConnectionState
,
mqttMessage
,
mqttDeviceSn
,
];
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment