Files
黄兆尉andCursor cb8009026a docs: 模拟器联调端口对齐 5001/5002
默认监听口与 appsettings 一致;启动日志按配置打印,脚本改为 Simple3。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 23:05:17 +08:00

188 lines
6.3 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace StandardScene.Fass2Simulator
{
public sealed class Fass2SimUdpHost : IDisposable
{
private readonly Fass2SimConfig _config;
private readonly Fass2SimVehicle _vehicle;
private readonly Fass2SimCommandHandler _commandHandler;
private readonly Fass2SimMotionEngine _motionEngine;
private readonly Fass2SimActionEngine _actionEngine;
private readonly IPEndPoint _schedulerEndpoint;
private UdpClient _listener;
private UdpClient _sender;
private Thread _receiveThread;
private Timer _reportTimer;
private volatile bool _running;
public Fass2SimUdpHost(
Fass2SimConfig config,
Fass2SimVehicle vehicle,
Fass2SimCommandHandler commandHandler,
Fass2SimMotionEngine motionEngine,
Fass2SimActionEngine actionEngine)
{
_config = config;
_vehicle = vehicle;
_commandHandler = commandHandler;
_motionEngine = motionEngine;
_actionEngine = actionEngine;
_schedulerEndpoint = new IPEndPoint(IPAddress.Parse(config.SchedulerHost), config.SchedulerListenPort);
}
public bool IsRunning => _running;
public event EventHandler StatusReported;
public void Start()
{
if (_running)
{
return;
}
var listenAddress = IPAddress.Parse(_config.VehicleListenAddress);
_listener = new UdpClient(new IPEndPoint(listenAddress, _config.VehicleListenPort));
_sender = new UdpClient();
_running = true;
_receiveThread = new Thread(ReceiveLoop)
{
IsBackground = true,
Name = $"Fass2SimRx:{_config.VehicleListenPort}"
};
_receiveThread.Start();
var interval = Math.Max(50, _config.ReportIntervalMs);
_reportTimer = new Timer(SendStateReport, null, 0, interval);
Fass2SimLog.WriteLine($"[{Now()}] 模拟车已启动");
Fass2SimLog.WriteLine($" 车体监听 : {_config.VehicleListenAddress}:{_config.VehicleListenPort}");
Fass2SimLog.WriteLine($" 上报目标 : {_config.SchedulerHost}:{_config.SchedulerListenPort}");
Fass2SimLog.WriteLine($" 车号 : {_config.VehicleCode}");
Fass2SimLog.WriteLine($" 上报周期 : {interval} ms");
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(
$"等待 Mag2Car 联调(address=127.0.0.1, Port={_config.VehicleListenPort}, ListenPort={_config.SchedulerListenPort}, VehicleCode={_config.VehicleCode}");
}
public void Stop()
{
_running = false;
_reportTimer?.Dispose();
_reportTimer = null;
try
{
_listener?.Close();
}
catch
{
}
try
{
_receiveThread?.Join(1000);
}
catch
{
}
_receiveThread = null;
_listener = null;
try
{
_sender?.Close();
}
catch
{
}
_sender = null;
Fass2SimLog.WriteLine($"[{Now()}] 模拟车已停止");
}
public void Dispose()
{
Stop();
}
private void ReceiveLoop()
{
while (_running)
{
try
{
var remote = new IPEndPoint(IPAddress.Any, 0);
var packet = _listener.Receive(ref remote);
_commandHandler.Handle(packet, remote.ToString());
}
catch (SocketException) when (!_running)
{
break;
}
catch (ObjectDisposedException) when (!_running)
{
break;
}
catch (Exception ex)
{
Fass2SimLog.WriteLine($"[{Now()}] 接收异常: {ex.Message}");
}
}
}
private void SendStateReport(object _)
{
if (!_running)
{
return;
}
try
{
var interval = Math.Max(50, _config.ReportIntervalMs);
_actionEngine.Tick(interval);
_motionEngine.Tick(interval);
var snapshot = _vehicle.Snapshot();
var frame = Fass2SimProtocol.BuildState(snapshot);
_sender.Send(frame, frame.Length, _schedulerEndpoint);
_vehicle.RecordStateReportSent();
StatusReported?.Invoke(this, EventArgs.Empty);
if (snapshot.StateReportsSent == 0 || snapshot.StateReportsSent % 25 == 0)
{
var pending = _actionEngine.ExpectedStation == null
? "-"
: Fass2SimActionResolver.DescribePending(_actionEngine.ExpectedStation, snapshot);
if (string.IsNullOrEmpty(pending))
{
pending = "-";
}
Fass2SimLog.WriteLine(
$"[{Now()}] TX 状态 #{snapshot.StateReportsSent + 1}: node={snapshot.Node}, dist={snapshot.Distance}, state={Fass2SimProtocol.StateText(snapshot.State)}, lift={snapshot.Lift}, pending={pending}, task={snapshot.Task}");
}
}
catch (Exception ex)
{
Fass2SimLog.WriteLine($"[{Now()}] 上报异常: {ex.Message}");
}
}
private static string Now()
{
return DateTime.Now.ToString("HH:mm:ss.fff");
}
}
}