From f31a20f1d99547fc38643d2c44f4b0668ec0f8aa Mon Sep 17 00:00:00 2001 From: Songzex <2402265378@qq.com> Date: Tue, 17 Mar 2026 14:19:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=B7=AF=E5=BE=84=E8=A7=84?= =?UTF-8?q?=E5=88=92=E5=8F=91=E9=80=81=E7=9A=84=E6=8F=A1=E6=89=8B=E6=9C=BA?= =?UTF-8?q?=E5=88=B6-=E4=B8=80=E5=AF=B9=E4=B8=80=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E5=BA=94=E7=AD=94-=E5=BE=85=E6=B5=8B=E8=AF=95=20=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E8=B7=AF=E5=BE=84=E8=A7=84=E5=88=92=E6=8F=A1=E6=89=8B?= =?UTF-8?q?=E6=9C=BA=E5=88=B6=E7=9A=84=E5=BC=80=E5=85=B3=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/env/path_planning_config.dart | 20 ++ lib/core/network/net_message_dispatcher.dart | 260 +++++++++++++++++- .../route_planning_repository_impl.dart | 88 +++++- .../widgets/center_control_area.dart | 2 +- lib/main.dart | 3 +- 5 files changed, 364 insertions(+), 9 deletions(-) create mode 100644 lib/core/env/path_planning_config.dart diff --git a/lib/core/env/path_planning_config.dart b/lib/core/env/path_planning_config.dart new file mode 100644 index 00000000..40d9e0bf --- /dev/null +++ b/lib/core/env/path_planning_config.dart @@ -0,0 +1,20 @@ +/// 路径规划配置类 +/// 用于控制路径规划功能的各种开关和配置 +class PathPlanningConfig { + /// 🔥 全局开关:控制使用哪套路径规划逻辑 + /// - `false`: 使用现有逻辑(不等待 ACK 确认) + /// - `true`: 使用新的 ACK 握手机制(等待 0x02 和 0x01 确认) + static bool useAckHandshake = false; + + /// 切换路径规划模式 + /// [useAck] true = 使用 ACK 握手机制,false = 使用现有逻辑 + static void setMode(bool useAck) { + useAckHandshake = useAck; + print('🔧 [PathPlanningConfig] 路径规划模式已切换:${useAck ? "ACK 握手机制" : "现有逻辑"}'); + } + + /// 获取当前模式描述 + static String getModeDescription() { + return useAckHandshake ? "ACK 握手机制 (等待 0x02 + 0x01)" : "现有逻辑 (仅等待 0x01)"; + } +} diff --git a/lib/core/network/net_message_dispatcher.dart b/lib/core/network/net_message_dispatcher.dart index dddf9b91..6503620b 100644 --- a/lib/core/network/net_message_dispatcher.dart +++ b/lib/core/network/net_message_dispatcher.dart @@ -1,12 +1,14 @@ import 'dart:convert'; import 'package:flutter/cupertino.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 '../../features/devices/domain/repositories/route_planning_repository.dart'; import '../../features/devices/presentation/bloc/devices_cubit.dart'; import '../../features/devices/services/path_planning_service.dart'; import '../di/injection.dart'; +import '../env/path_planning_config.dart'; import 'protocol_decoder.dart'; import 'tcp/tcp_client.dart'; // import '../../features/auth/data/models/user_model.dart'; @@ -15,9 +17,23 @@ class NetMessageDispatcher { final TcpClient tcpClient; final PathPlanningService _pathPlanningService; final RoutePlanningRepository routePlanningRepository; - // 🔥 修改:使用回调函数获取 AppState + // 修改:使用回调函数获取 AppState final Function? getAppState; + // 🔥 新增:ACK 握手状态管理 (仅新模式使用) + int _expectedPointIndex = -1; // 期望收到的点编号 + bool _waitingForAck = false; // 是否正在等待 ACK 确认 + bool _waitingForArrived = false; // 是否正在等待到达确认 + int _currentPointIndex = -1; // 当前发送的点索引 + + + // 🔥 新增:设置期望收到的点编号(供 PathPlanner 调用) + void setExpectedPointIndex(int index) { + _expectedPointIndex = index; + debugPrint('🎯 [Dispatcher-ACK] 设置期望点编号:$index'); + } + + NetMessageDispatcher(this.tcpClient, this.routePlanningRepository, this._pathPlanningService, {this.getAppState}); /// 过滤特定指令的流 @@ -79,7 +95,7 @@ class NetMessageDispatcher { /// 协议格式:AB AA 01 [DATA] CRC(2) AA AB /// 验证头部:data[0]=0xAB, data[1]=0xAA, data[2]=0x01 /// 检查状态位:data[5] == 0x01 表示回复成功 - Stream onPathPlanningResponse() { +/* Stream onPathPlanningResponse() { final devicesCubit = sl(); debugPrint('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x01)'); @@ -168,13 +184,118 @@ class NetMessageDispatcher { return false; }); + }*/ + ///新版的ack和现有的融合 + /// 监听路径规划指令应答 (现有逻辑 - 不修改) + /// 协议格式:AB AA 01 [DATA] CRC(2) AA AB + /// 验证头部:data[0]=0xAB, data[1]=0xAA, data[2]=0x01 + /// 检查状态位:data[5] == 0x01 表示回复成功 + Stream onPathPlanningResponse() { + final devicesCubit = sl(); + + debugPrint('[Dispatcher] 开始监听路径规划指令应答 (CMD: 0x01) - 模式:${PathPlanningConfig.getModeDescription()}'); + + // 🔥 根据配置决定使用哪个流 + if (PathPlanningConfig.useAckHandshake) { + debugPrint('⚙️ [Dispatcher] 使用 ACK 握手机制'); + return onPathPlanningResponseWithAck(); + } else { + debugPrint('⚙️ [Dispatcher] 使用现有逻辑'); + return onCommand(0x01).where((packet) { + // 🔥 修改:使用回调检查 AppState + if (getAppState != null) { + final currentState = getAppState!(); + if (currentState.toString() == 'AppState.none') { + debugPrint('⚠️ [Dispatcher] 当前 AppState 为 none,停止处理路径规划指令'); + return false; + } + } + // 构造完整数据包用于验证 + final fullData = [ + 0xAB, 0xAA, 0x01, + ...packet.payload, + 0x00, 0x00, // CRC 占位 + 0xAA, 0xAB + ]; + + // 验证头部协议 + if (fullData[0] == 0xAB && + fullData[1] == 0xAA && + fullData[2] == 0x01) { + + debugPrint('[Dispatcher] 下位机回复成功 - 完整数据包:${fullData.join(" ")}'); + // 检查状态位 (索引 5 对应 payload 的第 2 个字节) + if (fullData.length > 5 && fullData[5] == 0x01) { + debugPrint('✅ [Dispatcher] 状态位验证通过:0x01 - 可以发送下一个指令'); + ///取全局的路径规划发送实体 queue + /// 发送下一个指令 移除上一条数据 + var queue = _pathPlanningService.getQueue(); + // 🔥 关键:必须先检查空队列! + if (queue.isEmpty) { + devicesCubit.finishWork(); + Future.delayed(Duration(seconds: 1), () { + devicesCubit.resetWorkStatus(); + }); + // 手动停止 + //routePlanningRepository.stopRoutePlanning(); + debugPrint('⚠️ [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"); + //去除上一条数据 + queue.removeFirst(); + /// _pathPlanningService.updateQueue(queue as List); + _pathPlanningService.updateQueue(queue.toList()); + DateTime now = DateTime.now(); + 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}'); + ///如果还剩 0 个数据 就停止发送 + if (queue.length==0) { + debugPrint('⚠️ [Dispatcher] 队列已空,停止发送'); + + // final devicesCubit = sl(); + devicesCubit.finishWork(); + Future.delayed(Duration(seconds: 1), () { + devicesCubit.resetWorkStatus(); + }); + // 手动停止 + routePlanningRepository.stopRoutePlanning(); + debugPrint('⚠️ [Dispatcher] 队列已空,发送停止指令已发送'); + return false; + } + routePlanningRepository.startRoutePlanning(queue); + return true; + } else { + debugPrint('⚠️ [Dispatcher] 状态位异常:${fullData.length > 5 ? fullData[5].toRadixString(16) : "N/A"}'); + } + } else { + debugPrint('❌ [Dispatcher] 协议头验证失败'); + } + + return false; + }); + } } + + + /// 监听特定状态的回复(可复用) /// [commandCode] 指令码 /// [statusIndex] 状态字节在 payload 中的索引 /// [expectedStatus] 期望的状态值 - Stream onStatusResponse({ +/* Stream onStatusResponse({ required int commandCode, required int statusIndex, required int expectedStatus, @@ -195,5 +316,138 @@ class NetMessageDispatcher { } return false; }); + }*/ + +/// 监听特定状态的回复(ack握手机制) +// 🔥 新增:ACK 握手机制 (新模式) + /// 监听路径规划指令应答,实现完整的 ACK 握手机制 + /// 协议格式:AB AA 01 [DATA] CRC(2) AA AB + /// 上传协议(0x01 路径规划上传): + /// - Byte 3: pointCounts (点编号) + /// - Byte 4: 未使用 + /// - Byte 5: 状态 + /// - 0x01: 已到达 + /// - 0x02: 收到指令 + Stream onPathPlanningResponseWithAck() { + final devicesCubit = sl(); + + debugPrint('[Dispatcher-ACK] 开始监听路径规划指令应答 (CMD: 0x01) - ACK 握手机制已启用'); + + return onCommand(0x01).where((packet) { + // 修改:使用回调检查 AppState + if (getAppState != null) { + final currentState = getAppState!(); + if (currentState.toString() == 'AppState.none') { + debugPrint('️ [Dispatcher-ACK] 当前 AppState 为 none,停止处理路径规划指令'); + return false; + } + } + + // 🔥 构造完整数据包用于验证 + final fullData = [ + 0xAB, 0xAA, 0x01, + ...packet.payload, + 0x00, 0x00, // CRC 占位 + 0xAA, 0xAB + ]; + + // 验证头部协议 + if (fullData[0] == 0xAB && + fullData[1] == 0xAA && + fullData[2] == 0x01) { + + debugPrint('[Dispatcher-ACK] 收到路径规划应答 - 完整数据包:${fullData.join(" ")}'); + + // 🔥 检查数据包长度是否足够 + if (fullData.length < 6) { + debugPrint('❌ [Dispatcher-ACK] 数据包长度不足,无法解析状态位'); + return false; + } + + final status = fullData[5]; + final pointIndex = fullData.length > 3 ? fullData[3] : -1; + + debugPrint('📊 [Dispatcher-ACK] 状态位:0x${status.toRadixString(16)}, 点编号:$pointIndex, 期望编号:$_expectedPointIndex'); + + // 🔥 核心改进:精准匹配点编号 + // 如果正在等待 ACK,但收到的点编号不匹配,直接丢弃 + if ((_waitingForAck || _waitingForArrived) && pointIndex != _expectedPointIndex) { + debugPrint('⚠️ [Dispatcher-ACK] 点编号不匹配!收到:$pointIndex, 期望:$_expectedPointIndex,丢弃'); + return false; + } + + // 🔥 ACK 握手机制核心逻辑 + if (status == 0x02) { + // ✅ 下位机回复"收到指令" + debugPrint('✅ [Dispatcher-ACK] ACK 确认收到 - 点编号:$pointIndex'); + _waitingForAck = false; + _waitingForArrived = true; // 开始等待"已到达"信号 + return false; // 继续监听,不返回给流 + } + else if (status == 0x01) { + // ✅ 下位机回复"已到达" + debugPrint('✅ [Dispatcher-ACK] 已到达确认 - 点编号:$pointIndex'); + _waitingForArrived = false; + + // 🔥 关键:处理完成后的队列操作 + final queue = _pathPlanningService.getQueue(); + + if (queue.isEmpty) { + devicesCubit.finishWork(); + Future.delayed(Duration(seconds: 1), () { + devicesCubit.resetWorkStatus(); + }); + debugPrint('⚠️ [Dispatcher-ACK] 队列已经为空,作业完成'); + return false; + } + + // 更新已完成的经纬度 + var deviceAddPathPointModel = queue.first; + var d = deviceAddPathPointModel.latitude; + var e = deviceAddPathPointModel.longitude; + devicesCubit.setArrivedLocation( + deviceAddPathPointModel.latitude, + deviceAddPathPointModel.longitude + ); + debugPrint(" 准确更新完成的路径点经纬度:$d, $e"); + + // 移除已完成的点 + queue.removeFirst(); + _pathPlanningService.updateQueue(queue.toList()); + + DateTime now = DateTime.now(); + String timeStr = "${now.hour.toString().padLeft(2, '0')}:" + "${now.minute.toString().padLeft(2, '0')}:" + "${now.second.toString().padLeft(2, '0')}"; + + debugPrint(' $timeStr [Dispatcher-ACK] 准备触发下一个路径点,队列剩余:${queue.length}'); + + // 🔥 如果队列已空,停止发送 + if (queue.isEmpty) { + debugPrint('️ [Dispatcher-ACK] 队列已空,停止发送'); + devicesCubit.finishWork(); + Future.delayed(Duration(seconds: 1), () { + devicesCubit.resetWorkStatus(); + }); + routePlanningRepository.stopRoutePlanning(); + debugPrint('⚠️ [Dispatcher-ACK] 停止指令已发送'); + return false; + } + + // 🔥 发送下一个点(触发 ACK 握手循环) + routePlanningRepository.startRoutePlanning(queue); + return false; // 不返回给流,避免重复处理 + } + else { + debugPrint('⚠️ [Dispatcher-ACK] 未知状态位:0x${status.toRadixString(16)}'); + return false; + } + } else { + debugPrint(' [Dispatcher-ACK] 协议头验证失败'); + return false; + } + }); } + + } diff --git a/lib/features/devices/data/repositories/route_planning_repository_impl.dart b/lib/features/devices/data/repositories/route_planning_repository_impl.dart index 95316d3a..926b571c 100644 --- a/lib/features/devices/data/repositories/route_planning_repository_impl.dart +++ b/lib/features/devices/data/repositories/route_planning_repository_impl.dart @@ -1,6 +1,10 @@ import 'dart:collection'; +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 '../../../../core/di/injection.dart'; +import '../../../../core/env/path_planning_config.dart'; +import '../../../../core/network/net_message_dispatcher.dart'; import '../../../../core/network/tcp/tcp_client.dart'; import '../../domain/repositories/route_planning_repository.dart'; import '../models/route_plan_send_entity.dart'; @@ -8,6 +12,7 @@ import '../models/route_plan_send_entity.dart'; class RoutePlanningRepositoryImpl implements RoutePlanningRepository { final TcpClient tcp; late PathPlanner _planner; + // 🔥 新增:存储 AppState 的引用 dynamic _appState; @@ -50,15 +55,26 @@ class RoutePlanningRepositoryImpl implements RoutePlanningRepository { } } +// 全局开关:控制使用哪套路径规划逻辑 +// true = 使用新的 ACK 握手机制 (将来) +// false = 使用现有逻辑 (现在) +class PathPlanningMode { + static bool useAckHandshake = false; // 默认使用现有逻辑 +} + // PathPlanner class PathPlanner { final Queue locationQueue = Queue(); final TcpClient tcpClient; bool isStart = false; - // 🔥 新增:作业控制状态 + // 新增:作业控制状态 bool _isPaused = false; bool _isStopped = false; + // 新增:ACK 握手状态 (仅新模式使用) + bool _waitingForAck = false; // 是否正在等待 ACK 确认 + int _currentPointIndex = 0; // 🔥 当前发送的点编号(从 0 开始递增) + PathPlanner(this.tcpClient); @@ -70,7 +86,7 @@ class PathPlanner { // tcpClient.disconnect(); // return; // } - // 🔥 关键检查:暂停或停止时不再发送 + // 关键检查:暂停或停止时不再发送 if (_isPaused) { print("⏸️ 当前处于暂停状态,停止发送指令"); return; @@ -136,7 +152,15 @@ class PathPlanner { return; } print("[发送指令要转换类型了完成]"); - sendNextLocation(); + // sendNextLocation(); + // 🔥 根据全局开关选择使用哪套发送逻辑 + if (PathPlanningConfig.useAckHandshake) { + print("🔥 使用 ACK 握手机制发送路径点"); + sendNextLocationWithAck(sl()); + } else { + print("🔥 使用现有逻辑发送路径点"); + sendNextLocation(); + } } /// 暂停 @@ -170,9 +194,17 @@ class PathPlanner { tcpClient.sendDeviceStateChange(entity); // 恢复后继续发送下一个点 + // if (locationQueue.isNotEmpty) { + // print("📍 恢复发送下一个路径点"); + // sendNextLocation(); + // } if (locationQueue.isNotEmpty) { print("📍 恢复发送下一个路径点"); - sendNextLocation(); + if (PathPlanningConfig.useAckHandshake) { + sendNextLocationWithAck(sl()); + } else { + sendNextLocation(); + } } } /// 停止 @@ -199,7 +231,55 @@ class PathPlanner { tcpClient.sendDeviceStateChange(entity); } +// 🔥 新增:ACK 握手机制的发送方法 (新模式) + void sendNextLocationWithAck(NetMessageDispatcher dispatcher) { + print("[在发送指令 sendNextLocationWithAck 方法中 - ACK 模式]"); + // 🔥 关键检查:如果正在等待 ACK,不要重复发送 + if (_waitingForAck) { + print("⏳ 正在等待 ACK 确认,跳过发送"); + return; + } + // 🔥 关键检查:暂停或停止时不再发送 + if (_isPaused) { + print("⏸️ 当前处于暂停状态,停止发送指令"); + return; + } + + if (_isStopped || (isStart && locationQueue.isEmpty)) { + isStart = false; + if (_isStopped) { + print("️ 已停止作业,清空队列"); + locationQueue.clear(); // 清空剩余队列 + } else { + print("[路径点发送完毕]"); + } + _isStopped = false; // 重置停止标志 + return; + } + + final entity = locationQueue.removeFirst(); // 类型:DeviceAddPathPointModel + + // 🔥 核心改进:使用递增的点编号,确保精准匹配 + final pointIndex = _currentPointIndex++; + + final routePlanSendEntity = RoutePlanSendEntity( + commandType: 0x01, + pointCounts: pointIndex, // 🔥 使用点编号,而不是固定的 1 + targetLatitude: entity.latitude, + targetLongitude: entity.longitude, + speed: 1000, + ); + + print("====Lat:${entity.latitude} ====Lng:${entity.longitude}"); + tcpClient.sendPathPoint( routePlanSendEntity); + isStart = true; + + // 🔥 关键:标记为正在等待 ACK + _waitingForAck = true; + dispatcher.setExpectedPointIndex(pointIndex); // 🔥 通知 Dispatcher + print(" 已锁定发送,等待 ACK 确认 (0x02)..."); + } } diff --git a/lib/features/remote_control/presentation/widgets/center_control_area.dart b/lib/features/remote_control/presentation/widgets/center_control_area.dart index 5e2b1e02..cb53ce8e 100644 --- a/lib/features/remote_control/presentation/widgets/center_control_area.dart +++ b/lib/features/remote_control/presentation/widgets/center_control_area.dart @@ -70,7 +70,7 @@ class CenterControlArea extends StatelessWidget { width: expandedWidth, child: Column( mainAxisAlignment: MainAxisAlignment.end, - children: [_buildSliderBox(expandedWidth, "备用", false)], + children: [_buildSliderBox(expandedWidth, "割刀", false)], ), ), ], diff --git a/lib/main.dart b/lib/main.dart index 8a08aeb1..85d399a0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -72,7 +72,8 @@ class MyApp extends StatelessWidget { ), // 其他 Cubit... ], - child: MaterialApp.router(title: 'Maibu Satabot', theme: AppTheme.lightTheme, routerConfig: sl(), + child: MaterialApp.router(title: 'Maibu Satabot', + theme: AppTheme.lightTheme, routerConfig: sl(), builder: (context, child) { return _LifecycleListener(child: child); },),