636 lines
22 KiB
C#
636 lines
22 KiB
C#
|
||
|
||
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 = "SendMotion:4m 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);
|
||
}
|
||
}
|
||
}
|