2.0协议完善加简单交管配置
This commit is contained in:
@@ -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}ms(0xA1 到位),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,
|
||||
|
||||
Reference in New Issue
Block a user