diff --git a/MedullaAdapter/build/Medulla/plugins/CommonUsage.dll b/MedullaAdapter/build/Medulla/plugins/CommonUsage.dll
index 71ef99f..9ef0dbd 100644
Binary files a/MedullaAdapter/build/Medulla/plugins/CommonUsage.dll and b/MedullaAdapter/build/Medulla/plugins/CommonUsage.dll differ
diff --git a/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.dll b/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.dll
index 0602883..54db927 100644
Binary files a/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.dll and b/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.dll differ
diff --git a/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.pdb b/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.pdb
index 517fda7..f777dfe 100644
Binary files a/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.pdb and b/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.pdb differ
diff --git a/MultiWheelC/Control/Allocation/GcpKinematics.cs b/MultiWheelC/Control/Allocation/GcpKinematics.cs
new file mode 100644
index 0000000..952c2c6
--- /dev/null
+++ b/MultiWheelC/Control/Allocation/GcpKinematics.cs
@@ -0,0 +1,125 @@
+using System;
+using MyParking.Shared;
+
+namespace MultiWheelC.Control.Allocation
+{
+ ///
+ /// 在对称前后GCP方向命令与车体中心刚体速度之间执行纯几何转换。
+ ///
+ public static class GcpKinematics
+ {
+ private const double ParallelDirectionTolerance = 1e-9;
+ private const double StopSpeedDeadbandMetersPerSecond = 1e-6;
+
+ ///
+ /// 将有符号中心速度和前后GCP方向转换为真实车体坐标系中的Twist2D。
+ ///
+ public static Twist2D ToBodyTwist(
+ GcpMotionCommand command,
+ double controlPointRadiusMeters)
+ {
+ NumericGuard.EnsureFinitePositive(
+ controlPointRadiusMeters,
+ nameof(controlPointRadiusMeters));
+
+ if (Math.Abs(command.SpeedMetersPerSecond) <=
+ StopSpeedDeadbandMetersPerSecond)
+ {
+ return Twist2D.Zero;
+ }
+
+ var frontAngleRadians =
+ command.FrontAngleRadians;
+ var rearAngleRadians =
+ command.RearAngleRadians;
+ var directionDeterminant =
+ Math.Sin(
+ rearAngleRadians -
+ frontAngleRadians);
+
+ if (Math.Abs(directionDeterminant) <=
+ ParallelDirectionTolerance)
+ {
+ var averageDirectionRadians =
+ Math.Atan2(
+ Math.Sin(frontAngleRadians) +
+ Math.Sin(rearAngleRadians),
+ Math.Cos(frontAngleRadians) +
+ Math.Cos(rearAngleRadians));
+
+ return new Twist2D(
+ command.SpeedMetersPerSecond *
+ Math.Cos(averageDirectionRadians),
+ command.SpeedMetersPerSecond *
+ Math.Sin(averageDirectionRadians),
+ 0.0);
+ }
+
+ var frontCosine =
+ Math.Cos(frontAngleRadians);
+ var frontSine =
+ Math.Sin(frontAngleRadians);
+ var rearCosine =
+ Math.Cos(rearAngleRadians);
+ var rearSine =
+ Math.Sin(rearAngleRadians);
+
+ // 两个GCP速度方向的法线交点就是瞬时旋转中心,坐标位于真实车体系。
+ var rotationCenterXMeters =
+ controlPointRadiusMeters *
+ (frontCosine * rearSine +
+ frontSine * rearCosine) /
+ directionDeterminant;
+ var rotationCenterYMeters =
+ -2.0 *
+ controlPointRadiusMeters *
+ frontCosine *
+ rearCosine /
+ directionDeterminant;
+ var centerRadiusMeters =
+ Math.Sqrt(
+ rotationCenterXMeters *
+ rotationCenterXMeters +
+ rotationCenterYMeters *
+ rotationCenterYMeters);
+
+ if (!NumericGuard.IsFinite(centerRadiusMeters) ||
+ centerRadiusMeters <= 0.0)
+ {
+ throw new InvalidOperationException(
+ "前后GCP方向不能生成有效的车体中心旋转半径。");
+ }
+
+ // 用有符号速度决定绕ICR的实际转向,避免倒车时把同一组GCP轴向解释成反向运动。
+ var requestedDirectionSign =
+ Math.Sign(command.SpeedMetersPerSecond);
+ var positiveAngularFrontVelocityXMetersPerSecond =
+ rotationCenterYMeters;
+ var positiveAngularFrontVelocityYMetersPerSecond =
+ controlPointRadiusMeters -
+ rotationCenterXMeters;
+ var frontDirectionAlignment =
+ positiveAngularFrontVelocityXMetersPerSecond *
+ requestedDirectionSign *
+ frontCosine +
+ positiveAngularFrontVelocityYMetersPerSecond *
+ requestedDirectionSign *
+ frontSine;
+ var omegaSign =
+ frontDirectionAlignment >= 0.0
+ ? 1.0
+ : -1.0;
+ var omegaRadiansPerSecond =
+ omegaSign *
+ Math.Abs(command.SpeedMetersPerSecond) /
+ centerRadiusMeters;
+
+ return new Twist2D(
+ omegaRadiansPerSecond *
+ rotationCenterYMeters,
+ -omegaRadiansPerSecond *
+ rotationCenterXMeters,
+ omegaRadiansPerSecond);
+ }
+ }
+}
diff --git a/MultiWheelC/Experiments/CompositeMotionPlanTests.cs b/MultiWheelC/Experiments/CompositeMotionPlanTests.cs
index f7a9a1d..047557b 100644
--- a/MultiWheelC/Experiments/CompositeMotionPlanTests.cs
+++ b/MultiWheelC/Experiments/CompositeMotionPlanTests.cs
@@ -307,6 +307,8 @@ namespace MultiWheelC
out var detourVelocityValid,
out var rawWheelBodyVx,
out var filteredWheelBodyVx,
+ out var rawWheelBodyVy,
+ out var filteredWheelBodyVy,
out var wheelVelocityValid))
{
_recorder?.UpdateVelocityDiagnostics(
@@ -314,6 +316,8 @@ namespace MultiWheelC
detourVelocityValid,
rawWheelBodyVx,
filteredWheelBodyVx,
+ rawWheelBodyVy,
+ filteredWheelBodyVy,
wheelVelocityValid);
}
diff --git a/MultiWheelC/Experiments/NewControllerTrackingTests.cs b/MultiWheelC/Experiments/NewControllerTrackingTests.cs
index a5fa694..2ac7e9f 100644
--- a/MultiWheelC/Experiments/NewControllerTrackingTests.cs
+++ b/MultiWheelC/Experiments/NewControllerTrackingTests.cs
@@ -224,6 +224,9 @@ namespace MultiWheelC
referenceSpeed:
(float)CruiseSpeedMetersPerSecond,
sampleIntervalMs: 50,
+ referenceMotionFrameYawDegrees:
+ (float)AngleMath.RadiansToDegrees(
+ MotionDirectionInBodyRadians),
referenceAccelerationMetersPerSecondSquared:
(float)AccelerationMetersPerSecondSquared,
referenceDecelerationMetersPerSecondSquared:
@@ -432,6 +435,8 @@ namespace MultiWheelC
out var detourVelocityValid,
out var rawWheelBodyVx,
out var filteredWheelBodyVx,
+ out var rawWheelBodyVy,
+ out var filteredWheelBodyVy,
out var wheelVelocityValid))
{
return;
@@ -442,6 +447,8 @@ namespace MultiWheelC
detourVelocityValid,
rawWheelBodyVx,
filteredWheelBodyVx,
+ rawWheelBodyVy,
+ filteredWheelBodyVy,
wheelVelocityValid);
}
@@ -855,6 +862,8 @@ namespace MultiWheelC
out var detourVelocityValid,
out var rawWheelBodyVx,
out var filteredWheelBodyVx,
+ out var rawWheelBodyVy,
+ out var filteredWheelBodyVy,
out var wheelVelocityValid))
{
return;
@@ -865,6 +874,8 @@ namespace MultiWheelC
detourVelocityValid,
rawWheelBodyVx,
filteredWheelBodyVx,
+ rawWheelBodyVy,
+ filteredWheelBodyVy,
wheelVelocityValid);
}
diff --git a/MultiWheelC/Experiments/TrackingExperimentRecorder.cs b/MultiWheelC/Experiments/TrackingExperimentRecorder.cs
index 95fc6fd..b04bac1 100644
--- a/MultiWheelC/Experiments/TrackingExperimentRecorder.cs
+++ b/MultiWheelC/Experiments/TrackingExperimentRecorder.cs
@@ -53,12 +53,14 @@ namespace MultiWheelC
public double CurvaturePreviewDistanceMeters;
public double FeedforwardCurvaturePerMeter;
- // 并列保存Detour速度与轮速解算速度,避免StateBodyVx的数据来源产生歧义。
+ // 并列保存Detour速度与轮速解算速度,避免StateBodyVx/Vy的数据来源产生歧义。
public bool HasVelocityDiagnostics;
public double DetourEstimatedBodyVxMetersPerSecond;
public bool DetourVelocityEstimateValid;
public double WheelFeedbackRawBodyVxMetersPerSecond;
public double WheelFeedbackFilteredBodyVxMetersPerSecond;
+ public double WheelFeedbackRawBodyVyMetersPerSecond;
+ public double WheelFeedbackFilteredBodyVyMetersPerSecond;
public bool WheelFeedbackVelocityEstimateValid;
// 四舵轮机械角使用deg,前后虚拟GCP命令角使用rad。
@@ -176,6 +178,8 @@ namespace MultiWheelC
private bool _detourVelocityEstimateValid;
private double _wheelFeedbackRawBodyVxMetersPerSecond;
private double _wheelFeedbackFilteredBodyVxMetersPerSecond;
+ private double _wheelFeedbackRawBodyVyMetersPerSecond;
+ private double _wheelFeedbackFilteredBodyVyMetersPerSecond;
private bool _wheelFeedbackVelocityEstimateValid;
private bool _hasGcpCommand;
private double _requestedFrontGcpAngleRadians;
@@ -340,13 +344,15 @@ namespace MultiWheelC
}
///
- /// 保存同一控制周期的Detour纵向速度以及轮速解算的原始和滤波纵向速度。
+ /// 保存同一控制周期的Detour纵向速度以及轮速解算的原始和滤波平面速度。
///
public void UpdateVelocityDiagnostics(
double detourEstimatedBodyVxMetersPerSecond,
bool detourVelocityEstimateValid,
double wheelFeedbackRawBodyVxMetersPerSecond,
double wheelFeedbackFilteredBodyVxMetersPerSecond,
+ double wheelFeedbackRawBodyVyMetersPerSecond,
+ double wheelFeedbackFilteredBodyVyMetersPerSecond,
bool wheelFeedbackVelocityEstimateValid)
{
lock (_stateSyncRoot)
@@ -359,6 +365,10 @@ namespace MultiWheelC
wheelFeedbackRawBodyVxMetersPerSecond;
_wheelFeedbackFilteredBodyVxMetersPerSecond =
wheelFeedbackFilteredBodyVxMetersPerSecond;
+ _wheelFeedbackRawBodyVyMetersPerSecond =
+ wheelFeedbackRawBodyVyMetersPerSecond;
+ _wheelFeedbackFilteredBodyVyMetersPerSecond =
+ wheelFeedbackFilteredBodyVyMetersPerSecond;
_wheelFeedbackVelocityEstimateValid =
wheelFeedbackVelocityEstimateValid;
_hasVelocityDiagnostics = true;
@@ -543,6 +553,8 @@ namespace MultiWheelC
bool detourVelocityEstimateValid;
double wheelFeedbackRawBodyVxMetersPerSecond;
double wheelFeedbackFilteredBodyVxMetersPerSecond;
+ double wheelFeedbackRawBodyVyMetersPerSecond;
+ double wheelFeedbackFilteredBodyVyMetersPerSecond;
bool wheelFeedbackVelocityEstimateValid;
bool hasGcpCommand;
double requestedFrontGcpAngleRadians;
@@ -618,6 +630,10 @@ namespace MultiWheelC
_wheelFeedbackRawBodyVxMetersPerSecond;
wheelFeedbackFilteredBodyVxMetersPerSecond =
_wheelFeedbackFilteredBodyVxMetersPerSecond;
+ wheelFeedbackRawBodyVyMetersPerSecond =
+ _wheelFeedbackRawBodyVyMetersPerSecond;
+ wheelFeedbackFilteredBodyVyMetersPerSecond =
+ _wheelFeedbackFilteredBodyVyMetersPerSecond;
wheelFeedbackVelocityEstimateValid =
_wheelFeedbackVelocityEstimateValid;
hasGcpCommand = _hasGcpCommand;
@@ -681,6 +697,10 @@ namespace MultiWheelC
wheelFeedbackRawBodyVxMetersPerSecond,
WheelFeedbackFilteredBodyVxMetersPerSecond =
wheelFeedbackFilteredBodyVxMetersPerSecond,
+ WheelFeedbackRawBodyVyMetersPerSecond =
+ wheelFeedbackRawBodyVyMetersPerSecond,
+ WheelFeedbackFilteredBodyVyMetersPerSecond =
+ wheelFeedbackFilteredBodyVyMetersPerSecond,
WheelFeedbackVelocityEstimateValid =
wheelFeedbackVelocityEstimateValid,
HasGcpCommand = hasGcpCommand,
@@ -905,6 +925,8 @@ namespace MultiWheelC
"DetourVelocityEstimateValid," +
"WheelFeedbackRawBodyVxMetersPerSecond," +
"WheelFeedbackFilteredBodyVxMetersPerSecond," +
+ "WheelFeedbackRawBodyVyMetersPerSecond," +
+ "WheelFeedbackFilteredBodyVyMetersPerSecond," +
"WheelFeedbackVelocityEstimateValid," +
"HasSteeringDiagnostics," +
"TargetSteerLeftFrontDegrees," +
@@ -1034,6 +1056,12 @@ namespace MultiWheelC
FormatOptional(
sample.HasVelocityDiagnostics,
sample.WheelFeedbackFilteredBodyVxMetersPerSecond),
+ FormatOptional(
+ sample.HasVelocityDiagnostics,
+ sample.WheelFeedbackRawBodyVyMetersPerSecond),
+ FormatOptional(
+ sample.HasVelocityDiagnostics,
+ sample.WheelFeedbackFilteredBodyVyMetersPerSecond),
sample.HasVelocityDiagnostics
? sample.WheelFeedbackVelocityEstimateValid
? "1"
diff --git a/MultiWheelC/StateEstimation/ParkingVehicleStateProviderFactory.cs b/MultiWheelC/StateEstimation/ParkingVehicleStateProviderFactory.cs
index 19474e4..4d1a4e0 100644
--- a/MultiWheelC/StateEstimation/ParkingVehicleStateProviderFactory.cs
+++ b/MultiWheelC/StateEstimation/ParkingVehicleStateProviderFactory.cs
@@ -5,7 +5,7 @@ using MyParking.Shared;
namespace MultiWheelC.StateEstimation
{
///
- /// 根据车载配置创建Detour位姿过滤与电机反馈纵向速度组合的停车状态源。
+ /// 根据车载配置创建Detour位姿过滤与电机反馈平面速度组合的停车状态源。
///
public static class ParkingVehicleStateProviderFactory
{
diff --git a/MultiWheelC/StateEstimation/WheelFeedbackVehicleStateProvider.cs b/MultiWheelC/StateEstimation/WheelFeedbackVehicleStateProvider.cs
index 21a0099..efdb882 100644
--- a/MultiWheelC/StateEstimation/WheelFeedbackVehicleStateProvider.cs
+++ b/MultiWheelC/StateEstimation/WheelFeedbackVehicleStateProvider.cs
@@ -6,7 +6,7 @@ using System.Diagnostics;
namespace MultiWheelC.StateEstimation
{
///
- /// 保留外部状态源的Detour位姿,并以舵轮电机反馈解算的车体纵向速度替换Detour差分纵向速度。
+ /// 保留外部状态源的Detour位姿,并以舵轮电机反馈解算的车体平面速度替换Detour差分线速度。
///
public sealed class WheelFeedbackVehicleStateProvider
: IVehicleStateProvider
@@ -19,6 +19,7 @@ namespace MultiWheelC.StateEstimation
private readonly IVehicleStateProvider _poseProvider;
private readonly MultiWheelChassis _chassis;
private readonly FirstOrderLowPassFilter _longitudinalSpeedFilter;
+ private readonly FirstOrderLowPassFilter _lateralSpeedFilter;
private bool _hasPreviousTimestamp;
private double _previousTimestampSeconds;
@@ -27,10 +28,12 @@ namespace MultiWheelC.StateEstimation
private bool _latestDetourVelocityValid;
private double _latestRawWheelBodyVxMetersPerSecond;
private double _latestFilteredWheelBodyVxMetersPerSecond;
+ private double _latestRawWheelBodyVyMetersPerSecond;
+ private double _latestFilteredWheelBodyVyMetersPerSecond;
private bool _latestWheelVelocityValid;
///
- /// 创建使用默认0.10s低通时间常数的电机反馈纵向速度状态源。
+ /// 创建使用默认0.10s低通时间常数的电机反馈平面速度状态源。
///
public WheelFeedbackVehicleStateProvider(
IVehicleStateProvider poseProvider,
@@ -43,7 +46,7 @@ namespace MultiWheelC.StateEstimation
}
///
- /// 创建使用指定低通时间常数的电机反馈纵向速度状态源。
+ /// 创建使用指定低通时间常数的电机反馈平面速度状态源。
///
public WheelFeedbackVehicleStateProvider(
IVehicleStateProvider poseProvider,
@@ -59,6 +62,9 @@ namespace MultiWheelC.StateEstimation
_longitudinalSpeedFilter =
new FirstOrderLowPassFilter(
velocityFilterTimeConstantSeconds);
+ _lateralSpeedFilter =
+ new FirstOrderLowPassFilter(
+ velocityFilterTimeConstantSeconds);
}
///
@@ -87,40 +93,50 @@ namespace MultiWheelC.StateEstimation
{
var actualCarSpeed =
_chassis.GetCarSpeed(true);
- var rawLongitudinalSpeedMetersPerSecond =
+ var rawBodyVxMetersPerSecond =
(double)actualCarSpeed.Vx;
+ var rawBodyVyMetersPerSecond =
+ (double)actualCarSpeed.Vy;
NumericGuard.EnsureFinite(
- rawLongitudinalSpeedMetersPerSecond,
+ rawBodyVxMetersPerSecond,
"电机反馈车体纵向速度");
+ NumericGuard.EnsureFinite(
+ rawBodyVyMetersPerSecond,
+ "电机反馈车体横向速度");
var wheelSpeedTimestampSeconds =
_wheelSpeedClock.Elapsed.TotalSeconds;
- var filteredLongitudinalSpeedMetersPerSecond =
- UpdateLongitudinalSpeedFilter(
- rawLongitudinalSpeedMetersPerSecond,
- wheelSpeedTimestampSeconds,
- out var hasValidWheelSpeedEstimate);
+ UpdateBodyVelocityFilters(
+ rawBodyVxMetersPerSecond,
+ rawBodyVyMetersPerSecond,
+ wheelSpeedTimestampSeconds,
+ out var filteredBodyVxMetersPerSecond,
+ out var filteredBodyVyMetersPerSecond,
+ out var hasValidWheelSpeedEstimate);
_latestDetourBodyVxMetersPerSecond =
poseState.TwistInBody.VxMetersPerSecond;
_latestDetourVelocityValid =
poseState.HasValidVelocityEstimate;
_latestRawWheelBodyVxMetersPerSecond =
- rawLongitudinalSpeedMetersPerSecond;
+ rawBodyVxMetersPerSecond;
_latestFilteredWheelBodyVxMetersPerSecond =
- filteredLongitudinalSpeedMetersPerSecond;
+ filteredBodyVxMetersPerSecond;
+ _latestRawWheelBodyVyMetersPerSecond =
+ rawBodyVyMetersPerSecond;
+ _latestFilteredWheelBodyVyMetersPerSecond =
+ filteredBodyVyMetersPerSecond;
_latestWheelVelocityValid =
hasValidWheelSpeedEstimate;
_hasVelocityDiagnostics = true;
- // 第一阶段只替换控制器使用的车体纵向速度;横向速度和角速度
- // 继续使用Detour估计,避免轮速差和舵角误差放大Vy与Omega噪声。
+ // 车体平面线速度来自四轮电机和舵角反馈;角速度继续使用Detour,
+ // 避免轮速差和舵角误差放大Omega噪声。
var twistInBody = new Twist2D(
- filteredLongitudinalSpeedMetersPerSecond,
- poseState.TwistInBody
- .VyMetersPerSecond,
+ filteredBodyVxMetersPerSecond,
+ filteredBodyVyMetersPerSecond,
poseState.TwistInBody
.OmegaRadiansPerSecond);
@@ -151,13 +167,15 @@ namespace MultiWheelC.StateEstimation
}
///
- /// 读取最近一帧Detour纵向速度和轮速解算纵向速度,供实验记录使用。
+ /// 读取最近一帧Detour纵向速度和轮速解算平面速度,供实验记录使用。
///
public bool TryGetLatestVelocityDiagnostics(
out double detourBodyVxMetersPerSecond,
out bool detourVelocityValid,
out double rawWheelBodyVxMetersPerSecond,
out double filteredWheelBodyVxMetersPerSecond,
+ out double rawWheelBodyVyMetersPerSecond,
+ out double filteredWheelBodyVyMetersPerSecond,
out bool wheelVelocityValid)
{
lock (_syncRoot)
@@ -170,6 +188,10 @@ namespace MultiWheelC.StateEstimation
_latestRawWheelBodyVxMetersPerSecond;
filteredWheelBodyVxMetersPerSecond =
_latestFilteredWheelBodyVxMetersPerSecond;
+ rawWheelBodyVyMetersPerSecond =
+ _latestRawWheelBodyVyMetersPerSecond;
+ filteredWheelBodyVyMetersPerSecond =
+ _latestFilteredWheelBodyVyMetersPerSecond;
wheelVelocityValid =
_latestWheelVelocityValid;
return _hasVelocityDiagnostics;
@@ -184,6 +206,7 @@ namespace MultiWheelC.StateEstimation
lock (_syncRoot)
{
_longitudinalSpeedFilter.Reset();
+ _lateralSpeedFilter.Reset();
_wheelSpeedClock.Restart();
_hasPreviousTimestamp = false;
_previousTimestampSeconds = 0.0;
@@ -192,17 +215,22 @@ namespace MultiWheelC.StateEstimation
_latestDetourVelocityValid = false;
_latestRawWheelBodyVxMetersPerSecond = 0.0;
_latestFilteredWheelBodyVxMetersPerSecond = 0.0;
+ _latestRawWheelBodyVyMetersPerSecond = 0.0;
+ _latestFilteredWheelBodyVyMetersPerSecond = 0.0;
_latestWheelVelocityValid = false;
LastFailureReason = string.Empty;
}
}
///
- /// 使用真实状态时间间隔更新纵向速度低通滤波,并在首帧建立基准。
+ /// 使用同一个真实采样间隔更新车体Vx和Vy低通滤波,并在首帧建立共同时间基准。
///
- private double UpdateLongitudinalSpeedFilter(
- double rawLongitudinalSpeedMetersPerSecond,
+ private void UpdateBodyVelocityFilters(
+ double rawBodyVxMetersPerSecond,
+ double rawBodyVyMetersPerSecond,
double timestampSeconds,
+ out double filteredBodyVxMetersPerSecond,
+ out double filteredBodyVyMetersPerSecond,
out bool hasValidWheelSpeedEstimate)
{
NumericGuard.EnsureFiniteNonNegative(
@@ -212,11 +240,17 @@ namespace MultiWheelC.StateEstimation
if (!_hasPreviousTimestamp)
{
_longitudinalSpeedFilter.Reset(
- rawLongitudinalSpeedMetersPerSecond);
+ rawBodyVxMetersPerSecond);
+ _lateralSpeedFilter.Reset(
+ rawBodyVyMetersPerSecond);
_previousTimestampSeconds = timestampSeconds;
_hasPreviousTimestamp = true;
hasValidWheelSpeedEstimate = false;
- return rawLongitudinalSpeedMetersPerSecond;
+ filteredBodyVxMetersPerSecond =
+ rawBodyVxMetersPerSecond;
+ filteredBodyVyMetersPerSecond =
+ rawBodyVyMetersPerSecond;
+ return;
}
var deltaTimeSeconds =
@@ -227,15 +261,26 @@ namespace MultiWheelC.StateEstimation
if (deltaTimeSeconds <= 0.0)
{
_longitudinalSpeedFilter.Reset(
- rawLongitudinalSpeedMetersPerSecond);
+ rawBodyVxMetersPerSecond);
+ _lateralSpeedFilter.Reset(
+ rawBodyVyMetersPerSecond);
hasValidWheelSpeedEstimate = false;
- return rawLongitudinalSpeedMetersPerSecond;
+ filteredBodyVxMetersPerSecond =
+ rawBodyVxMetersPerSecond;
+ filteredBodyVyMetersPerSecond =
+ rawBodyVyMetersPerSecond;
+ return;
}
hasValidWheelSpeedEstimate = true;
- return _longitudinalSpeedFilter.Update(
- rawLongitudinalSpeedMetersPerSecond,
- deltaTimeSeconds);
+ filteredBodyVxMetersPerSecond =
+ _longitudinalSpeedFilter.Update(
+ rawBodyVxMetersPerSecond,
+ deltaTimeSeconds);
+ filteredBodyVyMetersPerSecond =
+ _lateralSpeedFilter.Update(
+ rawBodyVyMetersPerSecond,
+ deltaTimeSeconds);
}
}
diff --git a/MultiWheelC/build/Clumsy/CommonUsage.dll b/MultiWheelC/build/Clumsy/CommonUsage.dll
index 71ef99f..9ef0dbd 100644
Binary files a/MultiWheelC/build/Clumsy/CommonUsage.dll and b/MultiWheelC/build/Clumsy/CommonUsage.dll differ
diff --git a/MultiWheelC/build/Clumsy/MultiWheelC.dll b/MultiWheelC/build/Clumsy/MultiWheelC.dll
index ed0cd34..ac634e6 100644
Binary files a/MultiWheelC/build/Clumsy/MultiWheelC.dll and b/MultiWheelC/build/Clumsy/MultiWheelC.dll differ
diff --git a/MultiWheelC/build/Clumsy/MultiWheelC.pdb b/MultiWheelC/build/Clumsy/MultiWheelC.pdb
index 8412bc4..0569912 100644
Binary files a/MultiWheelC/build/Clumsy/MultiWheelC.pdb and b/MultiWheelC/build/Clumsy/MultiWheelC.pdb differ
diff --git a/Shared/Models/MotionModels.cs b/Shared/Models/MotionModels.cs
new file mode 100644
index 0000000..f36c14d
--- /dev/null
+++ b/Shared/Models/MotionModels.cs
@@ -0,0 +1,154 @@
+// 纯数据层:只描述坐标、速度和命令
+// 定义二维坐标、位姿、速度、车队布局和单车底盘命令。
+// Shared层统一使用SI单位:位置m、线速度m/s、角度rad、角速度rad/s。
+// 车体坐标系采用右手系:X向前、Y向左、逆时针角度和角速度为正。
+// 命名约定:XxxInYyy表示Xxx在Yyy坐标系中的表达。
+
+// 共享的二维运动模型,不包含车辆路由或底盘执行策略。
+namespace MyParking.Shared
+{
+ ///
+ /// 二维坐标点,X、Y单位均为米。
+ ///
+ public readonly struct Point2D
+ {
+ public Point2D(double xMeters, double yMeters)
+ {
+ XMeters = xMeters;
+ YMeters = yMeters;
+ }
+
+ public double XMeters { get; }
+
+ public double YMeters { get; }
+
+ public static Point2D Zero => new Point2D(0.0, 0.0);
+ }
+
+ ///
+ /// 二维局部坐标系在父坐标系中的位姿。
+ /// 位置单位为米,朝向单位为弧度,逆时针为正。
+ /// 具体父子关系由变量名称说明,例如RadarPoseInBody。
+ ///
+ public readonly struct Pose2D
+ {
+ public Pose2D(
+ double xMeters,
+ double yMeters,
+ double yawRadians)
+ {
+ XMeters = xMeters;
+ YMeters = yMeters;
+ YawRadians = yawRadians;
+ }
+
+ public double XMeters { get; }
+
+ public double YMeters { get; }
+
+ public double YawRadians { get; }
+
+ public Point2D Position =>
+ new Point2D(XMeters, YMeters);
+
+ public static Pose2D Identity =>
+ new Pose2D(0.0, 0.0, 0.0);
+ }
+
+ ///
+ /// 二维刚体速度。
+ /// 线速度单位为m/s,角速度单位为rad/s。
+ /// 速度所属坐标系由持有该Twist2D的外层类型或变量名称确定。
+ ///
+ public readonly struct Twist2D
+ {
+ public Twist2D(
+ double vxMetersPerSecond,
+ double vyMetersPerSecond,
+ double omegaRadiansPerSecond)
+ {
+ VxMetersPerSecond = vxMetersPerSecond;
+ VyMetersPerSecond = vyMetersPerSecond;
+ OmegaRadiansPerSecond = omegaRadiansPerSecond;
+ }
+
+ public double VxMetersPerSecond { get; }
+
+ public double VyMetersPerSecond { get; }
+
+ public double OmegaRadiansPerSecond { get; }
+
+ public static Twist2D Zero =>
+ new Twist2D(0.0, 0.0, 0.0);
+ }
+
+
+ ///
+ /// 单辆车的车体坐标系在车队坐标系中的位姿。
+ ///
+ public readonly struct VehicleLayout
+ {
+ public VehicleLayout(
+ int vehicleId,
+ Pose2D poseInFleet)
+ {
+ VehicleId = vehicleId;
+ PoseInFleet = poseInFleet;
+ }
+
+ public int VehicleId { get; }
+
+ public Pose2D PoseInFleet { get; }
+ }
+
+ ///
+ /// 车队整体运动命令,速度分量均在车队坐标系中表达。
+ ///
+ public readonly struct FleetMotionCommand
+ {
+ public FleetMotionCommand(
+ Point2D referencePointInFleet,
+ Twist2D twistAtReferencePoint)
+ {
+ ReferencePointInFleet = referencePointInFleet;
+ TwistAtReferencePoint = twistAtReferencePoint;
+ }
+
+ ///
+ /// 速度命令对应的参考点,也可作为自定义旋转中心。
+ ///
+ public Point2D ReferencePointInFleet { get; }
+
+ ///
+ /// 参考点处的车队速度。
+ ///
+ public Twist2D TwistAtReferencePoint { get; }
+
+ ///
+ /// 创建绕指定中心原地旋转的车队命令。
+ ///
+ public static FleetMotionCommand RotateAround(
+ Point2D rotationCenterInFleet,
+ double omegaRadiansPerSecond)
+ {
+ return new FleetMotionCommand(
+ rotationCenterInFleet,
+ new Twist2D(
+ 0.0,
+ 0.0,
+ omegaRadiansPerSecond));
+ }
+
+ ///
+ /// 创建车队停止命令。
+ ///
+ public static FleetMotionCommand Stop()
+ {
+ return new FleetMotionCommand(
+ Point2D.Zero,
+ Twist2D.Zero);
+ }
+ }
+
+
+}
diff --git a/data_process/plot_new_controller_experiment.py b/data_process/plot_new_controller_experiment.py
index 8af14eb..4211d71 100644
--- a/data_process/plot_new_controller_experiment.py
+++ b/data_process/plot_new_controller_experiment.py
@@ -293,11 +293,22 @@ def load_experiment(csv_path: Path) -> dict[str, object]:
)
if np.any(has_control_reference):
reference_speed[~has_control_reference] = np.nan
+ motion_frame_yaw_radians = np.deg2rad(
+ numeric_column(frame, "ReferenceMotionFrameYawDegrees", 0.0)
+ )
+ motion_direction_cosine = np.cos(motion_frame_yaw_radians)
+ motion_direction_sine = np.sin(motion_frame_yaw_radians)
state_body_vx = numeric_column(frame, "StateBodyVxMetersPerSecond")
+ state_body_vy = numeric_column(frame, "StateBodyVyMetersPerSecond")
velocity_valid = (
numeric_column(frame, "StateVelocityEstimateValid", 0.0) > 0.5
)
state_body_vx[~velocity_valid] = np.nan
+ state_body_vy[~velocity_valid] = np.nan
+ state_motion_speed = (
+ state_body_vx * motion_direction_cosine
+ + state_body_vy * motion_direction_sine
+ )
has_velocity_diagnostics = (
numeric_column(frame, "HasVelocityDiagnostics", 0.0) > 0.5
)
@@ -317,14 +328,22 @@ def load_experiment(csv_path: Path) -> dict[str, object]:
)
)
detour_speed[~detour_speed_valid] = np.nan
- wheel_raw_speed = numeric_column(
+ wheel_raw_body_vx = numeric_column(
frame,
"WheelFeedbackRawBodyVxMetersPerSecond",
)
- wheel_filtered_speed = numeric_column(
+ wheel_filtered_body_vx = numeric_column(
frame,
"WheelFeedbackFilteredBodyVxMetersPerSecond",
)
+ wheel_raw_body_vy = numeric_column(
+ frame,
+ "WheelFeedbackRawBodyVyMetersPerSecond",
+ )
+ wheel_filtered_body_vy = numeric_column(
+ frame,
+ "WheelFeedbackFilteredBodyVyMetersPerSecond",
+ )
wheel_speed_valid = (
has_velocity_diagnostics
& (
@@ -336,12 +355,33 @@ def load_experiment(csv_path: Path) -> dict[str, object]:
> 0.5
)
)
+ wheel_raw_speed = (
+ wheel_raw_body_vx * motion_direction_cosine
+ + wheel_raw_body_vy * motion_direction_sine
+ )
+ wheel_filtered_speed = (
+ wheel_filtered_body_vx * motion_direction_cosine
+ + wheel_filtered_body_vy * motion_direction_sine
+ )
+
+ # 兼容尚未记录轮速Vy的旧版β=0实验;非零β缺少Vy时不能伪造投影速度。
+ body_x_motion = np.abs(motion_direction_sine) <= 1e-12
+ missing_raw_projection = ~np.isfinite(wheel_raw_speed)
+ missing_filtered_projection = ~np.isfinite(wheel_filtered_speed)
+ wheel_raw_speed[body_x_motion & missing_raw_projection] = (
+ wheel_raw_body_vx[body_x_motion & missing_raw_projection]
+ )
+ wheel_filtered_speed[
+ body_x_motion & missing_filtered_projection
+ ] = wheel_filtered_body_vx[
+ body_x_motion & missing_filtered_projection
+ ]
wheel_raw_speed[~wheel_speed_valid] = np.nan
wheel_filtered_speed[~wheel_speed_valid] = np.nan
actual_speed = np.where(
np.isfinite(wheel_filtered_speed),
wheel_filtered_speed,
- state_body_vx,
+ state_motion_speed,
)
command_speed = numeric_column(frame, "CommandSpeed")
@@ -504,7 +544,7 @@ def plot_experiment(
)
axis.grid(True, alpha=0.3)
- # 4. 参考、命令、Detour估计和轮速解算速度。
+ # 4. 参考、命令、Detour车头分量和沿β投影的轮速解算速度。
speed_error = data["actual_speed"] - data["reference_speed"]
speed_rmse = finite_rmse(speed_error)
axis = axes[1, 1]
@@ -527,7 +567,7 @@ def plot_experiment(
data["detour_speed"],
":",
linewidth=1.2,
- label="Detour估计Vx",
+ label="Detour估计Vx(车头分量)",
)
if np.any(np.isfinite(data["wheel_filtered_speed"])):
wheel_filtered_valid = np.isfinite(
@@ -540,7 +580,7 @@ def plot_experiment(
s=14,
marker="o",
zorder=5,
- label="轮速解算滤波Vx(控制使用)",
+ label="轮速解算β方向速度(控制使用)",
)
else:
axis.plot(
@@ -553,7 +593,7 @@ def plot_experiment(
axis.set_ylabel("速度 / (m/s)")
axis.set_title(
"参考速度、控制命令与观测速度\n"
- f"轮速Vx相对参考速度RMSE={speed_rmse:.4f}m/s"
+ f"轮速β方向速度相对参考速度RMSE={speed_rmse:.4f}m/s"
)
axis.grid(True, alpha=0.3)
axis.legend(fontsize=8)
diff --git a/output/C/CommonUsage.dll b/output/C/CommonUsage.dll
index 71ef99f..9ef0dbd 100644
Binary files a/output/C/CommonUsage.dll and b/output/C/CommonUsage.dll differ
diff --git a/output/C/MultiWheelC.dll b/output/C/MultiWheelC.dll
index ed0cd34..ac634e6 100644
Binary files a/output/C/MultiWheelC.dll and b/output/C/MultiWheelC.dll differ
diff --git a/output/C/MultiWheelC.pdb b/output/C/MultiWheelC.pdb
index 8412bc4..0569912 100644
Binary files a/output/C/MultiWheelC.pdb and b/output/C/MultiWheelC.pdb differ
diff --git a/output/M/CommonUsage.dll b/output/M/CommonUsage.dll
index 71ef99f..9ef0dbd 100644
Binary files a/output/M/CommonUsage.dll and b/output/M/CommonUsage.dll differ
diff --git a/output/M/MedullaAdapter.dll b/output/M/MedullaAdapter.dll
index 0602883..54db927 100644
Binary files a/output/M/MedullaAdapter.dll and b/output/M/MedullaAdapter.dll differ
diff --git a/output/M/MedullaAdapter.pdb b/output/M/MedullaAdapter.pdb
index 517fda7..f777dfe 100644
Binary files a/output/M/MedullaAdapter.pdb and b/output/M/MedullaAdapter.pdb differ
diff --git a/ref/CommonUsage.dll b/ref/CommonUsage.dll
index 71ef99f..9ef0dbd 100644
Binary files a/ref/CommonUsage.dll and b/ref/CommonUsage.dll differ