2026-03-05 21:07:33 +08:00
|
|
|
|
import 'dart:async';
|
2026-02-27 15:33:59 +08:00
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
|
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
2026-04-15 08:43:39 +08:00
|
|
|
|
import 'package:get_it/get_it.dart';
|
2026-02-27 15:33:59 +08:00
|
|
|
|
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';
|
2026-03-05 21:07:33 +08:00
|
|
|
|
import 'package:maibu_satabot_v2/core/network/protocol_decoder.dart';
|
2026-02-27 15:33:59 +08:00
|
|
|
|
|
2026-04-15 08:43:39 +08:00
|
|
|
|
import '../../../../core/logging/i_logger_service.dart';
|
2026-02-27 15:33:59 +08:00
|
|
|
|
import '../../../../core/network/net_message_dispatcher.dart';
|
2026-04-22 15:16:30 +08:00
|
|
|
|
import '../../../../core/network/tcp/tcp_client.dart';
|
2026-02-27 15:33:59 +08:00
|
|
|
|
import 'device_status_event.dart';
|
|
|
|
|
|
import 'device_status_state.dart';
|
|
|
|
|
|
|
|
|
|
|
|
class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
|
|
|
|
|
|
final NetMessageDispatcher _dispatcher;
|
2026-04-22 15:16:30 +08:00
|
|
|
|
final TcpClient tcpClient; // 🔥 新增:直接访问 TcpClient
|
2026-04-15 08:43:39 +08:00
|
|
|
|
final ILoggerService _logger = GetIt.I<ILoggerService>();
|
|
|
|
|
|
|
2026-04-17 17:29:31 +08:00
|
|
|
|
// 🔥 保存订阅引用,用于管理生命周期
|
|
|
|
|
|
StreamSubscription? _tcpSubscription;
|
2026-02-27 15:33:59 +08:00
|
|
|
|
|
2026-04-22 15:16:30 +08:00
|
|
|
|
DeviceStatusBloc(this._dispatcher, {TcpClient? client})
|
|
|
|
|
|
: tcpClient = client ?? GetIt.I<TcpClient>(),
|
|
|
|
|
|
super(DeviceStatusInitial()) {
|
2026-04-17 17:29:31 +08:00
|
|
|
|
// 🔥 核心改动:直接在构造函数中建立TCP监听,类似RemoteControlCubit
|
|
|
|
|
|
_initDirectTcpListener();
|
2026-02-27 15:33:59 +08:00
|
|
|
|
|
2026-04-17 17:29:31 +08:00
|
|
|
|
// 保留事件处理(用于手动重置等场景)
|
2026-03-05 21:07:33 +08:00
|
|
|
|
on<DeviceStatusReset>(_handleReset);
|
2026-03-03 19:34:17 +08:00
|
|
|
|
on<DeviceStatusLoaded>(_handleDeviceStatusLoaded);
|
|
|
|
|
|
on<PushMessageReceived>(_handlePushMessageReceived);
|
2026-03-05 21:07:33 +08:00
|
|
|
|
}
|
2026-03-03 19:34:17 +08:00
|
|
|
|
|
2026-04-22 15:16:30 +08:00
|
|
|
|
// 🔥 已废弃:重新初始化TCP监听器会导致数据流中断
|
|
|
|
|
|
// DeviceStatusBloc是单例,TCP监听器在构造函数中建立后应始终保持活跃
|
|
|
|
|
|
// 任何页面只需通过 BlocBuilder 或 stream 订阅状态即可
|
|
|
|
|
|
/*
|
2026-04-17 17:29:31 +08:00
|
|
|
|
void reinitializeListener() {
|
|
|
|
|
|
debugPrint('🔄 [DeviceStatusBloc] 重新初始化TCP监听器 - isClosed=$isClosed');
|
|
|
|
|
|
_logger.log('🔄 [DeviceStatusBloc] 重新初始化TCP监听器 - isClosed=$isClosed');
|
|
|
|
|
|
|
|
|
|
|
|
if (isClosed) {
|
|
|
|
|
|
debugPrint('❌ [DeviceStatusBloc] BLoC已关闭,无法重新初始化!');
|
|
|
|
|
|
_logger.log('❌ [DeviceStatusBloc] BLoC已关闭,无法重新初始化!');
|
|
|
|
|
|
return;
|
2026-03-05 21:07:33 +08:00
|
|
|
|
}
|
2026-04-17 17:29:31 +08:00
|
|
|
|
|
2026-04-22 15:16:30 +08:00
|
|
|
|
// ❌ 取消旧的订阅会导致数据流中断!
|
2026-04-17 17:29:31 +08:00
|
|
|
|
debugPrint('📝 [DeviceStatusBloc] 取消旧订阅');
|
|
|
|
|
|
_tcpSubscription?.cancel();
|
|
|
|
|
|
|
2026-04-22 15:16:30 +08:00
|
|
|
|
// ❌ 重置状态会清空UI显示的数据!
|
2026-04-17 17:29:31 +08:00
|
|
|
|
debugPrint('📝 [DeviceStatusBloc] emit Initial状态');
|
|
|
|
|
|
emit(DeviceStatusInitial());
|
|
|
|
|
|
|
|
|
|
|
|
// 重新建立监听
|
|
|
|
|
|
debugPrint('📝 [DeviceStatusBloc] 调用 _initDirectTcpListener');
|
|
|
|
|
|
_initDirectTcpListener();
|
|
|
|
|
|
}
|
2026-04-22 15:16:30 +08:00
|
|
|
|
*/
|
2026-03-03 19:34:17 +08:00
|
|
|
|
|
2026-04-17 17:29:31 +08:00
|
|
|
|
// 🔥 新增:直接监听TCP 0x02指令,实时解析并emit状态
|
|
|
|
|
|
void _initDirectTcpListener() {
|
2026-04-22 15:16:30 +08:00
|
|
|
|
debugPrint('🔗 [DeviceStatusBloc] 初始化直接TCP监听器 - 使用 tcpClient.packetStream');
|
2026-04-17 17:29:31 +08:00
|
|
|
|
_logger.log('🔗 [DeviceStatusBloc] 初始化直接TCP监听器');
|
|
|
|
|
|
|
2026-04-22 15:16:30 +08:00
|
|
|
|
// 🔥 关键修复:直接监听 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);
|
2026-06-05 19:06:21 +08:00
|
|
|
|
// debugPrint('✅ [DeviceStatusBloc] 收到0x02数据: $result');
|
2026-04-22 15:16:30 +08:00
|
|
|
|
return result;
|
|
|
|
|
|
} catch (e) {
|
2026-06-05 19:06:21 +08:00
|
|
|
|
// debugPrint('❌ [DeviceStatusBloc] 解码失败: $e');
|
2026-04-22 15:16:30 +08:00
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.listen(
|
|
|
|
|
|
(message) {
|
2026-04-17 17:29:31 +08:00
|
|
|
|
//debugPrint('📩 [DeviceStatusBloc] 直接收到0x02数据,长度:${message.length}');
|
2026-04-22 15:16:30 +08:00
|
|
|
|
|
2026-04-17 17:29:31 +08:00
|
|
|
|
if (message.isEmpty) {
|
|
|
|
|
|
//debugPrint('⚠️ [DeviceStatusBloc] 消息为空,跳过');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-15 08:43:39 +08:00
|
|
|
|
|
2026-04-17 17:29:31 +08:00
|
|
|
|
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}');
|
2026-06-05 19:06:21 +08:00
|
|
|
|
// _logger.log('✅ [DeviceStatusBloc] 直接解析成功,更新状态');
|
2026-04-17 17:29:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 🔥 关键修复: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);
|
2026-06-05 19:06:21 +08:00
|
|
|
|
// debugPrint('📤 [DeviceStatusBloc] emit DeviceStatusUpdated - 电压:${status.voltage}, 电量:${status.battery}, 模式:${status.controlMode}');
|
2026-04-17 17:29:31 +08:00
|
|
|
|
emit(DeviceStatusUpdated(newStatus, newGps));
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e, stack) {
|
|
|
|
|
|
//debugPrint('❌ [DeviceStatusBloc] 直接解析异常:$e\n$stack');
|
2026-06-05 19:06:21 +08:00
|
|
|
|
// _logger.log('❌ [DeviceStatusBloc] 直接解析异常:$e');
|
2026-04-17 17:29:31 +08:00
|
|
|
|
if (!isClosed) {
|
|
|
|
|
|
emit(DeviceStatusError('解析失败:$e'));
|
|
|
|
|
|
}
|
2026-03-05 21:07:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-04-17 17:29:31 +08:00
|
|
|
|
onDone: () => debugPrint('⚠️ [DeviceStatusBloc] TCP流已结束(onDone)'),
|
|
|
|
|
|
onError: (e) => debugPrint('❌ [DeviceStatusBloc] TCP流错误:$e'),
|
2026-03-05 21:07:33 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
2026-04-17 17:29:31 +08:00
|
|
|
|
debugPrint('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成');
|
|
|
|
|
|
_logger.log('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成');
|
2026-02-27 15:33:59 +08:00
|
|
|
|
}
|
2026-03-03 19:34:17 +08:00
|
|
|
|
|
2026-04-17 17:29:31 +08:00
|
|
|
|
// 🔥 重置时仅清空状态
|
2026-03-05 21:07:33 +08:00
|
|
|
|
Future<void> _handleReset(DeviceStatusReset event, Emitter<DeviceStatusState> emit) async {
|
2026-04-22 15:16:30 +08:00
|
|
|
|
debugPrint('🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}');
|
|
|
|
|
|
_logger.log('🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}');
|
2026-02-27 15:33:59 +08:00
|
|
|
|
|
2026-04-17 17:29:31 +08:00
|
|
|
|
// 只 emit 初始状态,让 UI 清除旧设备的数据
|
2026-03-05 21:07:33 +08:00
|
|
|
|
emit(DeviceStatusInitial());
|
2026-04-22 15:16:30 +08:00
|
|
|
|
debugPrint('⚠️ [DeviceStatusBloc] 已emit DeviceStatusInitial');
|
2026-03-03 19:34:17 +08:00
|
|
|
|
}
|
2026-03-05 21:07:33 +08:00
|
|
|
|
|
2026-03-03 19:34:17 +08:00
|
|
|
|
Future<void> _handleDeviceStatusLoaded(
|
|
|
|
|
|
DeviceStatusLoaded event,
|
|
|
|
|
|
Emitter<DeviceStatusState> emit,
|
|
|
|
|
|
) async {
|
2026-02-27 15:33:59 +08:00
|
|
|
|
try {
|
2026-04-15 08:43:39 +08:00
|
|
|
|
// debugPrint('🔍 开始解析数据:${event.jsonString}');
|
|
|
|
|
|
_logger.log('🔍 开始解析数据:${event.jsonString}');
|
2026-03-03 19:34:17 +08:00
|
|
|
|
final fields = event.jsonString.trim().split(',');
|
|
|
|
|
|
|
2026-02-27 15:33:59 +08:00
|
|
|
|
if (fields.length < 18) {
|
2026-03-05 21:07:33 +08:00
|
|
|
|
debugPrint('⚠️ 字段不足:${fields.length}');
|
2026-03-03 19:34:17 +08:00
|
|
|
|
emit(DeviceStatusError('字段不足,期望 ≥18,实际:${fields.length}'));
|
2026-02-27 15:33:59 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-03 19:34:17 +08:00
|
|
|
|
|
|
|
|
|
|
final status = RunningStatusEntity.fromFields(fields);
|
2026-02-27 15:33:59 +08:00
|
|
|
|
final gps = GPSEntity(status.latitude, status.longitude);
|
2026-03-03 19:34:17 +08:00
|
|
|
|
|
2026-04-15 08:43:39 +08:00
|
|
|
|
//debugPrint('✅ 解析成功,更新状态:Lat=${gps.latitude}, Lng=${gps.longitude}');
|
|
|
|
|
|
_logger.log('✅ 解析成功,更新状态:Lat=${gps.latitude}, Lng=${gps.longitude}');
|
2026-03-05 21:07:33 +08:00
|
|
|
|
emit(DeviceStatusUpdated(status, gps));
|
|
|
|
|
|
} catch (e, stack) {
|
2026-04-15 08:43:39 +08:00
|
|
|
|
//debugPrint('❌ 解析异常:$e\n$stack');
|
|
|
|
|
|
_logger.log('❌ 解析异常:$e\n$stack');
|
2026-03-05 21:07:33 +08:00
|
|
|
|
emit(DeviceStatusError('解析失败:$e'));
|
2026-02-27 15:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-03 19:34:17 +08:00
|
|
|
|
|
|
|
|
|
|
Future<void> _handlePushMessageReceived(
|
|
|
|
|
|
PushMessageReceived event,
|
|
|
|
|
|
Emitter<DeviceStatusState> emit,
|
|
|
|
|
|
) async {
|
2026-02-27 15:33:59 +08:00
|
|
|
|
try {
|
|
|
|
|
|
final eventStr = event.jsonData['event'] ?? '';
|
|
|
|
|
|
final deviceId = event.jsonData['deviceId'] ?? '未知';
|
2026-04-15 08:43:39 +08:00
|
|
|
|
// debugPrint('收到推送事件:$eventStr, 设备:$deviceId');
|
|
|
|
|
|
_logger.log('收到推送事件:$eventStr, 设备:$deviceId');
|
2026-03-05 21:07:33 +08:00
|
|
|
|
// 这里可以根据需要 emit 新状态
|
2026-02-27 15:33:59 +08:00
|
|
|
|
} catch (e) {
|
2026-03-03 19:34:17 +08:00
|
|
|
|
emit(DeviceStatusError('解析推送消息失败:$e'));
|
2026-02-27 15:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 19:34:17 +08:00
|
|
|
|
@override
|
|
|
|
|
|
Future<void> close() {
|
2026-04-17 17:29:31 +08:00
|
|
|
|
debugPrint('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)');
|
|
|
|
|
|
_logger.log('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)');
|
2026-04-22 15:16:30 +08:00
|
|
|
|
//_tcpSubscription?.cancel();
|
2026-04-17 17:29:31 +08:00
|
|
|
|
// 🔥 关键修复:不调用 super.close(),保持 BLoC 活跃
|
2026-03-05 21:07:33 +08:00
|
|
|
|
return Future.value();
|
2026-03-03 19:34:17 +08:00
|
|
|
|
}
|
2026-02-27 15:33:59 +08:00
|
|
|
|
}
|