Files
ParkingRobot/MultiWheelC/Experiments/PathTrackingTests.cs
T

636 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Threading;
using ClumsyCore;
using ClumsyCore.Interfaces;
using ClumsyCore.Pilot;
using FundamentalLib;
using MDCSToolBox.Clumsy.Movements;
using MDCSToolBox.Clumsy.Pilot;
using MDCSToolBox.Clumsy.Tracks;
using MyParking.Shared;
namespace MultiWheelC
{
[MovementTest(name = "SendMotion:连续前进4m")]
public class TestForward4m : MovementTest
{
public float DistanceMillimeters = 4000f; // 测试距离,单位mm。
public float CruiseSpeed = 0.3f; // 巡航速度上限,单位m/s。
public int TrialNumber = 1; // 重复实验编号。
private DriveTask _task;
private TrackingExperimentRecorder _recorder;
// 从当前Detour位置沿车头方向生成4m连续直线并记录测试数据。
public override void Test()
{
if (!MovementTestPreparation.AreWheelsForward())
{
return;
}
var location = DetourInterface.getCartLocation();
if (double.IsNaN(location.x) ||
double.IsInfinity(location.x) ||
double.IsNaN(location.y) ||
double.IsInfinity(location.y) ||
double.IsNaN(location.th) ||
double.IsInfinity(location.th))
{
Console.WriteLine(
"Detour当前位姿无效,取消连续前进4m测试。");
return;
}
var source = new Vector2((float)location.x, (float)location.y);
// Detour航向单位是度,三角函数需要弧度。
var headingRadians =
AngleMath.DegreesToRadians(location.th);
var destination = new Vector2(
source.X + DistanceMillimeters * (float)Math.Cos(headingRadians),
source.Y + DistanceMillimeters * (float)Math.Sin(headingRadians));
_recorder =
new TrackingExperimentRecorder(
controllerName: "LegacyGeometricController",
trajectoryName: "LegacyStraight4m",
trialNumber: TrialNumber,
referenceStart: source,
referenceEnd: destination,
referenceSpeed: CruiseSpeed);
_recorder.Start();
try
{
_task = new DriveTask(
new DstTracker
{
Src = source,
Dst = destination,
CarDirectionBias = 0f,
MaxSpeed = CruiseSpeed
}.Get());
_task.Wait();
// 保留少量停车后数据,便于观察速度是否回到零。
Thread.Sleep(300);
}
finally
{
_task?.Stop();
_recorder?.UpdateCommand(0f, 0f);
_recorder?.StopAndSave();
_task = null;
_recorder = null;
}
}
public override void TestStop()
{
_task?.Stop();
_recorder?.UpdateCommand(0f, 0f);
_recorder?.StopAndSave();
}
}
[MovementTest(name = "SendMotion:左转90°半径2m圆弧")]
public class TestArcMovement : MovementTest
{
public float RadiusMillimeters = 2000f; // 左转圆的半径,单位mm。
public float CruiseSpeed = 0.3f; // 圆周运动速度上限,单位m/s。
public int TrialNumber = 1; // 重复实验编号。
private DriveTask _task;
private TrackingExperimentRecorder _recorder;
// 从当前位姿开始,沿半径2m的圆弧向左转弯90°。
public override void Test()
{
if (float.IsNaN(RadiusMillimeters) ||
float.IsInfinity(RadiusMillimeters) ||
RadiusMillimeters <= 0f ||
float.IsNaN(CruiseSpeed) ||
float.IsInfinity(CruiseSpeed) ||
CruiseSpeed <= 0f)
{
Console.WriteLine("圆弧运动测试参数无效。");
return;
}
if (!MovementTestPreparation.AreWheelsForward())
{
return;
}
var location = DetourInterface.getCartLocation();
if (double.IsNaN(location.x) ||
double.IsInfinity(location.x) ||
double.IsNaN(location.y) ||
double.IsInfinity(location.y) ||
double.IsNaN(location.th) ||
double.IsInfinity(location.th))
{
Console.WriteLine(
"Detour当前位姿无效,取消圆弧运动测试。");
return;
}
var source =
new Vector2((float)location.x, (float)location.y);
var headingRadians =
AngleMath.DegreesToRadians(location.th);
// 根据世界航向求车体左法向,左转圆心位于车辆左侧。
var center = new Vector2(
source.X -
RadiusMillimeters *
(float)Math.Sin(headingRadians),
source.Y +
RadiusMillimeters *
(float)Math.Cos(headingRadians));
// 从圆心指向车辆起点的极角,比车辆切线航向小90°。
var startRadialAngleDegrees =
(float)location.th - 90f;
var controller = new ChassisController
{
BaseSpeed = CruiseSpeed
}.Get();
controller.FinishSpeed = 0f;
var arc = new CircularArcTrack(
center,
RadiusMillimeters,
startRadialAngleDegrees,
startRadialAngleDegrees + 90f,
direction: 1)
{
Speed = CruiseSpeed,
CarDirectionBias = 0f
};
// 左转90°后,圆心到终点的径向方向等于起始车头方向。
var destination = center + new Vector2(
RadiusMillimeters *
(float)Math.Cos(headingRadians),
RadiusMillimeters *
(float)Math.Sin(headingRadians));
if (!controller.AddTrack(arc, "LeftArc90Degrees"))
{
Console.WriteLine(
"左转90°圆弧轨迹添加失败,取消测试。");
return;
}
_recorder = new TrackingExperimentRecorder(
controllerName: "LegacyGeometricController",
trajectoryName:
$"LegacyLeftArc90_R{RadiusMillimeters:0}mm",
trialNumber: TrialNumber,
referenceStart: source,
referenceEnd: destination,
referenceSpeed: CruiseSpeed);
_recorder.Start();
try
{
_task = new DriveTask(controller.Track());
_task.Wait();
// 保留少量停车后的样本,用于观察速度是否回到零。
Thread.Sleep(300);
}
finally
{
_task?.Stop();
_recorder?.UpdateCommand(0f, 0f);
_recorder?.StopAndSave();
_task = null;
_recorder = null;
}
}
// 停止圆弧运动并保存当前已经采集的实验数据。
public override void TestStop()
{
_task?.Stop();
_recorder?.UpdateCommand(0f, 0f);
_recorder?.StopAndSave();
}
}
[MovementTest(name = "SendMotion:蟹行直线4m")]
public class TestCrabForward4m : MovementTest
{
public float DistanceMillimeters = 4000f;
public float CruiseSpeed = 0.2f;
public int TrialNumber = 1;
private DriveTask _task;
private TrackingExperimentRecorder _recorder;
// 将车体左侧作为运动前向,沿直线蟹行4m并记录Detour实验数据。
public override void Test()
{
if (!TryReadStartPose(
out var source,
out var bodyYawRadians))
return;
var motionYaw =
bodyYawRadians + Math.PI / 2.0;
var destination = new Vector2(
source.X +
DistanceMillimeters *
(float)Math.Cos(motionYaw),
source.Y +
DistanceMillimeters *
(float)Math.Sin(motionYaw));
var tracker = new CrabMotionFrameTracker
{
CommandBackend =
CrabMotionFrameTracker
.ChassisCommandBackend
.SendMotion,
PathKind =
CrabMotionFrameTracker
.ReferencePathKind.Straight,
StartPosition = source,
InitialBodyYawRadians =
bodyYawRadians,
LengthMillimeters =
DistanceMillimeters,
CruiseSpeed = CruiseSpeed
};
_recorder = new TrackingExperimentRecorder(
controllerName:
"CrabSendMotionTracker",
trajectoryName:
"CrabStraight4m",
trialNumber: TrialNumber,
referenceStart: source,
referenceEnd: destination,
referenceSpeed: CruiseSpeed,
referenceMotionFrameYawDegrees: 90f);
tracker.CommandObserver =
(vx, vy, omega) =>
_recorder?.UpdateBodyCommand(
vx,
vy,
omega);
_recorder.Start();
try
{
_task = new DriveTask(tracker.Get());
_task.Wait();
Thread.Sleep(300);
}
finally
{
_task?.Stop();
_recorder?.UpdateBodyCommand(
0f,
0f,
0f);
_recorder?.StopAndSave();
_task = null;
_recorder = null;
}
}
public override void TestStop()
{
_task?.Stop();
_recorder?.UpdateBodyCommand(
0f,
0f,
0f);
_recorder?.StopAndSave();
}
// 读取并校验测试开始时的Detour世界位姿。
private static bool TryReadStartPose(
out Vector2 source,
out double bodyYawRadians)
{
var location =
DetourInterface.getCartLocation();
if (double.IsNaN(location.x) ||
double.IsInfinity(location.x) ||
double.IsNaN(location.y) ||
double.IsInfinity(location.y) ||
double.IsNaN(location.th) ||
double.IsInfinity(location.th))
{
Console.WriteLine(
"Detour当前位姿无效,取消蟹行直线测试。");
source = Vector2.Zero;
bodyYawRadians = 0.0;
return false;
}
source = new Vector2(
(float)location.x,
(float)location.y);
bodyYawRadians =
AngleMath.DegreesToRadians(location.th);
return true;
}
}
[MovementTest(name = "SendMotion:蟹行左转90°半径2m圆弧")]
public class TestCrabLeftArc90 : MovementTest
{
public float RadiusMillimeters = 2000f;
public float CruiseSpeed = 0.2f;
public int TrialNumber = 1;
private DriveTask _task;
private TrackingExperimentRecorder _recorder;
// 将车体左侧作为运动前向,沿半径2m的左转圆弧运动90°。
public override void Test()
{
var location =
DetourInterface.getCartLocation();
if (double.IsNaN(location.x) ||
double.IsInfinity(location.x) ||
double.IsNaN(location.y) ||
double.IsInfinity(location.y) ||
double.IsNaN(location.th) ||
double.IsInfinity(location.th))
{
Console.WriteLine(
"Detour当前位姿无效,取消蟹行圆弧测试。");
return;
}
var source = new Vector2(
(float)location.x,
(float)location.y);
var bodyYawRadians =
AngleMath.DegreesToRadians(location.th);
var tracker = new CrabMotionFrameTracker
{
CommandBackend =
CrabMotionFrameTracker
.ChassisCommandBackend
.SendMotion,
PathKind =
CrabMotionFrameTracker
.ReferencePathKind.LeftArc,
StartPosition = source,
InitialBodyYawRadians =
bodyYawRadians,
RadiusMillimeters =
RadiusMillimeters,
ArcSweepRadians = Math.PI / 2.0,
CruiseSpeed = CruiseSpeed
};
var destination =
tracker.GetArcDestination();
_recorder = new TrackingExperimentRecorder(
controllerName:
"CrabSendMotionTracker",
trajectoryName:
$"CrabLeftArc90_R{RadiusMillimeters:0}mm",
trialNumber: TrialNumber,
referenceStart: source,
referenceEnd: destination,
referenceSpeed: CruiseSpeed,
referenceMotionFrameYawDegrees: 90f);
tracker.CommandObserver =
(vx, vy, omega) =>
_recorder?.UpdateBodyCommand(
vx,
vy,
omega);
_recorder.Start();
try
{
_task = new DriveTask(tracker.Get());
_task.Wait();
Thread.Sleep(300);
}
finally
{
_task?.Stop();
_recorder?.UpdateBodyCommand(
0f,
0f,
0f);
_recorder?.StopAndSave();
_task = null;
_recorder = null;
}
}
public override void TestStop()
{
_task?.Stop();
_recorder?.UpdateBodyCommand(
0f,
0f,
0f);
_recorder?.StopAndSave();
}
}
[MovementTest(name = "SendMotion4m S型曲线")]
public class TestSCurve4m : MovementTest
{
public float LengthMillimeters = 4000f; // S型曲线纵向长度,单位mm。
public float LateralOffsetMillimeters = 400f; // S型曲线左右两侧的最大偏移,单位mm。
public float CruiseSpeed = 0.3f; // 首次实车测试建议使用0.3m/s。
public int TrialNumber = 1; // 重复实验编号。
private DriveTask _task;
private TrackingExperimentRecorder _recorder;
// 从当前Detour位姿开始,沿车头方向跟踪先左偏、再右偏并最终回中的完整S型曲线。
public override void Test()
{
if (float.IsNaN(LengthMillimeters) ||
float.IsInfinity(LengthMillimeters) ||
LengthMillimeters <= 0f ||
float.IsNaN(LateralOffsetMillimeters) ||
float.IsInfinity(LateralOffsetMillimeters) ||
LateralOffsetMillimeters <= 0f ||
float.IsNaN(CruiseSpeed) ||
float.IsInfinity(CruiseSpeed) ||
CruiseSpeed <= 0f)
{
Console.WriteLine("S型曲线测试参数无效。");
return;
}
if (!MovementTestPreparation.AreWheelsForward())
return;
var location = DetourInterface.getCartLocation();
if (double.IsNaN(location.x) ||
double.IsInfinity(location.x) ||
double.IsNaN(location.y) ||
double.IsInfinity(location.y) ||
double.IsNaN(location.th) ||
double.IsInfinity(location.th))
{
Console.WriteLine(
"Detour当前位姿无效,取消4m S型曲线测试。");
return;
}
var source =
new Vector2((float)location.x, (float)location.y);
var headingRadians =
AngleMath.DegreesToRadians(location.th);
var length = LengthMillimeters;
var offset = LateralOffsetMillimeters;
// 三段三次贝塞尔依次经过左侧峰值、中心线和右侧峰值,
// 起点、两个峰值和终点的切线均沿初始前向,连接处没有折角。
var firstControlPoints = new List<Vector2>
{
LocalToWorld(source, headingRadians, 0f, 0f),
LocalToWorld(
source, headingRadians,
length / 12f, 0f),
LocalToWorld(
source, headingRadians,
length / 6f, offset),
LocalToWorld(
source, headingRadians,
length * 0.25f, offset)
};
var secondControlPoints = new List<Vector2>
{
LocalToWorld(
source, headingRadians,
length * 0.25f, offset),
LocalToWorld(
source, headingRadians,
length / 3f, offset),
LocalToWorld(
source, headingRadians,
length * 2f / 3f, -offset),
LocalToWorld(
source, headingRadians,
length * 0.75f, -offset)
};
var thirdControlPoints = new List<Vector2>
{
LocalToWorld(
source, headingRadians,
length * 0.75f, -offset),
LocalToWorld(
source, headingRadians,
length * 5f / 6f, -offset),
LocalToWorld(
source, headingRadians,
length * 11f / 12f, 0f),
LocalToWorld(
source, headingRadians,
length, 0f)
};
var firstTrack = new BezierTrack(firstControlPoints)
{
Speed = CruiseSpeed,
CarDirectionBias = 0f
};
var secondTrack = new BezierTrack(secondControlPoints)
{
Speed = CruiseSpeed,
CarDirectionBias = 0f
};
var thirdTrack = new BezierTrack(thirdControlPoints)
{
Speed = CruiseSpeed,
CarDirectionBias = 0f
};
var controller = new ChassisController
{
BaseSpeed = CruiseSpeed
}.Get();
controller.FinishSpeed = 0f;
if (!controller.AddTrack(
firstTrack,
"SCurve4m-Part1") ||
!controller.AddTrack(
secondTrack,
"SCurve4m-Part2") ||
!controller.AddTrack(
thirdTrack,
"SCurve4m-Part3"))
{
Console.WriteLine(
"4m S型曲线轨迹添加失败,取消测试。");
return;
}
var destination =
LocalToWorld(
source,
headingRadians,
length,
0f);
_recorder = new TrackingExperimentRecorder(
controllerName: "LegacyGeometricController",
trajectoryName:
$"LegacySCurve4m_A{LateralOffsetMillimeters:0}mm",
trialNumber: TrialNumber,
referenceStart: source,
referenceEnd: destination,
referenceSpeed: CruiseSpeed);
_recorder.Start();
try
{
_task = new DriveTask(controller.Track());
_task.Wait();
// 保留少量停止后的数据,用于观察速度是否回到零。
Thread.Sleep(300);
}
finally
{
_task?.Stop();
_recorder?.UpdateCommand(0f, 0f);
_recorder?.StopAndSave();
_task = null;
_recorder = null;
}
}
// 停止S型曲线测试并保存当前已经采集的数据。
public override void TestStop()
{
_task?.Stop();
_recorder?.UpdateCommand(0f, 0f);
_recorder?.StopAndSave();
}
// 将车体起点局部坐标转换为Detour世界坐标,X向前、Y向左。
private static Vector2 LocalToWorld(
Vector2 origin,
double headingRadians,
float localX,
float localY)
{
var cos = (float)Math.Cos(headingRadians);
var sin = (float)Math.Sin(headingRadians);
return new Vector2(
origin.X + localX * cos - localY * sin,
origin.Y + localX * sin + localY * cos);
}
}
}