docs: package EM observation dashboard

This commit is contained in:
梁薄云
2026-08-06 11:50:59 +08:00
parent fbd3ba618a
commit 6034568942
4 changed files with 49 additions and 7 deletions
@@ -213,6 +213,7 @@ dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerifi
```text
plugins/
├── ClumsyPilot.dll
├── TrajectoryPlanningVisualization.dll
├── osqp.dll
└── licenses/
├── OSQP-LICENSE.txt
@@ -220,7 +221,11 @@ plugins/
└── OSQP-VERSION.txt
```
发布器会检查 64 位 PowerShell、原生 DLL 的 x64 PE 类型和固定 SHA-256。OSQP 加载器从托管程序集目录以绝对路径加载同级 `osqp.dll`;它不依赖 `PATH` 或当前工作目录。部署和执行职责的更多说明见 [TrajectoryExecution](../TrajectoryExecution/README.md)。
发布器只从显式 `ManagedDll` 的同级目录解析 `TrajectoryPlanningVisualization.dll`,先复制完整包到 staging
再原子替换 `plugins` 目录;它不会搜索 `PATH` 或当前工作目录。发布器会检查 64 位 PowerShell、原生 DLL 的 x64 PE
类型和固定 SHA-256,并保留既有恢复行为。OSQP 加载器从托管程序集目录以绝对路径加载同级 `osqp.dll`;它不依赖
`PATH` 或当前工作目录。部署和执行职责的更多说明见 [TrajectoryExecution](../TrajectoryExecution/README.md),网页
观察的开关、token URL、图表语义和停止清理见 [EM trajectory observation MovementTest](../tarjplanner_movementtest/README.md)。
## 常见错误(Common Errors
@@ -76,6 +76,30 @@ rejected before EM planning.
and must never be forwarded directly to a vehicle motion interface. This MovementTest performs no coordinate conversion
for driving and has no driving capability; any future execution mode requires a separate safety-reviewed design.
## 网页看板与可选 Painter
`EnableWebVisualization=true` 可启用本机网页看板;它只绑定 `127.0.0.1`,启动日志会给出完整的、带随机
会话令牌的 URL。端口为 `0` 时由系统选择可用回环端口;浏览器必须使用该完整 URL,不能删掉 token。默认
`AutoOpenWebVisualization=true` 会尝试打开默认浏览器;无桌面会话或打开失败只记录 URL,服务和观察循环继续运行。
网页默认以 `10 Hz` 发布不可变动态快照,网页关闭、断开或慢客户端只会丢弃网页帧,绝不会阻塞、序列化等待或改变
规划周期。
`EnableNativePainterVisualization=true` 才会为本会话创建 `TrajectoryObserver.World``LS``ST` Painter;默认关闭。
网页与 Painter 可以同时开启,也可全部关闭:全部关闭时仍保留 `OBSERVE_ONLY` 规划、控制台和 UI 状态。网页的启动、
快照或服务出现异常时会只记录一次中文诊断并熔断本次网页输出,不自动循环重启,也不会停止规划观察。
网页采用中文说明配合科研绘图约定:白底、细灰网格和细曲线;当前轨迹为蓝色实线,上一轮为灰色虚线,换向与交接点
为橙色,红色仅表示实际越界或失败。路径总览把已完成方向段画为灰色细实线、活动段画为蓝色细实线、未来段画为浅灰
虚线,并以深蓝覆盖当前规划视界。页面的 `ReferenceS (m)` 是将轨迹投影到**完整活动方向段**后的共享参考站;
`PathS (m)` 是每轮 ST 求解使用、从该轮局部起点累计的实际路径弧长,二者不可互相替代或跨轮直接相减。
运动学页中 `j[i]` 仅表示真实区间 `[tᵢ, tᵢ₊₁)`,所以 jerk 只有 `N-1` 个样本;末点之后没有虚构的 `j=0`
状态页将滚动续航、接近停车边界和精确停车边界分别标为 `RollingContinuation`
`ApproachStopBoundary``ExactStopAtBoundary`:前两者允许非零末端速度,只有真实边界才是精确停车锚点。换向高亮
仅会在投影位置满足容差、已连续停车保持 `GearSwitchStopHoldSeconds`,并取得
`DirectionConfirmationSamples` 个满足 `DirectionConfirmationSpeedMetersPerSecond` 阈值的下一方向带符号速度样本后,
严格从 `N -> N+1` 前进。
## Operator launch and stop checklist
The vehicle UI entry is `EM轨迹规划观察闭环测试`. It is an observation session only; it has no chassis,
@@ -97,6 +121,8 @@ command is diagnostic information only and must not be copied into a vehicle-con
To stop, use the vehicle UI's normal MovementTest stop action. Confirm the status says
`Observation stop requested; all observer layers were cleared.` The cancellation request stops observer work and clears
all three layers. A current-session runtime fault also clears all three layers so stale active diagnostics are not left
on screen; an intentional bootstrap failure preserves its diagnostic world view. Stopping, running, or starting this
test must not issue chassis, motor, steering, brake, or gear output.
an existing Painter, stops the web publisher/HTTP/SSE service, and releases its port. A current-session runtime fault
also clears an existing Painter and stops the web session so stale diagnostics are not left on screen; an intentional
bootstrap failure still reports through UI/console even when both visualization modes are off. Closing a browser tab does
not stop the session; `TestStop()` owns final web and Painter cleanup. Stopping, running, or starting this test must not
issue chassis, motor, steering, brake, or gear output.
@@ -47,6 +47,10 @@ if (Test-SamePath $outputDirectoryPath $workspaceRoot) {
if (-not [System.IO.File]::Exists($managedDllPath)) {
throw "Managed DLL does not exist: $managedDllPath"
}
$visualizationDll = Join-Path ([System.IO.Path]::GetDirectoryName($managedDllPath)) 'TrajectoryPlanningVisualization.dll'
if (-not [System.IO.File]::Exists($visualizationDll)) {
throw "Visualization DLL does not exist beside managed DLL: $visualizationDll"
}
if (-not [System.Environment]::Is64BitProcess) {
throw 'Refusing to publish from a non-x64 PowerShell host.'
}
@@ -89,6 +93,7 @@ $movedExistingPlugins = $false
try {
[System.IO.Directory]::CreateDirectory((Join-Path $stagingDirectory 'licenses')) | Out-Null
[System.IO.File]::Copy($managedDllPath, (Join-Path $stagingDirectory 'ClumsyPilot.dll'), $false)
[System.IO.File]::Copy($visualizationDll, (Join-Path $stagingDirectory 'TrajectoryPlanningVisualization.dll'), $false)
[System.IO.File]::Copy($nativeDll, (Join-Path $stagingDirectory 'osqp.dll'), $false)
[System.IO.File]::Copy($license, (Join-Path $stagingDirectory 'licenses\OSQP-LICENSE.txt'), $false)
[System.IO.File]::Copy($notice, (Join-Path $stagingDirectory 'licenses\OSQP-NOTICE.txt'), $false)
@@ -83,14 +83,20 @@ internal static class PluginPackagingChecks
relativeFiles.Add(Path.GetRelativePath(packageRoot, files[index]).Replace('\\', '/'));
relativeFiles.Sort(StringComparer.Ordinal);
Verification.Equal("plugins/ClumsyPilot.dll|plugins/licenses/OSQP-LICENSE.txt|plugins/licenses/OSQP-NOTICE.txt|" +
"plugins/licenses/OSQP-VERSION.txt|plugins/osqp.dll", string.Join("|", relativeFiles),
"plugin tree contains exactly the managed DLL, native DLL, and OSQP license files");
Verification.Equal("plugins/ClumsyPilot.dll|plugins/TrajectoryPlanningVisualization.dll|" +
"plugins/licenses/OSQP-LICENSE.txt|plugins/licenses/OSQP-NOTICE.txt|plugins/licenses/OSQP-VERSION.txt|" +
"plugins/osqp.dll", string.Join("|", relativeFiles),
"plugin tree contains exactly the managed DLLs, native DLL, and OSQP license files");
string managedPlugin = Path.Combine(pluginsDirectory, "ClumsyPilot.dll");
AssemblyName managedAssembly = AssemblyName.GetAssemblyName(managedPlugin);
Verification.True(!string.IsNullOrWhiteSpace(managedAssembly.Name), "packaged ClumsyPilot.dll is managed");
string visualizationPlugin = Path.Combine(pluginsDirectory, "TrajectoryPlanningVisualization.dll");
AssemblyName visualizationAssembly = AssemblyName.GetAssemblyName(visualizationPlugin);
Verification.Equal("TrajectoryPlanningVisualization", visualizationAssembly.Name,
"packaged visualization DLL has the expected managed assembly name");
string nativePlugin = Path.Combine(pluginsDirectory, "osqp.dll");
Verification.Equal((ushort)0x8664, ReadPeMachine(nativePlugin), "packaged OSQP binary is x64");
Verification.Equal(ReadExpectedOsqpHash(repositoryRoot), GetSha256(nativePlugin),