11 KiB
P1 粗路径 Clumsy UI 集成设计
目标
在不改变 P0 地图、Hybrid A* 与碰撞安全语义的前提下,为 Clumsy 增加可手动运行的粗路径场景测试。使用者能够从 MovementTest 列表启动固定场景,或传入 AMR 当前世界位姿并手动输入终点;界面显示栅格地图、起点、终点、连续路径、换向点和扩大车辆矩形检查点,并可随时停止正在进行的规划。
本设计只覆盖 P1 的首个交付:场景工厂、后台 MovementTest、Painter 可视化、自动化集成检查和模块 README。Release 性能基准属于 P1 的下一项交付;旧 TrapMap 迁移、旧验证脚本和旧入口的清理按已确认范围排除。
既有边界
- 业务入口仍唯一为
CoarsePathPlanningService.Plan(job, cancellationToken);MovementTest 不自行拼接PlanningMapFactory、HybridAStarPlanner、栅格化器、碰撞器、原语或搜索节点。 PlanningMapRequest的边界、分辨率和障碍物几何使用 mm;CoarsePathPlanningJob位姿、车辆尺寸和路径点使用 m,航向使用 rad。- 项目传入的 AMR 位姿采用世界
X/Y(mm)与航向th(deg)。P1 只在 UI 边界将其一次性转换为Pose2D(X / 1000, Y / 1000, th × pi / 180);规划核心不接受度或 mm 位姿。当前车队路径代码将来自getCartLocation().th的姿态与度制角相加,并在调用三角函数前显式除以 180 再乘 pi,因此 P1 不沿用旧Movements.cs直接对.th调用Math.Cos/Sin的不一致写法。 - AMR 起点必须表示车辆几何中心。若上游定位的参考点是雷达、天线或其他安装点,上游必须先按外参转换到车辆几何中心;安全余量仍只由
VehicleParameters表达。 PlanningResult只有Success才能携带完整路径;取消、超时、无解和失败不得在 UI 上表现为部分路径。Painter在现有后台多车线程中已被调用,因此本设计允许规划任务完成后的后台回调操作该图层;规划核心本身始终不依赖 UI。- 注释延续 P0 风格:公开类型与成员使用中文 XML 文档,说明单位、并发/停止语义和返回行为;会影响竞态的内部代码保留简短中文行注释。
方案选择
采用“六个薄 MovementTest 入口 + 共享后台执行器”的方案。
每个入口对应一个已命名的固定场景,便于在 Clumsy 的测试列表中直接运行;它们共用同一个静态 CoarsePathPlanningService,因此既能复用地图缓存,也不会让测试代码绕开门面。一个位于同一源文件内的执行器负责互斥会话、Task 生命周期、取消和绘制,避免六个入口复制并发逻辑。
不采用单一测试入口配合代码常量切换,因为手动验证需要反复改代码;也不采用运行时弹窗选项,因为这会增加 UI 输入状态和无法直接观察每个场景的可发现性。
文件与职责
| 文件 | 职责 |
|---|---|
ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs |
创建不读取 UI、传感器、定位或时钟的固定 CoarsePathPlanningJob 场景。每次创建均返回新请求对象。 |
ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs |
声明七个 MovementTest 入口,以及共享服务、后台执行、取消、结果日志、AMR 位姿/手动终点输入与 Painter 绘制。 |
ClumsyPilot/tests/verify_coarse_path_integration.ps1 |
通过真实程序集反射验证场景、门面调用约束、缓存、换向、无解、取消和测试代码结构。 |
ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md |
补充 P1 手动测试方法、颜色图例、单位转换、停止语义和非目标。 |
所有 UI 辅助类型保留在 CoarsePath/Test 内;不会向 Map、Search、Vehicle 或 Facade 增加 UI 依赖。
场景工厂
CoarsePathScenarioFactory 公开一个场景枚举和按枚举创建请求的方法。工厂的职责仅是构造纯输入;它不持有服务、缓存、Painter 或取消源。每个请求采用同一组可验证的车辆和搜索默认值,再按场景覆盖障碍物、起终点和方向约束。
场景固定使用世界 mm 地图边界和分辨率,向 Pose2D 写入对应的 m 坐标。障碍物只通过 ManualObstacleSource 和 TwoLegObstacleSource 进入 PlanningMapRequest,并为内容变化提供固定且正确的 SourceVersion。
| 场景 | 地图与预期 |
|---|---|
| 显式空图 | AllowExplicitEmptyMap=true,直达前进路径成功,用于检查最短调用链。 |
| 单矩形绕行 | 中央矩形阻断直线,路径成功且必须绕障。 |
| 手工圆、矩形与 TwoLeg | 同时使用手工圆形、手工矩形和有效 TwoLeg 快照,路径成功,证明多来源经过同一门面。 |
| 缓存命中 | 连续以新建但完全相同的输入调用同一服务两次;第二次 MapResult.CacheHit 必须为 Input。 |
| 倒车换向 | 起步方向限制为前进、终点进入方向限制为倒车;成功路径必须出现标记的换向点。 |
| 无解 | 完全贯穿地图的障碍带隔开起点和终点,返回 NoFeasiblePath 且无路径。 |
| AMR 位姿与手动终点 | 起点使用上层传入并冻结的 AMR 世界位姿;操作者输入同一世界系的终点 X/Y/航向。该入口仅使用明确提供的障碍物快照,显式空图只能作为演示,不能代表现场无障碍。 |
实现期间先用自动化断言固定每个场景的状态;若需为当前 P0 运动原语调整数值,只能调整场景几何或请求参数,不能放宽碰撞、目标或失败语义。
后台会话与取消
共享执行器持有一个静态、长期存活的 CoarsePathPlanningService。任一入口启动时会创建新的会话:运行编号、专用 CancellationTokenSource、场景描述和后台 Task。启动新会话前取消旧会话,以确保同时最多只有一个可绘制的规划结果。
AMR 位姿和手动终点在创建任务前被转换、有限值校验并冻结,随后只作为 CoarsePathPlanningJob 数据传给后台。MovementTest 不在规划后台持续读取定位;若未来接入可能阻塞的 DetourInterface.getCartLocation(),它必须位于独立的上游快照提供者,不能阻塞 UI 或绕过本设计的输入契约。
MovementTest.Test
-> 生成场景的全新 CoarsePathPlanningJob
-> 创建运行编号和 CancellationTokenSource
-> Task.Run(() => service.Plan(job, token))
-> 完成回调:仅当运行编号仍为当前会话时记录并绘制结果
MovementTest.TestStop
-> 取消当前 CancellationTokenSource
-> 使当前运行编号失效并解绑 Task 引用
-> 清空专用 Painter 图层
-> 旧任务完成后只释放其 CancellationTokenSource,不再绘制
Test 绝不等待 Task、不读取 Task.Result,因此不会阻塞 Clumsy 界面。TestStop 不等待规划任务退出;P0 的共享预算会将令牌传递至建图、EDT、Dijkstra 和 Hybrid A*,任务在其检查点返回 Cancelled。运行编号检查可防止已取消的旧任务在新任务结果之后覆盖画面。
任务异常只记录清晰的测试诊断并释放资源,不伪造 PlanningResult。正常停止、超时、无解与输入失败均使用门面实际返回的状态。
所有七个入口只创建规划请求、任务和绘制;不得引用 BasicPilotBase.Chassis、SendMotion、DriveTask 或任何底盘控制 API。
绘制规则
使用独立的全局世界坐标 Painter 图层,例如 CoarsePathPlanningV1。Painter 输入为 mm,因此所有来自 Pose2D 与 CoarsePathPoint 的 X/Y 必须乘以 1000;航向仍以 rad 计算旋转矩形。不得混用 Map 的 mm 和 CoarsePath 的 m。所有地图输入、AMR 起点和手动终点均处于同一个世界坐标系。
- 先绘制地图
[XMin, XMax) × [YMin, YMax)的粗外边界、世界 X/Y 参考和栅格网络。格线遵循真实ResolutionMm;当格线数量超过显示上限时,按整数格距抽稀,并在状态文本中保留真实分辨率与显示步距。 - 从
PlanningGridMap.IsOccupied(row, col)绘制占据格,而不是重新绘制原始障碍物几何;因此显示内容与实际规划快照一致。空闲格使用背景,不为每个空格增加填充。 - 起点为绿色圆、方向短线和“起点”标签;终点为橙色圆、方向短线、“终点”标签及目标位置容差圈。
- 成功路径逐段连接:前进与倒车使用不同颜色,并以固定间距绘制方向箭头;路径不成功时不绘制任何路径段。
IsGearSwitchPoint=true的点使用紫色标记和“换向”标签。- 对首点、末点、每个换向点和固定间隔点绘制旋转矩形。矩形半长/半宽为
Vehicle.LengthMeters / 2 + SafetyMarginMeters和Vehicle.WidthMeters / 2 + SafetyMarginMeters,仅用于显示 P0 已采用的扩大车体,不参与碰撞判断。 - 在地图角落绘制固定图例:边界、占据格、起点、终点、前进、倒车、换向与扩大车体检查框的颜色含义。无论成功与否,绘制文本状态:场景名称、地图快照 ID、地图构建状态、缓存层级、规划状态、耗时和终止原因。停止或新会话开始时先清空旧图层。
绘制只消费 CoarsePathPlanningJobResult 的只读结果;不修改 PlanningMapRequest、地图快照、路径、调试开关或服务缓存。
测试与验收
按 Red-Green-Refactor 顺序扩展 verify_coarse_path_integration.ps1:先增加以下会失败的反射/行为断言,再实现最小代码,最后运行相同脚本。
- 断言
CoarsePath/Test中的场景工厂和 MovementTest 文件存在;工厂提供六类固定场景与一个 AMR 位姿/手动终点入口,且每次创建返回独立请求。 - 使用同一
CoarsePathPlanningService运行工厂场景:空图、矩形、多来源和倒车换向均成功;倒车换向路径含IsGearSwitchPoint;无解结果为NoFeasiblePath且路径为空。 - 对缓存场景连续调用两次,断言第二个地图结果为
Input命中,且路径状态和点数不因缓存改变。 - 断言 AMR
0 deg、90 deg的 UI 输入分别转换为0 rad、pi/2 rad,同时 X/Y 由 mm 转为 m;手动目标与起点都使用相同转换与有限值校验。 - 对预先取消的后台调用断言门面映射为
Cancelled、地图或路径不发布部分结果;结构检查确认 MovementTest 使用Task.Run和CancellationTokenSource,且没有等待任务。 - 结构检查确认测试入口只通过
CoarsePathPlanningService进行规划,且不引用底盘命令、栅格化器、碰撞器、原语生成器或搜索节点;并检查绘制代码消费PlanningGridMap的边界、分辨率与占据状态,包含图例和成功路径保护。 - 更新 README 断言,确认 P1 的 UI、AMR 位姿单位、手动终点、取消和“无底盘命令”边界可被调用方查阅。
完成后运行 Debug 构建及现有 P0 Map/CoarsePath 验证脚本(不恢复或改动已被排除的旧 TrapMap 验证脚本)。
非目标
- 不实现路径平滑、速度规划、跟踪控制、底盘命令、实时重规划或传感器采集。
- 不改变地图指纹、缓存键、障碍物栅格化、车辆碰撞、终点判定、搜索代价或资源上限。
- 不在本交付中实现 Release 性能基准,也不清理、迁移或恢复任何 TrapMap 文件与脚本。