2.0协议完善加简单交管配置

This commit is contained in:
ykkokluo
2026-08-14 09:49:23 +08:00
parent a28bc68e23
commit 1e780c2b65
51 changed files with 4013 additions and 393 deletions
+2
View File
@@ -7,6 +7,8 @@ namespace StandardScene.Fass2Simulator
{
protected override void OnStartup(StartupEventArgs e)
{
Fass2SimBootstrap.ApplyCommandLine(e.Args);
if (HasTestArg(e.Args))
{
var code = Fass2SimSelfTests.Run(e.Args);
@@ -58,6 +58,15 @@ namespace StandardScene.Fass2Simulator
}
}
public void ResetForNewTask()
{
lock (_syncRoot)
{
_pendingActions.Clear();
_expectedStation = null;
}
}
public void OnActionCommand(ulong actionId, Fass2SimNodeMessage patch)
{
if (patch == null)
@@ -67,13 +76,16 @@ namespace StandardScene.Fass2Simulator
lock (_syncRoot)
{
var targetNode = patch.Node != 0 ? patch.Node : _vehicle.Node;
if (patch.Node != 0 && patch.Node != _vehicle.Node)
{
Fass2SimLog.WriteLine(
$"[{Now()}] -> 0xA1 节点不匹配: patch={patch.Node}, current={_vehicle.Node},仍尝试应用");
$"[{Now()}] -> 0xA1 忽略: patch node={patch.Node}, current={_vehicle.Node}");
return;
}
var delay = ResolveActionDelayMs(patch.Node != 0 ? patch.Node : _vehicle.Node);
var delay = ResolveActionDelayMs(targetNode);
MergeExpectedLocked(patch);
SchedulePatch(patch, actionId, 0, delay);
Fass2SimLog.WriteLine(
$"[{Now()}] -> 0xA1 actionId={actionId} 已排队,delay={delay}ms, pending={DescribePatch(patch)}");
@@ -161,6 +173,37 @@ namespace StandardScene.Fass2Simulator
}
}
public void MergeExpected(Fass2SimNodeMessage patch)
{
if (patch == null)
{
return;
}
lock (_syncRoot)
{
MergeExpectedLocked(patch);
}
}
private void MergeExpectedLocked(Fass2SimNodeMessage patch)
{
if (_expectedStation == null)
{
return;
}
if (patch.Node != 0 && patch.Node != _expectedStation.Node)
{
return;
}
if (patch.StartStop > 0)
{
_expectedStation.StartStop = patch.StartStop;
}
}
private bool IsStationCompleteLocked()
{
if (_pendingActions.Count > 0)
@@ -229,9 +272,9 @@ namespace StandardScene.Fass2Simulator
private void ApplyPatch(Fass2SimNodeMessage patch)
{
if (patch.Node != 0)
if (patch.Node != 0 && patch.Node != _vehicle.Node)
{
_vehicle.Node = patch.Node;
return;
}
if (patch.StartStop > 0)
@@ -10,13 +10,14 @@ namespace StandardScene.Fass2Simulator
{
public const byte StartStopPass = 1;
public const byte StartStopStop = 2;
public const byte StartStopControlStart = 11;
public const byte StartStopControlStop = 12;
public const byte StartStopPrecision = 22;
private static readonly (string Name, Func<Fass2SimNodeMessage, byte> Get)[] ActionFields =
public static bool VerifyTrajectoryFields { get; set; }
private static readonly (string Name, Func<Fass2SimNodeMessage, byte> Get)[] MechanismFields =
{
("Direction", n => n.Direction),
("Orientation", n => n.Orientation),
("Byroad", n => n.Byroad),
("Obstacle", n => n.Obstacle),
("Audio", n => n.Audio),
("Light", n => n.Light),
@@ -29,6 +30,18 @@ namespace StandardScene.Fass2Simulator
("Shutdown", n => n.Shutdown)
};
private static readonly (string Name, Func<Fass2SimNodeMessage, byte> Get)[] TrajectoryFields =
{
("Direction", n => n.Direction),
("Orientation", n => n.Orientation),
("Byroad", n => n.Byroad)
};
public static bool AllowsAutoContinue(byte startStop)
{
return startStop == StartStopPass || startStop == StartStopControlStart;
}
public static bool RequiresActionWait(Fass2SimNodeMessage expected)
{
if (expected == null)
@@ -36,12 +49,25 @@ namespace StandardScene.Fass2Simulator
return false;
}
if (expected.StartStop is StartStopStop or StartStopPrecision)
if (expected.StartStop is StartStopStop or StartStopPrecision or StartStopControlStop or StartStopControlStart)
{
return true;
}
foreach (var field in ActionFields)
foreach (var field in MechanismFields)
{
if (field.Get(expected) != 0)
{
return true;
}
}
if (!VerifyTrajectoryFields)
{
return false;
}
foreach (var field in TrajectoryFields)
{
if (field.Get(expected) != 0)
{
@@ -64,7 +90,7 @@ namespace StandardScene.Fass2Simulator
return false;
}
if (vehicleState == 1)
if (vehicleState is 1 or 5)
{
return false;
}
@@ -79,6 +105,11 @@ namespace StandardScene.Fass2Simulator
return true;
}
if (expected.StartStop == StartStopControlStop)
{
return false;
}
return ListPendingFields(expected, actual).Count == 0;
}
@@ -95,12 +126,24 @@ namespace StandardScene.Fass2Simulator
pending.Add("StartStop");
}
if (expected.Speed != 0 && actual.Speed != expected.Speed)
if (VerifyTrajectoryFields)
{
pending.Add("Speed");
if (expected.Speed != 0 && actual.Speed != expected.Speed)
{
pending.Add("Speed");
}
foreach (var field in TrajectoryFields)
{
var expectedValue = field.Get(expected);
if (expectedValue != 0 && GetActualField(actual, field.Name) != expectedValue)
{
pending.Add(field.Name);
}
}
}
foreach (var field in ActionFields)
foreach (var field in MechanismFields)
{
var expectedValue = field.Get(expected);
if (expectedValue != 0 && GetActualField(actual, field.Name) != expectedValue)
@@ -8,12 +8,30 @@ namespace StandardScene.Fass2Simulator
{
public static class Fass2SimBootstrap
{
public static Fass2SimConfig LoadConfig()
public static string SettingsFile { get; private set; } = "appsettings.json";
public static void ApplyCommandLine(string[] args)
{
if (args == null)
{
return;
}
for (var i = 0; i < args.Length - 1; i++)
{
if (string.Equals(args[i], "--config", StringComparison.OrdinalIgnoreCase))
{
SettingsFile = args[i + 1];
}
}
}
public static Fass2SimConfig LoadConfig(string settingsFile = null)
{
var basePath = AppContext.BaseDirectory;
var builder = new ConfigurationBuilder()
.SetBasePath(basePath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false);
.AddJsonFile(settingsFile ?? SettingsFile, optional: true, reloadOnChange: false);
var configuration = builder.Build();
var config = new Fass2SimConfig();
@@ -41,6 +41,12 @@ namespace StandardScene.Fass2Simulator
_windowNodes = nodes;
_vehicle.Task = taskId;
if (taskChanged)
{
_actions.ResetForNewTask();
ResetSegment();
}
ApplyCurrentNodeFields(nodes);
LogNodes(nodes);
@@ -63,10 +69,35 @@ namespace StandardScene.Fass2Simulator
}
}
if (!ContainsNode(nodes, _vehicle.Node))
{
Fass2SimLog.WriteLine(
$"[{Now()}] -> 0xB1 窗口不含当前 node={_vehicle.Node},忽略启动路段(避免回退到窗口首站)");
return;
}
TryStartNextSegment();
}
}
private static bool ContainsNode(Fass2SimNodeMessage[] nodes, ushort node)
{
if (nodes == null)
{
return false;
}
for (var i = 0; i < nodes.Length; i++)
{
if (nodes[i].Node == node)
{
return true;
}
}
return false;
}
public void OnStartCommand()
{
lock (_syncRoot)
@@ -147,7 +178,7 @@ namespace StandardScene.Fass2Simulator
}
_segmentTarget = target;
_segmentLength = ResolveSegmentLength(target);
_segmentLength = ResolveSegmentLength(_vehicle.Node, target);
_segmentProgress = 0;
_segmentActive = true;
_vehicle.State = 1;
@@ -184,7 +215,7 @@ namespace StandardScene.Fass2Simulator
}
var expected = _actions.ExpectedStation;
if (expected == null || expected.StartStop != Fass2SimActionResolver.StartStopPass)
if (expected == null || !Fass2SimActionResolver.AllowsAutoContinue(expected.StartStop))
{
return;
}
@@ -218,15 +249,24 @@ namespace StandardScene.Fass2Simulator
return null;
}
// 必须先在窗口内定位当前站,再取其后一站。
// 旧逻辑在窗口不含当前站时会落到 nodes[0],把更早的站当成下一目标 → 车体 Node 瞬间回退/瞬移。
var foundCurrent = false;
var startIndex = 0;
for (var i = 0; i < nodes.Length; i++)
{
if (nodes[i].Node == currentNode)
{
foundCurrent = true;
startIndex = i + 1;
}
}
if (!foundCurrent)
{
return null;
}
for (var i = startIndex; i < nodes.Length; i++)
{
var node = nodes[i];
@@ -248,12 +288,21 @@ namespace StandardScene.Fass2Simulator
if (node.Node == _vehicle.Node && node.StartStop > 0)
{
_vehicle.StartStop = node.StartStop;
_actions.MergeExpected(node);
}
}
}
private double ResolveSegmentLength(Fass2SimNodeMessage target)
private double ResolveSegmentLength(ushort fromNode, Fass2SimNodeMessage target)
{
foreach (var node in _windowNodes)
{
if (node.Node == fromNode && node.Distance > 0)
{
return node.Distance;
}
}
if (target.Distance > 0)
{
return target.Distance;
@@ -272,12 +321,12 @@ namespace StandardScene.Fass2Simulator
{
if (target.Speed > 0)
{
return target.Speed;
return Fass2SimProtocol.ProtocolSpeedToMmPerSec(target.Speed);
}
if (_vehicle.Speed > 0)
{
return _vehicle.Speed;
return Fass2SimProtocol.ProtocolSpeedToMmPerSec(_vehicle.Speed);
}
return _config.DefaultSpeed;
@@ -152,6 +152,17 @@ namespace StandardScene.Fass2Simulator
}
}
/// <summary>协议速度(0.1 m/min) → mm/s。</summary>
public static double ProtocolSpeedToMmPerSec(ushort protocolSpeed)
{
if (protocolSpeed == 0)
{
return 0;
}
return protocolSpeed * 0.1 * 1000.0 / 60.0;
}
public static string ToHex(byte[] bytes)
{
return bytes == null ? string.Empty : string.Join(" ", bytes.Select(b => b.ToString("X2")));
@@ -21,6 +21,7 @@ namespace StandardScene.Fass2Simulator
private static int RunMotionSelfTest()
{
Fass2SimFileLogger.Configure(new Fass2SimConfig { EnableFileLog = false });
var config = new Fass2SimConfig
{
InitialNode = 1,
@@ -31,9 +32,9 @@ namespace StandardScene.Fass2Simulator
var (vehicle, motion, _) = Fass2SimBootstrap.CreateTestStack(config);
var nodes = new[]
{
new Fass2SimNodeMessage { Node = 1, StartStop = 1 },
new Fass2SimNodeMessage { Node = 1, StartStop = 1, Distance = 500 },
new Fass2SimNodeMessage { Node = 2, StartStop = 1, Distance = 500 },
new Fass2SimNodeMessage { Node = 3, StartStop = 2, Distance = 500 }
new Fass2SimNodeMessage { Node = 3, StartStop = 2 }
};
vehicle.State = 1;
@@ -57,6 +58,7 @@ namespace StandardScene.Fass2Simulator
private static int RunActionSelfTest()
{
Fass2SimFileLogger.Configure(new Fass2SimConfig { EnableFileLog = false });
var config = new Fass2SimConfig
{
InitialNode = 3,
@@ -69,7 +69,7 @@ namespace StandardScene.Fass2Simulator
Fass2SimLog.WriteLine($" 路段模拟 : dist={_config.DefaultSegmentDistance}mm 或 node.Distance, speed={_config.DefaultSpeed}mm/s");
Fass2SimLog.WriteLine($" 动作延时 : {_config.ActionDelayMs}ms0xA1 到位),AutoComplete={_config.AutoCompleteStationActions}");
Fass2SimLog.WriteLine($" 初始节点 : {_config.InitialNode}, 状态={Fass2SimProtocol.StateText(_config.InitialState)}");
Fass2SimLog.WriteLine("等待 MagFass2Car 联调(address=127.0.0.1, Port=5000, ListenPort=20103, VehicleCode=1");
Fass2SimLog.WriteLine("等待 Mag2Car 联调(address=127.0.0.1, Port=5000, ListenPort=20103, VehicleCode=1");
}
public void Stop()
@@ -21,6 +21,7 @@
<ItemGroup>
<None Update="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
<None Update="appsettings.car2.json" CopyToOutputDirectory="PreserveNewest" />
<None Update="sim-nodes.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
@@ -0,0 +1,25 @@
{
"Fass2Simulator": {
"VehicleCode": 2,
"VehicleListenAddress": "0.0.0.0",
"VehicleListenPort": 5002,
"SchedulerHost": "127.0.0.1",
"SchedulerListenPort": 20103,
"ReportIntervalMs": 200,
"InitialNode": 5,
"InitialState": 1,
"BatteryCharge": 100,
"CarLength": 1200,
"CarWidth": 800,
"LogRawFrames": true,
"DefaultSpeed": 500,
"DefaultSegmentDistance": 1000,
"SecondsPerSegment": 2,
"AutoContinueOnPass": true,
"ActionDelayMs": 1000,
"AutoCompleteStationActions": false,
"NodeProfilesPath": "sim-nodes.json",
"EnableFileLog": true,
"LogDirectory": "logs\\car2"
}
}
@@ -2,12 +2,12 @@
"Fass2Simulator": {
"VehicleCode": 1,
"VehicleListenAddress": "0.0.0.0",
"VehicleListenPort": 5000,
"VehicleListenPort": 5001,
"SchedulerHost": "127.0.0.1",
"SchedulerListenPort": 20103,
"ReportIntervalMs": 200,
"InitialNode": 1,
"InitialState": 0,
"InitialState": 1,
"BatteryCharge": 100,
"CarLength": 1200,
"CarWidth": 800,