终于解决了 第二次作业之后的 路径不清除的问题 ,一直保留着上一次作业发送的完成点

This commit is contained in:
mmc
2026-04-17 10:00:39 +08:00
parent 2aadaa0205
commit e3026f61bb
6 changed files with 157 additions and 170 deletions

View File

@@ -23,13 +23,11 @@ class NetMessageDispatcher {
final ILoggerService _logger = GetIt.I<ILoggerService>();
// 🔥 新增:ACK 握手状态管理 (仅新模式使用)
int _expectedPointIndex = -1; // 期望收到的点编号
bool _waitingForAck = false; // 是否正在等待 ACK 确认
bool _waitingForArrived = false; // 是否正在等待到达确认
int _currentPointIndex = -1; // 当前发送的点索引
int _expectedPointIndex = -1; // 期望收到的点编号
bool _waitingForAck = false; // 是否正在等待 ACK 确认
bool _waitingForArrived = false; // 是否正在等待到达确认
int _currentPointIndex = -1; // 当前发送的点索引
// 🔥 新增:设置期望收到的点编号(供 PathPlanner 调用)
void setExpectedPointIndex(int index) {
@@ -38,7 +36,6 @@ class NetMessageDispatcher {
_logger.log('[Dispatcher-ACK] 设置期望点编号:$index');
}
NetMessageDispatcher(this.tcpClient, this.routePlanningRepository, this._pathPlanningService, {this.getAppState});
/// 过滤特定指令的流
@@ -58,12 +55,12 @@ class NetMessageDispatcher {
_logger.log('✅ 解码 0x02 成功:$result');
return result;
} catch (e) {
// print('❌ 解码 0x02 失败:$e, 原始字节:${p.payload}');
// print('❌ 解码 0x02 失败:$e, 原始字节:${p.payload}');
_logger.log('❌ 解码 0x02 失败:$e, 原始字节:${p.payload}');
return ''; // 返回空字符串,避免流中断
}
});
/* ///return onCommand(0x02).map((p) => utf8.decode(p.payload));*/
/* ///return onCommand(0x02).map((p) => utf8.decode(p.payload));*/
}
/// 示例解析方法:将 0x12 指令解析为 JSON 并转为模型
@@ -72,15 +69,14 @@ class NetMessageDispatcher {
// }
Stream<Map<String, dynamic>> onJsonMessage(int cmd) {
//print('🔵 [Dispatcher] 开始监听 0x${cmd.toRadixString(16)} 指令的 JSON 流');
_logger.log('🔵 [Dispatcher] 监听 0x${cmd.toRadixString(16)} 指令的 JSON 流');
_logger.log('🔵 [Dispatcher] 监听 0x${cmd.toRadixString(16)} 指令的 JSON 流');
return onCommand(cmd).map((p) {
try {
final jsonString = utf8.decode(p.payload);
//print('🔵 [Dispatcher] 收到 0x${cmd.toRadixString(16)} 原始数据: $jsonString');
_logger
.log('🔵 [Dispatcher] 监听 0x${cmd.toRadixString(16)} 指令的 JSON 流');
_logger.log('🔵 [Dispatcher] 监听 0x${cmd.toRadixString(16)} 指令的 JSON 流');
final jsonMap = jsonDecode(jsonString);
// print('🔵 [Dispatcher] JSON 解析成功:$jsonMap');
// print('🔵 [Dispatcher] JSON 解析成功:$jsonMap');
_logger.log('🔵 [Dispatcher] JSON 解析成功:$jsonMap');
return jsonMap;
@@ -94,19 +90,19 @@ _logger
//监听路径规划的开始作业的指令的应答
// if (data[0] == (byte) 0xAB && data[1] == (byte) 0xAA && data[2] == (byte) 0x01) {
// System.out.println("=====下位机回复成功=====" + data[0] + " " + data[1] + " " + data[2] + " " + data[3] + " " +
// data[4] + " " + data[5] + " " + data[6] + " " + data[7] + " " + data[8] + " " + data[9]);
// if (data[5] == (byte) 0x01) {
// Point point = new Point();
// //发送下一个指令
// }
// if (data[0] == (byte) 0xAB && data[1] == (byte) 0xAA && data[2] == (byte) 0x01) {
// System.out.println("=====下位机回复成功=====" + data[0] + " " + data[1] + " " + data[2] + " " + data[3] + " " +
// data[4] + " " + data[5] + " " + data[6] + " " + data[7] + " " + data[8] + " " + data[9]);
// if (data[5] == (byte) 0x01) {
// Point point = new Point();
// //发送下一个指令
// }
/// 监听路径规划的开始作业指令的应答 (0x01 指令)
/// 协议格式:AB AA 01 [DATA] CRC(2) AA AB
/// 验证头部:data[0]=0xAB, data[1]=0xAA, data[2]=0x01
/// 检查状态位:data[5] == 0x01 表示回复成功
/* Stream<RawPacket> onPathPlanningResponse() {
/* Stream<RawPacket> onPathPlanningResponse() {
final devicesCubit = sl<DevicesCubit>();
debugPrint('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x01)');
@@ -196,7 +192,7 @@ _logger
return false;
});
}*/
///新版的ack和现有的融合
///新版的ack和现有的融合
/// 监听路径规划指令应答 (现有逻辑 - 不修改)
/// 协议格式:AB AA 01 [DATA] CRC(2) AA AB
/// 验证头部:data[0]=0xAB, data[1]=0xAA, data[2]=0x01
@@ -205,7 +201,7 @@ _logger
final devicesCubit = sl<DevicesCubit>();
//debugPrint('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x01) - 模式:${PathPlanningConfig.getModeDescription()}');
_logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x01) - 模式:${PathPlanningConfig.getModeDescription()}');
_logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x01) - 模式:${PathPlanningConfig.getModeDescription()}');
// 🔥 根据配置决定使用哪个流
if (PathPlanningConfig.useAckHandshake) {
//debugPrint('⚙️ [Dispatcher] 使用 ACK 握手机制');
@@ -219,7 +215,7 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
final currentState = getAppState!();
if (currentState.toString() == 'AppState.none') {
//debugPrint('⚠️ [Dispatcher] 当前 AppState 为 none,停止处理路径规划指令');
_logger.logWithLevel('⚠️ [Dispatcher] 当前 AppState 为 none,停止处理路径规划指令');
_logger.logWithLevel('⚠️ [Dispatcher] 当前 AppState 为 none,停止处理路径规划指令');
return false;
}
}
@@ -228,26 +224,24 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
0xAB, 0xAA, 0x01,
...packet.payload,
0x00, 0x00, // CRC 占位
0xAA, 0xAB
0xAA, 0xAB,
];
// 验证头部协议
if (fullData[0] == 0xAB &&
fullData[1] == 0xAA &&
fullData[2] == 0x01) {
if(fullData[5] == 0x02){
// debugPrint('下位机回复收到指令 - 完整数据包:${fullData.join(" ")}');
if (fullData[0] == 0xAB && fullData[1] == 0xAA && fullData[2] == 0x01) {
if (fullData[5] == 0x02) {
// debugPrint('下位机回复收到指令 - 完整数据包:${fullData.join(" ")}');
_logger.logWithLevel('下位机回复收到指令 - 完整数据包:${fullData.join(" ")}');
}
debugPrint('[Dispatcher] 下位机回复成功 - 完整数据包:${fullData.join(" ")}');
// 检查状态位 (索引 5 对应 payload 的第 2 个字节)
if (fullData.length > 5 && fullData[5] == 0x01) {
// debugPrint('✅ [Dispatcher] 状态位验证通过:0x01 - 可以发送下一个指令');
// debugPrint('✅ [Dispatcher] 状态位验证通过:0x01 - 可以发送下一个指令');
_logger.logWithLevel('✅ [Dispatcher] 状态位验证通过:0x01 - 可以发送下一个指令');
///取全局的路径规划发送实体 queue
/// 发送下一个指令 移除上一条数据
var queue = _pathPlanningService.getQueue();
var queue = _pathPlanningService.getQueue();
// 🔥 关键:必须先检查空队列!
if (queue.isEmpty) {
devicesCubit.finishWork();
@@ -260,31 +254,33 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
_logger.logWithLevel('⚠️ [Dispatcher] 队列已经为空,跳过本次响应处理');
return false;
}
///更新完成 jwd
var deviceAddPathPointModel = queue.first;
var d = deviceAddPathPointModel.latitude;
var e = deviceAddPathPointModel.longitude;
devicesCubit.setArrivedLocation(
deviceAddPathPointModel.latitude,
deviceAddPathPointModel.longitude
);
// debugPrint("准确更新完成的路径点经纬度,$d,$e");
devicesCubit.setArrivedLocation(deviceAddPathPointModel.latitude, deviceAddPathPointModel.longitude);
// debugPrint("准确更新完成的路径点经纬度,$d,$e");
_logger.logWithLevel("准确更新完成路径点经纬度,$d,$e");
//去除上一条数据
queue.removeFirst();
/// _pathPlanningService.updateQueue(queue as List<DeviceAddPathPointModel>);
_pathPlanningService.updateQueue(queue.toList());
DateTime now = DateTime.now();
String timeStr = "${now.hour.toString().padLeft(2, '0')}:"
String timeStr =
"${now.hour.toString().padLeft(2, '0')}:"
"${now.minute.toString().padLeft(2, '0')}:"
"${now.second.toString().padLeft(2, '0')}";
//debugPrint(' $timeStr [Dispatcher] 准备触发下一个路径点发送,队列剩余:${queue.length}');
_logger.logWithLevel(' $timeStr [Dispatcher] 触发下一个路径点发送,队列剩余:${queue.length}');
///如果还剩 0 个数据 就停止发送
if (queue.length==0) {
if (queue.length == 0) {
//debugPrint('⚠️ [Dispatcher] 队列已空,停止发送');
_logger.logWithLevel('⚠️ [Dispatcher] 队列已空,停止发送');
devicesCubit.setArrivedLocation(0.0, 0.0);
// final devicesCubit = sl<DevicesCubit>();
devicesCubit.finishWork();
@@ -293,6 +289,7 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
});
// 手动停止
routePlanningRepository.stopRoutePlanning();
//debugPrint('⚠️ [Dispatcher] 队列已空,发送停止指令已发送');
_logger.logWithLevel('⚠️ [Dispatcher] 状态位异常:${fullData.length > 5 ? fullData[5].toRadixString(16) : "N/A"}');
return false;
@@ -304,7 +301,7 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
_logger.logWithLevel('⚠️ [Dispatcher] 状态位异常:${fullData.length > 5 ? fullData[5].toRadixString(16) : "N/A"}');
}
} else {
// debugPrint('❌ [Dispatcher] 协议头验证失败');
// debugPrint('❌ [Dispatcher] 协议头验证失败');
_logger.logWithLevel('❌ [Dispatcher] 协议头验证失败');
}
@@ -313,14 +310,11 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
}
}
/// 监听特定状态的回复(可复用)
/// [commandCode] 指令码
/// [statusIndex] 状态字节在 payload 中的索引
/// [expectedStatus] 期望的状态值
/* Stream<RawPacket> onStatusResponse({
/* Stream<RawPacket> onStatusResponse({
required int commandCode,
required int statusIndex,
required int expectedStatus,
@@ -343,8 +337,8 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
});
}*/
/// 监听特定状态的回复(ack握手机制)
// 🔥 新增:ACK 握手机制 (新模式)
/// 监听特定状态的回复(ack握手机制)
// 🔥 新增:ACK 握手机制 (新模式)
/// 监听路径规划指令应答,实现完整的 ACK 握手机制
/// 协议格式:AB AA 01 [DATA] CRC(2) AA AB
/// 上传协议(0x01 路径规划上传):
@@ -373,14 +367,11 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
0xAB, 0xAA, 0x01,
...packet.payload,
0x00, 0x00, // CRC 占位
0xAA, 0xAB
0xAA, 0xAB,
];
// 验证头部协议
if (fullData[0] == 0xAB &&
fullData[1] == 0xAA &&
fullData[2] == 0x01) {
if (fullData[0] == 0xAB && fullData[1] == 0xAA && fullData[2] == 0x01) {
debugPrint('[Dispatcher-ACK] 收到路径规划应答 - 完整数据包:${fullData.join(" ")}');
// 🔥 检查数据包长度是否足够
@@ -406,10 +397,9 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
// ✅ 下位机回复"收到指令"
debugPrint('✅ [Dispatcher-ACK] ACK 确认收到 - 点编号:$pointIndex');
_waitingForAck = false;
_waitingForArrived = true; // 开始等待"已到达"信号
return false; // 继续监听,不返回给流
}
else if (status == 0x01) {
_waitingForArrived = true; // 开始等待"已到达"信号
return false; // 继续监听,不返回给流
} else if (status == 0x01) {
// ✅ 下位机回复"已到达"
debugPrint('✅ [Dispatcher-ACK] 已到达确认 - 点编号:$pointIndex');
_waitingForArrived = false;
@@ -430,10 +420,7 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
var deviceAddPathPointModel = queue.first;
var d = deviceAddPathPointModel.latitude;
var e = deviceAddPathPointModel.longitude;
devicesCubit.setArrivedLocation(
deviceAddPathPointModel.latitude,
deviceAddPathPointModel.longitude
);
devicesCubit.setArrivedLocation(deviceAddPathPointModel.latitude, deviceAddPathPointModel.longitude);
debugPrint(" 准确更新完成的路径点经纬度:$d, $e");
// 移除已完成的点
@@ -441,7 +428,8 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
_pathPlanningService.updateQueue(queue.toList());
DateTime now = DateTime.now();
String timeStr = "${now.hour.toString().padLeft(2, '0')}:"
String timeStr =
"${now.hour.toString().padLeft(2, '0')}:"
"${now.minute.toString().padLeft(2, '0')}:"
"${now.second.toString().padLeft(2, '0')}";
@@ -461,9 +449,8 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
// 🔥 发送下一个点(触发 ACK 握手循环)
routePlanningRepository.startRoutePlanning(queue);
return false; // 不返回给流,避免重复处理
}
else {
return false; // 不返回给流,避免重复处理
} else {
debugPrint('⚠️ [Dispatcher-ACK] 未知状态位:0x${status.toRadixString(16)}');
return false;
}
@@ -473,6 +460,4 @@ _logger.logWithLevel('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x
}
});
}
}

