Files
ParkingRobot/MedullaAdapter/DiverCartDefinition.cs
T

431 lines
17 KiB
C#

// 定义车型、上下层IO、参数和MCU初始化
using CartActivator;
using MCUSerialBridgeCLR;
using MDCSToolBox.Medulla.Chassis.MultiWheel;
using Medulla.Types;
using System;
using System.Collections.Generic;
using System.Threading;
using MyParking.Shared;
namespace MedullaAdapter
{
[UseLadderLogic(logic = typeof(AlarmRoutine), scanInterval = 50)]
[UseLadderLogic(logic = typeof(MotorRoutine), scanInterval = 50)]
[UseLadderLogic(logic = typeof(MCURoutine), scanInterval = 20)]
[UseManualController(manualController = typeof(Remote))]
public class DiverCartDefinition : MultiWheelCartDefinition
{
#region 基本成员
public MCUSerialBridge Bridge;
internal enum ManualControlMode
{
Normal = 0, // 正常模式
Crab = 1, // 螃蟹模式
Spin = 2, // 自旋模式
}
internal ManualControlMode TransmitterControlMode = ManualControlMode.Normal;
internal DateTime TransmitterLastTime = DateTime.Now; // 物理遥控器计算两次实体遥控器指令之间的时间间隔
private ManualControlMode? _pendingManualMode;
private ManualControlMode? _activeManualMode;
#endregion
#region AsUpperIO
[AsUpperIO(desc = "从C上复位")] public bool ResetFromC;
[AsUpperIO(desc = "从C将驱动轮下使能")] public bool DisableFromC;
[AsUpperIO(desc = "左夹臂下发速度", timeOutReset = true)] public float SpeedLeftArm;
[AsUpperIO(desc = "右夹臂下发速度", timeOutReset = true)] public float SpeedRightArm;
[AsUpperIO(desc = "夹臂不同步报警")] public bool ClampOutOfSync;
#endregion
#region AsLowerIO
[AsLowerIO(desc = "左前左轮实际位置")] public float LFLActualPos;
[AsLowerIO(desc = "左前右轮实际位置")] public float LFRActualPos;
[AsLowerIO(desc = "右前左轮实际位置")] public float RFLActualPos;
[AsLowerIO(desc = "右前右轮实际位置")] public float RFRActualPos;
[AsLowerIO(desc = "左后左轮实际位置")] public float LRLActualPos;
[AsLowerIO(desc = "左后右轮实际位置")] public float LRRActualPos;
[AsLowerIO(desc = "右后左轮实际位置")] public float RRLActualPos;
[AsLowerIO(desc = "右后右轮实际位置")] public float RRRActualPos;
[AsLowerIO(desc = "左夹臂实际速度")] public float ActualSpeedLeftArm;
[AsLowerIO(desc = "右夹臂实际速度")] public float ActualSpeedRightArm;
[AsLowerIO(desc = "左夹臂状态字")] public int LeftArmStateCode;
[AsLowerIO(desc = "右夹臂状态字")] public int RightArmStateCode;
[AsLowerIO(desc = "左夹臂错误字")] public int LeftArmErrorCode;
[AsLowerIO(desc = "右夹臂错误字")] public int RightArmErrorCode;
[AsLowerIO(desc = "左夹臂电流")] public float LeftArmElectric;
[AsLowerIO(desc = "右夹臂电流")] public float RightArmElectric;
[AsLowerIO(desc = "左夹臂实际位置")] public float ActualPosLeftArm;
[AsLowerIO(desc = "右夹臂实际位置")] public float ActualPosRightArm;
[AsLowerIO(desc = "驱动轮使能状态")] public bool WheelAbleState = true;
[AsLowerIO(desc = "电池健康状态")] public float SOH;
[AsInitParam(desc = "车号")][AsLowerIO] public int CarNum = 1;
#endregion
#region 初始参数
[AsInitParam(desc = "MCU端口号")] public string MCUPort = "COM4";
[AsInitParam(desc = "遥控器速度上限")] public float TransmitterSpeedUpperLimit = 1.0f;
[AsInitParam(desc = "遥控器速度下限")] public float TransmitterSpeedLowerLimit = 0.0f;
[AsInitParam(desc = "手动控制夹臂速度系数")] public float ManualArmSpeedFac = 1.0f;
[AsInitParam(desc = "遥控转弯舵角同步限速宽度,单位为度")]
public float ManualSteeringAlignmentSigmaDegrees = 8.0f;
[AsInitParam(desc = "左夹臂低限位")][AsLowerIO] public int LeftArmLowerPos = -10000;
[AsInitParam(desc = "左夹臂高限位")][AsLowerIO] public int LeftArmUpperPos = 5927610;
[AsInitParam(desc = "右夹臂低限位")][AsLowerIO] public int RightArmLowerPos = -17295;
[AsInitParam(desc = "右夹臂高限位")][AsLowerIO] public int RightArmUpperPos = 5927610;
#endregion
#region 监控参数
[IOObjectMonitor(desc = "从M上复位")] public bool ResetFromM;
[IOObjectMonitor(desc = "从M将驱动轮下使能")] public bool DisableFromM;
[IOObjectMonitor(desc = "左前左轮PID修正后速度")] public float SpeedLFL;
[IOObjectMonitor(desc = "左前右轮PID修正后速度")] public float SpeedLFR;
[IOObjectMonitor(desc = "右前左轮PID修正后速度")] public float SpeedRFL;
[IOObjectMonitor(desc = "右前右轮PID修正后速度")] public float SpeedRFR;
[IOObjectMonitor(desc = "左后左轮PID修正后速度")] public float SpeedLRL;
[IOObjectMonitor(desc = "左后右轮PID修正后速度")] public float SpeedLRR;
[IOObjectMonitor(desc = "右后左轮PID修正后速度")] public float SpeedRRL;
[IOObjectMonitor(desc = "右后右轮PID修正后速度")] public float SpeedRRR;
[IOObjectMonitor(desc = "灯光模式")] public int LightMode = 0;
[IOObjectMonitor(desc = "实体遥控器当前速度倍率")] public float TransmitterSpeed = 0.3f;
[IOObjectMonitor(desc = "左前左驱动器远程帧701")] public byte LFLRemoteCode = 0;
[IOObjectMonitor(desc = "左前右驱动器远程帧702")] public byte LFRRemoteCode = 0;
[IOObjectMonitor(desc = "右前左驱动器远程帧703")] public byte RFLRemoteCode = 0;
[IOObjectMonitor(desc = "右前右驱动器远程帧704")] public byte RFRRemoteCode = 0;
[IOObjectMonitor(desc = "左后左驱动器远程帧705")] public byte LRLRemoteCode = 0;
[IOObjectMonitor(desc = "左后右驱动器远程帧706")] public byte LRRRemoteCode = 0;
[IOObjectMonitor(desc = "右后左驱动器远程帧707")] public byte RRLRemoteCode = 0;
[IOObjectMonitor(desc = "右后右驱动器远程帧708")] public byte RRRRemoteCode = 0;
[IOObjectMonitor(desc = "左夹臂驱动器远程帧709")] public byte LArmRemoteCode = 0;
[IOObjectMonitor(desc = "右夹臂驱动器远程帧70A")] public byte RArmRemoteCode = 0;
#endregion
#region 操作按钮
// M层单车硬件:向驱动轮发送复位请求。
[IOObjectUtility]
public void WheelReset()
{
ResetFromM = true;
}
// M层单车硬件:向驱动轮发送下使能请求。
[IOObjectUtility]
public void WheelDisable()
{
DisableFromM = true;
}
#endregion
public override void CommunicationInit()
{
if (GhostMode) return;
State = -1;
Bridge = new MCUSerialBridge();
//Step1:打开指定串口连接
var err = Bridge.Open(MCUPort, 1000000u);
if (err != MCUSerialBridgeError.OK)
{
Console.WriteLine($"MCU Open FAILED: {err.ToDescription()}");
return;
}
else
{
Console.WriteLine("MCU Open OK");
}
//Step2:远程复位MCU
err = Bridge.Reset();
if (err != MCUSerialBridgeError.OK)
{
Console.WriteLine($"MCU Reset FAILED: {err.ToDescription()}");
return;
}
else
{
Thread.Sleep(500);
Console.WriteLine("MCU Reset OK");
}
//Step3:获取MCU版本号
err = Bridge.GetVersion(out var version, 100);
if (err != MCUSerialBridgeError.OK)
{
Console.WriteLine($"MCU GetVersion FAILED: {err.ToDescription()}");
return;
}
else
{
Console.WriteLine($"MCU GetVersion OK: {version}");
}
//Step4:获取MCU状态
err = Bridge.GetState(out var state, 100);
if (err != MCUSerialBridgeError.OK)
{
Console.WriteLine($"MCU GetState FAILED: {err.ToDescription()}");
return;
}
else
{
Console.WriteLine($"MCU GetState OK: {state}");
}
//Step5:串口/CAN配置
try
{
var ports = new List<PortConfig>();
for (int i = 0; i < 1; i++)
ports.Add(new CANPortConfig(500000, 10));
for (int i = 0; i < 3; i++)
ports.Add(new SerialPortConfig(9600, 10));
Console.WriteLine("=== Port Configuration ===");
for (int i = 0; i < ports.Count; i++)
{
if (ports[i] is SerialPortConfig s)
Console.WriteLine($"Port {i}: Serial, Baud={s.Baud}, ReceiveFrameMs={s.ReceiveFrameMs}");
else if (ports[i] is CANPortConfig c)
Console.WriteLine($"Port {i}: CAN, Baud={c.Baud}, RetryTimeMs={c.RetryTimeMs}");
}
var ret = Bridge.Configure(ports, 200);
if (ret != MCUSerialBridgeError.OK)
{
Console.WriteLine($"MCU Configure FAILED: {ret.ToDescription()}");
return;
}
else
{
Console.WriteLine("MCU Configure OK");
}
Console.WriteLine("MCU Configure {0}", ret == MCUSerialBridgeError.OK ? "OK" : $"FAILED: 0x{(uint)ret:X8}");
}
catch (Exception ex)
{
Console.WriteLine($"Configure Exception: {ex.Message}");
return;
}
State = 0;
}
internal void ManualControl(
ManualControlMode mode,
float x,
float y,
float frontDirection,
float speedThreshold,
TimeSpan? interval = null)
{
if (Chassis == null) return;
var adapter = GetChassisAdapter();
if (adapter == null) return;
// 模式变化时先停车并下发舵轮准备角度;
// 在实际舵角到位之前,不开放驱动速度。
if (!EnsureManualModeReady(mode, interval))
{
adapter.StopImmediately();
return;
}
var speed = speedThreshold * y;
var normalizedSteering =
(float)Math.Pow(
Math.Abs(x),
ManualThetaPow) *
Math.Sign(x);
var steeringDegrees =
-normalizedSteering * MaxManualTheta;
var omega = CalculateManualOmega(
speed,
steeringDegrees,
adapter.HalfWheelBaseMeters);
ManualMode = (int)mode;
switch (mode)
{
case ManualControlMode.Normal:
// 普通模式统一使用车体速度命令:
// X向前,行驶中连续改变角速度时舵轮边转、车辆边走。
SendBodyCommand(
vx: speed,
vy: 0.0,
omegaRadiansPerSecond: omega,
interval);
break;
case ManualControlMode.Crab:
SendBodyCommand(vx: 0.0, vy: speed, omegaRadiansPerSecond: omega, interval);
break;
case ManualControlMode.Spin:
// 自转时speed表示最外侧舵轮的目标切向速度。
// 根据v=omega*r换算角速度,不能把m/s与deg/s直接相乘。
var spinOmega =
speed /
adapter.MaximumWheelRadiusMeters;
SendBodyCommand(
vx: 0.0,
vy: 0.0,
omegaRadiansPerSecond: spinOmega,
interval);
break;
default:
ManualMode = -1;
adapter.StopImmediately();
break;
}
}
// 停车后切换模式:先预转舵轮,实际角度到位后才允许发送运动命令。
private bool EnsureManualModeReady(
ManualControlMode mode,
TimeSpan? interval)
{
var adapter = GetChassisAdapter();
if (adapter == null)
return false;
// 当前模式已经完成准备,可以直接接受运动命令。
if (_activeManualMode == mode &&
_pendingManualMode == null)
{
return true;
}
// 第一次收到新模式时,停车并下发一次舵轮准备姿态。
if (_pendingManualMode != mode)
{
adapter.StopImmediately();
var preparationAccepted = mode switch
{
ManualControlMode.Normal =>
adapter.PrepareParallelDirection(0.0),
ManualControlMode.Crab =>
adapter.PrepareParallelDirection(
Math.PI / 2.0),
ManualControlMode.Spin =>
adapter.PrepareSpin(interval),
_ => false
};
if (!preparationAccepted)
{
_pendingManualMode = null;
return false;
}
_pendingManualMode = mode;
return false;
}
// 后续控制周期保持停车,并读取实际舵角判断是否到位。
adapter.StopImmediately();
const double toleranceRadians =
2.0 * Math.PI / 180.0;
bool aligned;
if (mode == ManualControlMode.Spin)
{
// 自转的四个舵轮目标角不同,等待期间持续刷新其目标。
var preparationAccepted =
adapter.PrepareSpin(interval);
aligned =
preparationAccepted &&
adapter.AreSpinWheelsAligned;
}
else
{
var targetDirection = mode ==
ManualControlMode.Crab
? Math.PI / 2.0
: 0.0;
aligned =
adapter.AreParallelWheelsAligned(
targetDirection,
toleranceRadians);
}
if (!aligned)
return false;
_activeManualMode = mode;
_pendingManualMode = null;
return true;
}
private double CalculateManualOmega(
float speed,
float steeringDegrees,
double halfWheelBaseMeters)
{
var steeringRadians =
steeringDegrees * Math.PI / 180.0;
return speed *
Math.Tan(steeringRadians) /
Math.Max(halfWheelBaseMeters, 0.01);
}
internal void SendBodyCommand(double vx, double vy, double omegaRadiansPerSecond, TimeSpan? interval = null)
{
var adapter = GetChassisAdapter();
if (adapter == null)
return;
var command = new ChassisCommand(
CarNum,
new Twist2D(vx, vy, omegaRadiansPerSecond));
if (!adapter.Send(command, interval))
{
adapter.StopImmediately();
Console.WriteLine(
"底盘命令分解失败,车辆已经停车:" +
adapter.LastFailureReason);
}
}
private MultiWheelChassisAdapter _chassisAdapter;
private MultiWheelChassisAdapter GetChassisAdapter()
{
if (Chassis == null)
return null;
if (_chassisAdapter == null ||
_chassisAdapter.VehicleId != CarNum)
{
_chassisAdapter =
new MultiWheelChassisAdapter(Chassis, CarNum);
}
_chassisAdapter.SteeringAlignmentSigmaDegrees =
Math.Max(
ManualSteeringAlignmentSigmaDegrees,
0.1f);
return _chassisAdapter;
}
#region MCURoutine兼容参数(暂保留原硬件协议)
// 保存MCU读取到的原始输入字节,供M层监控和硬件排查使用。
[AsLowerIO(desc = "MCU原始输入字节")]
public float test;
// 保留原MCU灯光分支;单车默认值-1表示使用本车LightMode。
[AsUpperIO(desc = "多车灯光同步兼容值,-1使用本车灯光")]
public int MultiVehicleLightSync = -1;
#endregion
}
}