Commit eec1450c authored by 张宏's avatar 张宏

设备告警、摄像头告警 上下行对接联调完成

parent fb9b8d79
import 'dart:async'; import 'dart:async';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:intl/intl.dart';
import 'device_control_event.dart'; import 'device_control_event.dart';
import 'device_control_state.dart'; import 'device_control_state.dart';
import 'package:laki_icu_app/models/bo/device_control_bo.dart'; import 'package:laki_icu_app/models/bo/device_control_bo.dart';
import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart'; import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/blocs/device_threshold_config/device_threshold_config.dart';
import 'package:laki_icu_app/utils/bluetooth/index.dart'; import 'package:laki_icu_app/utils/bluetooth/index.dart';
import 'package:laki_icu_app/utils/event_bus.dart'; import 'package:laki_icu_app/utils/event_bus.dart';
import 'package:laki_icu_app/utils/logger.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 'package:laki_icu_app/utils/storage/storage_service.dart';
/// 用途:设备控制Bloc /// 用途:设备控制Bloc
...@@ -27,7 +31,7 @@ class DeviceControlBloc ...@@ -27,7 +31,7 @@ class DeviceControlBloc
// ==================== 基础事件 ==================== // ==================== 基础事件 ====================
on<DeviceControlLoadRequested>(_onLoadRequested); on<DeviceControlLoadRequested>(_onLoadRequested);
on<DeviceControlDataUpdated>(_onDataUpdated); on<DeviceControlDataUpdated>(_onDataUpdated);
on<DeviceControlMcuReportFetched>(_onMcuReportFetched); on<DeviceControlMcuReportFetched>(_onMcuReportFetched); // 蓝牙上报设备值,更新bloc
// ==================== 设备SN事件 ==================== // ==================== 设备SN事件 ====================
on<DeviceControlSnFetched>(_onSnFetched); on<DeviceControlSnFetched>(_onSnFetched);
...@@ -121,6 +125,10 @@ class DeviceControlBloc ...@@ -121,6 +125,10 @@ class DeviceControlBloc
late final StreamSubscription<McuReportChangedEvent> _mcuReportSub; late final StreamSubscription<McuReportChangedEvent> _mcuReportSub;
late final StreamSubscription<BluetoothDataPacket> _bluetoothDataSub; late final StreamSubscription<BluetoothDataPacket> _bluetoothDataSub;
/// 上次设备告警分发时间(30s 防抖)
DateTime? _lastAlarmDispatchTime;
static const Duration _alarmDispatchInterval = Duration(seconds: 30);
// ==================== HydratedBloc 持久化 ==================== // ==================== HydratedBloc 持久化 ====================
@override @override
...@@ -244,6 +252,89 @@ class DeviceControlBloc ...@@ -244,6 +252,89 @@ class DeviceControlBloc
), ),
); );
emit(state.copyWith(data: newData, clearError: true)); emit(state.copyWith(data: newData, clearError: true));
// 异步分发:触发设备异常告警(MQTT 上行 + UI 通知)
_dispatchDeviceAlarm(newData);
}
// ==================== 设备异常告警分发 ====================
/// 每隔 [_alarmDispatchInterval](默认 30s)检查一次传感器值是否超出阈值,
/// 并通过 MQTT 上行告警、EventBus 通知 UI 层插入/更新设备告警。
void _dispatchDeviceAlarm(DeviceControlBO data) {
// 30s 防抖
final now = DateTime.now();
if (_lastAlarmDispatchTime != null &&
now.difference(_lastAlarmDispatchTime!) < _alarmDispatchInterval) {
return;
}
_lastAlarmDispatchTime = now;
// 获取全局阈值配置
final thresholdBloc = globalThresholdConfigBloc;
if (thresholdBloc == null) return;
// 设备 SN 为空时无法上行 MQTT 和通知 UI
final sn = data.sn;
if (sn == null || sn.isEmpty) return;
// 解析传感器值(温度 ×10 对齐阈值精度)
final tempDouble = double.tryParse(data.cabinTemp.currentValue);
final temp = tempDouble != null ? (tempDouble * 10).round() : null;
final humidity = int.tryParse(data.cabinHumidity.currentValue);
final oxygen = int.tryParse(data.oxygenConcentration.currentValue);
final config = thresholdBloc.state.config;
// 6 个独立阈值判断(温度阈值 ×10 对齐)
final tempUpper = temp != null && temp > config.tempMax * 10;
final tempLower = temp != null && temp < config.tempMin * 10;
final humiUpper = humidity != null && humidity > config.humidityMax;
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;
// MQTT 上行(仅存在告警时才发送)
final mqttClient = globalMqttClient;
if (hasAnyAlarm && mqttClient != null && mqttClient.isConnected) {
mqttClient.publishDeviceAlarm(
deviceSn: sn,
temperatureAlarmUpper: tempUpper,
temperatureAlarmLower: tempLower,
humidityAlarmUpper: humiUpper,
humidityAlarmLower: humiLower,
oxygenAlarmUpper: oxyUpper,
oxygenAlarmLower: oxyLower,
);
}
// 通过 EventBus 通知 UI 层
if (hasAnyAlarm) {
final descParts = <String>[];
if (tempUpper) descParts.add('温度过高');
if (tempLower) descParts.add('温度过低');
if (humiUpper) descParts.add('湿度过高');
if (humiLower) descParts.add('湿度过低');
if (oxyUpper) descParts.add('氧浓度过高');
if (oxyLower) descParts.add('氧浓度过低');
final alarmInfo = AlertInfoBO(
title: '设备传感器异常',
description: descParts.join('、'),
time: DateFormat('yyyy-MM-dd HH:mm:ss').format(now),
status: '待处理',
isDeviceAlarm: true,
);
eventBus.emit(
DeviceSensorAlarmEvent(sn: sn, alarmInfo: alarmInfo),
);
} else {
eventBus.emit(
DeviceSensorAlarmEvent(sn: sn),
);
}
} }
// ==================== 设备SN事件处理 ==================== // ==================== 设备SN事件处理 ====================
......
...@@ -278,3 +278,8 @@ class DeviceThresholdConfigBloc extends HydratedBloc<DeviceThresholdConfigEvent, ...@@ -278,3 +278,8 @@ class DeviceThresholdConfigBloc extends HydratedBloc<DeviceThresholdConfigEvent,
}; };
} }
} }
/// 全局阈值配置 Bloc 引用。
///
/// 由 main.dart 在创建时写入,[DeviceControlBloc._dispatchDeviceAlarm] 通过此引用读取阈值。
DeviceThresholdConfigBloc? globalThresholdConfigBloc;
...@@ -90,8 +90,12 @@ class _MyAppState extends State<MyApp> { ...@@ -90,8 +90,12 @@ class _MyAppState extends State<MyApp> {
DeviceControlBloc get _deviceControlBlocInstance => DeviceControlBloc get _deviceControlBlocInstance =>
_deviceControlBloc ??= DeviceControlBloc(); _deviceControlBloc ??= DeviceControlBloc();
DeviceThresholdConfigBloc get _deviceThresholdConfigBlocInstance => DeviceThresholdConfigBloc get _deviceThresholdConfigBlocInstance {
_deviceThresholdConfigBloc ??= DeviceThresholdConfigBloc(); if (_deviceThresholdConfigBloc != null) return _deviceThresholdConfigBloc!;
_deviceThresholdConfigBloc = DeviceThresholdConfigBloc();
globalThresholdConfigBloc = _deviceThresholdConfigBloc;
return _deviceThresholdConfigBloc!;
}
@override @override
void initState() { void initState() {
......
...@@ -125,12 +125,16 @@ class AlertInfoBO extends Equatable { ...@@ -125,12 +125,16 @@ class AlertInfoBO extends Equatable {
/// MQTT 下发告警记录 ID,上行 camera/{SN}/up 时使用 /// MQTT 下发告警记录 ID,上行 camera/{SN}/up 时使用
final int? alarmId; final int? alarmId;
/// 是否为设备传感器告警(区别于摄像头告警)
final bool isDeviceAlarm;
const AlertInfoBO({ const AlertInfoBO({
required this.title, required this.title,
required this.description, required this.description,
required this.time, required this.time,
this.status = '已处理', this.status = '已处理',
this.alarmId, this.alarmId,
this.isDeviceAlarm = false,
}); });
factory AlertInfoBO.fromJson(Map<String, dynamic> json) { factory AlertInfoBO.fromJson(Map<String, dynamic> json) {
...@@ -140,11 +144,41 @@ class AlertInfoBO extends Equatable { ...@@ -140,11 +144,41 @@ class AlertInfoBO extends Equatable {
time: json['time'] as String? ?? '', time: json['time'] as String? ?? '',
status: json['status'] as String? ?? '已处理', status: json['status'] as String? ?? '已处理',
alarmId: json['alarmId'] as int?, alarmId: json['alarmId'] as int?,
isDeviceAlarm: json['isDeviceAlarm'] as bool? ?? false,
);
}
Map<String, dynamic> toJson() {
return {
'title': title,
'description': description,
'time': time,
'status': status,
'alarmId': alarmId,
'isDeviceAlarm': isDeviceAlarm,
};
}
AlertInfoBO copyWith({
String? title,
String? description,
String? time,
String? status,
int? alarmId,
bool? isDeviceAlarm,
}) {
return AlertInfoBO(
title: title ?? this.title,
description: description ?? this.description,
time: time ?? this.time,
status: status ?? this.status,
alarmId: alarmId ?? this.alarmId,
isDeviceAlarm: isDeviceAlarm ?? this.isDeviceAlarm,
); );
} }
@override @override
List<Object?> get props => [title, description, time, status, alarmId]; List<Object?> get props => [title, description, time, status, alarmId, isDeviceAlarm];
} }
/// 用途:首页监护舱右侧设备菜单数据 /// 用途:首页监护舱右侧设备菜单数据
......
...@@ -20,8 +20,8 @@ class CabinRepository { ...@@ -20,8 +20,8 @@ class CabinRepository {
// } // }
return DioRequest.instance.get<CabinDetailResponse>( return DioRequest.instance.get<CabinDetailResponse>(
'/icu/detail', '/icu/detail',
// queryParameters: {'carbinSn': carbinSn}, queryParameters: {'carbinSn': carbinSn},
queryParameters: {'carbinSn': 'CNA07212L'}, // queryParameters: {'carbinSn': 'CNA07212L'},
fromJsonT: (data) => fromJsonT: (data) =>
CabinDetailResponse.fromJson(data as Map<String, dynamic>), CabinDetailResponse.fromJson(data as Map<String, dynamic>),
); );
......
import 'dart:async'; import 'dart:async';
import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart'; import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/utils/bluetooth/index.dart'; import 'package:laki_icu_app/utils/bluetooth/index.dart';
class EventBus { class EventBus {
...@@ -49,3 +50,11 @@ class McuReportChangedEvent { ...@@ -49,3 +50,11 @@ class McuReportChangedEvent {
const McuReportChangedEvent(this.report); const McuReportChangedEvent(this.report);
} }
// 设备传感器告警事件(温度/湿度/氧浓度阈值异常)
class DeviceSensorAlarmEvent {
final String sn;
final AlertInfoBO? alarmInfo; // 非 null = 有异常,null = 传感器正常
const DeviceSensorAlarmEvent({required this.sn, this.alarmInfo});
}
...@@ -301,6 +301,42 @@ class LakiMqttClient { ...@@ -301,6 +301,42 @@ class LakiMqttClient {
); );
} }
/// 上行 传感器(设备)异常告警
///
/// 对应 topic: icu/{SN}/alarm → DeviceAlarmHandler
/// 仅当任一 alarm 字段为 true 时才调用此方法。
int publishDeviceAlarm({
required String deviceSn,
required bool temperatureAlarmUpper,
required bool temperatureAlarmLower,
required bool humidityAlarmUpper,
required bool humidityAlarmLower,
required bool oxygenAlarmUpper,
required bool oxygenAlarmLower,
MqttQos qos = MqttQos.atLeastOnce,
}) {
return publishJson(
LakiMqttTopics.icuAlarm(deviceSn),
{
'eventId': deviceSn,
'deviceVersionNo': '',
'deviceType': 'ICU',
'timestamp': DateTime.now().millisecondsSinceEpoch,
'type': 'alarm',
'data': {
'sn': deviceSn,
'temperatureAlarmUpper': temperatureAlarmUpper,
'temperatureAlarmLower': temperatureAlarmLower,
'humidityAlarmUpper': humidityAlarmUpper,
'humidityAlarmLower': humidityAlarmLower,
'oxygenAlarmUpper': oxygenAlarmUpper,
'oxygenAlarmLower': oxygenAlarmLower,
},
},
qos: qos,
);
}
/// 上行 出舱数据 /// 上行 出舱数据
/// create by stephen /// create by stephen
/// [deviceSn] 设备 SN,[type] 操作类型(unbindPet), /// [deviceSn] 设备 SN,[type] 操作类型(unbindPet),
...@@ -399,3 +435,9 @@ class LakiMqttClient { ...@@ -399,3 +435,9 @@ class LakiMqttClient {
} }
} }
} }
/// 全局 MQTT 客户端引用。
///
/// 由 [MonitoringIndexCubit] 在连接成功时写入,断开时清空。
/// [DeviceControlBloc._dispatchDeviceAlarm] 通过此引用获取 client 上行告警。
LakiMqttClient? globalMqttClient;
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:laki_icu_app/enums/video_stream_mode_enum.dart'; import 'package:laki_icu_app/enums/video_stream_mode_enum.dart';
import 'package:laki_icu_app/utils/date_utils.dart'; import 'package:laki_icu_app/utils/date_utils.dart';
...@@ -21,7 +21,7 @@ import 'monitoring_index_state.dart'; ...@@ -21,7 +21,7 @@ import 'monitoring_index_state.dart';
/// 设备凭据 —— 从舱详情接口获取 cameraSn 和 wifiPwd /// 设备凭据 —— 从舱详情接口获取 cameraSn 和 wifiPwd
class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { class MonitoringIndexCubit extends HydratedCubit<MonitoringIndexState> {
final CabinService _cabinService; final CabinService _cabinService;
final WebrtcService _webrtcService; final WebrtcService _webrtcService;
final P2pVideoService _p2pVideoService; final P2pVideoService _p2pVideoService;
...@@ -38,6 +38,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -38,6 +38,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
StreamSubscription<BluetoothDataPacket>? _bluetoothDataSub; StreamSubscription<BluetoothDataPacket>? _bluetoothDataSub;
StreamSubscription<BluetoothReadInfoChangedEvent>? _bluetoothReadInfoSub; StreamSubscription<BluetoothReadInfoChangedEvent>? _bluetoothReadInfoSub;
StreamSubscription<McuReportChangedEvent>? _mcuReportChangedSub; StreamSubscription<McuReportChangedEvent>? _mcuReportChangedSub;
StreamSubscription<DeviceSensorAlarmEvent>? _deviceAlarmSub;
StreamSubscription<LakiMqttConnectionState>? _mqttStateSub; StreamSubscription<LakiMqttConnectionState>? _mqttStateSub;
StreamSubscription<LakiMqttMessage>? _mqttMessageSub; StreamSubscription<LakiMqttMessage>? _mqttMessageSub;
StreamSubscription<String>? _mqttErrorSub; StreamSubscription<String>? _mqttErrorSub;
...@@ -75,11 +76,12 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -75,11 +76,12 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_listenBluetoothState(); _listenBluetoothState();
_listenBluetoothReadInfo(); _listenBluetoothReadInfo();
_listenMcuReportChanged(); _listenMcuReportChanged();
_listenDeviceSensorAlarm();
_loadBoundBluetoothDevice(); _loadBoundBluetoothDevice();
// // todo 测试使用 待删除 // // todo 测试使用 待删除
_fetchCabinDetailAndConnectVideo("CNA07212L"); // _fetchCabinDetailAndConnectVideo("CNA07212L");
_connectMqttForDeviceSn("CNA07212L"); // _connectMqttForDeviceSn("CNA07212L");
} }
/// 监听 WebRTC 连接状态变化并同步到 State /// 监听 WebRTC 连接状态变化并同步到 State
...@@ -198,6 +200,34 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -198,6 +200,34 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
}); });
} }
void _listenDeviceSensorAlarm() {
_deviceAlarmSub =
eventBus.on<DeviceSensorAlarmEvent>().listen(_onDeviceSensorAlarm);
}
void _onDeviceSensorAlarm(DeviceSensorAlarmEvent event) {
if (isClosed) return;
if (event.alarmInfo != null) {
// 有传感器异常:插入或更新设备告警
final existingIndex = state.alerts.indexWhere((a) => a.isDeviceAlarm);
if (existingIndex >= 0) {
// 已存在 → 替换,保留原有 status
final existingStatus = state.alerts[existingIndex].status;
final updatedAlerts = List<AlertInfoBO>.from(state.alerts);
updatedAlerts[existingIndex] =
event.alarmInfo!.copyWith(status: existingStatus);
emit(state.copyWith(alerts: updatedAlerts));
} else {
// 不存在 → 插入头部
emit(state.copyWith(alerts: [event.alarmInfo!, ...state.alerts]));
}
} else {
// 传感器恢复正常,不删除已有告警
// TODO: 后续业务需要持久化时在此补充持久化逻辑
}
}
void _listenBluetoothReadInfo() { void _listenBluetoothReadInfo() {
_bluetoothReadInfoSub = _bluetoothReadInfoSub =
eventBus.on<BluetoothReadInfoChangedEvent>().listen((event) { eventBus.on<BluetoothReadInfoChangedEvent>().listen((event) {
...@@ -638,6 +668,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -638,6 +668,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
logging: kDebugMode, logging: kDebugMode,
), ),
); );
globalMqttClient = _mqttClient;
_listenMqttState(_mqttClient!); _listenMqttState(_mqttClient!);
} }
...@@ -857,6 +888,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -857,6 +888,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_mqttErrorSub = null; _mqttErrorSub = null;
await _mqttClient?.dispose(); await _mqttClient?.dispose();
_mqttClient = null; _mqttClient = null;
globalMqttClient = null;
} }
List<MonitoringMetricBO> _metricsFromBluetoothReport( List<MonitoringMetricBO> _metricsFromBluetoothReport(
...@@ -1084,6 +1116,20 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -1084,6 +1116,20 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
emit(state.copyWith(selectedAlert: alert)); emit(state.copyWith(selectedAlert: alert));
} }
// ==================== HydratedCubit 持久化 ====================
@override
MonitoringIndexState? fromJson(Map<String, dynamic> json) {
return MonitoringIndexState.fromJson(json);
}
@override
Map<String, dynamic>? toJson(MonitoringIndexState state) {
return state.toJson();
}
// ==================== 告警操作 ====================
/// 清除选中的告警(用于关闭弹窗) /// 清除选中的告警(用于关闭弹窗)
void clearSelectedAlert() { void clearSelectedAlert() {
if (isClosed) return; if (isClosed) return;
...@@ -1092,36 +1138,60 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -1092,36 +1138,60 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
/// 标记告警为已处理 /// 标记告警为已处理
/// ///
/// 通过 MQTT camera/{SN}/up 上行处理结果,并从列表中移除。 /// 设备告警:仅从 alerts 中移除,不上行 MQTT camera。
/// 摄像头告警:通过 MQTT camera/{SN}/up 上行处理结果,并从列表中移除。
void markAlertAsProcessed(AlertInfoBO alert) { void markAlertAsProcessed(AlertInfoBO alert) {
if (isClosed) return; if (isClosed) return;
_publishCameraAlarmResult(alert, 'handled'); if (alert.isDeviceAlarm) {
final removedCameraAlerts = // 设备告警:不上行 MQTT,仅移除
state.cameraAlerts.where((item) => item != alert).toList(); final removedAlerts =
final removedAlerts = state.alerts.where((item) => item != alert).toList();
state.alerts.where((item) => item != alert).toList(); emit(state.copyWith(
emit(state.copyWith( alerts: removedAlerts,
cameraAlerts: removedCameraAlerts, clearSelectedAlert: true,
alerts: removedAlerts, ));
selectedAlert: null, } else {
)); // 摄像头告警:保持原有逻辑
_publishCameraAlarmResult(alert, 'handled');
final removedCameraAlerts =
state.cameraAlerts.where((item) => item != alert).toList();
final removedAlerts =
state.alerts.where((item) => item != alert).toList();
emit(state.copyWith(
cameraAlerts: removedCameraAlerts,
alerts: removedAlerts,
clearSelectedAlert: true,
));
}
} }
/// 标记告警为误报 /// 标记告警为误报
/// ///
/// 通过 MQTT camera/{SN}/up 上行处理结果,并从列表中移除。 /// 设备告警:仅从 alerts 中移除,不上行 MQTT camera。
/// 摄像头告警:通过 MQTT camera/{SN}/up 上行处理结果,并从列表中移除。
void markAlertAsFalse(AlertInfoBO alert) { void markAlertAsFalse(AlertInfoBO alert) {
if (isClosed) return; if (isClosed) return;
_publishCameraAlarmResult(alert, 'false_alarm'); if (alert.isDeviceAlarm) {
final removedCameraAlerts = // 设备告警:不上行 MQTT,仅移除
state.cameraAlerts.where((item) => item != alert).toList(); final removedAlerts =
final removedAlerts = state.alerts.where((item) => item != alert).toList();
state.alerts.where((item) => item != alert).toList(); emit(state.copyWith(
emit(state.copyWith( alerts: removedAlerts,
cameraAlerts: removedCameraAlerts, clearSelectedAlert: true,
alerts: removedAlerts, ));
selectedAlert: null, } else {
)); // 摄像头告警:保持原有逻辑
_publishCameraAlarmResult(alert, 'false_alarm');
final removedCameraAlerts =
state.cameraAlerts.where((item) => item != alert).toList();
final removedAlerts =
state.alerts.where((item) => item != alert).toList();
emit(state.copyWith(
cameraAlerts: removedCameraAlerts,
alerts: removedAlerts,
clearSelectedAlert: true,
));
}
} }
/// 上行 MQTT camera/{SN}/up 告警处理结果 /// 上行 MQTT camera/{SN}/up 告警处理结果
...@@ -1156,6 +1226,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -1156,6 +1226,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_bluetoothDataSub?.cancel(); _bluetoothDataSub?.cancel();
_bluetoothReadInfoSub?.cancel(); _bluetoothReadInfoSub?.cancel();
_mcuReportChangedSub?.cancel(); _mcuReportChangedSub?.cancel();
_deviceAlarmSub?.cancel();
_webrtcService.dispose(); _webrtcService.dispose();
_p2pVideoService.dispose(); _p2pVideoService.dispose();
return _disposeMqttClient().then((_) => super.close()); return _disposeMqttClient().then((_) => super.close());
......
...@@ -189,6 +189,28 @@ class MonitoringIndexState extends Equatable { ...@@ -189,6 +189,28 @@ class MonitoringIndexState extends Equatable {
); );
} }
/// 从本地持久化 JSON 反序列化(仅恢复 alerts 和 cameraAlerts)
factory MonitoringIndexState.fromJson(Map<String, dynamic> json) {
return MonitoringIndexState(
alerts: (json['alerts'] as List<dynamic>?)
?.map((e) => AlertInfoBO.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
cameraAlerts: (json['cameraAlerts'] as List<dynamic>?)
?.map((e) => AlertInfoBO.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
);
}
/// 序列化为本地持久化 JSON(仅持久化 alerts 和 cameraAlerts)
Map<String, dynamic> toJson() {
return {
'alerts': alerts.map((e) => e.toJson()).toList(),
'cameraAlerts': cameraAlerts.map((e) => e.toJson()).toList(),
};
}
@override @override
List<Object?> get props => [ List<Object?> get props => [
status, status,
......
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