2026-08-05 18:07:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
2026-08-07 13:00:56 +08:00
|
|
|
|
using System.Globalization;
|
2026-08-05 18:07:01 +08:00
|
|
|
|
using System.Numerics;
|
|
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using ClumsyCore;
|
|
|
|
|
|
using ClumsyCore.Interfaces;
|
|
|
|
|
|
using ClumsyCore.Pilot;
|
2026-08-19 17:38:17 +08:00
|
|
|
|
using CommonUsage.Chassis;
|
2026-08-05 18:07:01 +08:00
|
|
|
|
using FundamentalLib;
|
|
|
|
|
|
using MDCSToolBox.Clumsy.Movements;
|
|
|
|
|
|
using MDCSToolBox.Clumsy.Pilot;
|
|
|
|
|
|
using MyParking.Shared;
|
2026-08-19 17:38:17 +08:00
|
|
|
|
using MultiWheelC.StateEstimation;
|
2026-08-05 18:07:01 +08:00
|
|
|
|
|
|
|
|
|
|
namespace MultiWheelC
|
|
|
|
|
|
{
|
|
|
|
|
|
public abstract class InPlaceRotateTestBase : MovementTest
|
|
|
|
|
|
{
|
|
|
|
|
|
public float RelativeAngleDegrees; // 相对当前航向的旋转角度,逆时针为正。
|
|
|
|
|
|
public int TrialNumber = 1; // 重复实验编号。
|
2026-08-21 17:33:29 +08:00
|
|
|
|
public InPlaceRotationFeedbackMode FeedbackMode =
|
|
|
|
|
|
InPlaceRotationFeedbackMode.DetourAbsoluteHeading;
|
2026-08-05 18:07:01 +08:00
|
|
|
|
|
|
|
|
|
|
private DriveTask _task;
|
|
|
|
|
|
private TrackingExperimentRecorder _recorder;
|
|
|
|
|
|
private readonly string _trajectoryName;
|
|
|
|
|
|
|
|
|
|
|
|
protected InPlaceRotateTestBase(
|
|
|
|
|
|
float relativeAngleDegrees,
|
|
|
|
|
|
string trajectoryName)
|
|
|
|
|
|
{
|
|
|
|
|
|
RelativeAngleDegrees =
|
|
|
|
|
|
relativeAngleDegrees;
|
|
|
|
|
|
_trajectoryName =
|
|
|
|
|
|
trajectoryName;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从当前Detour航向开始,原地相对旋转指定角度并记录实验数据。
|
|
|
|
|
|
public override void Test()
|
|
|
|
|
|
{
|
2026-08-07 13:00:56 +08:00
|
|
|
|
var config = PilotDefinition.Conf;
|
|
|
|
|
|
|
2026-08-05 18:07:01 +08:00
|
|
|
|
if (float.IsNaN(RelativeAngleDegrees) ||
|
|
|
|
|
|
float.IsInfinity(RelativeAngleDegrees) ||
|
2026-08-07 13:00:56 +08:00
|
|
|
|
float.IsNaN(config.InPlaceRotateMaxSpeed) ||
|
|
|
|
|
|
float.IsInfinity(config.InPlaceRotateMaxSpeed) ||
|
|
|
|
|
|
config.InPlaceRotateMaxSpeed <= 0f ||
|
|
|
|
|
|
float.IsNaN(config.InPlaceRotateMinimumSpeed) ||
|
|
|
|
|
|
float.IsInfinity(config.InPlaceRotateMinimumSpeed) ||
|
|
|
|
|
|
config.InPlaceRotateMinimumSpeed <= 0f ||
|
|
|
|
|
|
config.InPlaceRotateMinimumSpeed >
|
|
|
|
|
|
config.InPlaceRotateMaxSpeed)
|
2026-08-05 18:07:01 +08:00
|
|
|
|
{
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-19 17:38:17 +08:00
|
|
|
|
var chassis =
|
|
|
|
|
|
PilotDefinition.Chassis as MultiWheelChassis;
|
|
|
|
|
|
if (chassis == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
"当前底盘不是MultiWheelChassis,无法执行原地旋转测试。");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var stateProvider =
|
|
|
|
|
|
ParkingVehicleStateProviderFactory.Create(
|
|
|
|
|
|
chassis);
|
|
|
|
|
|
if (!stateProvider.TryGetState(out _))
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
"无法读取原地旋转起点状态:" +
|
|
|
|
|
|
stateProvider.LastFailureReason);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-05 18:07:01 +08:00
|
|
|
|
var rotationCenter =
|
|
|
|
|
|
new Vector2((float)location.x, (float)location.y);
|
|
|
|
|
|
var targetWorldAngle =
|
|
|
|
|
|
(float)AngleMath.NormalizeDegrees(
|
|
|
|
|
|
location.th + RelativeAngleDegrees);
|
2026-08-21 17:33:29 +08:00
|
|
|
|
var movementAngleTarget =
|
|
|
|
|
|
FeedbackMode ==
|
|
|
|
|
|
InPlaceRotationFeedbackMode
|
|
|
|
|
|
.RelativeWheelOdometry
|
|
|
|
|
|
? RelativeAngleDegrees
|
|
|
|
|
|
: targetWorldAngle;
|
2026-08-05 18:07:01 +08:00
|
|
|
|
|
2026-08-07 13:00:56 +08:00
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
"原地自转实际参数:" +
|
|
|
|
|
|
$"Kp={config.InPlaceRotateKp:F3}," +
|
|
|
|
|
|
$"Ki={config.InPlaceRotateKi:F3}," +
|
|
|
|
|
|
$"Kd={config.InPlaceRotateKd:F3}," +
|
|
|
|
|
|
$"到位误差={config.InPlaceRotateArriveDeg:F2}°," +
|
|
|
|
|
|
$"最小角速度={config.InPlaceRotateMinimumSpeed:F2}°/s," +
|
|
|
|
|
|
$"最大角速度={config.InPlaceRotateMaxSpeed:F2}°/s," +
|
|
|
|
|
|
$"角加速度={config.InPlaceRotateAcc:F2}°/s²," +
|
|
|
|
|
|
$"舵轮到位误差={config.InPlaceRotateWheelAlignDeg:F2}°," +
|
|
|
|
|
|
$"旋转超时={config.InPlaceRotateTimeoutSec:F1}s;" +
|
|
|
|
|
|
$"起点航向={location.th:F2}°," +
|
2026-08-21 17:33:29 +08:00
|
|
|
|
$"目标航向={targetWorldAngle:F2}°," +
|
|
|
|
|
|
$"反馈模式={FeedbackMode}。");
|
2026-08-07 13:00:56 +08:00
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
"原地自转CSV保存目录:" +
|
|
|
|
|
|
TrackingExperimentRecorder.DefaultOutputDirectory);
|
|
|
|
|
|
|
2026-08-05 18:07:01 +08:00
|
|
|
|
_recorder = new TrackingExperimentRecorder(
|
2026-08-21 17:33:29 +08:00
|
|
|
|
controllerName:
|
|
|
|
|
|
FeedbackMode ==
|
|
|
|
|
|
InPlaceRotationFeedbackMode
|
|
|
|
|
|
.RelativeWheelOdometry
|
|
|
|
|
|
? "InPlaceRotateWheelOdometry"
|
|
|
|
|
|
: "InPlaceRotateFilteredPID",
|
2026-08-05 18:07:01 +08:00
|
|
|
|
trajectoryName: _trajectoryName,
|
|
|
|
|
|
trialNumber: TrialNumber,
|
|
|
|
|
|
referenceStart: rotationCenter,
|
|
|
|
|
|
referenceEnd: rotationCenter,
|
|
|
|
|
|
referenceSpeed: 0f,
|
|
|
|
|
|
referenceAngularSpeed:
|
|
|
|
|
|
(float)AngleMath.DegreesToRadians(
|
2026-08-19 17:38:17 +08:00
|
|
|
|
config.InPlaceRotateMaxSpeed),
|
|
|
|
|
|
diagnosticChassis: chassis,
|
|
|
|
|
|
diagnosticStateProvider: stateProvider);
|
2026-08-05 18:07:01 +08:00
|
|
|
|
_recorder.Start();
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
_task = new DriveTask(
|
|
|
|
|
|
new MultiWheelRotateInPlace
|
|
|
|
|
|
{
|
2026-08-21 17:33:29 +08:00
|
|
|
|
AngleTarget = movementAngleTarget,
|
|
|
|
|
|
FeedbackMode = FeedbackMode,
|
2026-08-19 17:38:17 +08:00
|
|
|
|
Chassis = chassis,
|
|
|
|
|
|
StateProvider = stateProvider,
|
2026-08-05 18:07:01 +08:00
|
|
|
|
CommandAngularSpeedObserver =
|
|
|
|
|
|
commandAngularSpeed =>
|
|
|
|
|
|
_recorder?.UpdateCommand(
|
|
|
|
|
|
0f,
|
|
|
|
|
|
(float)AngleMath.DegreesToRadians(
|
|
|
|
|
|
commandAngularSpeed))
|
|
|
|
|
|
}.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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-21 17:33:29 +08:00
|
|
|
|
// 读取并校验测试使用的有符号相对旋转角度。
|
|
|
|
|
|
protected static bool TryReadRelativeAngleDegrees(
|
|
|
|
|
|
out float relativeAngleDegrees)
|
2026-08-05 18:07:01 +08:00
|
|
|
|
{
|
2026-08-07 13:00:56 +08:00
|
|
|
|
var input = UI.GetInput(
|
|
|
|
|
|
"输入相对旋转角度(deg,正数逆时针,负数顺时针,范围-180到180之间):");
|
|
|
|
|
|
|
|
|
|
|
|
if ((!float.TryParse(
|
|
|
|
|
|
input,
|
|
|
|
|
|
NumberStyles.Float,
|
|
|
|
|
|
CultureInfo.CurrentCulture,
|
2026-08-21 17:33:29 +08:00
|
|
|
|
out relativeAngleDegrees) &&
|
2026-08-07 13:00:56 +08:00
|
|
|
|
!float.TryParse(
|
|
|
|
|
|
input,
|
|
|
|
|
|
NumberStyles.Float,
|
|
|
|
|
|
CultureInfo.InvariantCulture,
|
|
|
|
|
|
out relativeAngleDegrees)) ||
|
|
|
|
|
|
float.IsNaN(relativeAngleDegrees) ||
|
|
|
|
|
|
float.IsInfinity(relativeAngleDegrees))
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine("旋转角度输入无效,测试已经取消。");
|
2026-08-21 17:33:29 +08:00
|
|
|
|
return false;
|
2026-08-07 13:00:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-21 17:33:29 +08:00
|
|
|
|
if (Math.Abs(relativeAngleDegrees) < 1e-3f)
|
2026-08-19 17:38:17 +08:00
|
|
|
|
{
|
2026-08-21 17:33:29 +08:00
|
|
|
|
Console.WriteLine("旋转角度不能为0,测试已经取消。");
|
|
|
|
|
|
return false;
|
2026-08-19 17:38:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-21 17:33:29 +08:00
|
|
|
|
if (Math.Abs(relativeAngleDegrees) >= 180f)
|
2026-08-19 17:38:17 +08:00
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine(
|
2026-08-21 17:33:29 +08:00
|
|
|
|
"输入角度必须满足-180° < angle < 180°。");
|
|
|
|
|
|
return false;
|
2026-08-19 17:38:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-21 17:33:29 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[MovementTest(name = "SendXYThSpeed:输入角度原地自转")]
|
|
|
|
|
|
public sealed class TestRotateAngle :
|
|
|
|
|
|
InPlaceRotateTestBase
|
|
|
|
|
|
{
|
|
|
|
|
|
public TestRotateAngle()
|
|
|
|
|
|
: base(0f, "RotateCustomAngle")
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 读取相对旋转角度并按正值逆时针、负值顺时针执行原地自转。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public override void Test()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!TryReadRelativeAngleDegrees(
|
|
|
|
|
|
out var relativeAngleDegrees))
|
2026-08-07 13:00:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-21 17:33:29 +08:00
|
|
|
|
RelativeAngleDegrees = relativeAngleDegrees;
|
|
|
|
|
|
base.Test();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[MovementTest(name = "轮组里程计:输入角度原地相对自转")]
|
|
|
|
|
|
public sealed class TestWheelOdometryRotateAngle :
|
|
|
|
|
|
InPlaceRotateTestBase
|
|
|
|
|
|
{
|
|
|
|
|
|
public TestWheelOdometryRotateAngle()
|
|
|
|
|
|
: base(0f, "RotateWheelOdometryCustomAngle")
|
|
|
|
|
|
{
|
|
|
|
|
|
FeedbackMode =
|
|
|
|
|
|
InPlaceRotationFeedbackMode
|
|
|
|
|
|
.RelativeWheelOdometry;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 读取相对角度并仅用滤波后的轮组角速度积分完成自转。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public override void Test()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!TryReadRelativeAngleDegrees(
|
|
|
|
|
|
out var relativeAngleDegrees))
|
2026-08-07 13:00:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RelativeAngleDegrees = relativeAngleDegrees;
|
|
|
|
|
|
base.Test();
|
2026-08-05 18:07:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-07 13:00:56 +08:00
|
|
|
|
|
2026-08-05 18:07:01 +08:00
|
|
|
|
}
|