Files
feature-next-arch/lib/features/devices/presentation/bloc/device_status_bloc.dart
Songzex 354f7d6290 优化了tcp实时更新对UI线程的压力和对用弹窗的重复弹窗的影响。
更换了路径规划的的经纬度的字段名和取操作的名字更换。
2026-06-08 13:35:36 +08:00

245 lines
9.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/running_status_entity.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/gps_entity.dart';
import 'package:maibu_satabot_v2/core/network/protocol_decoder.dart';
import '../../../../core/logging/i_logger_service.dart';
import '../../../../core/network/net_message_dispatcher.dart';
import '../../../../core/network/tcp/tcp_client.dart';
import 'device_status_event.dart';
import 'device_status_state.dart';
class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
final NetMessageDispatcher _dispatcher;
final TcpClient tcpClient; // 🔥 新增:直接访问 TcpClient
final ILoggerService _logger = GetIt.I<ILoggerService>();
// 🔥 保存订阅引用,用于管理生命周期
StreamSubscription? _tcpSubscription;
// 🔥 节流相关:500ms节流控制0x02数据推送频率
Timer? _throttleTimer;
static const _throttleDuration = Duration(milliseconds: 500);
RunningStatusEntity? _cachedStatus;
GPSEntity? _cachedGps;
DeviceStatusBloc(this._dispatcher, {TcpClient? client})
: tcpClient = client ?? GetIt.I<TcpClient>(),
super(DeviceStatusInitial()) {
// 🔥 核心改动:直接在构造函数中建立TCP监听,类似RemoteControlCubit
_initDirectTcpListener();
// 保留事件处理(用于手动重置等场景)
on<DeviceStatusReset>(_handleReset);
on<DeviceStatusLoaded>(_handleDeviceStatusLoaded);
on<PushMessageReceived>(_handlePushMessageReceived);
}
// 🔥 已废弃:重新初始化TCP监听器会导致数据流中断
// DeviceStatusBloc是单例,TCP监听器在构造函数中建立后应始终保持活跃
// 任何页面只需通过 BlocBuilder 或 stream 订阅状态即可
/*
void reinitializeListener() {
debugPrint('🔄 [DeviceStatusBloc] 重新初始化TCP监听器 - isClosed=$isClosed');
_logger.log('🔄 [DeviceStatusBloc] 重新初始化TCP监听器 - isClosed=$isClosed');
if (isClosed) {
debugPrint('❌ [DeviceStatusBloc] BLoC已关闭,无法重新初始化!');
_logger.log('❌ [DeviceStatusBloc] BLoC已关闭,无法重新初始化!');
return;
}
// ❌ 取消旧的订阅会导致数据流中断!
debugPrint('📝 [DeviceStatusBloc] 取消旧订阅');
_tcpSubscription?.cancel();
// ❌ 重置状态会清空UI显示的数据!
debugPrint('📝 [DeviceStatusBloc] emit Initial状态');
emit(DeviceStatusInitial());
// 重新建立监听
debugPrint('📝 [DeviceStatusBloc] 调用 _initDirectTcpListener');
_initDirectTcpListener();
}
*/
// 🔥 新增:直接监听TCP 0x02指令,实时解析并emit状态
void _initDirectTcpListener() {
debugPrint('🔗 [DeviceStatusBloc] 初始化直接TCP监听器 - 使用 tcpClient.packetStream');
_logger.log('🔗 [DeviceStatusBloc] 初始化直接TCP监听器');
// 🔥 关键修复:直接监听 tcpClient.packetStream,不经过 dispatcher 的 filtered stream
// 这样即使没有其他监听者,TCP流也不会暂停
_tcpSubscription = tcpClient.packetStream
.where((p) => p.command == 0x02)
.map((p) {
try {
final result = utf8.decode(p.payload, allowMalformed: true);
// debugPrint('✅ [DeviceStatusBloc] 收到0x02数据: $result');
return result;
} catch (e) {
// debugPrint('❌ [DeviceStatusBloc] 解码失败: $e');
return '';
}
})
.listen(
(message) {
//debugPrint('📩 [DeviceStatusBloc] 直接收到0x02数据,长度:${message.length}');
if (message.isEmpty) {
//debugPrint('⚠️ [DeviceStatusBloc] 消息为空,跳过');
return;
}
try {
// 🔥 直接解析,不经过事件转换
final fields = message.trim().split(',');
if (fields.length < 18) {
//debugPrint('⚠️ [DeviceStatusBloc] 字段不足:${fields.length},期望≥18');
// 🔥 错误不节流,立即emit以便UI显示错误
if (!isClosed) {
emit(DeviceStatusError('字段不足,期望≥18,实际:${fields.length}'));
}
return;
}
final status = RunningStatusEntity.fromFields(fields);
final gps = GPSEntity(status.latitude, status.longitude);
// 🔥 缓存最新数据用于节流发射
_cachedStatus = RunningStatusEntity(
voltage: status.voltage,
leftTargetSpeed: status.leftTargetSpeed,
rightTargetSpeed: status.rightTargetSpeed,
leftMeasureSpeed: status.leftMeasureSpeed,
rightMeasureSpeed: status.rightMeasureSpeed,
leftCurrent: status.leftCurrent,
rightCurrent: status.rightCurrent,
leftMotorTemp: status.leftMotorTemp,
rightMotorTemp: status.rightMotorTemp,
chipTemp: status.chipTemp,
yaw: status.yaw,
pitch: status.pitch,
roll: status.roll,
satelliteCnt: status.satelliteCnt,
qual: status.qual,
headingStatus: status.headingStatus,
latitude: status.latitude,
longitude: status.longitude,
timestamp: status.timestamp,
knifeCuttingSpeed: status.knifeCuttingSpeed,
controlMode: status.controlMode,
battery: status.battery,
workingArea: status.workingArea,
obstacleFlag: status.obstacleFlag,
);
_cachedGps = GPSEntity(status.latitude, status.longitude);
// 🔥 节流:取消之前的timer,重新计时500ms
_throttleTimer?.cancel();
_throttleTimer = Timer(_throttleDuration, () {
_emitCachedStatus();
});
} catch (e, stack) {
//debugPrint('❌ [DeviceStatusBloc] 直接解析异常:$e\n$stack');
// _logger.log('❌ [DeviceStatusBloc] 直接解析异常:$e');
// 🔥 解析错误不节流,立即emit以便UI显示错误
if (!isClosed) {
emit(DeviceStatusError('解析失败:$e'));
}
}
},
onDone: () => debugPrint('⚠️ [DeviceStatusBloc] TCP流已结束(onDone)'),
onError: (e) => debugPrint('❌ [DeviceStatusBloc] TCP流错误:$e'),
);
debugPrint('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成');
_logger.log('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成');
}
// 🔥 节流发射:500ms到期后发射缓存的最新数据
void _emitCachedStatus() {
if (_cachedStatus != null && _cachedGps != null && !isClosed) {
// debugPrint('📤 [DeviceStatusBloc] 🔥节流发射 - 电压:${_cachedStatus!.voltage}, 电量:${_cachedStatus!.battery}');
emit(DeviceStatusUpdated(_cachedStatus!, _cachedGps!));
}
}
// 🔥 重置时仅清空状态
Future<void> _handleReset(
DeviceStatusReset event,
Emitter<DeviceStatusState> emit,
) async {
debugPrint('🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}');
_logger.log(
'🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}',
);
// 只 emit 初始状态,让 UI 清除旧设备的数据
emit(DeviceStatusInitial());
debugPrint('⚠️ [DeviceStatusBloc] 已emit DeviceStatusInitial');
}
Future<void> _handleDeviceStatusLoaded(
DeviceStatusLoaded event,
Emitter<DeviceStatusState> emit,
) async {
try {
// debugPrint('🔍 开始解析数据:${event.jsonString}');
_logger.log('🔍 开始解析数据:${event.jsonString}');
final fields = event.jsonString.trim().split(',');
if (fields.length < 18) {
debugPrint('⚠️ 字段不足:${fields.length}');
emit(DeviceStatusError('字段不足,期望 ≥18,实际:${fields.length}'));
return;
}
final status = RunningStatusEntity.fromFields(fields);
final gps = GPSEntity(status.latitude, status.longitude);
//debugPrint('✅ 解析成功,更新状态:Lat=${gps.latitude}, Lng=${gps.longitude}');
_logger.log('✅ 解析成功,更新状态:Lat=${gps.latitude}, Lng=${gps.longitude}');
emit(DeviceStatusUpdated(status, gps));
} catch (e, stack) {
//debugPrint('❌ 解析异常:$e\n$stack');
_logger.log('❌ 解析异常:$e\n$stack');
emit(DeviceStatusError('解析失败:$e'));
}
}
Future<void> _handlePushMessageReceived(
PushMessageReceived event,
Emitter<DeviceStatusState> emit,
) async {
try {
final eventStr = event.jsonData['event'] ?? '';
final deviceId = event.jsonData['deviceId'] ?? '未知';
// debugPrint('收到推送事件:$eventStr, 设备:$deviceId');
_logger.log('收到推送事件:$eventStr, 设备:$deviceId');
// 这里可以根据需要 emit 新状态
} catch (e) {
emit(DeviceStatusError('解析推送消息失败:$e'));
}
}
@override
Future<void> close() {
debugPrint('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)');
_logger.log('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)');
//_tcpSubscription?.cancel();
// 🔥 清理节流timer和缓存
_throttleTimer?.cancel();
_throttleTimer = null;
_cachedStatus = null;
_cachedGps = null;
// 🔥 关键修复:不调用 super.close(),保持 BLoC 活跃
return Future.value();
}
}