2026-08-05 18:07:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using ClumsyCore.Interfaces;
|
|
|
|
|
|
using ClumsyCore.Pilot;
|
|
|
|
|
|
using CommonUsage.Chassis;
|
|
|
|
|
|
using MDCSToolBox.Commons.Controllers;
|
|
|
|
|
|
using MyParking.Shared;
|
2026-08-07 13:00:56 +08:00
|
|
|
|
using MultiWheelC.StateEstimation;
|
2026-08-05 18:07:01 +08:00
|
|
|
|
|
|
|
|
|
|
namespace MultiWheelC
|
|
|
|
|
|
{
|
2026-08-11 17:06:26 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将四个舵轮准备到自转姿态并按世界航向闭环旋转,正常完成后等待舵轮回正。
|
|
|
|
|
|
/// </summary>
|
2026-08-05 18:07:01 +08:00
|
|
|
|
public class MultiWheelRotateInPlace : MovementDefinition
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 旋转目标角度
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public float AngleTarget;
|
|
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
// 留作标定或单元测试时显式替换;为空时使用配置化Detour与电机反馈组合状态源。
|
2026-08-07 13:00:56 +08:00
|
|
|
|
public Func<float> ThetaReader;
|
|
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
public IVehicleStateProvider StateProvider;
|
2026-08-05 18:07:01 +08:00
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
public MultiWheelChassis Chassis =
|
|
|
|
|
|
PilotDefinition.Chassis as MultiWheelChassis;
|
2026-08-05 18:07:01 +08:00
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取或设置本次动作的PID参数读取覆盖;为空时读取车辆配置。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public Func<PIDParams> PidparamsRead;
|
2026-08-05 18:07:01 +08:00
|
|
|
|
|
|
|
|
|
|
public PIDController thPid;
|
|
|
|
|
|
|
|
|
|
|
|
// 将本周期PID角速度输出提供给实验记录器,单位deg/s。
|
|
|
|
|
|
public Action<float> CommandAngularSpeedObserver;
|
|
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
// 自转前舵轮实际角度允许误差覆盖值,单位deg;为空时读取车辆配置。
|
|
|
|
|
|
public float? WheelAlignmentToleranceDegrees;
|
2026-08-05 18:07:01 +08:00
|
|
|
|
|
|
|
|
|
|
// 自转舵轮连续保持到位的时间,单位s。
|
|
|
|
|
|
public float WheelAlignmentStableSeconds = 0.3f;
|
|
|
|
|
|
|
|
|
|
|
|
// 自转舵轮准备超时时间,单位s。
|
|
|
|
|
|
public float WheelAlignmentTimeoutSeconds = 10f;
|
|
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
// 航向尚未到位时允许下发的最小有效角速度覆盖值,单位deg/s;为空时读取车辆配置。
|
|
|
|
|
|
public float? MinimumAngularSpeedDegreesPerSecond;
|
2026-08-07 13:00:56 +08:00
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
// 舵轮到位后执行航向闭环允许的最长时间覆盖值,单位s;为空时读取车辆配置。
|
|
|
|
|
|
public float? RotationTimeoutSeconds;
|
2026-08-07 13:00:56 +08:00
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 读取一次有效配置,闭环旋转到目标航向并在正常完成后等待舵轮稳定回正。
|
|
|
|
|
|
/// </summary>
|
2026-08-05 18:07:01 +08:00
|
|
|
|
public override IEnumerable<bool> Get()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Chassis == null)
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
"当前底盘不是MultiWheelChassis,无法执行原地自转。");
|
|
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
var config = PilotDefinition.Conf;
|
|
|
|
|
|
var pidParameters =
|
|
|
|
|
|
PidparamsRead == null
|
|
|
|
|
|
? new PIDParams
|
|
|
|
|
|
{
|
|
|
|
|
|
Kp = config.InPlaceRotateKp,
|
|
|
|
|
|
Ki = config.InPlaceRotateKi,
|
|
|
|
|
|
Kd = config.InPlaceRotateKd,
|
|
|
|
|
|
MaxI = config.InPlaceRotateMaxI,
|
|
|
|
|
|
DeadZone = config.InPlaceRotateArriveDeg,
|
|
|
|
|
|
SpeedAccPerSec = config.InPlaceRotateAcc,
|
|
|
|
|
|
OutputUpperThreshold =
|
|
|
|
|
|
config.InPlaceRotateMaxSpeed
|
|
|
|
|
|
}
|
|
|
|
|
|
: PidparamsRead();
|
|
|
|
|
|
var wheelAlignmentToleranceDegrees =
|
|
|
|
|
|
WheelAlignmentToleranceDegrees ??
|
|
|
|
|
|
config.InPlaceRotateWheelAlignDeg;
|
|
|
|
|
|
var minimumAngularSpeedDegreesPerSecond =
|
|
|
|
|
|
MinimumAngularSpeedDegreesPerSecond ??
|
|
|
|
|
|
config.InPlaceRotateMinimumSpeed;
|
|
|
|
|
|
var rotationTimeoutSeconds =
|
|
|
|
|
|
RotationTimeoutSeconds ??
|
|
|
|
|
|
config.InPlaceRotateTimeoutSec;
|
|
|
|
|
|
var stateProvider =
|
|
|
|
|
|
StateProvider ??
|
|
|
|
|
|
ParkingVehicleStateProviderFactory.Create(
|
|
|
|
|
|
Chassis,
|
|
|
|
|
|
config);
|
|
|
|
|
|
|
|
|
|
|
|
ValidateParameters(
|
|
|
|
|
|
pidParameters,
|
|
|
|
|
|
wheelAlignmentToleranceDegrees,
|
|
|
|
|
|
minimumAngularSpeedDegreesPerSecond,
|
|
|
|
|
|
rotationTimeoutSeconds);
|
2026-08-07 13:00:56 +08:00
|
|
|
|
|
2026-08-05 18:07:01 +08:00
|
|
|
|
var adapter = new MultiWheelChassisAdapter(
|
|
|
|
|
|
Chassis,
|
|
|
|
|
|
PilotDefinition.Self.CarNum);
|
|
|
|
|
|
adapter.ResetToBodyFrame();
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var alignmentStarted = DateTime.Now;
|
|
|
|
|
|
DateTime? alignedSince = null;
|
|
|
|
|
|
while (true)
|
|
|
|
|
|
{
|
2026-08-07 13:00:56 +08:00
|
|
|
|
if (!adapter.PrepareSpin(
|
|
|
|
|
|
alignmentToleranceDegrees:
|
2026-08-11 17:06:26 +08:00
|
|
|
|
wheelAlignmentToleranceDegrees))
|
2026-08-05 18:07:01 +08:00
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
"无法生成原地自转舵轮目标:" +
|
|
|
|
|
|
adapter.LastFailureReason);
|
|
|
|
|
|
|
|
|
|
|
|
if (adapter.AreSpinWheelsAligned)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (alignedSince == null)
|
|
|
|
|
|
alignedSince = DateTime.Now;
|
|
|
|
|
|
|
|
|
|
|
|
if ((DateTime.Now - alignedSince.Value)
|
|
|
|
|
|
.TotalSeconds >=
|
|
|
|
|
|
WheelAlignmentStableSeconds)
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
alignedSince = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ((DateTime.Now - alignmentStarted)
|
|
|
|
|
|
.TotalSeconds >
|
|
|
|
|
|
WheelAlignmentTimeoutSeconds)
|
|
|
|
|
|
throw new TimeoutException(
|
|
|
|
|
|
"原地自转舵轮在限定时间内未稳定到位。");
|
|
|
|
|
|
|
|
|
|
|
|
yield return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-07 13:00:56 +08:00
|
|
|
|
var alignmentToleranceRadians =
|
|
|
|
|
|
AngleMath.DegreesToRadians(
|
2026-08-11 17:06:26 +08:00
|
|
|
|
wheelAlignmentToleranceDegrees);
|
2026-08-07 13:00:56 +08:00
|
|
|
|
if (!adapter.AdoptPreparedSpinForXYTh(
|
|
|
|
|
|
alignmentToleranceRadians))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
"无法将已到位的自转舵角交接给XYTh:" +
|
|
|
|
|
|
adapter.LastFailureReason);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-05 18:07:01 +08:00
|
|
|
|
var targetAngle =
|
|
|
|
|
|
(float)AngleMath.NormalizeDegrees(AngleTarget);
|
2026-08-11 17:06:26 +08:00
|
|
|
|
var currentAngle =
|
|
|
|
|
|
ReadCurrentAngleDegrees(stateProvider);
|
2026-08-07 13:00:56 +08:00
|
|
|
|
var cachedCurrentAngle = currentAngle;
|
|
|
|
|
|
thPid = new PIDController(
|
|
|
|
|
|
() => cachedCurrentAngle,
|
2026-08-11 17:06:26 +08:00
|
|
|
|
pidParameters.Kp);
|
|
|
|
|
|
thPid.ChangeParameters(
|
|
|
|
|
|
pidParameters.Kp,
|
|
|
|
|
|
pidParameters.Ki,
|
|
|
|
|
|
pidParameters.Kd,
|
|
|
|
|
|
pidParameters.MaxI,
|
|
|
|
|
|
pidParameters.DeadZone,
|
|
|
|
|
|
pidParameters.OutputUpperThreshold,
|
|
|
|
|
|
pidParameters.SpeedAccPerSec);
|
2026-08-05 18:07:01 +08:00
|
|
|
|
var lastCommandTime = DateTime.Now;
|
2026-08-07 13:00:56 +08:00
|
|
|
|
var rotationStarted = DateTime.Now;
|
2026-08-05 18:07:01 +08:00
|
|
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
|
{
|
2026-08-07 13:00:56 +08:00
|
|
|
|
if ((DateTime.Now - rotationStarted)
|
|
|
|
|
|
.TotalSeconds >
|
2026-08-11 17:06:26 +08:00
|
|
|
|
rotationTimeoutSeconds)
|
2026-08-07 13:00:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
throw new TimeoutException(
|
2026-08-11 17:06:26 +08:00
|
|
|
|
$"原地自转超过{rotationTimeoutSeconds:F1}s仍未到位。");
|
2026-08-07 13:00:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
currentAngle =
|
|
|
|
|
|
ReadCurrentAngleDegrees(stateProvider);
|
2026-08-07 13:00:56 +08:00
|
|
|
|
cachedCurrentAngle = currentAngle;
|
2026-08-05 18:07:01 +08:00
|
|
|
|
var s = thPid.GetResponse(targetAngle, true);
|
2026-08-07 13:00:56 +08:00
|
|
|
|
var angleErrorDegrees =
|
|
|
|
|
|
(float)AngleMath
|
|
|
|
|
|
.ShortestDifferenceDegrees(
|
|
|
|
|
|
targetAngle,
|
|
|
|
|
|
currentAngle);
|
|
|
|
|
|
|
|
|
|
|
|
// PID进入到位死区后等待其0.3s稳定确认;等待期间
|
|
|
|
|
|
// 只清零驱动速度,不清除已经准备好的自转舵角状态。
|
|
|
|
|
|
if (Math.Abs(angleErrorDegrees) <=
|
2026-08-11 17:06:26 +08:00
|
|
|
|
pidParameters.DeadZone)
|
2026-08-07 13:00:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
CommandAngularSpeedObserver?.Invoke(0f);
|
|
|
|
|
|
adapter
|
|
|
|
|
|
.StopXYThDrivePreserveSteeringState();
|
|
|
|
|
|
|
|
|
|
|
|
if (thPid.IsArrived())
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
yield return true;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// PID输出低于底盘有效轮速范围时提高到最小可执行值,
|
|
|
|
|
|
// 避免接近目标时反复出现微小命令但车辆实际不动。
|
|
|
|
|
|
if (Math.Abs(s) > 1e-6f &&
|
|
|
|
|
|
Math.Abs(s) <
|
2026-08-11 17:06:26 +08:00
|
|
|
|
minimumAngularSpeedDegreesPerSecond)
|
2026-08-07 13:00:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
s = Math.Sign(angleErrorDegrees) *
|
2026-08-11 17:06:26 +08:00
|
|
|
|
minimumAngularSpeedDegreesPerSecond;
|
2026-08-07 13:00:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// PID加速限制在首周期可能暂时输出零;此时保留
|
|
|
|
|
|
// 已交接的自转状态,等待下一周期产生有效角速度。
|
|
|
|
|
|
if (Math.Abs(s) <= 1e-6f)
|
|
|
|
|
|
{
|
|
|
|
|
|
CommandAngularSpeedObserver?.Invoke(0f);
|
|
|
|
|
|
adapter
|
|
|
|
|
|
.StopXYThDrivePreserveSteeringState();
|
|
|
|
|
|
yield return true;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-05 18:07:01 +08:00
|
|
|
|
CommandAngularSpeedObserver?.Invoke(s);
|
|
|
|
|
|
var now = DateTime.Now;
|
|
|
|
|
|
var interval = now - lastCommandTime;
|
|
|
|
|
|
lastCommandTime = now;
|
|
|
|
|
|
|
|
|
|
|
|
// PID输出s为deg/s,Shared命令统一使用rad/s。
|
|
|
|
|
|
// adapter.Send最终调用普通安全版SendXYThSpeed。
|
|
|
|
|
|
var omegaRadiansPerSecond =
|
|
|
|
|
|
(float)AngleMath.DegreesToRadians(s);
|
|
|
|
|
|
if (!adapter.Send(
|
|
|
|
|
|
new ChassisCommand(
|
|
|
|
|
|
PilotDefinition.Self.CarNum,
|
|
|
|
|
|
new Twist2D(
|
|
|
|
|
|
0.0,
|
|
|
|
|
|
0.0,
|
|
|
|
|
|
omegaRadiansPerSecond)),
|
|
|
|
|
|
interval))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
"安全XYTh原地旋转底盘解算失败:" +
|
|
|
|
|
|
adapter.LastFailureReason);
|
|
|
|
|
|
}
|
|
|
|
|
|
yield return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
CommandAngularSpeedObserver?.Invoke(0f);
|
|
|
|
|
|
adapter.StopXYThDrivePreserveSteeringState();
|
|
|
|
|
|
|
|
|
|
|
|
// 航向正常到位后复用统一回正动作;异常或取消会直接进入finally停车。
|
|
|
|
|
|
var wheelPreparation =
|
|
|
|
|
|
new PrepareWheelsForward();
|
|
|
|
|
|
foreach (var keepRunning in wheelPreparation.Get())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!keepRunning)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
yield return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!wheelPreparation.Completed)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
"原地自转完成后舵轮未能稳定回到车头方向。");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
$"final rotate to {targetAngle}, wheels forward");
|
2026-08-05 18:07:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
CommandAngularSpeedObserver?.Invoke(0f);
|
|
|
|
|
|
adapter.StopImmediately();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-07 13:00:56 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检查原地自转的舵轮准备、最小速度和超时参数是否可执行。
|
|
|
|
|
|
/// </summary>
|
2026-08-11 17:06:26 +08:00
|
|
|
|
private void ValidateParameters(
|
|
|
|
|
|
PIDParams pidParameters,
|
|
|
|
|
|
float wheelAlignmentToleranceDegrees,
|
|
|
|
|
|
float minimumAngularSpeedDegreesPerSecond,
|
|
|
|
|
|
float rotationTimeoutSeconds)
|
2026-08-07 13:00:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
EnsureFinitePositive(
|
2026-08-11 17:06:26 +08:00
|
|
|
|
wheelAlignmentToleranceDegrees,
|
2026-08-07 13:00:56 +08:00
|
|
|
|
nameof(WheelAlignmentToleranceDegrees),
|
|
|
|
|
|
allowZero: true);
|
|
|
|
|
|
EnsureFinitePositive(
|
|
|
|
|
|
WheelAlignmentStableSeconds,
|
|
|
|
|
|
nameof(WheelAlignmentStableSeconds),
|
|
|
|
|
|
allowZero: true);
|
|
|
|
|
|
EnsureFinitePositive(
|
|
|
|
|
|
WheelAlignmentTimeoutSeconds,
|
|
|
|
|
|
nameof(WheelAlignmentTimeoutSeconds));
|
|
|
|
|
|
EnsureFinitePositive(
|
2026-08-11 17:06:26 +08:00
|
|
|
|
minimumAngularSpeedDegreesPerSecond,
|
2026-08-07 13:00:56 +08:00
|
|
|
|
nameof(MinimumAngularSpeedDegreesPerSecond));
|
|
|
|
|
|
EnsureFinitePositive(
|
2026-08-11 17:06:26 +08:00
|
|
|
|
rotationTimeoutSeconds,
|
2026-08-07 13:00:56 +08:00
|
|
|
|
nameof(RotationTimeoutSeconds));
|
|
|
|
|
|
|
|
|
|
|
|
if (pidParameters == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
"原地自转PID参数读取结果为空。");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EnsureFinitePositive(
|
|
|
|
|
|
pidParameters.DeadZone,
|
|
|
|
|
|
"PidparamsRead.DeadZone");
|
|
|
|
|
|
EnsureFinitePositive(
|
|
|
|
|
|
pidParameters.OutputUpperThreshold,
|
|
|
|
|
|
"PidparamsRead.OutputUpperThreshold");
|
|
|
|
|
|
EnsureFinitePositive(
|
|
|
|
|
|
pidParameters.SpeedAccPerSec,
|
|
|
|
|
|
"PidparamsRead.SpeedAccPerSec");
|
|
|
|
|
|
EnsureFinitePositive(
|
|
|
|
|
|
pidParameters.Kp,
|
|
|
|
|
|
"PidparamsRead.Kp");
|
|
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
if (minimumAngularSpeedDegreesPerSecond >
|
2026-08-07 13:00:56 +08:00
|
|
|
|
pidParameters.OutputUpperThreshold)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
"原地自转最小有效角速度不能大于最大角速度。");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 读取经过状态源校验的世界航向,显式设置ThetaReader时优先使用替代读数。
|
|
|
|
|
|
/// </summary>
|
2026-08-11 17:06:26 +08:00
|
|
|
|
private float ReadCurrentAngleDegrees(
|
|
|
|
|
|
IVehicleStateProvider stateProvider)
|
2026-08-07 13:00:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (ThetaReader != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var angleDegrees = ThetaReader();
|
|
|
|
|
|
if (float.IsNaN(angleDegrees) ||
|
|
|
|
|
|
float.IsInfinity(angleDegrees))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
"自定义航向读取结果不是有效角度。");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (float)AngleMath.NormalizeDegrees(
|
|
|
|
|
|
angleDegrees);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
if (stateProvider == null ||
|
|
|
|
|
|
!stateProvider.TryGetState(out var state))
|
2026-08-07 13:00:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
"无法从Detour状态源读取有效车辆航向。" +
|
2026-08-11 17:06:26 +08:00
|
|
|
|
GetStateProviderFailureReason(
|
|
|
|
|
|
stateProvider));
|
2026-08-07 13:00:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (float)AngleMath.RadiansToDegrees(
|
|
|
|
|
|
state.PoseInWorld.YawRadians);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 17:06:26 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取已知停车状态源最近一次失败原因,未知实现返回空字符串。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static string GetStateProviderFailureReason(
|
|
|
|
|
|
IVehicleStateProvider stateProvider)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (stateProvider is
|
|
|
|
|
|
WheelFeedbackVehicleStateProvider wheelProvider)
|
|
|
|
|
|
{
|
|
|
|
|
|
return wheelProvider.LastFailureReason;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (stateProvider is
|
|
|
|
|
|
DetourVehicleStateProvider detourProvider)
|
|
|
|
|
|
{
|
|
|
|
|
|
return detourProvider.LastFailureReason;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-07 13:00:56 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检查原地自转参数是否为正有限值,部分时间和容差参数允许为零。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static void EnsureFinitePositive(
|
|
|
|
|
|
float value,
|
|
|
|
|
|
string parameterName,
|
|
|
|
|
|
bool allowZero = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (float.IsNaN(value) ||
|
|
|
|
|
|
float.IsInfinity(value) ||
|
|
|
|
|
|
(allowZero
|
|
|
|
|
|
? value < 0f
|
|
|
|
|
|
: value <= 0f))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentOutOfRangeException(
|
|
|
|
|
|
parameterName,
|
|
|
|
|
|
"原地自转参数必须是有效的正数。");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-05 18:07:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|