2026-07-17 13:36:01 +08:00
|
|
|
using StandardScene.Magnetic.Protocol;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
|
|
namespace StandardScene.Magnetic.Tasking
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 比对期望节点与上报节点,判定到站与动作是否完成(对齐 backend <c>CarResponseService</c>)。
|
2026-08-14 09:49:23 +08:00
|
|
|
/// 默认只闭环机构类动作;轨迹字段(Speed/Orientation 等)需显式开启。
|
2026-07-17 13:36:01 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public static class Fass2ActionResolver
|
|
|
|
|
{
|
2026-08-14 09:49:23 +08:00
|
|
|
/// <summary>是否在停车站比对 Speed/Orientation/Direction/Byroad 等轨迹字段。</summary>
|
|
|
|
|
public static bool VerifyTrajectoryFields { get; set; }
|
|
|
|
|
|
|
|
|
|
private static readonly (string Name, Func<Fass2NodeMessage, byte> Get, Action<Fass2NodeMessage, byte> Set)[] MechanismFields =
|
2026-07-17 13:36:01 +08:00
|
|
|
{
|
|
|
|
|
("Obstacle", n => n.Obstacle, (n, v) => n.Obstacle = v),
|
|
|
|
|
("Audio", n => n.Audio, (n, v) => n.Audio = v),
|
|
|
|
|
("Light", n => n.Light, (n, v) => n.Light = v),
|
|
|
|
|
("Charge", n => n.Charge, (n, v) => n.Charge = v),
|
|
|
|
|
("Rest", n => n.Rest, (n, v) => n.Rest = v),
|
|
|
|
|
("Lift", n => n.Lift, (n, v) => n.Lift = v),
|
|
|
|
|
("Clamp", n => n.Clamp, (n, v) => n.Clamp = v),
|
|
|
|
|
("Tray", n => n.Tray, (n, v) => n.Tray = v),
|
|
|
|
|
("Roll", n => n.Roll, (n, v) => n.Roll = v),
|
|
|
|
|
("Shutdown", n => n.Shutdown, (n, v) => n.Shutdown = v)
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-14 09:49:23 +08:00
|
|
|
private static readonly (string Name, Func<Fass2NodeMessage, byte> Get, Action<Fass2NodeMessage, byte> Set)[] TrajectoryFields =
|
|
|
|
|
{
|
|
|
|
|
("Direction", n => n.Direction, (n, v) => n.Direction = v),
|
|
|
|
|
("Orientation", n => n.Orientation, (n, v) => n.Orientation = v),
|
|
|
|
|
("Byroad", n => n.Byroad, (n, v) => n.Byroad = v)
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-17 13:36:01 +08:00
|
|
|
public static bool IsAtNode(Fass2StateReport report, ushort targetNode)
|
|
|
|
|
{
|
|
|
|
|
return report?.Node != null && report.Node.Node == targetNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool IsVehicleMoving(byte vehicleState)
|
|
|
|
|
{
|
2026-08-14 09:49:23 +08:00
|
|
|
return vehicleState is 1 or 5;
|
2026-07-17 13:36:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool RequiresActionWait(Fass2NodeMessage expected)
|
|
|
|
|
{
|
|
|
|
|
if (expected == null)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-14 09:49:23 +08:00
|
|
|
if (expected.StartStop is Fass2TaskBuilder.StartStopStop
|
|
|
|
|
or Fass2TaskBuilder.StartStopPrecision
|
|
|
|
|
or Fass2TaskBuilder.StartStopControlStop
|
|
|
|
|
or Fass2TaskBuilder.StartStopControlStart)
|
2026-07-17 13:36:01 +08:00
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-14 09:49:23 +08:00
|
|
|
foreach (var field in MechanismFields)
|
|
|
|
|
{
|
|
|
|
|
if (field.Get(expected) != 0)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!VerifyTrajectoryFields)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var field in TrajectoryFields)
|
2026-07-17 13:36:01 +08:00
|
|
|
{
|
|
|
|
|
if (field.Get(expected) != 0)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return expected.Speed != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool IsStationActionComplete(Fass2NodeMessage expected, Fass2NodeMessage actual, byte vehicleState)
|
|
|
|
|
{
|
|
|
|
|
if (expected == null || actual == null)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actual.Node != expected.Node)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsVehicleMoving(vehicleState))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!RequiresActionWait(expected))
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (expected.StartStop == Fass2TaskBuilder.StartStopPass)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-14 09:49:23 +08:00
|
|
|
if (expected.StartStop == Fass2TaskBuilder.StartStopControlStop)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-17 13:36:01 +08:00
|
|
|
return ListPendingFields(expected, actual).Count == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static IReadOnlyList<string> ListPendingFields(Fass2NodeMessage expected, Fass2NodeMessage actual)
|
|
|
|
|
{
|
|
|
|
|
var pending = new List<string>();
|
|
|
|
|
if (expected == null || actual == null)
|
|
|
|
|
{
|
|
|
|
|
return pending;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (expected.StartStop != 0 && actual.StartStop != expected.StartStop)
|
|
|
|
|
{
|
|
|
|
|
pending.Add("StartStop");
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-14 09:49:23 +08:00
|
|
|
if (VerifyTrajectoryFields)
|
2026-07-17 13:36:01 +08:00
|
|
|
{
|
2026-08-14 09:49:23 +08:00
|
|
|
if (expected.Speed != 0 && actual.Speed != expected.Speed)
|
|
|
|
|
{
|
|
|
|
|
pending.Add("Speed");
|
|
|
|
|
}
|
2026-07-17 13:36:01 +08:00
|
|
|
|
2026-08-14 09:49:23 +08:00
|
|
|
foreach (var field in TrajectoryFields)
|
2026-07-17 13:36:01 +08:00
|
|
|
{
|
2026-08-14 09:49:23 +08:00
|
|
|
var expectedValue = field.Get(expected);
|
|
|
|
|
if (expectedValue != 0 && field.Get(actual) != expectedValue)
|
|
|
|
|
{
|
|
|
|
|
pending.Add(field.Name);
|
|
|
|
|
}
|
2026-07-17 13:36:01 +08:00
|
|
|
}
|
2026-08-14 09:49:23 +08:00
|
|
|
}
|
2026-07-17 13:36:01 +08:00
|
|
|
|
2026-08-14 09:49:23 +08:00
|
|
|
foreach (var field in MechanismFields)
|
|
|
|
|
{
|
2026-07-17 13:36:01 +08:00
|
|
|
var expectedValue = field.Get(expected);
|
|
|
|
|
if (expectedValue != 0 && field.Get(actual) != expectedValue)
|
|
|
|
|
{
|
|
|
|
|
pending.Add(field.Name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pending;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Fass2NodeMessage BuildActionPatch(Fass2NodeMessage expected, Fass2NodeMessage actual)
|
|
|
|
|
{
|
|
|
|
|
if (expected == null)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var patch = new Fass2NodeMessage { Node = expected.Node };
|
|
|
|
|
var hasPatch = false;
|
|
|
|
|
|
|
|
|
|
if (expected.StartStop != 0 && (actual == null || actual.StartStop != expected.StartStop))
|
|
|
|
|
{
|
|
|
|
|
patch.StartStop = expected.StartStop;
|
|
|
|
|
hasPatch = true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-14 09:49:23 +08:00
|
|
|
if (VerifyTrajectoryFields)
|
2026-07-17 13:36:01 +08:00
|
|
|
{
|
2026-08-14 09:49:23 +08:00
|
|
|
if (expected.Speed != 0 && (actual == null || actual.Speed != expected.Speed))
|
|
|
|
|
{
|
|
|
|
|
patch.Speed = expected.Speed;
|
|
|
|
|
hasPatch = true;
|
|
|
|
|
}
|
2026-07-17 13:36:01 +08:00
|
|
|
|
2026-08-14 09:49:23 +08:00
|
|
|
foreach (var field in TrajectoryFields)
|
2026-07-17 13:36:01 +08:00
|
|
|
{
|
2026-08-14 09:49:23 +08:00
|
|
|
var expectedValue = field.Get(expected);
|
|
|
|
|
if (expectedValue != 0 && (actual == null || field.Get(actual) != expectedValue))
|
|
|
|
|
{
|
|
|
|
|
field.Set(patch, expectedValue);
|
|
|
|
|
hasPatch = true;
|
|
|
|
|
}
|
2026-07-17 13:36:01 +08:00
|
|
|
}
|
2026-08-14 09:49:23 +08:00
|
|
|
}
|
2026-07-17 13:36:01 +08:00
|
|
|
|
2026-08-14 09:49:23 +08:00
|
|
|
foreach (var field in MechanismFields)
|
|
|
|
|
{
|
2026-07-17 13:36:01 +08:00
|
|
|
var expectedValue = field.Get(expected);
|
|
|
|
|
if (expectedValue != 0 && (actual == null || field.Get(actual) != expectedValue))
|
|
|
|
|
{
|
|
|
|
|
field.Set(patch, expectedValue);
|
|
|
|
|
hasPatch = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hasPatch ? patch : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string DescribePending(Fass2NodeMessage expected, Fass2NodeMessage actual)
|
|
|
|
|
{
|
|
|
|
|
var pending = ListPendingFields(expected, actual);
|
|
|
|
|
if (pending.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
return string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var builder = new StringBuilder();
|
|
|
|
|
for (var i = 0; i < pending.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i > 0)
|
|
|
|
|
{
|
|
|
|
|
builder.Append(',');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
builder.Append(pending[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return builder.ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|