2026-07-21 11:11:01 +08:00
|
|
|
|
using ClumsyCore;
|
2026-07-21 17:38:16 +08:00
|
|
|
|
using ClumsyCore.DTools;
|
2026-07-21 11:11:01 +08:00
|
|
|
|
using ClumsyCore.Interfaces;
|
|
|
|
|
|
using ClumsyCore.Pilot;
|
2026-07-28 18:14:33 +08:00
|
|
|
|
using CommonUsage.Chassis;
|
2026-07-21 17:38:16 +08:00
|
|
|
|
using MDCSToolBox.Commons.Controllers;
|
2026-07-28 18:14:33 +08:00
|
|
|
|
using MDCSToolBox.Clumsy.Tracks;
|
|
|
|
|
|
using MyParking.Shared;
|
2026-07-21 17:38:16 +08:00
|
|
|
|
using System;
|
2026-07-29 18:21:29 +08:00
|
|
|
|
using System.Collections.Generic;
|
2026-07-21 17:38:16 +08:00
|
|
|
|
using System.Numerics;
|
2026-07-28 18:14:33 +08:00
|
|
|
|
using System.Threading;
|
2026-07-21 11:11:01 +08:00
|
|
|
|
|
2026-07-21 17:38:16 +08:00
|
|
|
|
namespace MultiWheelC
|
2026-07-21 11:11:01 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
internal static class MovementTestPreparation
|
2026-07-21 11:11:01 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
// 在测试正式开始前,将四个舵轮稳定回正到车体前向。
|
|
|
|
|
|
public static bool AlignWheelsForward(
|
|
|
|
|
|
ref DriveTask activeTask)
|
|
|
|
|
|
{
|
|
|
|
|
|
var preparation = new PrepareWheelsForward();
|
|
|
|
|
|
var task = new DriveTask(preparation.Get());
|
|
|
|
|
|
activeTask = task;
|
2026-07-21 11:11:01 +08:00
|
|
|
|
|
2026-07-28 18:14:33 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
task.Wait();
|
|
|
|
|
|
return preparation.Completed;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
$"测试前舵轮回正失败:{ex.Message}");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
task.Stop();
|
|
|
|
|
|
if (ReferenceEquals(activeTask, task))
|
|
|
|
|
|
activeTask = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-21 11:11:01 +08:00
|
|
|
|
|
2026-07-28 18:14:33 +08:00
|
|
|
|
// 只读取实际舵角,检查四个舵轮是否已与车头方向一致。
|
|
|
|
|
|
public static bool AreWheelsForward(
|
|
|
|
|
|
float toleranceDegrees = 2f)
|
2026-07-21 11:11:01 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
var chassis =
|
|
|
|
|
|
PilotDefinition.Chassis as MultiWheelChassis;
|
|
|
|
|
|
if (chassis == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
"当前底盘不是MultiWheelChassis,无法检查舵轮方向。");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var adapter = new MultiWheelChassisAdapter(
|
|
|
|
|
|
chassis,
|
|
|
|
|
|
PilotDefinition.Self.CarNum);
|
|
|
|
|
|
var toleranceRadians =
|
|
|
|
|
|
toleranceDegrees * Math.PI / 180.0;
|
|
|
|
|
|
|
|
|
|
|
|
if (adapter.AreParallelWheelsAligned(
|
|
|
|
|
|
0.0,
|
|
|
|
|
|
toleranceRadians))
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
"四个舵轮尚未与车头方向一致,请先执行“准备:四个舵轮与车头方向一致”。");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
$"检查舵轮方向失败:{ex.Message}");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[MovementTest(name = "准备:四个舵轮与车头方向一致")]
|
|
|
|
|
|
public class AlignWheelsForwardTest : MovementTest
|
|
|
|
|
|
{
|
|
|
|
|
|
private DriveTask _task;
|
|
|
|
|
|
|
|
|
|
|
|
// 单独将四个舵轮转到车体前向0°并等待实际反馈稳定到位。
|
|
|
|
|
|
public override void Test()
|
|
|
|
|
|
{
|
|
|
|
|
|
MovementTestPreparation.AlignWheelsForward(
|
|
|
|
|
|
ref _task);
|
2026-07-21 11:11:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 18:14:33 +08:00
|
|
|
|
// 停止正在执行的舵轮回正任务并清零底盘运动命令。
|
2026-07-21 17:38:16 +08:00
|
|
|
|
public override void TestStop()
|
2026-07-21 11:11:01 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
_task?.Stop();
|
|
|
|
|
|
_task = null;
|
2026-07-21 11:11:01 +08:00
|
|
|
|
}
|
2026-07-28 18:14:33 +08:00
|
|
|
|
}
|
2026-07-21 11:11:01 +08:00
|
|
|
|
|
2026-07-29 18:21:29 +08:00
|
|
|
|
[MovementTest(name = "旧版SendMotion:连续前进4m")]
|
2026-07-28 18:14:33 +08:00
|
|
|
|
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连续直线并记录测试数据。
|
2026-07-21 17:38:16 +08:00
|
|
|
|
public override void Test()
|
2026-07-21 11:11:01 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
if (!MovementTestPreparation.AreWheelsForward())
|
2026-07-21 11:11:01 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
return;
|
2026-07-21 11:11:01 +08:00
|
|
|
|
}
|
2026-07-28 18:14:33 +08:00
|
|
|
|
|
|
|
|
|
|
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))
|
2026-07-21 11:11:01 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
"Detour当前位姿无效,取消连续前进4m测试。");
|
|
|
|
|
|
return;
|
2026-07-21 11:11:01 +08:00
|
|
|
|
}
|
2026-07-28 18:14:33 +08:00
|
|
|
|
var source = new Vector2((float)location.x, (float)location.y);
|
|
|
|
|
|
// Detour航向单位是度,三角函数需要弧度。
|
|
|
|
|
|
var headingRadians = location.th * Math.PI / 180.0;
|
|
|
|
|
|
var destination = new Vector2(
|
|
|
|
|
|
source.X + DistanceMillimeters * (float)Math.Cos(headingRadians),
|
|
|
|
|
|
source.Y + DistanceMillimeters * (float)Math.Sin(headingRadians));
|
|
|
|
|
|
_recorder =
|
|
|
|
|
|
new TrackingExperimentRecorder(
|
2026-07-29 18:21:29 +08:00
|
|
|
|
controllerName: "LegacyGeometricController",
|
|
|
|
|
|
trajectoryName: "LegacyStraight4m",
|
2026-07-28 18:14:33 +08:00
|
|
|
|
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
|
2026-07-21 11:11:01 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
_task?.Stop();
|
|
|
|
|
|
_recorder?.UpdateCommand(0f, 0f);
|
|
|
|
|
|
_recorder?.StopAndSave();
|
|
|
|
|
|
_task = null;
|
|
|
|
|
|
_recorder = null;
|
|
|
|
|
|
}
|
2026-07-21 11:11:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 18:14:33 +08:00
|
|
|
|
public override void TestStop()
|
|
|
|
|
|
{
|
|
|
|
|
|
_task?.Stop();
|
|
|
|
|
|
_recorder?.UpdateCommand(0f, 0f);
|
|
|
|
|
|
_recorder?.StopAndSave();
|
|
|
|
|
|
}
|
2026-07-21 11:11:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 18:14:33 +08:00
|
|
|
|
[MovementTest(name = "测试原地自转90°")]
|
|
|
|
|
|
public class TestRotate90 : MovementTest
|
2026-07-21 11:11:01 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
public float RelativeAngleDegrees = 90f; // 相对当前航向的旋转角度,逆时针为正。
|
|
|
|
|
|
public float MaxAngularSpeedDegreesPerSecond = 20f; // PID输出的最大角速度。
|
|
|
|
|
|
public int TrialNumber = 1; // 重复实验编号。
|
|
|
|
|
|
|
|
|
|
|
|
private DriveTask _task;
|
|
|
|
|
|
private TrackingExperimentRecorder _recorder;
|
|
|
|
|
|
|
|
|
|
|
|
// 从当前Detour航向开始,原地相对旋转指定角度并记录实验数据。
|
|
|
|
|
|
public override void Test()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (float.IsNaN(RelativeAngleDegrees) ||
|
|
|
|
|
|
float.IsInfinity(RelativeAngleDegrees) ||
|
|
|
|
|
|
float.IsNaN(MaxAngularSpeedDegreesPerSecond) ||
|
|
|
|
|
|
float.IsInfinity(MaxAngularSpeedDegreesPerSecond) ||
|
|
|
|
|
|
MaxAngularSpeedDegreesPerSecond <= 0f)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine("原地旋转测试参数无效。");
|
|
|
|
|
|
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 rotationCenter =
|
|
|
|
|
|
new Vector2((float)location.x, (float)location.y);
|
|
|
|
|
|
var targetWorldAngle =
|
|
|
|
|
|
NormalizeDegrees(
|
|
|
|
|
|
(float)location.th + RelativeAngleDegrees);
|
|
|
|
|
|
|
|
|
|
|
|
_recorder = new TrackingExperimentRecorder(
|
|
|
|
|
|
controllerName: "InPlaceRotatePID",
|
|
|
|
|
|
trajectoryName: "Rotate90",
|
|
|
|
|
|
trialNumber: TrialNumber,
|
|
|
|
|
|
referenceStart: rotationCenter,
|
|
|
|
|
|
referenceEnd: rotationCenter,
|
2026-07-29 18:21:29 +08:00
|
|
|
|
referenceSpeed: 0f,
|
|
|
|
|
|
referenceAngularSpeed:
|
|
|
|
|
|
MaxAngularSpeedDegreesPerSecond *
|
|
|
|
|
|
(float)Math.PI / 180f);
|
2026-07-28 18:14:33 +08:00
|
|
|
|
_recorder.Start();
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
_task = new DriveTask(
|
|
|
|
|
|
new MultiWheelRotateInPlace
|
|
|
|
|
|
{
|
|
|
|
|
|
// MultiWheelRotateInPlace接收世界坐标系绝对航向。
|
|
|
|
|
|
AngleTarget = targetWorldAngle,
|
|
|
|
|
|
PidparamsRead = () => new PIDParams
|
|
|
|
|
|
{
|
|
|
|
|
|
Kp =
|
|
|
|
|
|
PilotDefinition.Conf.InPlaceRotateKp,
|
|
|
|
|
|
Ki =
|
|
|
|
|
|
PilotDefinition.Conf.InPlaceRotateKi,
|
|
|
|
|
|
Kd =
|
|
|
|
|
|
PilotDefinition.Conf.InPlaceRotateKd,
|
|
|
|
|
|
DeadZone =
|
|
|
|
|
|
PilotDefinition.Conf
|
|
|
|
|
|
.InPlaceRotateArriveDeg,
|
|
|
|
|
|
SpeedAccPerSec =
|
|
|
|
|
|
PilotDefinition.Conf.InPlaceRotateAcc,
|
|
|
|
|
|
OutputUpperThreshold =
|
|
|
|
|
|
MaxAngularSpeedDegreesPerSecond,
|
|
|
|
|
|
MaxI =
|
|
|
|
|
|
PilotDefinition.Conf.InPlaceRotateMaxI
|
|
|
|
|
|
},
|
|
|
|
|
|
CommandAngularSpeedObserver =
|
|
|
|
|
|
commandAngularSpeed =>
|
|
|
|
|
|
_recorder?.UpdateCommand(
|
|
|
|
|
|
0f,
|
2026-07-29 18:21:29 +08:00
|
|
|
|
commandAngularSpeed *
|
|
|
|
|
|
(float)Math.PI / 180f)
|
2026-07-28 18:14:33 +08:00
|
|
|
|
}.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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将世界航向归一化到大约[-180°,180°]。
|
|
|
|
|
|
private static float NormalizeDegrees(float angleDegrees)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (float)(
|
|
|
|
|
|
angleDegrees -
|
|
|
|
|
|
Math.Round(angleDegrees / 360.0) * 360.0);
|
|
|
|
|
|
}
|
2026-07-21 11:11:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-29 18:21:29 +08:00
|
|
|
|
[MovementTest(name = "旧版SendMotion:左转90°圆弧")]
|
2026-07-28 18:14:33 +08:00
|
|
|
|
public class TestArcMovement : MovementTest
|
2026-07-21 11:11:01 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
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 =
|
|
|
|
|
|
location.th * Math.PI / 180.0;
|
|
|
|
|
|
|
|
|
|
|
|
// 根据世界航向求车体左法向,左转圆心位于车辆左侧。
|
|
|
|
|
|
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
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-29 18:21:29 +08:00
|
|
|
|
// 左转90°后,圆心到终点的径向方向等于起始车头方向。
|
|
|
|
|
|
var destination = center + new Vector2(
|
|
|
|
|
|
RadiusMillimeters *
|
|
|
|
|
|
(float)Math.Cos(headingRadians),
|
|
|
|
|
|
RadiusMillimeters *
|
|
|
|
|
|
(float)Math.Sin(headingRadians));
|
|
|
|
|
|
|
2026-07-28 18:14:33 +08:00
|
|
|
|
if (!controller.AddTrack(arc, "LeftArc90Degrees"))
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
"左转90°圆弧轨迹添加失败,取消测试。");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_recorder = new TrackingExperimentRecorder(
|
2026-07-29 18:21:29 +08:00
|
|
|
|
controllerName: "LegacyGeometricController",
|
2026-07-28 18:14:33 +08:00
|
|
|
|
trajectoryName:
|
2026-07-29 18:21:29 +08:00
|
|
|
|
$"LegacyLeftArc90_R{RadiusMillimeters:0}mm",
|
2026-07-28 18:14:33 +08:00
|
|
|
|
trialNumber: TrialNumber,
|
|
|
|
|
|
referenceStart: source,
|
2026-07-29 18:21:29 +08:00
|
|
|
|
referenceEnd: destination,
|
2026-07-28 18:14:33 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-27 17:47:50 +08:00
|
|
|
|
|
2026-07-28 18:14:33 +08:00
|
|
|
|
// 停止圆弧运动并保存当前已经采集的实验数据。
|
2026-07-21 17:38:16 +08:00
|
|
|
|
public override void TestStop()
|
2026-07-21 11:11:01 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
_task?.Stop();
|
|
|
|
|
|
_recorder?.UpdateCommand(0f, 0f);
|
|
|
|
|
|
_recorder?.StopAndSave();
|
2026-07-21 11:11:01 +08:00
|
|
|
|
}
|
2026-07-28 18:14:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-29 18:21:29 +08:00
|
|
|
|
[MovementTest(name = "测试蟹行前进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
|
|
|
|
|
|
{
|
|
|
|
|
|
PathKind =
|
|
|
|
|
|
CrabMotionFrameTracker
|
|
|
|
|
|
.ReferencePathKind.Straight,
|
|
|
|
|
|
StartPosition = source,
|
|
|
|
|
|
InitialBodyYawRadians =
|
|
|
|
|
|
bodyYawRadians,
|
|
|
|
|
|
LengthMillimeters =
|
|
|
|
|
|
DistanceMillimeters,
|
|
|
|
|
|
CruiseSpeed = CruiseSpeed
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
_recorder = new TrackingExperimentRecorder(
|
|
|
|
|
|
controllerName:
|
|
|
|
|
|
"CrabMotionFrameTracker",
|
|
|
|
|
|
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 =
|
|
|
|
|
|
location.th * Math.PI / 180.0;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[MovementTest(name = "测试蟹行左转90°圆弧")]
|
|
|
|
|
|
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 =
|
|
|
|
|
|
location.th * Math.PI / 180.0;
|
|
|
|
|
|
var tracker = new CrabMotionFrameTracker
|
|
|
|
|
|
{
|
|
|
|
|
|
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:
|
|
|
|
|
|
"CrabMotionFrameTracker",
|
|
|
|
|
|
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 = "测试蟹行4m S型曲线")]
|
|
|
|
|
|
public class TestCrabSCurve4m : MovementTest
|
|
|
|
|
|
{
|
|
|
|
|
|
public float LengthMillimeters = 4000f;
|
|
|
|
|
|
public float LateralOffsetMillimeters = 400f;
|
|
|
|
|
|
public float CruiseSpeed = 0.2f;
|
|
|
|
|
|
public int TrialNumber = 1;
|
|
|
|
|
|
|
|
|
|
|
|
private DriveTask _task;
|
|
|
|
|
|
private TrackingExperimentRecorder _recorder;
|
|
|
|
|
|
|
|
|
|
|
|
// 将车体左侧作为运动前向,跟踪与普通测试参数一致的4m 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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当前位姿无效,取消蟹行S型曲线测试。");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var source = new Vector2(
|
|
|
|
|
|
(float)location.x,
|
|
|
|
|
|
(float)location.y);
|
|
|
|
|
|
var bodyYawRadians =
|
|
|
|
|
|
location.th * Math.PI / 180.0;
|
|
|
|
|
|
var initialMotionYaw =
|
|
|
|
|
|
bodyYawRadians + Math.PI / 2.0;
|
|
|
|
|
|
var destination = new Vector2(
|
|
|
|
|
|
source.X +
|
|
|
|
|
|
LengthMillimeters *
|
|
|
|
|
|
(float)Math.Cos(
|
|
|
|
|
|
initialMotionYaw),
|
|
|
|
|
|
source.Y +
|
|
|
|
|
|
LengthMillimeters *
|
|
|
|
|
|
(float)Math.Sin(
|
|
|
|
|
|
initialMotionYaw));
|
|
|
|
|
|
var tracker = new CrabMotionFrameTracker
|
|
|
|
|
|
{
|
|
|
|
|
|
PathKind =
|
|
|
|
|
|
CrabMotionFrameTracker
|
|
|
|
|
|
.ReferencePathKind.SCurve,
|
|
|
|
|
|
StartPosition = source,
|
|
|
|
|
|
InitialBodyYawRadians =
|
|
|
|
|
|
bodyYawRadians,
|
|
|
|
|
|
LengthMillimeters =
|
|
|
|
|
|
LengthMillimeters,
|
|
|
|
|
|
SCurveLateralOffsetMillimeters =
|
|
|
|
|
|
LateralOffsetMillimeters,
|
|
|
|
|
|
CruiseSpeed = CruiseSpeed
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
_recorder = new TrackingExperimentRecorder(
|
|
|
|
|
|
controllerName:
|
|
|
|
|
|
"CrabMotionFrameTracker",
|
|
|
|
|
|
trajectoryName:
|
|
|
|
|
|
$"CrabSCurve4m_A{LateralOffsetMillimeters: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 =
|
|
|
|
|
|
location.th * Math.PI / 180.0;
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// C层单车测试:统一使用车体速度命令和SendXYThSpeed跟踪普通模式轨迹。
|
|
|
|
|
|
public abstract class XYThNormalTrajectoryTestBase : MovementTest
|
|
|
|
|
|
{
|
|
|
|
|
|
public float LengthMillimeters = 4000f;
|
|
|
|
|
|
public float RadiusMillimeters = 2000f;
|
|
|
|
|
|
public float LateralOffsetMillimeters = 400f;
|
|
|
|
|
|
public float CruiseSpeed = 0.3f;
|
|
|
|
|
|
public int TrialNumber = 1;
|
|
|
|
|
|
|
|
|
|
|
|
private DriveTask _task;
|
|
|
|
|
|
private TrackingExperimentRecorder _recorder;
|
|
|
|
|
|
|
|
|
|
|
|
protected abstract CrabMotionFrameTracker.ReferencePathKind ReferencePath { get; }
|
|
|
|
|
|
|
|
|
|
|
|
protected abstract string TrajectoryName { get; }
|
|
|
|
|
|
|
|
|
|
|
|
// C层单车测试:读取Detour起点并执行普通模式SendXYThSpeed轨迹。
|
|
|
|
|
|
public override void Test()
|
|
|
|
|
|
{
|
|
|
|
|
|
ValidateParameters();
|
|
|
|
|
|
|
|
|
|
|
|
if (!TryReadDetourPose(
|
|
|
|
|
|
out var source,
|
|
|
|
|
|
out var initialBodyYawRadians))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
"Detour当前位置或航向无效,无法开始新版SendXYThSpeed测试。");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var tracker = new CrabMotionFrameTracker
|
|
|
|
|
|
{
|
|
|
|
|
|
PathKind = ReferencePath,
|
|
|
|
|
|
// 普通模式的运动坐标系与车体坐标系重合。
|
|
|
|
|
|
MotionFrameYawInBodyRadians = 0.0,
|
|
|
|
|
|
StartPosition = source,
|
|
|
|
|
|
InitialBodyYawRadians = initialBodyYawRadians,
|
|
|
|
|
|
LengthMillimeters = LengthMillimeters,
|
|
|
|
|
|
RadiusMillimeters = RadiusMillimeters,
|
|
|
|
|
|
ArcSweepRadians = Math.PI / 2.0,
|
|
|
|
|
|
SCurveLateralOffsetMillimeters =
|
|
|
|
|
|
LateralOffsetMillimeters,
|
|
|
|
|
|
CruiseSpeed = CruiseSpeed,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var destination = ReferencePath ==
|
|
|
|
|
|
CrabMotionFrameTracker.ReferencePathKind
|
|
|
|
|
|
.LeftArc
|
|
|
|
|
|
? tracker.GetArcDestination()
|
|
|
|
|
|
: new Vector2(
|
|
|
|
|
|
source.X +
|
|
|
|
|
|
LengthMillimeters *
|
|
|
|
|
|
(float)Math.Cos(initialBodyYawRadians),
|
|
|
|
|
|
source.Y +
|
|
|
|
|
|
LengthMillimeters *
|
|
|
|
|
|
(float)Math.Sin(initialBodyYawRadians));
|
|
|
|
|
|
|
|
|
|
|
|
_recorder = new TrackingExperimentRecorder(
|
|
|
|
|
|
controllerName: "UnifiedXYThTracker",
|
|
|
|
|
|
trajectoryName: TrajectoryName,
|
|
|
|
|
|
trialNumber: TrialNumber,
|
|
|
|
|
|
referenceStart: source,
|
|
|
|
|
|
referenceEnd: destination,
|
|
|
|
|
|
referenceSpeed: CruiseSpeed);
|
|
|
|
|
|
|
|
|
|
|
|
tracker.CommandObserver =
|
|
|
|
|
|
(vx, vy, omegaRadiansPerSecond) =>
|
|
|
|
|
|
_recorder?.UpdateBodyCommand(
|
|
|
|
|
|
vx,
|
|
|
|
|
|
vy,
|
|
|
|
|
|
omegaRadiansPerSecond);
|
|
|
|
|
|
|
|
|
|
|
|
_recorder.Start();
|
|
|
|
|
|
_task = new DriveTask(tracker.Get());
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
_task.Wait();
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
_recorder?.UpdateBodyCommand(0f, 0f, 0f);
|
|
|
|
|
|
_recorder?.StopAndSave();
|
|
|
|
|
|
_recorder = null;
|
|
|
|
|
|
_task = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// C层单车测试:停止新版SendXYThSpeed轨迹并保存已有记录。
|
|
|
|
|
|
public override void TestStop()
|
|
|
|
|
|
{
|
|
|
|
|
|
_task?.Stop();
|
|
|
|
|
|
_recorder?.UpdateBodyCommand(0f, 0f, 0f);
|
|
|
|
|
|
_recorder?.StopAndSave();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// C层单车测试:检查新版轨迹的长度、半径、偏移和速度参数。
|
|
|
|
|
|
private void ValidateParameters()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!IsPositiveFinite(LengthMillimeters))
|
|
|
|
|
|
throw new ArgumentOutOfRangeException(
|
|
|
|
|
|
nameof(LengthMillimeters),
|
|
|
|
|
|
"轨迹长度必须是正有限值。");
|
|
|
|
|
|
|
|
|
|
|
|
if (!IsPositiveFinite(RadiusMillimeters))
|
|
|
|
|
|
throw new ArgumentOutOfRangeException(
|
|
|
|
|
|
nameof(RadiusMillimeters),
|
|
|
|
|
|
"圆弧半径必须是正有限值。");
|
|
|
|
|
|
|
|
|
|
|
|
if (!IsPositiveFinite(LateralOffsetMillimeters))
|
|
|
|
|
|
throw new ArgumentOutOfRangeException(
|
|
|
|
|
|
nameof(LateralOffsetMillimeters),
|
|
|
|
|
|
"S型曲线横向偏移必须是正有限值。");
|
|
|
|
|
|
|
|
|
|
|
|
if (!IsPositiveFinite(CruiseSpeed))
|
|
|
|
|
|
throw new ArgumentOutOfRangeException(
|
|
|
|
|
|
nameof(CruiseSpeed),
|
|
|
|
|
|
"巡航速度必须是正有限值。");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// C层单车测试:读取并验证Detour毫米坐标和角度制航向。
|
|
|
|
|
|
private static bool TryReadDetourPose(
|
|
|
|
|
|
out Vector2 position,
|
|
|
|
|
|
out double yawRadians)
|
|
|
|
|
|
{
|
|
|
|
|
|
var location = DetourInterface.getCartLocation();
|
|
|
|
|
|
var x = location.x;
|
|
|
|
|
|
var y = location.y;
|
|
|
|
|
|
var thetaDegrees = location.th;
|
|
|
|
|
|
|
|
|
|
|
|
position = new Vector2((float)x, (float)y);
|
|
|
|
|
|
yawRadians = thetaDegrees * Math.PI / 180.0;
|
|
|
|
|
|
|
|
|
|
|
|
return IsFinite(x) &&
|
|
|
|
|
|
IsFinite(y) &&
|
|
|
|
|
|
IsFinite(thetaDegrees);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// C层单车测试:判断浮点参数是否为有限值。
|
|
|
|
|
|
private static bool IsFinite(double value)
|
|
|
|
|
|
{
|
|
|
|
|
|
return !double.IsNaN(value) &&
|
|
|
|
|
|
!double.IsInfinity(value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// C层单车测试:判断浮点参数是否为正有限值。
|
|
|
|
|
|
private static bool IsPositiveFinite(double value)
|
|
|
|
|
|
{
|
|
|
|
|
|
return IsFinite(value) && value > 0.0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[MovementTest(name = "新版SendXYThSpeed:连续前进4m")]
|
|
|
|
|
|
public sealed class TestXYThForward4m :
|
|
|
|
|
|
XYThNormalTrajectoryTestBase
|
|
|
|
|
|
{
|
|
|
|
|
|
protected override CrabMotionFrameTracker.ReferencePathKind
|
|
|
|
|
|
ReferencePath =>
|
|
|
|
|
|
CrabMotionFrameTracker.ReferencePathKind.Straight;
|
|
|
|
|
|
|
|
|
|
|
|
protected override string TrajectoryName =>
|
|
|
|
|
|
"XYThStraight4m";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[MovementTest(name = "新版SendXYThSpeed:左转90°圆弧")]
|
|
|
|
|
|
public sealed class TestXYThLeftArc90 :
|
|
|
|
|
|
XYThNormalTrajectoryTestBase
|
|
|
|
|
|
{
|
|
|
|
|
|
protected override CrabMotionFrameTracker.ReferencePathKind
|
|
|
|
|
|
ReferencePath =>
|
|
|
|
|
|
CrabMotionFrameTracker.ReferencePathKind.LeftArc;
|
|
|
|
|
|
|
|
|
|
|
|
protected override string TrajectoryName =>
|
|
|
|
|
|
$"XYThLeftArc90_R{RadiusMillimeters:0}mm";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[MovementTest(name = "新版SendXYThSpeed:4m S型曲线")]
|
|
|
|
|
|
public sealed class TestXYThSCurve4m :
|
|
|
|
|
|
XYThNormalTrajectoryTestBase
|
|
|
|
|
|
{
|
|
|
|
|
|
protected override CrabMotionFrameTracker.ReferencePathKind
|
|
|
|
|
|
ReferencePath =>
|
|
|
|
|
|
CrabMotionFrameTracker.ReferencePathKind.SCurve;
|
|
|
|
|
|
|
|
|
|
|
|
protected override string TrajectoryName =>
|
|
|
|
|
|
$"XYThSCurve4m_A{LateralOffsetMillimeters:0}mm";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 18:14:33 +08:00
|
|
|
|
public abstract class ClampMovementTestBase : MovementTest
|
|
|
|
|
|
{
|
|
|
|
|
|
public float TimeoutSeconds = 30f; // 动作超时时间,单位s。
|
2026-07-21 11:11:01 +08:00
|
|
|
|
|
2026-07-28 18:14:33 +08:00
|
|
|
|
private DriveTask _task;
|
|
|
|
|
|
protected abstract bool Close { get; }
|
|
|
|
|
|
|
|
|
|
|
|
// 根据派生测试类型驱动左右夹臂同步夹紧或打开。
|
2026-07-21 17:38:16 +08:00
|
|
|
|
public override void Test()
|
2026-07-21 11:11:01 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
var leftTarget = Close
|
|
|
|
|
|
? PilotDefinition.Self.LeftArmUpperPos
|
|
|
|
|
|
: PilotDefinition.Self.LeftArmLowerPos;
|
|
|
|
|
|
var rightTarget = Close
|
|
|
|
|
|
? PilotDefinition.Self.RightArmUpperPos
|
|
|
|
|
|
: PilotDefinition.Self.RightArmLowerPos;
|
|
|
|
|
|
|
|
|
|
|
|
if (float.IsNaN(leftTarget) ||
|
|
|
|
|
|
float.IsInfinity(leftTarget) ||
|
|
|
|
|
|
float.IsNaN(rightTarget) ||
|
|
|
|
|
|
float.IsInfinity(rightTarget))
|
2026-07-21 17:38:16 +08:00
|
|
|
|
{
|
2026-07-27 17:47:50 +08:00
|
|
|
|
Console.WriteLine(
|
2026-07-28 18:14:33 +08:00
|
|
|
|
"夹臂目标位置无效,取消夹臂运动测试。");
|
2026-07-27 17:47:50 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-07-28 18:14:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 防止重复点击时上一项夹臂任务仍在运行。
|
|
|
|
|
|
TestStop();
|
|
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
$"开始夹臂{(Close ? "夹紧" : "打开")}测试:" +
|
|
|
|
|
|
$"左目标={leftTarget},右目标={rightTarget}");
|
|
|
|
|
|
|
2026-07-27 17:47:50 +08:00
|
|
|
|
var task = new DriveTask(
|
2026-07-28 18:14:33 +08:00
|
|
|
|
new ClampToTarget
|
2026-07-21 17:38:16 +08:00
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
LeftClampTarget = leftTarget,
|
|
|
|
|
|
RightClampTarget = rightTarget,
|
|
|
|
|
|
TimeoutSeconds = TimeoutSeconds
|
2026-07-27 17:47:50 +08:00
|
|
|
|
}.Get());
|
2026-07-28 18:14:33 +08:00
|
|
|
|
_task = task;
|
|
|
|
|
|
|
2026-07-27 17:47:50 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
task.Wait();
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2026-07-28 18:14:33 +08:00
|
|
|
|
PilotDefinition.Self.SpeedLeftArm = 0f;
|
|
|
|
|
|
PilotDefinition.Self.SpeedRightArm = 0f;
|
|
|
|
|
|
if (ReferenceEquals(_task, task))
|
|
|
|
|
|
_task = null;
|
2026-07-27 17:47:50 +08:00
|
|
|
|
}
|
2026-07-21 17:38:16 +08:00
|
|
|
|
}
|
2026-07-28 18:14:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 停止夹臂任务并立即清零左右夹臂下发速度。
|
|
|
|
|
|
public override void TestStop()
|
|
|
|
|
|
{
|
|
|
|
|
|
_task?.Stop();
|
|
|
|
|
|
_task = null;
|
|
|
|
|
|
PilotDefinition.Self.SpeedLeftArm = 0f;
|
|
|
|
|
|
PilotDefinition.Self.SpeedRightArm = 0f;
|
|
|
|
|
|
}
|
2026-07-21 11:11:01 +08:00
|
|
|
|
}
|
2026-07-28 18:14:33 +08:00
|
|
|
|
|
|
|
|
|
|
[MovementTest(name = "夹臂关闭测试")]
|
|
|
|
|
|
public sealed class TestClampOpenMovement
|
|
|
|
|
|
: ClampMovementTestBase
|
|
|
|
|
|
{
|
|
|
|
|
|
protected override bool Close => false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[MovementTest(name = "夹臂启动测试")]
|
|
|
|
|
|
public sealed class TestClampCloseMovement
|
|
|
|
|
|
: ClampMovementTestBase
|
|
|
|
|
|
{
|
|
|
|
|
|
protected override bool Close => true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-21 11:11:01 +08:00
|
|
|
|
}
|
2026-07-28 18:14:33 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region 旧版测试
|
|
|
|
|
|
// public abstract class DstTrackerTestBase : MovementTest
|
|
|
|
|
|
// {
|
|
|
|
|
|
// public bool UseInteractivePick = true;
|
|
|
|
|
|
// public float srcX;
|
|
|
|
|
|
// public float srcY;
|
|
|
|
|
|
// public float dstX;
|
|
|
|
|
|
// public float dstY;
|
|
|
|
|
|
// public float carDirectionBias;
|
|
|
|
|
|
|
|
|
|
|
|
// private readonly Painter _painter = UI.GetPainter("DstTrackerTest");
|
|
|
|
|
|
// private DriveTask _dt;
|
|
|
|
|
|
|
|
|
|
|
|
// protected DstTrackerTestBase(float defaultCarDirectionBias)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// carDirectionBias = defaultCarDirectionBias;
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// public override void TestStop()
|
|
|
|
|
|
// {
|
|
|
|
|
|
// _dt?.Stop();
|
|
|
|
|
|
// _painter?.Clear();
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// public override void Test()
|
|
|
|
|
|
// {
|
|
|
|
|
|
// Vector2 p1;
|
|
|
|
|
|
// Vector2 p2;
|
|
|
|
|
|
// if (UseInteractivePick)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// p1 = UI.GetPoint("point1");
|
|
|
|
|
|
// p2 = UI.GetPoint("point2");
|
|
|
|
|
|
// }
|
|
|
|
|
|
// else
|
|
|
|
|
|
// {
|
|
|
|
|
|
// p1 = new Vector2(srcX, srcY);
|
|
|
|
|
|
// p2 = new Vector2(dstX, dstY);
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// _painter.Clear();
|
|
|
|
|
|
// _dt = new DriveTask(new DstTracker
|
|
|
|
|
|
// {
|
|
|
|
|
|
// Src = p1,
|
|
|
|
|
|
// Dst = p2,
|
|
|
|
|
|
// CarDirectionBias = carDirectionBias,
|
|
|
|
|
|
// }.Get());
|
|
|
|
|
|
// _dt.Wait();
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// [MovementTest(name = "测试终点跟踪动作-前进")]
|
|
|
|
|
|
// public sealed class DstTrackerForward : DstTrackerTestBase
|
|
|
|
|
|
// {
|
|
|
|
|
|
// public DstTrackerForward() : base(0f) { }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// [MovementTest(name = "测试终点跟踪动作-后退")]
|
|
|
|
|
|
// public sealed class DstTrackerBackward : DstTrackerTestBase
|
|
|
|
|
|
// {
|
|
|
|
|
|
// public DstTrackerBackward() : base(180f) { }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// [MovementTest(name = "底盘旋转测试")]
|
|
|
|
|
|
// public class RotateToAngleTest : MovementTest
|
|
|
|
|
|
// {
|
|
|
|
|
|
// private DriveTask _dt;
|
|
|
|
|
|
|
|
|
|
|
|
// // 停止当前正在执行的底盘原地旋转任务。
|
|
|
|
|
|
// public override void TestStop()
|
|
|
|
|
|
// {
|
|
|
|
|
|
// _dt?.Stop();
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 交互输入目标角度后执行底盘原地旋转测试。
|
|
|
|
|
|
// public override void Test()
|
|
|
|
|
|
// {
|
|
|
|
|
|
// var input = UI.GetInput("输入旋转角度:");
|
|
|
|
|
|
// if (!float.TryParse(input, out var angleTarget))
|
|
|
|
|
|
// {
|
|
|
|
|
|
// Console.WriteLine(
|
|
|
|
|
|
// $"旋转测试输入无效:{input}");
|
|
|
|
|
|
// return;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// // 防止重复启动测试时,上一项旋转任务仍在运行。
|
|
|
|
|
|
// _dt?.Stop();
|
|
|
|
|
|
// var task = new DriveTask(
|
|
|
|
|
|
// new MultiWheelRotateInPlace
|
|
|
|
|
|
// {
|
|
|
|
|
|
// AngleTarget = angleTarget,
|
|
|
|
|
|
|
|
|
|
|
|
// PidparamsRead = () => new PIDParams
|
|
|
|
|
|
// {
|
|
|
|
|
|
// Kp = PilotDefinition.Conf.InPlaceRotateKp,
|
|
|
|
|
|
// Ki = PilotDefinition.Conf.InPlaceRotateKi,
|
|
|
|
|
|
// Kd = PilotDefinition.Conf.InPlaceRotateKd,
|
|
|
|
|
|
// DeadZone = PilotDefinition.Conf.InPlaceRotateArriveDeg,
|
|
|
|
|
|
// SpeedAccPerSec = PilotDefinition.Conf.InPlaceRotateAcc,
|
|
|
|
|
|
// OutputUpperThreshold = PilotDefinition.Conf.InPlaceRotateMaxSpeed,
|
|
|
|
|
|
// MaxI = PilotDefinition.Conf.InPlaceRotateMaxI,
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }.Get());
|
|
|
|
|
|
// _dt = task;
|
|
|
|
|
|
// try
|
|
|
|
|
|
// {
|
|
|
|
|
|
// task.Wait();
|
|
|
|
|
|
// }
|
|
|
|
|
|
// finally
|
|
|
|
|
|
// {
|
|
|
|
|
|
// // 防止旧任务结束时,错误清除后来启动的新任务。
|
|
|
|
|
|
// if (ReferenceEquals(_dt, task))
|
|
|
|
|
|
// {
|
|
|
|
|
|
// _dt = null;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|