添加单车底盘仿真平台并完善运动控制与夹臂功能
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user