feat: add scientific planning dashboard
This commit is contained in:
@@ -1,16 +1,26 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using TrajectoryPlanningVisualization;
|
||||
|
||||
namespace TrajectoryPlanningVisualizationVerificationHost;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private static int Main()
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
if (args.Length == 2 && args[0] == "--smoke-seconds" &&
|
||||
int.TryParse(args[1], out int seconds) && seconds > 0)
|
||||
{
|
||||
return RunSmoke(seconds);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ContractChecks.Run();
|
||||
RuntimeChecks.Run();
|
||||
ServerChecks.Run();
|
||||
WebAssetChecks.Run();
|
||||
Console.WriteLine("PASS trajectory-planning-visualization");
|
||||
return 0;
|
||||
}
|
||||
@@ -20,4 +30,36 @@ internal static class Program
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int RunSmoke(int seconds)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var session = new PlanningVisualizationSession(
|
||||
new PlanningVisualizationOptions { Port = 0, RefreshRateHz = 10, HistoryCycleLimit = 60 });
|
||||
PlanningVisualizationSessionInfo info = session.Start(SampleSnapshotFactory.CreateStaticSnapshot());
|
||||
Console.WriteLine(info.Uri.AbsoluteUri);
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
long sequence = 1L;
|
||||
while (stopwatch.Elapsed < TimeSpan.FromSeconds(seconds))
|
||||
{
|
||||
session.Publish(SampleSnapshotFactory.CreateDynamicSnapshot(sequence++));
|
||||
TimeSpan remaining = TimeSpan.FromSeconds(seconds) - stopwatch.Elapsed;
|
||||
if (remaining <= TimeSpan.Zero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Thread.Sleep(remaining < TimeSpan.FromMilliseconds(100) ? remaining : TimeSpan.FromMilliseconds(100));
|
||||
}
|
||||
|
||||
session.Stop();
|
||||
return 0;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.Error.WriteLine(exception);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TrajectoryPlanningVisualization;
|
||||
|
||||
namespace TrajectoryPlanningVisualizationVerificationHost;
|
||||
|
||||
internal static class SampleSnapshotFactory
|
||||
{
|
||||
private static readonly DateTimeOffset Origin = DateTimeOffset.Parse("2026-08-06T00:00:00+00:00");
|
||||
|
||||
public static PlanningVisualizationStaticSnapshot CreateStaticSnapshot()
|
||||
{
|
||||
return new PlanningVisualizationStaticSnapshot(
|
||||
"合成轨迹规划观察",
|
||||
new VisualizationBounds(0d, 12d, 0d, 8d),
|
||||
new VisualizationOccupancyGrid(new VisualizationBounds(0d, 12d, 0d, 8d), 2d, 4, 6,
|
||||
new byte[] { 0b00100001, 0b00000100, 0b00000010 }),
|
||||
new[]
|
||||
{
|
||||
new VisualizationPolyline("global", "全局参考路径", "global", VisualizationLineStyle.Solid,
|
||||
Points((0d, 1d), (3d, 1d), (6d, 2d), (8d, 4d), (11d, 6d)))
|
||||
},
|
||||
new[] { new VisualizationMarker("gear-1", "gear-switch", "换向 1", new VisualizationPoint(6d, 2d)) },
|
||||
new[]
|
||||
{
|
||||
new VisualizationDirectionSegment(0, "forward", false, true, Points((0d, 1d), (3d, 1d), (6d, 2d))),
|
||||
new VisualizationDirectionSegment(1, "reverse", true, false, Points((6d, 2d), (8d, 4d), (11d, 6d)))
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new VisualizationConfigurationGroup("调度", new[]
|
||||
{
|
||||
new VisualizationValue("网页刷新率", "RefreshRateHz", "10", "Hz", "normal"),
|
||||
new VisualizationValue("历史容量", "HistoryCycleLimit", "60", "cycles", "normal")
|
||||
}),
|
||||
new VisualizationConfigurationGroup("观察安全边界", new[]
|
||||
{
|
||||
new VisualizationValue("观察模式", "OBSERVE_ONLY", "true", "", "notice")
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public static PlanningVisualizationDynamicSnapshot CreateDynamicSnapshot(long sequence)
|
||||
{
|
||||
double offset = (sequence - 1L) * 0.05d;
|
||||
return new PlanningVisualizationDynamicSnapshot(
|
||||
sequence,
|
||||
Origin.AddMilliseconds(sequence * 100L),
|
||||
"合成数据运行中",
|
||||
0,
|
||||
"forward",
|
||||
new VisualizationPose(2d + offset, 1d, 0d),
|
||||
new[]
|
||||
{
|
||||
new VisualizationPolyline("previous", "上一轮轨迹", "previous", VisualizationLineStyle.Dashed, Points((1d, 1d), (2.5d, 1d), (4d, 1.3d))),
|
||||
new VisualizationPolyline("current", "当前轨迹", "current", VisualizationLineStyle.Solid, Points((2d + offset, 1d), (3.8d + offset, 1.2d), (5.8d + offset, 1.9d)))
|
||||
},
|
||||
new[] { new VisualizationMarker("handoff", "handoff", "交接", new VisualizationPoint(2d + offset, 1d)) },
|
||||
CreateCharts(offset),
|
||||
new[]
|
||||
{
|
||||
new VisualizationValue("规划耗时", "PlanningElapsedMilliseconds", "4.2", "ms", "normal"),
|
||||
new VisualizationValue("轨迹年龄", "TrajectoryAge", "0.1", "s", "normal")
|
||||
},
|
||||
new VisualizationCycleSummary(sequence, Origin.AddMilliseconds(sequence * 100L), "成功", true, 4.2d, 0,
|
||||
"forward", "rolling", "horizon", 1.2d, 0d, ""));
|
||||
}
|
||||
|
||||
private static IReadOnlyList<VisualizationChart> CreateCharts(double offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
Chart("ls", "横向偏移", "ReferenceS (m)", "l (m)", Points((0d, .05d), (2d, .08d), (4d, .02d))),
|
||||
Chart("st", "时空轨迹", "t (s)", "PathS (m)", Points((0d, 0d), (1d, 1.2d), (2d, 3.0d))),
|
||||
Chart("curvature-s", "曲率—距离", "s (m)", "κ (m⁻¹)", Points((0d, .01d), (2d, .04d), (4d, .02d))),
|
||||
Chart("curvature-t", "曲率—时间", "t (s)", "κ (m⁻¹)", Points((0d, .01d), (1d, .04d), (2d, .02d))),
|
||||
Chart("velocity-t", "速度", "t (s)", "v (m/s)", Points((0d, 0d), (1d, 1d), (2d, 1.2d + offset))),
|
||||
Chart("acceleration-t", "加速度", "t (s)", "a (m/s²)", Points((0d, .8d), (1d, .2d), (2d, 0d))),
|
||||
Chart("jerk-t", "加加速度", "t (s)", "j (m/s³)", Points((0d, -.6d), (1d, -.2d))),
|
||||
Chart("yaw-rate-t", "横摆角速度", "t (s)", "yaw rate (rad/s)", Points((0d, 0d), (1d, .08d), (2d, .04d)))
|
||||
};
|
||||
}
|
||||
|
||||
private static VisualizationChart Chart(string id, string title, string xAxis, string yAxis, IReadOnlyList<VisualizationPoint> points)
|
||||
{
|
||||
return new VisualizationChart(id, title, xAxis, yAxis,
|
||||
new[] { new VisualizationSeries(id + "-current", "当前轨迹", VisualizationLineStyle.Solid, points) });
|
||||
}
|
||||
|
||||
private static IReadOnlyList<VisualizationPoint> Points(params (double x, double y)[] values)
|
||||
{
|
||||
var points = new List<VisualizationPoint>(values.Length);
|
||||
foreach ((double x, double y) in values)
|
||||
{
|
||||
points.Add(new VisualizationPoint(x, y));
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Linq;
|
||||
using TrajectoryPlanningVisualization;
|
||||
|
||||
namespace TrajectoryPlanningVisualizationVerificationHost;
|
||||
|
||||
internal static class WebAssetChecks
|
||||
{
|
||||
public static void Run()
|
||||
{
|
||||
BuildsDeterministicSmokeSnapshots();
|
||||
|
||||
string html = EmbeddedWebAssets.ReadText("index.html");
|
||||
string css = EmbeddedWebAssets.ReadText("app.css");
|
||||
string js = EmbeddedWebAssets.ReadText("app.js");
|
||||
|
||||
Verification.True(html.Contains("路径总览"), "overview Chinese title");
|
||||
Verification.True(html.Contains("LS / ST"), "LS/ST tab");
|
||||
Verification.True(html.Contains("曲率与运动学"), "kinematics tab");
|
||||
Verification.True(html.Contains("周期历史"), "history tab");
|
||||
Verification.True(html.Contains("生效配置"), "configuration panel");
|
||||
Verification.True(css.Contains("--current-trajectory: #1769aa"), "scientific current color");
|
||||
Verification.True(css.Contains("stroke-width: 1.1"), "thin scientific line");
|
||||
Verification.True(js.Contains("末点后无时间区间"), "jerk terminal explanation");
|
||||
Verification.True(html.Contains("occupancy-grid"), "single occupancy canvas exists");
|
||||
Verification.True(html.Contains("world-overlay"), "SVG trajectory overlay exists");
|
||||
Verification.True(js.Contains("atob"), "compact occupancy bitset is decoded in browser");
|
||||
Verification.True(!html.Contains("http://") && !html.Contains("https://"), "page has no CDN URL");
|
||||
}
|
||||
|
||||
private static void BuildsDeterministicSmokeSnapshots()
|
||||
{
|
||||
PlanningVisualizationStaticSnapshot staticSnapshot = SampleSnapshotFactory.CreateStaticSnapshot();
|
||||
PlanningVisualizationDynamicSnapshot dynamicSnapshot = SampleSnapshotFactory.CreateDynamicSnapshot(1L);
|
||||
|
||||
Verification.Equal(2, staticSnapshot.DirectionSegments.Count, "smoke snapshot has two direction segments");
|
||||
Verification.True(staticSnapshot.StaticMarkers.Any(marker => marker.Kind == "gear-switch"), "smoke snapshot has a gear marker");
|
||||
Verification.True(staticSnapshot.ConfigurationGroups.Count > 0, "smoke snapshot has effective configuration");
|
||||
foreach (string chartId in new[] { "ls", "st", "curvature-s", "curvature-t", "velocity-t", "acceleration-t", "jerk-t", "yaw-rate-t" })
|
||||
{
|
||||
Verification.True(dynamicSnapshot.Charts.Any(chart => chart.Id == chartId), "smoke snapshot includes chart " + chartId);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user