187 lines
6.2 KiB
C#
187 lines
6.2 KiB
C#
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}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)");
|
||
}
|
||
|
||
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");
|
||
}
|
||
}
|
||
}
|