Files
flutterApp/lib/features/devices/presentation/bloc/device_status_bloc.dart

197 lines
7.9 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 'device_status_event.dart';
import 'device_status_state.dart';
class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
final NetMessageDispatcher _dispatcher;
final ILoggerService _logger = GetIt.I<ILoggerService>();
// 🔥 保存订阅引用,用于管理生命周期
StreamSubscription? _tcpSubscription;
DeviceStatusBloc(this._dispatcher) : super(DeviceStatusInitial()) {
// 🔥 核心改动:直接在构造函数中建立TCP监听,类似RemoteControlCubit
_initDirectTcpListener();
// 保留事件处理(用于手动重置等场景)
on<DeviceStatusReset>(_handleReset);
on<DeviceStatusLoaded>(_handleDeviceStatusLoaded);
on<PushMessageReceived>(_handlePushMessageReceived);
}
// 🔥 新增:重新初始化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] 取消旧订阅');
_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('🔄 [DeviceStatusBloc] 收到重置事件:清空状态');
_logger.log('🔄 [DeviceStatusBloc] 收到重置事件:清空状态');
// 只 emit 初始状态,让 UI 清除旧设备的数据
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();
// 🔥 关键修复:不调用 super.close(),保持 BLoC 活跃
return Future.value();
}
}