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-06-24 14:44:16 +08:00
|
|
|
|
import 'package:maibu_satabot_v2/core/network/mqtt/domain/repositories/task_message_repository.dart';
|
2026-09-01 08:59:20 +08:00
|
|
|
|
import 'package:maibu_satabot_v2/core/network/mqtt/domain/repositories/mower_realtime_repository.dart';
|
2026-06-24 14:44:16 +08:00
|
|
|
|
import 'package:maibu_satabot_v2/core/network/mqtt/domain/entities/task_arrive_entity.dart';
|
2026-08-07 08:49:29 +08:00
|
|
|
|
import 'package:maibu_satabot_v2/core/network/mqtt/domain/entities/task_status_entity.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-09-01 08:59:20 +08:00
|
|
|
|
import '../../../../core/logging/log_time.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';
|
2026-06-24 14:44:16 +08:00
|
|
|
|
import 'devices_cubit.dart';
|
|
|
|
|
|
|
|
|
|
|
|
// 定义全局的TaskMessageRepository获取方式
|
|
|
|
|
|
TaskMessageRepository get _taskMessageRepo => GetIt.I<TaskMessageRepository>();
|
2026-02-27 15:33:59 +08:00
|
|
|
|
|
2026-09-01 08:59:20 +08:00
|
|
|
|
// 🔥 MQTT机器状态数据源(替代TCP 0x02)
|
|
|
|
|
|
MowerRealtimeRepository get _mowerRealtimeRepo =>
|
|
|
|
|
|
GetIt.I<MowerRealtimeRepository>();
|
|
|
|
|
|
|
2026-02-27 15:33:59 +08:00
|
|
|
|
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-06-24 14:44:16 +08:00
|
|
|
|
StreamSubscription? _mqttArriveSubscription;
|
2026-08-07 08:49:29 +08:00
|
|
|
|
StreamSubscription? _mqttStatusSubscription;
|
2026-02-27 15:33:59 +08:00
|
|
|
|
|
2026-09-01 08:59:20 +08:00
|
|
|
|
// 🔥 MQTT机器状态订阅(车辆实时+定位,替代TCP 0x02)
|
|
|
|
|
|
StreamSubscription? _mqttVehicleSubscription;
|
|
|
|
|
|
StreamSubscription? _mqttLocationSubscription;
|
|
|
|
|
|
|
2026-06-08 13:35:36 +08:00
|
|
|
|
// 🔥 节流相关:500ms节流控制0x02数据推送频率
|
|
|
|
|
|
Timer? _throttleTimer;
|
|
|
|
|
|
static const _throttleDuration = Duration(milliseconds: 500);
|
|
|
|
|
|
RunningStatusEntity? _cachedStatus;
|
|
|
|
|
|
GPSEntity? _cachedGps;
|
|
|
|
|
|
|
2026-08-07 08:49:29 +08:00
|
|
|
|
// 🔥 调试计数器:跟踪0x02收包序号,排查断断续续问题
|
|
|
|
|
|
int _packetSeq = 0;
|
|
|
|
|
|
|
2026-06-24 14:44:16 +08:00
|
|
|
|
// 🔥 当前监听的设备ID
|
|
|
|
|
|
String? _currentDeviceId;
|
|
|
|
|
|
|
2026-09-01 08:59:20 +08:00
|
|
|
|
// 🔥 MQTT机器状态收帧计数(用于链路日志)
|
|
|
|
|
|
int _mqttVehicleFrameCount = 0;
|
|
|
|
|
|
int _mqttLocationFrameCount = 0;
|
|
|
|
|
|
|
2026-06-08 13:35:36 +08:00
|
|
|
|
DeviceStatusBloc(this._dispatcher, {TcpClient? client})
|
|
|
|
|
|
: tcpClient = client ?? GetIt.I<TcpClient>(),
|
|
|
|
|
|
super(DeviceStatusInitial()) {
|
2026-04-17 17:29:31 +08:00
|
|
|
|
// 🔥 核心改动:直接在构造函数中建立TCP监听,类似RemoteControlCubit
|
2026-09-01 08:59:20 +08:00
|
|
|
|
// 🔥 已停用:机器状态数据源已改为MQTT(见 startMqttRealtimeListening,
|
|
|
|
|
|
// 由用户切换设备时触发)。TCP监听逻辑保留,回滚时取消下行注释即可。
|
|
|
|
|
|
// _initDirectTcpListener();
|
2026-02-27 15:33:59 +08:00
|
|
|
|
|
2026-06-24 14:44:16 +08:00
|
|
|
|
// 🔥 初始化MQTT到达点监听
|
|
|
|
|
|
_initMqttArriveListener();
|
|
|
|
|
|
|
2026-08-07 08:49:29 +08:00
|
|
|
|
// 🔥 初始化MQTT任务状态监听(接收完成推送)
|
|
|
|
|
|
_initMqttStatusListener();
|
|
|
|
|
|
|
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-06-24 14:44:16 +08:00
|
|
|
|
on<DeviceTaskArrivePointEvent>(_handleDeviceTaskArrivePointEvent);
|
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
|
2026-08-07 08:49:29 +08:00
|
|
|
|
.where((p) {
|
|
|
|
|
|
final is02 = p.command == 0x02;
|
|
|
|
|
|
if (is02) {
|
|
|
|
|
|
_packetSeq++;
|
|
|
|
|
|
debugPrint('📥 [0x02] #$_packetSeq 收到原始TCP包 | payload长度=${p.payload.length} | ${DateTime.now().toString().substring(11, 19)}');
|
|
|
|
|
|
}
|
|
|
|
|
|
return is02;
|
|
|
|
|
|
})
|
2026-06-08 13:35:36 +08:00
|
|
|
|
.map((p) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
final result = utf8.decode(p.payload, allowMalformed: true);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
} catch (e) {
|
2026-08-07 08:49:29 +08:00
|
|
|
|
debugPrint('❌ [0x02] #$_packetSeq 解码失败: $e, payload前20字节=${p.payload.take(20).toList()}');
|
2026-06-08 13:35:36 +08:00
|
|
|
|
return '';
|
2026-04-17 17:29:31 +08:00
|
|
|
|
}
|
2026-06-08 13:35:36 +08:00
|
|
|
|
})
|
|
|
|
|
|
.listen(
|
|
|
|
|
|
(message) {
|
|
|
|
|
|
if (message.isEmpty) {
|
2026-08-07 08:49:29 +08:00
|
|
|
|
debugPrint('⚠️ [0x02] #$_packetSeq 解码后为空,跳过');
|
2026-06-08 13:35:36 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-17 17:29:31 +08:00
|
|
|
|
|
2026-06-08 13:35:36 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 🔥 直接解析,不经过事件转换
|
|
|
|
|
|
final fields = message.trim().split(',');
|
|
|
|
|
|
|
|
|
|
|
|
if (fields.length < 18) {
|
2026-08-07 08:49:29 +08:00
|
|
|
|
debugPrint('⚠️ [0x02] #$_packetSeq 字段不足:${fields.length},期望≥18, 原始数据前100字符=${message.substring(0, message.length > 100 ? 100 : message.length)}');
|
2026-06-08 13:35:36 +08:00
|
|
|
|
// 🔥 错误不节流,立即emit以便UI显示错误
|
|
|
|
|
|
if (!isClosed) {
|
|
|
|
|
|
emit(DeviceStatusError('字段不足,期望≥18,实际:${fields.length}'));
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
final status = RunningStatusEntity.fromFields(fields);
|
|
|
|
|
|
final gps = GPSEntity(status.latitude, status.longitude);
|
|
|
|
|
|
|
2026-08-07 08:49:29 +08:00
|
|
|
|
debugPrint('✅ [0x02] #$_packetSeq 解析成功 | 字段数=${fields.length} | 电压=${status.voltage}V 电量=${status.battery}% 控制模式=${status.controlMode} | 节流等待${_throttleDuration.inMilliseconds}ms');
|
|
|
|
|
|
|
2026-06-08 13:35:36 +08:00
|
|
|
|
// 🔥 缓存最新数据用于节流发射
|
|
|
|
|
|
_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);
|
|
|
|
|
|
|
2026-08-07 08:49:29 +08:00
|
|
|
|
// 🔥 节流:如果定时器已在运行,只更新缓存不重置;否则启动新的500ms节流周期
|
|
|
|
|
|
if (_throttleTimer == null || !_throttleTimer!.isActive) {
|
|
|
|
|
|
_throttleTimer = Timer(_throttleDuration, () {
|
|
|
|
|
|
_emitCachedStatus();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-06-08 13:35:36 +08:00
|
|
|
|
} catch (e, stack) {
|
2026-08-07 08:49:29 +08:00
|
|
|
|
debugPrint('❌ [0x02] #$_packetSeq 解析异常:$e');
|
2026-06-08 13:35:36 +08:00
|
|
|
|
// _logger.log('❌ [DeviceStatusBloc] 直接解析异常:$e');
|
|
|
|
|
|
// 🔥 解析错误不节流,立即emit以便UI显示错误
|
|
|
|
|
|
if (!isClosed) {
|
|
|
|
|
|
emit(DeviceStatusError('解析失败:$e'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
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-09-01 08:59:20 +08:00
|
|
|
|
/// 🔥 MQTT机器状态监听(替代TCP 0x02数据源)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// [deviceSn] 与TCP时代 switchDevice / connectBySwitch 的设备号获取方式一致
|
|
|
|
|
|
/// (即 DeviceEntity.deviceName),由用户点击设备列表切换设备时调用。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 第一阶段:订阅并打印原始报文;确认格式后在第二阶段解析为
|
|
|
|
|
|
/// RunningStatusEntity + GPSEntity,复用现有500ms节流逻辑。
|
|
|
|
|
|
void startMqttRealtimeListening(String deviceSn) {
|
|
|
|
|
|
if (deviceSn.isEmpty) {
|
|
|
|
|
|
debugPrint('${LogTime.now()} ⚠️ [DeviceStatusBloc] MQTT订阅跳过:设备SN为空');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
debugPrint('${LogTime.now()} 🚀 [DeviceStatusBloc] 启动MQTT机器状态监听: $deviceSn');
|
|
|
|
|
|
|
|
|
|
|
|
// 按SN订阅(数据源内部自动退旧订新、同SN去重)
|
|
|
|
|
|
_mowerRealtimeRepo.startListening(deviceSn: deviceSn).then((result) {
|
|
|
|
|
|
result.fold(
|
|
|
|
|
|
(failure) => debugPrint(
|
|
|
|
|
|
'${LogTime.now()} ❌ [DeviceStatusBloc] MQTT机器状态订阅失败: ${failure.message}',
|
|
|
|
|
|
),
|
|
|
|
|
|
(_) => debugPrint(
|
|
|
|
|
|
'${LogTime.now()} ✅ [DeviceStatusBloc] MQTT机器状态订阅完成: $deviceSn',
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 切换设备时重置收帧计数,便于观察新设备的首帧与频率
|
|
|
|
|
|
_mqttVehicleFrameCount = 0;
|
|
|
|
|
|
_mqttLocationFrameCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 流监听只建立一次,第一阶段仅打印解析结果验证链路
|
|
|
|
|
|
_mqttVehicleSubscription ??= _mowerRealtimeRepo.vehicleStream.listen((
|
|
|
|
|
|
message,
|
|
|
|
|
|
) {
|
|
|
|
|
|
_mqttVehicleFrameCount++;
|
|
|
|
|
|
debugPrint(
|
|
|
|
|
|
'${LogTime.now()} 📊 [MQTT-机器状态] 车辆实时第$_mqttVehicleFrameCount帧 type=${message.type} 数据点=${message.data.length}',
|
|
|
|
|
|
);
|
|
|
|
|
|
// TODO 第二阶段:将数据点映射到 RunningStatusEntity 字段,复用500ms节流emit
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
_mqttLocationSubscription ??= _mowerRealtimeRepo.locationStream.listen((
|
|
|
|
|
|
location,
|
|
|
|
|
|
) {
|
|
|
|
|
|
_mqttLocationFrameCount++;
|
|
|
|
|
|
debugPrint(
|
|
|
|
|
|
'${LogTime.now()} 📍 [MQTT-location] 定位第$_mqttLocationFrameCount帧 lat=${location.latitude}, lng=${location.longitude}, heading=${location.heading}',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 核心:用 MQTT location 数据更新缓存并节流 emit
|
|
|
|
|
|
if (location.isValid) {
|
|
|
|
|
|
// 更新 _cachedGps
|
|
|
|
|
|
_cachedGps = GPSEntity(location.latitude!, location.longitude!);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新 _cachedStatus 中的航向角和坐标(复用已有缓存或新建空壳)
|
|
|
|
|
|
final base = _cachedStatus ?? RunningStatusEntity();
|
|
|
|
|
|
_cachedStatus = base.copyWith(
|
|
|
|
|
|
latitude: location.latitude!,
|
|
|
|
|
|
longitude: location.longitude!,
|
|
|
|
|
|
yaw: location.heading ?? base.yaw,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 500ms节流:与 TCP 0x02 时代逻辑一致
|
|
|
|
|
|
if (_throttleTimer == null || !_throttleTimer!.isActive) {
|
|
|
|
|
|
_throttleTimer = Timer(_throttleDuration, () {
|
|
|
|
|
|
_emitCachedStatus();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
debugPrint(
|
|
|
|
|
|
'${LogTime.now()} ⚠️ [MQTT-location] 坐标无效,跳过 - lat=${location.latitude}, lng=${location.longitude}',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
debugPrint('${LogTime.now()} ✅ [DeviceStatusBloc] MQTT机器状态监听已启动: $deviceSn');
|
|
|
|
|
|
_logger.log('✅ [DeviceStatusBloc] MQTT机器状态监听已启动: $deviceSn');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 14:44:16 +08:00
|
|
|
|
// 🔥 初始化MQTT到达点监听
|
|
|
|
|
|
void _initMqttArriveListener() {
|
|
|
|
|
|
debugPrint('🔗 [DeviceStatusBloc] 初始化MQTT到达点监听器');
|
|
|
|
|
|
_logger.log('🔗 [DeviceStatusBloc] 初始化MQTT到达点监听器');
|
|
|
|
|
|
|
|
|
|
|
|
_mqttArriveSubscription = _taskMessageRepo.taskArriveStream.listen(
|
|
|
|
|
|
(TaskArriveEntity arrive) {
|
|
|
|
|
|
debugPrint(
|
|
|
|
|
|
'📍 [DeviceStatusBloc] 收到MQTT到达点消息: type=${arrive.type}, deviceId=${arrive.deviceId}',
|
|
|
|
|
|
);
|
|
|
|
|
|
_logger.log(
|
|
|
|
|
|
'📍 [DeviceStatusBloc] 收到MQTT到达点消息: type=${arrive.type}, deviceId=${arrive.deviceId}',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤出路径规划到达点消息
|
|
|
|
|
|
if (arrive.type == 'device_task_arrive_point') {
|
|
|
|
|
|
// 检查设备ID是否匹配当前监听的设备
|
|
|
|
|
|
if (_currentDeviceId != null && arrive.deviceId != _currentDeviceId) {
|
|
|
|
|
|
debugPrint(
|
|
|
|
|
|
'⚠️ [DeviceStatusBloc] 设备ID不匹配,跳过 - 当前:$_currentDeviceId, 收到:${arrive.deviceId}',
|
|
|
|
|
|
);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取坐标(支持空entity场景)
|
|
|
|
|
|
double? lat = arrive.entity?.lat;
|
|
|
|
|
|
double? lng = arrive.entity?.lng;
|
|
|
|
|
|
|
|
|
|
|
|
// 发送事件到Bloc
|
|
|
|
|
|
add(
|
|
|
|
|
|
DeviceTaskArrivePointEvent(
|
|
|
|
|
|
deviceId: arrive.deviceId,
|
|
|
|
|
|
taskId: arrive.taskId,
|
|
|
|
|
|
lat: lat,
|
|
|
|
|
|
lng: lng,
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onError: (e) {
|
|
|
|
|
|
debugPrint('❌ [DeviceStatusBloc] MQTT到达点监听错误: $e');
|
|
|
|
|
|
_logger.log('❌ [DeviceStatusBloc] MQTT到达点监听错误: $e');
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
debugPrint('✅ [DeviceStatusBloc] MQTT到达点监听器已建立完成');
|
|
|
|
|
|
_logger.log('✅ [DeviceStatusBloc] MQTT到达点监听器已建立完成');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-07 08:49:29 +08:00
|
|
|
|
// 🔥 初始化MQTT任务状态监听(接收 task/+/status 完成推送)
|
|
|
|
|
|
void _initMqttStatusListener() {
|
|
|
|
|
|
debugPrint('🔗 [DeviceStatusBloc] 初始化MQTT任务状态监听器');
|
|
|
|
|
|
_logger.log('🔗 [DeviceStatusBloc] 初始化MQTT任务状态监听器');
|
|
|
|
|
|
|
|
|
|
|
|
_mqttStatusSubscription = _taskMessageRepo.taskStatusStream.listen(
|
|
|
|
|
|
(TaskStatusEntity status) {
|
|
|
|
|
|
debugPrint(
|
|
|
|
|
|
'📋 [DeviceStatusBloc] 收到MQTT任务状态: type=${status.type}, deviceId=${status.deviceId}, status=${status.status}',
|
|
|
|
|
|
);
|
|
|
|
|
|
_logger.log(
|
|
|
|
|
|
'📋 [DeviceStatusBloc] 收到MQTT任务状态: type=${status.type}, deviceId=${status.deviceId}, status=${status.status}',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 检查设备ID是否匹配当前监听的设备
|
|
|
|
|
|
if (_currentDeviceId != null && status.deviceId != _currentDeviceId) {
|
|
|
|
|
|
debugPrint(
|
|
|
|
|
|
'⚠️ [DeviceStatusBloc] 任务状态设备ID不匹配,跳过 - 当前:$_currentDeviceId, 收到:${status.deviceId}',
|
|
|
|
|
|
);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 状态为 FINISH 表示任务完成
|
|
|
|
|
|
if (status.status == 'FINISH') {
|
|
|
|
|
|
debugPrint('🏁 [DeviceStatusBloc] 收到任务完成推送,触发完成流程');
|
|
|
|
|
|
_logger.log('🏁 [DeviceStatusBloc] 收到任务完成推送,触发完成流程');
|
|
|
|
|
|
|
|
|
|
|
|
final devicesCubit = GetIt.I<DevicesCubit>();
|
|
|
|
|
|
devicesCubit.finishWork();
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 无论缓存是否有效,都必须 emit 触发 BlocBuilder 重建
|
|
|
|
|
|
if (!isClosed) {
|
|
|
|
|
|
emit(DeviceStatusUpdated(
|
|
|
|
|
|
_cachedStatus ?? RunningStatusEntity(),
|
|
|
|
|
|
_cachedGps ?? GPSEntity(0.0, 0.0),
|
|
|
|
|
|
));
|
|
|
|
|
|
debugPrint('📤 [DeviceStatusBloc] 已 emit 完成信号,触发 UI 更新');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Future.delayed(Duration(seconds: 1), () {
|
|
|
|
|
|
devicesCubit.resetWorkStatus();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onError: (e) {
|
|
|
|
|
|
debugPrint('❌ [DeviceStatusBloc] MQTT任务状态监听错误: $e');
|
|
|
|
|
|
_logger.log('❌ [DeviceStatusBloc] MQTT任务状态监听错误: $e');
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
debugPrint('✅ [DeviceStatusBloc] MQTT任务状态监听器已建立完成');
|
|
|
|
|
|
_logger.log('✅ [DeviceStatusBloc] MQTT任务状态监听器已建立完成');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 14:44:16 +08:00
|
|
|
|
// 🔥 设置当前监听的设备ID(用于过滤MQTT消息)
|
|
|
|
|
|
void setListeningDeviceId(String deviceId) {
|
|
|
|
|
|
debugPrint('📱 [DeviceStatusBloc] 设置监听设备ID: $deviceId');
|
|
|
|
|
|
_logger.log('📱 [DeviceStatusBloc] 设置监听设备ID: $deviceId');
|
|
|
|
|
|
_currentDeviceId = deviceId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 13:35:36 +08:00
|
|
|
|
// 🔥 节流发射:500ms到期后发射缓存的最新数据
|
|
|
|
|
|
void _emitCachedStatus() {
|
|
|
|
|
|
if (_cachedStatus != null && _cachedGps != null && !isClosed) {
|
2026-09-01 08:59:20 +08:00
|
|
|
|
debugPrint('📤 [节流发射] 电压=${_cachedStatus!.voltage}V 电量=${_cachedStatus!.battery}% 经纬度=(${_cachedGps!.latitude}, ${_cachedGps!.longitude}) yaw=${_cachedStatus!.yaw} | ${DateTime.now().toString().substring(11, 19)}');
|
2026-06-08 13:35:36 +08:00
|
|
|
|
emit(DeviceStatusUpdated(_cachedStatus!, _cachedGps!));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-17 17:29:31 +08:00
|
|
|
|
// 🔥 重置时仅清空状态
|
2026-06-08 13:35:36 +08:00
|
|
|
|
Future<void> _handleReset(
|
|
|
|
|
|
DeviceStatusReset event,
|
|
|
|
|
|
Emitter<DeviceStatusState> emit,
|
|
|
|
|
|
) async {
|
2026-04-22 15:16:30 +08:00
|
|
|
|
debugPrint('🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}');
|
2026-06-08 13:35:36 +08:00
|
|
|
|
_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(
|
2026-06-08 13:35:36 +08:00
|
|
|
|
DeviceStatusLoaded event,
|
|
|
|
|
|
Emitter<DeviceStatusState> emit,
|
|
|
|
|
|
) async {
|
2026-02-27 15:33:59 +08:00
|
|
|
|
try {
|
2026-06-08 13:35:36 +08:00
|
|
|
|
// debugPrint('🔍 开始解析数据:${event.jsonString}');
|
2026-04-15 08:43:39 +08:00
|
|
|
|
_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(
|
2026-06-08 13:35:36 +08:00
|
|
|
|
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-06-08 13:35:36 +08:00
|
|
|
|
// debugPrint('收到推送事件:$eventStr, 设备:$deviceId');
|
2026-04-15 08:43:39 +08:00
|
|
|
|
_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-06-24 14:44:16 +08:00
|
|
|
|
// 🔥 处理MQTT到达点事件
|
|
|
|
|
|
Future<void> _handleDeviceTaskArrivePointEvent(
|
|
|
|
|
|
DeviceTaskArrivePointEvent event,
|
|
|
|
|
|
Emitter<DeviceStatusState> emit,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
debugPrint(
|
|
|
|
|
|
'📍 [DeviceStatusBloc] 处理到达点事件: deviceId=${event.deviceId}, taskId=${event.taskId}, lat=${event.lat}, lng=${event.lng}',
|
|
|
|
|
|
);
|
|
|
|
|
|
_logger.log(
|
|
|
|
|
|
'📍 [DeviceStatusBloc] 处理到达点事件: deviceId=${event.deviceId}, taskId=${event.taskId}, lat=${event.lat}, lng=${event.lng}',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (event.lat != null && event.lng != null && event.lat != 0.0) {
|
|
|
|
|
|
// ✅ 收到有效到达点 - 更新位置状态
|
|
|
|
|
|
final gps = GPSEntity(event.lat!, event.lng!);
|
|
|
|
|
|
|
|
|
|
|
|
// 发送到DevicesCubit更新到达位置(触发地图轨迹更新)
|
|
|
|
|
|
final devicesCubit = GetIt.I<DevicesCubit>();
|
|
|
|
|
|
devicesCubit.setArrivedLocation(event.lat!, event.lng!);
|
|
|
|
|
|
|
|
|
|
|
|
// 同时更新DeviceStatusBloc的状态
|
|
|
|
|
|
emit(DeviceStatusUpdated(_cachedStatus ?? RunningStatusEntity(), gps));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// ❌ entity为空或坐标无效 - 表示任务停止
|
|
|
|
|
|
debugPrint('🛑 [DeviceStatusBloc] 收到任务停止信号');
|
|
|
|
|
|
_logger.log('🛑 [DeviceStatusBloc] 收到任务停止信号');
|
|
|
|
|
|
|
|
|
|
|
|
final devicesCubit = GetIt.I<DevicesCubit>();
|
|
|
|
|
|
devicesCubit.finishWork();
|
2026-08-07 08:49:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 🔥 关键:无论缓存是否有效,都必须 emit 触发 BlocBuilder 重建
|
|
|
|
|
|
if (!isClosed) {
|
|
|
|
|
|
emit(DeviceStatusUpdated(
|
|
|
|
|
|
_cachedStatus ?? RunningStatusEntity(),
|
|
|
|
|
|
_cachedGps ?? GPSEntity(0.0, 0.0),
|
|
|
|
|
|
));
|
|
|
|
|
|
debugPrint('📤 [DeviceStatusBloc] 已 emit 停止信号,触发 UI 更新');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 14:44:16 +08:00
|
|
|
|
Future.delayed(Duration(seconds: 1), () {
|
|
|
|
|
|
devicesCubit.resetWorkStatus();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 19:34:17 +08:00
|
|
|
|
@override
|
|
|
|
|
|
Future<void> close() {
|
2026-06-24 14:44:16 +08:00
|
|
|
|
debugPrint('🚫 [DeviceStatusBloc] 页面退出,清理资源');
|
|
|
|
|
|
_logger.log('🚫 [DeviceStatusBloc] 页面退出,清理资源');
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 清理MQTT订阅
|
|
|
|
|
|
_mqttArriveSubscription?.cancel();
|
|
|
|
|
|
_mqttArriveSubscription = null;
|
2026-08-07 08:49:29 +08:00
|
|
|
|
_mqttStatusSubscription?.cancel();
|
|
|
|
|
|
_mqttStatusSubscription = null;
|
2026-06-24 14:44:16 +08:00
|
|
|
|
|
2026-09-01 08:59:20 +08:00
|
|
|
|
// 🔥 清理MQTT机器状态订阅
|
|
|
|
|
|
_mqttVehicleSubscription?.cancel();
|
|
|
|
|
|
_mqttVehicleSubscription = null;
|
|
|
|
|
|
_mqttLocationSubscription?.cancel();
|
|
|
|
|
|
_mqttLocationSubscription = null;
|
|
|
|
|
|
|
2026-06-08 13:35:36 +08:00
|
|
|
|
// 🔥 清理节流timer和缓存
|
|
|
|
|
|
_throttleTimer?.cancel();
|
|
|
|
|
|
_throttleTimer = null;
|
|
|
|
|
|
_cachedStatus = null;
|
|
|
|
|
|
_cachedGps = null;
|
2026-06-24 14:44:16 +08:00
|
|
|
|
|
|
|
|
|
|
// 🔥 重置设备ID
|
|
|
|
|
|
_currentDeviceId = null;
|
|
|
|
|
|
|
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
|
|
|
|
}
|