重构机器状态页面的数据获取设定保证数据及时显示
This commit is contained in:
@@ -47,6 +47,7 @@ class NetMessageDispatcher {
|
||||
Stream<String> onStringMessage() {
|
||||
//print("0x02--TCP拦截推送解析开始");
|
||||
_logger.log('0x02--TCP拦截推送解析开始');
|
||||
// 🔥 关键修复:使用asBroadcastStream()确保多个监听者都能收到数据
|
||||
return onCommand(0x02).map((p) {
|
||||
try {
|
||||
// 尝试解码
|
||||
@@ -59,7 +60,7 @@ class NetMessageDispatcher {
|
||||
_logger.log('❌ 解码 0x02 失败:$e, 原始字节:${p.payload}');
|
||||
return ''; // 返回空字符串,避免流中断
|
||||
}
|
||||
});
|
||||
}).asBroadcastStream();
|
||||
/* ///return onCommand(0x02).map((p) => utf8.decode(p.payload));*/
|
||||
}
|
||||
|
||||
|
||||
@@ -16,73 +16,130 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
|
||||
final NetMessageDispatcher _dispatcher;
|
||||
final ILoggerService _logger = GetIt.I<ILoggerService>();
|
||||
|
||||
|
||||
// 持有订阅引用,仅在 close 时取消
|
||||
StreamSubscription? _stringSub;
|
||||
StreamSubscription? _jsonSub;
|
||||
|
||||
// 🔥 新增:标记是否已初始化订阅,防止重复订阅
|
||||
bool _isSubscribed = false;
|
||||
// 🔥 保存订阅引用,用于管理生命周期
|
||||
StreamSubscription? _tcpSubscription;
|
||||
|
||||
DeviceStatusBloc(this._dispatcher) : super(DeviceStatusInitial()) {
|
||||
// 1. 初始建立订阅(终身有效,除非 Bloc 关闭)
|
||||
_setupStreamListeners();
|
||||
// 🔥 核心改动:直接在构造函数中建立TCP监听,类似RemoteControlCubit
|
||||
_initDirectTcpListener();
|
||||
|
||||
// 2. 注册事件处理
|
||||
// 保留事件处理(用于手动重置等场景)
|
||||
on<DeviceStatusReset>(_handleReset);
|
||||
on<DeviceStatusLoaded>(_handleDeviceStatusLoaded);
|
||||
on<PushMessageReceived>(_handlePushMessageReceived);
|
||||
}
|
||||
|
||||
// 🔥 核心修复:订阅逻辑只执行一次,不再随意 cancel/relisten
|
||||
void _setupStreamListeners() {
|
||||
if (_isSubscribed) {
|
||||
return; // 如果已经订阅过,直接返回,避免重复操作
|
||||
// 🔥 新增:重新初始化TCP监听器(用于页面重新进入时调用)
|
||||
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] 初始化 TCP 数据流订阅(终身有效)');
|
||||
|
||||
|
||||
// 订阅字符串流 (0x02)
|
||||
_stringSub = _dispatcher.onStringMessage().listen(
|
||||
(jsonString) {
|
||||
debugPrint('Bloc层已经!!收到 0x02 数据事件,长度:${jsonString.length}');
|
||||
if (!isClosed) {
|
||||
add(DeviceStatusLoaded(jsonString));
|
||||
}
|
||||
},
|
||||
onDone: () => debugPrint('⚠️ [Bloc] 0x02 流已结束 (onDone) - 这通常意味着底层 TCP 彻底关闭'),
|
||||
onError: (e) => debugPrint('❌ [Bloc] 0x02 流发生错误:$e'),
|
||||
);
|
||||
|
||||
// 订阅 JSON 流 (0x12)
|
||||
// _jsonSub = _dispatcher.onJsonMessage(0x12).listen(
|
||||
// (jsonData) {
|
||||
// debugPrint('📩 [Bloc] 收到 JSON 数据事件:$jsonData');
|
||||
// if (!isClosed) {
|
||||
// add(PushMessageReceived(jsonData));
|
||||
// }
|
||||
// },
|
||||
// onDone: () => debugPrint('⚠️ [Bloc] JSON 流已结束 (onDone)'),
|
||||
// onError: (e) => debugPrint('❌ [Bloc] JSON 流发生错误:$e'),
|
||||
// );
|
||||
|
||||
_isSubscribed = true;
|
||||
// debugPrint('✅ [DeviceStatusBloc] 订阅建立完成,将持续监听数据流');
|
||||
_logger.log('✅ [DeviceStatusBloc] 订阅建立完成,将持续监听数据流');
|
||||
|
||||
// 取消旧的订阅
|
||||
debugPrint('📝 [DeviceStatusBloc] 取消旧订阅');
|
||||
_tcpSubscription?.cancel();
|
||||
|
||||
// 重置状态为初始状态
|
||||
debugPrint('📝 [DeviceStatusBloc] emit Initial状态');
|
||||
emit(DeviceStatusInitial());
|
||||
|
||||
// 重新建立监听
|
||||
debugPrint('📝 [DeviceStatusBloc] 调用 _initDirectTcpListener');
|
||||
_initDirectTcpListener();
|
||||
}
|
||||
|
||||
// 🔥 核心修复:重置时仅清空状态,绝对不再触碰订阅关系
|
||||
// 🔥 新增:直接监听TCP 0x02指令,实时解析并emit状态
|
||||
void _initDirectTcpListener() {
|
||||
debugPrint('🔗 [DeviceStatusBloc] 初始化直接TCP监听器(类似RemoteControlCubit)');
|
||||
_logger.log('🔗 [DeviceStatusBloc] 初始化直接TCP监听器');
|
||||
|
||||
_tcpSubscription = _dispatcher.onStringMessage().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');
|
||||
if (!isClosed) {
|
||||
emit(DeviceStatusError('字段不足,期望≥18,实际:${fields.length}'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final status = RunningStatusEntity.fromFields(fields);
|
||||
final gps = GPSEntity(status.latitude, status.longitude);
|
||||
|
||||
//debugPrint('✅ [DeviceStatusBloc] 直接解析成功,更新状态:Lat=${gps.latitude}, Lng=${gps.longitude}');
|
||||
_logger.log('✅ [DeviceStatusBloc] 直接解析成功,更新状态');
|
||||
|
||||
// 🔥 关键修复:BLoC有Equatable去重机制,必须创建新对象才能触发UI更新
|
||||
if (!isClosed) {
|
||||
// 创建全新的status和gps对象,绕过Equatable去重
|
||||
final newStatus = 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,
|
||||
);
|
||||
final newGps = GPSEntity(status.latitude, status.longitude);
|
||||
emit(DeviceStatusUpdated(newStatus, newGps));
|
||||
}
|
||||
} catch (e, stack) {
|
||||
//debugPrint('❌ [DeviceStatusBloc] 直接解析异常:$e\n$stack');
|
||||
_logger.log('❌ [DeviceStatusBloc] 直接解析异常:$e');
|
||||
if (!isClosed) {
|
||||
emit(DeviceStatusError('解析失败:$e'));
|
||||
}
|
||||
}
|
||||
},
|
||||
onDone: () => debugPrint('⚠️ [DeviceStatusBloc] TCP流已结束(onDone)'),
|
||||
onError: (e) => debugPrint('❌ [DeviceStatusBloc] TCP流错误:$e'),
|
||||
);
|
||||
|
||||
debugPrint('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成');
|
||||
_logger.log('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成');
|
||||
}
|
||||
|
||||
// 🔥 重置时仅清空状态
|
||||
Future<void> _handleReset(DeviceStatusReset event, Emitter<DeviceStatusState> emit) async {
|
||||
// debugPrint('🔄 收到重置事件:仅清空状态,保持订阅活跃(不重连)');
|
||||
_logger.log('🔄 收到重置事件:仅清空状态,保持订阅活跃(不重连)');
|
||||
debugPrint('🔄 [DeviceStatusBloc] 收到重置事件:清空状态');
|
||||
_logger.log('🔄 [DeviceStatusBloc] 收到重置事件:清空状态');
|
||||
|
||||
|
||||
// 只 emit 初始状态,让 UI 清除旧设备的数据(如速度归零、轨迹清除)
|
||||
// 只 emit 初始状态,让 UI 清除旧设备的数据
|
||||
emit(DeviceStatusInitial());
|
||||
|
||||
// ❌ 严禁在此处调用 _setupStreamListeners() 或 cancel 订阅
|
||||
// 因为 TCP 重连期间数据流可能一直在推送,cancel 会导致关键首包丢失
|
||||
}
|
||||
|
||||
Future<void> _handleDeviceStatusLoaded(
|
||||
@@ -130,15 +187,10 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
// 只有在 Bloc 彻底销毁时才取消订阅
|
||||
//debugPrint('🚫 [DeviceStatusBloc] 正在关闭,取消所有订阅 ');
|
||||
// debugPrint('🚫 [DeviceStatusBloc] 正在关闭,取消所有订阅');
|
||||
//debugPrint('🔥 [DeviceStatusBloc] 销毁堆栈跟踪:\n${StackTrace.current}');
|
||||
// _stringSub?.cancel();
|
||||
// _jsonSub?.cancel();
|
||||
//return super.close();
|
||||
debugPrint('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)');
|
||||
_logger.log('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)');
|
||||
_tcpSubscription?.cancel();
|
||||
// 🔥 关键修复:不调用 super.close(),保持 BLoC 活跃
|
||||
return Future.value();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import '../../../../core/di/injection.dart';
|
||||
import '../../../../core/network/net_message_dispatcher.dart';
|
||||
import '../../../../core/network/protocol_decoder.dart';
|
||||
import '../../../devices/presentation/bloc/devices_cubit.dart';
|
||||
import '../../../devices/presentation/bloc/device_status_bloc.dart';
|
||||
import '../../../devices/presentation/bloc/device_status_cubit.dart';
|
||||
import '../../../devices/presentation/bloc/device_status_state.dart';
|
||||
|
||||
// 配置:数据超时时间(5秒)
|
||||
@@ -28,7 +28,10 @@ class RunningStatusPage extends StatefulWidget {
|
||||
State<RunningStatusPage> createState() => _RunningStatusPageState();
|
||||
}
|
||||
|
||||
class _RunningStatusPageState extends State<RunningStatusPage> {
|
||||
class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindingObserver {
|
||||
// 🔥 静态实例引用,用于生命周期回调
|
||||
static _RunningStatusPageState? _instance;
|
||||
|
||||
bool _isCardView = true;
|
||||
|
||||
// 仪表盘/折线图切换状态
|
||||
@@ -58,12 +61,31 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 初始化超时计时器
|
||||
// 🔥 注册静态实例
|
||||
_instance = this;
|
||||
debugPrint('🚀 [RunningStatusPage] initState 被调用');
|
||||
// 🔥 简化:直接重置状态,不需要重新初始化监听器
|
||||
context.read<DeviceStatusCubit>().reset();
|
||||
// 🔥 关键:立即启动超时计时器,如果5秒内没收到数据就显示"暂无数据"
|
||||
_startDataTimeoutTimer();
|
||||
|
||||
// 🔥 新增:监听应用生命周期
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
debugPrint('🔄 [RunningStatusPage] 应用恢复,强制刷新UI');
|
||||
_forceRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// 🔥 移除生命周期观察者
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_instance = null;
|
||||
// 销毁计时器,防止内存泄漏
|
||||
_dataTimeoutTimer?.cancel();
|
||||
super.dispose();
|
||||
@@ -819,7 +841,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
|
||||
body: CustomScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
slivers: [
|
||||
BlocBuilder<DeviceStatusBloc, DeviceStatusState>(
|
||||
BlocBuilder<DeviceStatusCubit, DeviceStatusState>(
|
||||
builder: (context, state) {
|
||||
// 新增:超时状态下显示--
|
||||
String qual = _isDataTimeout ? '--' : '--';
|
||||
@@ -927,7 +949,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
|
||||
),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: BlocBuilder<DeviceStatusBloc, DeviceStatusState>(
|
||||
child: BlocBuilder<DeviceStatusCubit, DeviceStatusState>(
|
||||
builder: (context, state) {
|
||||
debugPrint('🎨 [UI-Build] BlocBuilder 重建!当前状态类型:${state.runtimeType}');
|
||||
|
||||
@@ -942,6 +964,12 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
|
||||
if (!_isDataTimeout && state is DeviceStatusUpdated) {
|
||||
debugPrint('📈 [UI] 检测到 Updated 状态,准备追加图表数据');
|
||||
_appendChartData(state);
|
||||
// 🔥 关键:收到数据后立即重置超时计时器
|
||||
_startDataTimeoutTimer();
|
||||
// 如果之前是超时状态,现在恢复
|
||||
if (_isDataTimeout) {
|
||||
setState(() => _isDataTimeout = false);
|
||||
}
|
||||
}
|
||||
return Container(margin: const EdgeInsets.all(8), child: _isCardView ? _buildCardContentView(state) : _buildChartContentView(state));
|
||||
},
|
||||
@@ -992,6 +1020,18 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
|
||||
_voltageHistory.clear();
|
||||
_timeIndex = 0;
|
||||
}
|
||||
|
||||
// 🔥 新增:强制刷新UI(用于息屏恢复)
|
||||
void _forceRefresh() {
|
||||
if (mounted) {
|
||||
debugPrint('🔄 [RunningStatusPage] 执行强制刷新');
|
||||
setState(() {
|
||||
// 触发 UI 重建
|
||||
});
|
||||
// 重置超时计时器
|
||||
_startDataTimeoutTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== 带动画的圆形仪表盘 Widget ======================
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:maibu_satabot_v2/features/home/presentation/pages/productDesc.da
|
||||
|
||||
import '../../../../core/di/injection.dart';
|
||||
import '../../../../core/router/route_paths.dart';
|
||||
import '../../../devices/presentation/bloc/device_status_bloc.dart';
|
||||
import '../../../devices/presentation/bloc/device_status_cubit.dart';
|
||||
import '../../../remote_control/presentation/bloc/remote_control_cubit.dart';
|
||||
import '../../../remote_control/presentation/pages/remote_control_page.dart';
|
||||
import '../pages/route_plan_page.dart';
|
||||
@@ -33,7 +33,7 @@ class HomeRoutes {
|
||||
path: RoutePaths.runningStatus,
|
||||
// builder: (context, state) => const RunningStatusPage(),
|
||||
builder: (context, state) => BlocProvider(
|
||||
create: (_) => sl<DeviceStatusBloc>(), // 从 GetIt 获取单例
|
||||
create: (_) => DeviceStatusCubit(sl()), // 使用简化版 Cubit
|
||||
child: const RunningStatusPage(),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user