终于解决了 第二次作业之后的 路径不清除的问题 ,一直保留着上一次作业发送的完成点
This commit is contained in:
@@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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("📡 发送停止指令到设备...");
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user