添加单车底盘仿真平台并完善运动控制与夹臂功能

This commit is contained in:
2026-07-27 17:47:50 +08:00
parent 580a936a83
commit e6b99c45b3
47 changed files with 4238 additions and 122 deletions
@@ -0,0 +1,121 @@
using MyParking.Simulation.Core;
namespace MyParking.Simulation.Commands;
/// <summary>
/// 内置离线测试动作;新增带特性的方法后网页会自动生成按钮。
/// </summary>
public static class BuiltInSimulationActions
{
[SimulationAction(
"mode-normal",
"正常",
"舵轮模式",
10)]
public static bool NormalMode(
SimulationVehicle vehicle)
{
return vehicle.SetMode("Normal");
}
[SimulationAction(
"mode-crab-left",
"左蟹行",
"舵轮模式",
20)]
public static bool CrabLeftMode(
SimulationVehicle vehicle)
{
return vehicle.SetMode("CrabLeft");
}
[SimulationAction(
"mode-crab-right",
"右蟹行",
"舵轮模式",
30)]
public static bool CrabRightMode(
SimulationVehicle vehicle)
{
return vehicle.SetMode("CrabRight");
}
[SimulationAction(
"mode-spin",
"自转",
"舵轮模式",
40)]
public static bool SpinMode(
SimulationVehicle vehicle)
{
return vehicle.SetMode("Spin");
}
[SimulationAction(
"forward",
"前进",
"运动测试",
10)]
public static bool Forward(
SimulationVehicle vehicle)
{
return vehicle.Move(1.0);
}
[SimulationAction(
"turn-left",
"左转",
"运动测试",
20)]
public static bool TurnLeft(
SimulationVehicle vehicle)
{
return vehicle.Turn(1.0);
}
[SimulationAction(
"stop",
"停止",
"运动测试",
30)]
public static bool Stop(
SimulationVehicle vehicle)
{
vehicle.Stop();
return true;
}
[SimulationAction(
"turn-right",
"右转",
"运动测试",
40)]
public static bool TurnRight(
SimulationVehicle vehicle)
{
return vehicle.Turn(-1.0);
}
[SimulationAction(
"backward",
"后退",
"运动测试",
50)]
public static bool Backward(
SimulationVehicle vehicle)
{
return vehicle.Move(-1.0);
}
[SimulationAction(
"reset",
"单车复位",
"维护",
10)]
public static bool Reset(
SimulationVehicle vehicle)
{
vehicle.Reset();
return true;
}
}
+31
View File
@@ -0,0 +1,31 @@
using MyParking.Shared;
using MyParking.Simulation.Core;
namespace MyParking.Simulation.Commands;
/// <summary>
/// 我自己增加的停车机器人仿真测试动作。
/// </summary>
public static class MySimulationTests
{
/// <summary>
/// 测试车辆以0.2m/s向车体左侧运动。
/// </summary>
// [SimulationAction(
// key: "move-left-020",
// displayName: "向左移动0.2m/s",
// group: "我的测试",
// order: 10)]
// public static bool MoveLeft(
// SimulationVehicle vehicle)
// {
// var command = new ChassisCommand(
// vehicle.VehicleId,
// new Twist2D(
// vxMetersPerSecond: 0.0,
// vyMetersPerSecond: 0.2,
// omegaRadiansPerSecond: 0.0));
// return vehicle.ApplyCommand(command);
// }
}
@@ -0,0 +1,38 @@
namespace MyParking.Simulation.Commands;
/// <summary>
/// 将一个静态仿真测试方法自动注册为网页按钮。
/// 方法签名必须为bool Xxx(SimulationVehicle vehicle)。
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class SimulationActionAttribute : Attribute
{
public SimulationActionAttribute(
string key,
string displayName,
string group,
int order = 0)
{
Key = key;
DisplayName = displayName;
Group = group;
Order = order;
}
public string Key { get; }
public string DisplayName { get; }
public string Group { get; }
public int Order { get; }
}
/// <summary>
/// 网页生成测试按钮所需的命令元数据。
/// </summary>
public sealed record SimulationActionDescriptor(
string Key,
string DisplayName,
string Group,
int Order);
@@ -0,0 +1,150 @@
using System.Reflection;
using MyParking.Simulation.Core;
namespace MyParking.Simulation.Commands;
/// <summary>
/// 自动发现带SimulationAction特性的测试方法并分发网页命令。
/// </summary>
public sealed class SimulationCommandDispatcher
{
private readonly SimulationWorld _world;
private readonly IReadOnlyDictionary<string, RegisteredAction> _actions;
public SimulationCommandDispatcher(
SimulationWorld world)
{
_world = world;
_actions = DiscoverActions();
}
/// <summary>
/// 返回网页动态生成按钮所需的全部命令。
/// </summary>
public IReadOnlyList<SimulationActionDescriptor> GetActions()
{
return _actions.Values
.Select(action => action.Descriptor)
.OrderBy(action => action.Group)
.ThenBy(action => action.Order)
.ToArray();
}
/// <summary>
/// 对指定车辆执行一个已注册的测试方法。
/// </summary>
public CommandResult Execute(
int vehicleId,
string command)
{
if (!_actions.TryGetValue(
command,
out var registeredAction))
{
return new CommandResult(
false,
$"未注册仿真命令:{command}。");
}
try
{
return _world.WithVehicle(vehicleId, vehicle =>
{
var success = registeredAction.Handler(vehicle);
var message = success
? $"车辆{vehicleId}已执行:{registeredAction.Descriptor.DisplayName}。"
: $"车辆{vehicleId}暂时无法执行:{registeredAction.Descriptor.DisplayName}。";
return new CommandResult(success, message);
});
}
catch (KeyNotFoundException exception)
{
return new CommandResult(
false,
exception.Message);
}
}
private static IReadOnlyDictionary<string, RegisteredAction>
DiscoverActions()
{
var actions = new Dictionary<string, RegisteredAction>(
StringComparer.OrdinalIgnoreCase);
var methods = Assembly.GetExecutingAssembly()
.GetTypes()
.SelectMany(type => type.GetMethods(
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Static));
foreach (var method in methods)
{
var attribute =
method.GetCustomAttribute<SimulationActionAttribute>();
if (attribute == null)
continue;
ValidateMethod(method, attribute);
var handler =
(Func<SimulationVehicle, bool>)method.CreateDelegate(
typeof(Func<SimulationVehicle, bool>));
var descriptor = new SimulationActionDescriptor(
attribute.Key,
attribute.DisplayName,
attribute.Group,
attribute.Order);
if (!actions.TryAdd(
attribute.Key,
new RegisteredAction(descriptor, handler)))
{
throw new InvalidOperationException(
$"仿真命令Key重复:{attribute.Key}。");
}
}
return actions;
}
private static void ValidateMethod(
MethodInfo method,
SimulationActionAttribute attribute)
{
var parameters = method.GetParameters();
if (method.ReturnType != typeof(bool) ||
parameters.Length != 1 ||
parameters[0].ParameterType !=
typeof(SimulationVehicle))
{
throw new InvalidOperationException(
$"[{nameof(SimulationActionAttribute)}]方法" +
$"{method.DeclaringType?.FullName}.{method.Name}" +
"必须是static bool Xxx(SimulationVehicle vehicle)。");
}
if (string.IsNullOrWhiteSpace(attribute.Key) ||
string.IsNullOrWhiteSpace(attribute.DisplayName) ||
string.IsNullOrWhiteSpace(attribute.Group))
{
throw new InvalidOperationException(
$"仿真命令{method.Name}的特性参数不能为空。");
}
}
private sealed record RegisteredAction(
SimulationActionDescriptor Descriptor,
Func<SimulationVehicle, bool> Handler);
}
/// <summary>
/// 网页测试命令的执行结果。
/// </summary>
public sealed record CommandResult(
bool Success,
string Message);