241 lines
7.6 KiB
C#
241 lines
7.6 KiB
C#
using System;
|
||
using System.Numerics;
|
||
using CommonUsage.Chassis;
|
||
using MultiWheelC.Fleet;
|
||
using MyParking.Shared;
|
||
|
||
namespace MultiWheelC.Tests
|
||
{
|
||
internal static class FleetMemberAgentTests
|
||
{
|
||
private const long PlanId = 11;
|
||
private const int VehicleId = 2;
|
||
|
||
public static void Run()
|
||
{
|
||
VerifyActiveWatchdogExpires();
|
||
VerifyValidMotionCommandRefreshesDeadline();
|
||
VerifyRepeatedActivationDoesNotRefreshDeadline();
|
||
VerifyLateActivationCannotResumeMotion();
|
||
VerifyStopClearsWatchdog();
|
||
|
||
Console.WriteLine(
|
||
"FleetMemberAgent本地命令看门狗测试通过,共5个场景。");
|
||
}
|
||
|
||
private static void VerifyActiveWatchdogExpires()
|
||
{
|
||
var agent = CreateReadyAgent();
|
||
AssertTrue(
|
||
agent.Activate(
|
||
PlanId,
|
||
commandReceivedTimeSeconds: 10.0,
|
||
validForSeconds: 0.5),
|
||
"成员车应当成功激活");
|
||
|
||
AssertTrue(
|
||
agent.UpdateCommandWatchdog(10.5),
|
||
"截止时刻仍应视为有效");
|
||
AssertFalse(
|
||
agent.UpdateCommandWatchdog(10.501),
|
||
"超过命令有效期后应停车");
|
||
AssertState(
|
||
agent,
|
||
FleetMemberAgentState.Faulted,
|
||
"命令超时");
|
||
AssertTrue(
|
||
!string.IsNullOrWhiteSpace(
|
||
agent.LastFailureReason),
|
||
"命令超时应保留故障原因");
|
||
}
|
||
|
||
private static void VerifyValidMotionCommandRefreshesDeadline()
|
||
{
|
||
var agent = CreateReadyAgent();
|
||
agent.Activate(
|
||
PlanId,
|
||
commandReceivedTimeSeconds: 20.0,
|
||
validForSeconds: 0.5);
|
||
|
||
var accepted = agent.Execute(
|
||
PlanId,
|
||
new FleetMemberCommand(
|
||
VehicleId,
|
||
Twist2D.Zero),
|
||
commandReceivedTimeSeconds: 20.4,
|
||
validForSeconds: 0.7);
|
||
|
||
AssertTrue(
|
||
accepted,
|
||
"有效速度命令应被接受");
|
||
AssertNear(
|
||
agent.LastAcceptedCommandTimeSeconds,
|
||
20.4,
|
||
"最近命令接收时间");
|
||
AssertNear(
|
||
agent.CommandDeadlineSeconds,
|
||
21.1,
|
||
"速度命令刷新后的截止时间");
|
||
AssertTrue(
|
||
agent.UpdateCommandWatchdog(20.8),
|
||
"刷新截止时间后车辆应保持Active");
|
||
}
|
||
|
||
private static void VerifyRepeatedActivationDoesNotRefreshDeadline()
|
||
{
|
||
var agent = CreateReadyAgent();
|
||
agent.Activate(
|
||
PlanId,
|
||
commandReceivedTimeSeconds: 30.0,
|
||
validForSeconds: 0.5);
|
||
|
||
AssertTrue(
|
||
agent.Activate(
|
||
PlanId,
|
||
commandReceivedTimeSeconds: 30.4,
|
||
validForSeconds: 0.5),
|
||
"未超时的重复激活命令应允许幂等确认");
|
||
AssertNear(
|
||
agent.CommandDeadlineSeconds,
|
||
30.5,
|
||
"重复激活不能替代运动命令刷新截止时间");
|
||
AssertFalse(
|
||
agent.UpdateCommandWatchdog(30.501),
|
||
"没有收到运动命令时仍应按最初激活期限停车");
|
||
}
|
||
|
||
private static void VerifyLateActivationCannotResumeMotion()
|
||
{
|
||
var agent = CreateReadyAgent();
|
||
agent.Activate(
|
||
PlanId,
|
||
commandReceivedTimeSeconds: 40.0,
|
||
validForSeconds: 0.5);
|
||
|
||
AssertFalse(
|
||
agent.Activate(
|
||
PlanId,
|
||
commandReceivedTimeSeconds: 40.6,
|
||
validForSeconds: 0.5),
|
||
"迟到的激活命令不能恢复已经失联的车辆");
|
||
AssertState(
|
||
agent,
|
||
FleetMemberAgentState.Faulted,
|
||
"迟到激活命令");
|
||
}
|
||
|
||
private static void VerifyStopClearsWatchdog()
|
||
{
|
||
var agent = CreateReadyAgent();
|
||
agent.Activate(
|
||
PlanId,
|
||
commandReceivedTimeSeconds: 50.0,
|
||
validForSeconds: 0.5);
|
||
|
||
agent.Stop();
|
||
|
||
AssertState(
|
||
agent,
|
||
FleetMemberAgentState.Idle,
|
||
"正常停止");
|
||
AssertTrue(
|
||
!agent.LastAcceptedCommandTimeSeconds.HasValue &&
|
||
!agent.CommandDeadlineSeconds.HasValue,
|
||
"正常停止后应清除命令看门狗");
|
||
}
|
||
|
||
private static FleetMemberAgent CreateReadyAgent()
|
||
{
|
||
var chassis = new MultiWheelChassis();
|
||
chassis.AddWheel(CreateWheel(-500f, 300f));
|
||
chassis.AddWheel(CreateWheel(-500f, -300f));
|
||
chassis.AddWheel(CreateWheel(500f, 300f));
|
||
chassis.AddWheel(CreateWheel(500f, -300f));
|
||
chassis.Initialize();
|
||
|
||
var agent = new FleetMemberAgent(
|
||
new MultiWheelChassisAdapter(
|
||
chassis,
|
||
VehicleId),
|
||
alignmentToleranceRadians:
|
||
AngleMath.DegreesToRadians(1.0),
|
||
alignmentStableSeconds: 0.0);
|
||
|
||
AssertTrue(
|
||
agent.BeginRollingPreparation(
|
||
PlanId,
|
||
motionDirectionInBodyRadians: 0.0),
|
||
"滚动运动系准备命令应被接受");
|
||
AssertState(
|
||
agent,
|
||
FleetMemberAgentState.Preparing,
|
||
"开始准备");
|
||
AssertTrue(
|
||
agent.UpdatePreparation(0.01) ==
|
||
FleetMemberAgentState.Ready,
|
||
"内存底盘舵轮应立即准备完成");
|
||
|
||
return agent;
|
||
}
|
||
|
||
private static SteerWheel CreateWheel(
|
||
float xMillimeters,
|
||
float yMillimeters)
|
||
{
|
||
var speed = 0f;
|
||
var angle = 0f;
|
||
|
||
return new SteerWheel(
|
||
new Vector2(
|
||
xMillimeters,
|
||
yMillimeters),
|
||
angleLowerLimit: -120f,
|
||
angleUpperLimit: 120f,
|
||
speedWriter: value => speed = value,
|
||
speedReader: () => speed,
|
||
angleWriter: value => angle = value,
|
||
angleReader: () => angle);
|
||
}
|
||
|
||
private static void AssertState(
|
||
FleetMemberAgent agent,
|
||
FleetMemberAgentState expected,
|
||
string scenario)
|
||
{
|
||
AssertTrue(
|
||
agent.State == expected,
|
||
$"{scenario}后的状态应为{expected}," +
|
||
$"实际为{agent.State}。");
|
||
}
|
||
|
||
private static void AssertNear(
|
||
double? actual,
|
||
double expected,
|
||
string name)
|
||
{
|
||
AssertTrue(
|
||
actual.HasValue &&
|
||
Math.Abs(actual.Value - expected) <= 1e-9,
|
||
$"{name}不正确,期望{expected:F6}," +
|
||
$"实际{actual?.ToString("F6") ?? "null"}。");
|
||
}
|
||
|
||
private static void AssertTrue(
|
||
bool condition,
|
||
string message)
|
||
{
|
||
if (!condition)
|
||
{
|
||
throw new InvalidOperationException(message);
|
||
}
|
||
}
|
||
|
||
private static void AssertFalse(
|
||
bool condition,
|
||
string message)
|
||
{
|
||
AssertTrue(!condition, message);
|
||
}
|
||
}
|
||
}
|