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( $"等待 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"); } } }