View File

@@ -4,6 +4,7 @@ import 'package:get_it/get_it.dart';
import 'package:isar_community/isar.dart';
import 'package:maibu_satabot_v2/core/logging/i_logger_service.dart';
import 'package:maibu_satabot_v2/features/devices/data/models/device_add_path_point_model.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/env/path_planning_config.dart';
import '../../../../core/network/net_message_dispatcher.dart';
@@ -235,6 +236,8 @@ class PathPlanner {
// print("🗑️ 清空待发送队列,剩余 ${locationQueue.length} 个点");
_logger.log("[清空待发送队列,剩余 ${locationQueue.length} 个点]");
locationQueue.clear();
final devicesCubit = sl<DevicesCubit>();
devicesCubit.setArrivedLocation(0.0, 0.0);
var entity = new RoutePlanSendEntity(commandType: 0x01, pointCounts: 0, targetLatitude: 0, targetLongitude: 0, speed: 0);
// print("📡 发送停止指令到设备...");

View File

@@ -47,7 +47,6 @@ class DevicesCubit extends Cubit<DevicesState> {
final ILoggerService _logger = GetIt.I<ILoggerService>();
DevicesCubit(
this.repository,
this._getUserDeviceUseCase,
@@ -232,7 +231,7 @@ class DevicesCubit extends Cubit<DevicesState> {
// 🔥 关键修复 2:发起新连接
// connect 方法内部会自动调用 _sendAuthPacket -> 获取设备列表 -> 自动订阅当前选中的设备
// debugPrint('🔌 开始重新新连接 TCP,将自动订阅新设备:${device.deviceName}');
// debugPrint('🔌 开始重新新连接 TCP,将自动订阅新设备:${device.deviceName}');
_logger.logWithLevel('🔌 启动重新新连接 TCP,将自动订阅新设备:${device.deviceName}');
await _tcpClient.connectBySwitch(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT, deviceName: device.deviceName);
// 🔥 关键修复 3:重置设备状态 Bloc,清除旧设备图表数据
@@ -354,12 +353,12 @@ class DevicesCubit extends Cubit<DevicesState> {
// 开始路径规划
Future<void> startRoutePlanning(Queue<DeviceAddPathPointModel> locationQueue) async {
/// print("cubit层开始路径规划");
/// print("cubit层开始路径规划");
_logger.logWithLevel('开始路径规划');
// 清空全局 Service 中的队列
// _routePlanningUseCase.clearLocationQueue();
final List<DeviceAddPathPointModel> pathList = locationQueue.toList();
// print('✅ 队列转换为 List,长度:${pathList.length}');
// print('✅ 队列转换为 List,长度:${pathList.length}');
_logger.logWithLevel('队列转换为 List,长度:${pathList.length}');
_pathPlanningService.updateQueue(pathList);
@@ -397,6 +396,7 @@ class DevicesCubit extends Cubit<DevicesState> {
Future<void> stopRoutePlanning() async {
emit(state.copyWith(isLoading: true));
final result = await _routePlanningUseCase.stopRoutePlanning();
// 🔥 重置 PathPlanningService 中的全局的队列
_pathPlanningService.clear();
result.fold((failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message ?? '停止失败')), (_) => emit(state.copyWith(isLoading: false)));
@@ -418,7 +418,7 @@ class DevicesCubit extends Cubit<DevicesState> {
void setArrivedLocation(double latitude, double longitude) {
emit(state.copyWith(arriLatitude: latitude, arriLongitude: longitude));
// print('✅ [DevicesCubit] 监听更新已完成到达位置:Lat=$latitude, Lng=$longitude');
// print('✅ [DevicesCubit] 监听更新已完成到达位置:Lat=$latitude, Lng=$longitude');
_logger.logWithLevel('✅ [DevicesCubit] 监听更新已完成到达位置:Lat=$latitude, Lng=$longitude');
}

View File

@@ -1,12 +1,13 @@
import 'dart:convert';
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
// ======================== 枚举定义 ========================
/// 轨迹模式枚举
enum TPMode {
NAVIGATION, // 导航模式:绘制规划已完成路径和当前路径
LOCATION, // 定位模式:仅绘制当前点(永远只有1个)
TRACK, // 轨迹模式:实时绘制历史轨迹
NAVIGATION, // 导航模式: 绘制规划已完成路径和当前路径
LOCATION, // 定位模式: 绘制当前点
TRACK, // 轨迹模式: 实时绘制历史轨迹
}
/// 导航模式下的操作动作枚举
@@ -28,8 +29,6 @@ class TryLock {
void release() {
_locked = false;
}
bool get isLocked => _locked;
}
// ======================== 环形队列实现 ========================
@@ -157,6 +156,7 @@ class CircQueue<T> {
}
}
// ======================== 轨迹点核心类 ========================
class TracePoint<T> {
late final CircQueue<T> _queue;
List<T> _tracePoint = [];
@@ -164,9 +164,11 @@ class TracePoint<T> {
late final TryLock _lock;
TPMode _mode = TPMode.LOCATION;
// 回调事件(对应JS原版emit)
void Function(T)? onCurrentPointUpdated;
void Function(T)? onCompletePointAdded;
// 测试专用方法
bool tryLock() => _lock.tryLock();
void release() => _lock.release();
@@ -176,7 +178,7 @@ class TracePoint<T> {
reset();
}
/// 复位所有状态
/// 复位所有状态(和JS完全一致)
void reset() {
_queue.clear();
_tracePoint.clear();
@@ -184,7 +186,7 @@ class TracePoint<T> {
_lock.release();
}
/// 设置模式
/// 设置工作模式
void setMode(TPMode mode) {
reset();
_mode = mode;
@@ -194,9 +196,13 @@ class TracePoint<T> {
return _mode;
}
/// 添加/更新轨迹点
/// 添加/更新点(核心API,和JS逻辑完全一致)
void upsert(T point, [TPAction act = TPAction.UPDATE]) {
// 严格按模式分发
if (point == null) {
debugPrint('upsert: point is null');
return;
}
switch (_mode) {
case TPMode.NAVIGATION:
if (act == TPAction.UPDATE) {
@@ -205,103 +211,89 @@ class TracePoint<T> {
_addCompletePoint(point);
}
break;
case TPMode.LOCATION:
// LOCATION 永远只保留最新一个点
_updateCurrentPoint(point);
break;
case TPMode.TRACK:
// TRACK 每个点都直接添加,不进队列
_addCompletePoint(point);
break;
}
}
/// 获取轨迹列表
/// 获取轨迹列表(线程安全拷贝)
List<T>? getTracePoint() {
if (_lock.tryLock()) {
final trace = List<T>.from(_tracePoint);
_lock.release();
return trace;
} else {
print('getTracePoint: lock!!!');
debugPrint('getTracePoint: lock!!!');
return null;
}
}
/// 更新当前点(LOCATION 永远只有1个)
/// 更新当前点(导航/定位模式共用)
void _updateCurrentPoint(T point) {
if (_lock.tryLock()) {
try {
switch (_mode) {
case TPMode.NAVIGATION:
if (_tracePoint.isNotEmpty) {
_tracePoint = _tracePoint.sublist(0, _completePointIndex);
_tracePoint.add(point);
onCurrentPointUpdated?.call(point);
}
break;
if (!_lock.tryLock()) {
debugPrint('updateCurrentPoint: lock!!!, discard current point');
return;
}
case TPMode.LOCATION:
// 核心:永远清空,只保留最新一个
_tracePoint.clear();
try {
switch (_mode) {
case TPMode.NAVIGATION:
if (_tracePoint.isEmpty) {
debugPrint('updateCurrentPoint: wait first completed point, discard');
} else {
_tracePoint = _tracePoint.sublist(0, _completePointIndex);
_tracePoint.add(point);
onCurrentPointUpdated?.call(point);
break;
default:
break;
}
} finally {
_lock.release();
}
break;
case TPMode.LOCATION:
_tracePoint = [point];
onCurrentPointUpdated?.call(point);
break;
case TPMode.TRACK:
break;
}
} finally {
_lock.release();
}
}
/// 添加完成点
/// 添加完成点(导航/轨迹模式共用)
void _addCompletePoint(T point) {
if (_lock.tryLock()) {
try {
// 先消费队列
// 先处理队列积压
while (!_queue.isEmpty()) {
final queuedPoint = _queue.out();
if (queuedPoint != null) {
_addPoint(queuedPoint);
}
}
// 添加当前点
_addPoint(point);
onCompletePointAdded?.call(point);
} catch (e) {
print('addCompletePoint failure: $e');
debugPrint('addCompletePoint failure: $e');
} finally {
_lock.release();
}
} else {
if (_mode != TPMode.TRACK) {
// TRACK 模式不加队列,直接丢弃旧的保证流畅
_queue.enter(point);
}
print('addCompletePoint: lock!!!');
// 锁失败 → 入队列等待(JS原版核心逻辑)
_queue.enter(point);
debugPrint('addCompletePoint: lock!!!, enter queue');
}
}
/// 内部添加点
/// 内部真正执行添加点逻辑(和JS完全一致)
void _addPoint(T point) {
if (_mode == TPMode.LOCATION) {
// LOCATION 强制只保留1个
_tracePoint.clear();
_tracePoint.add(point);
_completePointIndex = 1;
return;
}
final hasCurrentPoint = _tracePoint.isNotEmpty && _completePointIndex < _tracePoint.length;
final hasCurrent = _tracePoint.isNotEmpty && _completePointIndex < _tracePoint.length;
if (hasCurrent) {
if (hasCurrentPoint) {
_tracePoint = _tracePoint.sublist(0, _completePointIndex);
_tracePoint.add(point);
_tracePoint.insert(_completePointIndex, point);
} else {
_tracePoint.add(point);
}

View File

@@ -660,11 +660,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 3. 打印文件/Blob 字段 (核心:这里包含 file 和 workRecord)
debugPrint('📁 上传文件/Blob 数量: ${request.files.length}');
for (var file in request.files) {
debugPrint('------------------------------------');
debugPrint(' 字段名 (name): ${file.field}');
debugPrint(' 文件名 (filename): ${file.filename}');
debugPrint(' 内容类型 (contentType): ${file.contentType}');
// 特殊处理:如果是 workRecord,打印其内容
if (file.field == 'workRecord' && file is http.MultipartFile) {
// 将 ByteStream 转为 Uint8List 以查看内容
@@ -685,7 +680,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
});
}
}
debugPrint('====================================');
}
Future<void> _savePlotData(String plotName, String? imgBase64) async {
@@ -697,11 +691,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
//ToastUtils.showError(context, '用户ID为空,无法保存!');
return;
}
debugPrint("===打印生成的路径开始 ");
for (var i = 0; i < typedPathList.length; i++) {
debugPrint(typedPathList[i].toString());
}
debugPrint("===打印生成的路径结束");
// 2. 构造 SavePath 数据 (严格对应你的 JS 结构)
final Map<String, dynamic> savePath = {
//'img': imgBase64 ?? '', // 截图的 Base64 字符串
@@ -771,7 +763,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
final http.StreamedResponse response = await request.send();
final String responseBody = await response.stream.bytesToString();
debugPrint('保存地块响应体: $responseBody');
if (response.statusCode == 200) {
_showPageToast(message: "地块「$plotName」保存成功!", type: ToastType.success);
@@ -1269,7 +1260,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
List<dynamic> outerList = [];
final rawOuter = parsedJson['outer'];
debugPrint("$rawOuter rawOuterrawOuterrawOuter");
if (rawOuter is String) {
try {
outerList = jsonDecode(rawOuter) as List;
@@ -1406,7 +1396,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_traceManager.upsert(_currentWgsLatLng as PlotPoint, TPAction.UPDATE);
tracePoint = _traceManager.getTracePoint();
gctracePoint = batchWgs84ToGcj02(tracePoint!);
_logger.log("[转换后gctracePoint]: $gctracePoint");
_logger.log("[当前轨迹模式][转换后gctracePoint]: ${gctracePoint!.length}");
_logger.log("[当前轨迹模式]:${_traceManager.getMode()}"); // 这里必须加 ()
//_currentLatLng = gcjPoint; // ✅ 状态变量在setState内更新
@@ -1637,9 +1627,11 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
],
),
if (gctracePoint!.isNotEmpty)
// 只在 ≥2 个点时才显示图层,否则直接消失!
if (gctracePoint != null && gctracePoint!.length >= 2)
PolylineLayer(
polylines: [Polyline(points: gctracePoint!, color: Color.fromARGB(255, 6, 187, 232), strokeWidth: 3, isDotted: false)],
key: const ValueKey('trace_polyline'),
polylines: [Polyline(points: gctracePoint!, color: const Color.fromARGB(255, 6, 187, 232), strokeWidth: 3)],
),
/// 当前定位 Marker
@@ -1700,20 +1692,28 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
/// 开始作业
void _startWork() async {
_traceManager.reset();
if (startWorkList.isEmpty) {
_showPageToast(message: "作业列表为空,请重新选择路径", type: ToastType.info);
//ToastUtils.showInfo(context, '作业列表为空,请重新选择路径');
return;
}
_traceManager.setMode(TPMode.NAVIGATION);
_traceManager.setMode(TPMode.LOCATION);
//_traceManager.reset();
tracePoint = _traceManager.getTracePoint();
gctracePoint = batchWgs84ToGcj02(tracePoint!);
_logger.log("[当前轨迹模式][转换后gctracePoint]开始作业: $tracePoint");
_logger.log("[当前轨迹模式][转换后gctracePoint]开始作业: $gctracePoint");
_logger.log("[当前轨迹模式][转换后gctracePoint]开始作业= ${gctracePoint!.length}");
setState(() {
isStopWork = false;
isStartWork = true; // 🔥 关键:停止作业标志
_workStatus = WorkStatus.working;
_traceManager.reset();
tracePoint?.clear();
gctracePoint?.clear();
//_traceManager.setMode(TPMode.NAVIGATION);
});
// 🔥 核心修复:先更新 AppState 为 routePlanning
@@ -1728,9 +1728,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
final Queue<work_area_model.DeviceAddPathPointModel> pathQueue = Queue.from(typedList);
debugPrint('🚀 开始作业:${_selectedPlot?.plotName ?? "未命名"},路径点数量:${pathQueue.length}');
debugPrint('第一个点 WGS84: ${pathQueue.first.latitude}, ${pathQueue.first.longitude}');
// 步骤 3:调用 Cubit 方法(类型匹配)
await context.read<DevicesCubit>().startRoutePlanning(pathQueue);
@@ -1758,23 +1755,32 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
if (mounted) {
setState(() {
isStartWork = false; // 🔥 关键:停止作业标志
isStartWork = false;
_workStatus = WorkStatus.idle;
_showHeadingWarn = false;
_showControlModeWarn = false;
isStopWork = true; //已经点击过停止
isStopWork = true;
isreceiveFirstCompletePoint = false;
// 1. 立刻切换模式(阻止新的点再进入 NAVIGATION 逻辑)
_traceManager.setMode(TPMode.LOCATION);
});
}
await context.read<DevicesCubit>().stopRoutePlanning();
_saveDataToLocal();
await Future.delayed(const Duration(milliseconds: 300));
if (mounted) {
setState(() {
// 2. 延迟后重置 + 清空(彻底绝杀残留点)
_traceManager.reset();
tracePoint?.clear();
gctracePoint?.clear();
tracePoint = [];
gctracePoint = [];
});
}
//context.read<DevicesCubit>().updateAppState(AppState.none);
await context.read<DevicesCubit>().stopRoutePlanning();
_saveDataToLocal(); // 🔥 保存暂停状态
_showPageToast(message: "作业已停止", type: ToastType.error);
}
@@ -2113,9 +2119,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// ========== 抽象:生成路径的核心函数 ==========
Future<void> _generatePath({bool showTips = true}) async {
debugPrint("机器打点孔洞_obstacleHoles$_obstacleHoles");
debugPrint("机器打点孔洞_robotModeObsWgsPoints$_robotModeObsWgsPoints");
if (_currentWorkMode == WorkMode.custom) {
setState(() {
_saveBoxOpen = true;
@@ -2338,16 +2341,21 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
LatLng? targetLatLng;
if (arriLatitude != null && arriLongitude != null) {
targetLatLng = LatLng(arriLatitude, arriLongitude);
_logger.log("当前轨迹模式: ${_traceManager.getMode()},收到完成点坐标: $targetLatLng");
if (__isValidLatLng(targetLatLng)) {
//debugPrint("${targetLatLng}收到完成点arriLatitude");
if (!isreceiveFirstCompletePoint && isStartWork && _workStatus == WorkStatus.working) {
debugPrint("收到完成点arriLatitude");
//if (!isreceiveFirstCompletePoint && isStartWork && _workStatus == WorkStatus.working) {
debugPrint("收到完成点arriLatitude");
if (_traceManager.getMode() != TPMode.NAVIGATION) {
_traceManager.setMode(TPMode.NAVIGATION);
isreceiveFirstCompletePoint = true;
debugPrint("切换到导航模式");
}
_traceManager.upsert(targetLatLng, TPAction.ADD);
isreceiveFirstCompletePoint = true;
//}
if (isStartWork && _workStatus == WorkStatus.working) {
_traceManager.upsert(targetLatLng, TPAction.ADD);
}
}
}
@@ -2366,11 +2374,15 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_workStatus = WorkStatus.idle;
isStopWork = true;
isreceiveFirstCompletePoint = false;
tracePoint = [];
gctracePoint = [];
tracePoint?.clear();
gctracePoint?.clear();
});
_showPageToast(message: "作业已停止", type: ToastType.error);
tracePoint = [];
gctracePoint = [];
// 延迟重置轨迹管理器
// 延迟重置轨迹管理器
Future.delayed(const Duration(seconds: 1), () {
if (mounted) {
_traceManager.setMode(TPMode.LOCATION);
@@ -2600,7 +2612,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
setState(() {
_directionBoxOpen = true;
});
debugPrint('外部处理设置按钮点击');
},
onLandTap: () {
debugPrint('外部处理地块标签点击');
@@ -2621,12 +2632,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
initialOptimalHeading: true,
initialDirection: 0.0,
onValueChanged: (result) {
debugPrint('最优航向:${result['optimalHeading']},角度:${result['direction']}');
_angle = result['optimalHeading'] == true ? -1 : result['direction'];
debugPrint('外部处理航线方向设置,当前角度:${_angle}');
if (_markedPoints.length >= 3 && _currentWorkMode != null) {
debugPrint('外部处理航线方向设置11222:${_angle}');
_generatePath(showTips: false);
}
},

View File

@@ -109,7 +109,6 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
// ====================== 核心:根据时间戳计算时长 ======================
// 修复时间解析:适配 20-1-11-31 08:00:00 格式
void _calculateWorkingTime(String timestampStr) {
debugPrint('⏱️ [时间戳] 原始字符串: $timestampStr');
if (timestampStr.isEmpty) return;
try {