using System;
using System.Collections.Generic;
using System.Net;
namespace StandardScene.MagCarSimulator
{
public sealed class MagCarSimVehicleSnapshot
{
public string Name { get; set; }
public ushort VehicleCode { get; set; }
public int ListenPort { get; set; }
public int StartSiteId { get; set; }
public int EndSiteId { get; set; }
public int IntervalMs { get; set; }
public int CurrentSiteId { get; set; }
public ushort Node { get; set; }
public byte State { get; set; }
public string StateText { get; set; }
public bool WaitingRelease { get; set; }
public bool HoldingForStop { get; set; }
public bool Looping { get; set; }
public bool Listening { get; set; }
public int ClientCount { get; set; }
public string PathText { get; set; }
}
///
/// 单车循环:普通站可叠车;停止点与交管点同时只允许一辆,后车停在上一站并保持运行中;仅 Mag_NeedStop 停车等 0x01。
///
public sealed class MagCarSimVehicle : IDisposable
{
private readonly object _syncRoot = new object();
private readonly MagCarSimConfig _hostConfig;
private MagCarSimTcpServer _server;
private SimpleLiteMap _map;
private List _loopSiteIds = new List();
private int _index;
private DateTime _nextHopAt = DateTime.MaxValue;
private DateTime _dispatchAt = DateTime.MinValue;
private DateTime _lastHoldLogAt = DateTime.MinValue;
private bool _looping;
private bool _dispatched;
private bool _waitingRelease;
private bool _pausedByStopCommand;
private bool _holdingForStop;
public MagCarSimVehicle(MagCarSimVehicleConfig config, MagCarSimConfig hostConfig)
{
Config = config ?? throw new ArgumentNullException(nameof(config));
_hostConfig = hostConfig ?? throw new ArgumentNullException(nameof(hostConfig));
Name = string.IsNullOrWhiteSpace(config.Name) ? $"AGV-{config.VehicleCode}" : config.Name;
Charge = 100;
Voltage = 24f;
Speed = 50;
}
public MagCarSimVehicleConfig Config { get; }
public string Name { get; }
public ushort VehicleCode => Config.VehicleCode;
public int ListenPort => Config.ListenPort;
public int CurrentSiteId { get; private set; }
public ushort Node { get; private set; }
public byte State { get; private set; }
public byte Charge { get; private set; }
public float Current { get; private set; }
public float Voltage { get; private set; }
public byte Speed { get; private set; }
public ushort Angle { get; private set; }
public byte Task { get; private set; }
public byte Lift { get; private set; }
public byte Roll { get; private set; }
public bool WaitingRelease => _waitingRelease;
public bool HoldingForStop => _holdingForStop;
public bool Looping => _looping;
public bool Listening => _server != null && _server.IsRunning;
public void AttachMap(SimpleLiteMap map)
{
lock (_syncRoot)
{
_map = map;
if (!_looping)
{
PlaceAtStartUnlocked();
}
}
}
public void StartListen()
{
MagCarSimTcpServer server;
lock (_syncRoot)
{
if (_server != null && _server.IsRunning)
{
return;
}
_server?.Dispose();
var address = string.IsNullOrWhiteSpace(_hostConfig.ListenAddress)
? "0.0.0.0"
: _hostConfig.ListenAddress;
server = new MagCarSimTcpServer(IPAddress.Parse(address), Config.ListenPort, HandleFrame);
_server = server;
PlaceAtStartUnlocked();
Log($"监听 {address}:{Config.ListenPort},当前站={CurrentSiteId} node={Node} {MagCarSimProtocol.StateText(State)}");
}
server.Start();
}
public void StopListen()
{
MagCarSimTcpServer server;
lock (_syncRoot)
{
server = _server;
_server = null;
}
server?.Dispose();
}
public void StartLoop(DateTime dispatchAt)
{
lock (_syncRoot)
{
if (_map == null)
{
throw new InvalidOperationException($"{Name} 尚未加载地图");
}
_loopSiteIds = MapPathFinder.ResolveLoop(_map, Config);
_index = 0;
if (Config.StartSiteId > 0)
{
var found = _loopSiteIds.IndexOf(Config.StartSiteId);
if (found >= 0)
{
_index = found;
}
}
_looping = true;
_dispatched = false;
_holdingForStop = false;
_pausedByStopCommand = false;
_waitingRelease = false;
_dispatchAt = dispatchAt;
PlaceAtStartUnlocked();
// 待命阶段若起点是停止点,先报运行中,避免交管误放行并占住 pending,导致真发车后长时间不放。
if (_map != null && _map.NeedStop(CurrentSiteId))
{
State = MagCarSimProtocol.StateRunning;
_waitingRelease = false;
}
var delayMs = Math.Max(0, (int)(dispatchAt - DateTime.Now).TotalMilliseconds);
Log($"待命发车,环线 {MapPathFinder.FormatPath(_loopSiteIds, 16)},间隔 {IntervalOrDefault()}ms" +
(delayMs > 50 ? $",{delayMs}ms 后启动" : ",立即启动"));
}
}
public void StopLoop()
{
lock (_syncRoot)
{
_looping = false;
_dispatched = false;
_waitingRelease = false;
_holdingForStop = false;
_pausedByStopCommand = false;
_nextHopAt = DateTime.MaxValue;
State = MagCarSimProtocol.StateStopping;
Log("已停止循环");
}
}
public void Release()
{
lock (_syncRoot)
{
ApplyStartUnlocked("界面放行");
}
}
public void Tick(DateTime now, Dictionary occupancy)
{
lock (_syncRoot)
{
if (!_looping || _pausedByStopCommand)
{
return;
}
if (!_dispatched)
{
TryDispatchUnlocked(now, occupancy);
return;
}
if (_waitingRelease)
{
return;
}
if (State != MagCarSimProtocol.StateRunning)
{
return;
}
if (now < _nextHopAt)
{
return;
}
AdvanceUnlocked(now, occupancy);
}
}
public MagCarSimVehicleSnapshot Snapshot()
{
lock (_syncRoot)
{
return new MagCarSimVehicleSnapshot
{
Name = Name,
VehicleCode = VehicleCode,
ListenPort = ListenPort,
StartSiteId = Config.StartSiteId,
EndSiteId = Config.EndSiteId,
IntervalMs = Config.IntervalMs,
CurrentSiteId = CurrentSiteId,
Node = Node,
State = State,
StateText = MagCarSimProtocol.StateText(State),
WaitingRelease = _waitingRelease,
HoldingForStop = _holdingForStop,
Looping = _looping,
Listening = Listening,
ClientCount = _server?.ClientCount ?? 0,
PathText = MapPathFinder.FormatPath(_loopSiteIds, 12)
};
}
}
public void Dispose()
{
lock (_syncRoot)
{
_looping = false;
}
StopListen();
}
private byte[] HandleFrame(byte[] request)
{
lock (_syncRoot)
{
if (!MagCarSimProtocol.TryParse(request, out var parsed))
{
Log("收到非法帧");
return BuildStatusUnlocked(MagCarSimProtocol.CmdQueryState);
}
if (parsed.VehicleCode != 0 && parsed.VehicleCode != VehicleCode)
{
Log($"帧车号 {parsed.VehicleCode} 与本车 {VehicleCode} 不一致,仍按本车应答");
}
if (_hostConfig.LogRawFrames)
{
Log($"RX {MagCarSimProtocol.CommandName(parsed.Command)} {MagCarSimProtocol.ToHex(request)}");
}
else if (parsed.Command != MagCarSimProtocol.CmdQueryState || _hostConfig.LogQueries)
{
Log($"RX {MagCarSimProtocol.CommandName(parsed.Command)}");
}
switch (parsed.Command)
{
case MagCarSimProtocol.CmdStart:
ApplyStartUnlocked("0x01");
break;
case MagCarSimProtocol.CmdStop:
_pausedByStopCommand = true;
State = MagCarSimProtocol.StateStopping;
Log("收到停止,暂停循环");
break;
case MagCarSimProtocol.CmdSpeed:
Speed = (byte)Math.Clamp((int)parsed.Data0 >> 8, 0, 100);
break;
case MagCarSimProtocol.CmdAngle:
Angle = parsed.Data0;
break;
case MagCarSimProtocol.CmdLegacyNode:
case MagCarSimProtocol.CmdTask:
Log($"忽略路径命令 {MagCarSimProtocol.CommandName(parsed.Command)}(本模拟器按 WinForm 循环跑)");
break;
}
return BuildStatusUnlocked(parsed.Command);
}
}
private void ApplyStartUnlocked(string source)
{
if (!_dispatched)
{
Log($"待命中忽略放行({source})");
return;
}
if (_waitingRelease)
{
_waitingRelease = false;
_pausedByStopCommand = false;
State = MagCarSimProtocol.StateRunning;
_nextHopAt = DateTime.Now.AddMilliseconds(IntervalOrDefault());
Log($"放行成功({source}),站点 {CurrentSiteId} 改为运行中,{IntervalOrDefault()}ms 后前往下一站");
return;
}
if (_pausedByStopCommand && _looping)
{
_pausedByStopCommand = false;
State = MagCarSimProtocol.StateRunning;
_nextHopAt = DateTime.Now.AddMilliseconds(IntervalOrDefault());
Log($"从暂停恢复({source})");
return;
}
if (_looping && State != MagCarSimProtocol.StateRunning)
{
State = MagCarSimProtocol.StateRunning;
_nextHopAt = DateTime.Now.AddMilliseconds(IntervalOrDefault());
Log($"启动运行({source})");
}
}
private void PlaceAtStartUnlocked()
{
CurrentSiteId = Config.StartSiteId;
Node = _map == null ? (ushort)Math.Clamp(Config.StartSiteId, 0, ushort.MaxValue) : _map.ResolveNode(Config.StartSiteId);
State = MagCarSimProtocol.StateStopping;
_waitingRelease = false;
_pausedByStopCommand = false;
}
private void ArriveUnlocked(int siteId, bool isStart)
{
CurrentSiteId = siteId;
Node = _map.ResolveNode(siteId);
var needStop = _map.NeedStop(siteId);
if (needStop)
{
State = MagCarSimProtocol.StateStopping;
_waitingRelease = true;
_nextHopAt = DateTime.MaxValue;
Log($"{(isStart ? "起点" : "到站")} {siteId} node={Node} Mag_NeedStop=true → 停止中,等待放行");
return;
}
State = MagCarSimProtocol.StateRunning;
_waitingRelease = false;
_nextHopAt = DateTime.Now.AddMilliseconds(IntervalOrDefault());
if (isStart)
{
Log($"起点 {siteId} node={Node} 过站,{IntervalOrDefault()}ms 后下一地标");
}
}
private void TryDispatchUnlocked(DateTime now, Dictionary occupancy)
{
if (now < _dispatchAt)
{
return;
}
if (_loopSiteIds.Count == 0)
{
return;
}
var startSite = _loopSiteIds[_index];
if (IsOccupiedUnlocked(startSite, occupancy))
{
HoldForOccupiedUnlocked(now, startSite, "发车");
return;
}
OccupyUnlocked(occupancy, startSite);
_dispatched = true;
_holdingForStop = false;
ArriveUnlocked(startSite, isStart: true);
}
private void AdvanceUnlocked(DateTime now, Dictionary occupancy)
{
if (_loopSiteIds.Count < 2)
{
return;
}
var nextIndex = (_index + 1) % _loopSiteIds.Count;
var nextSite = _loopSiteIds[nextIndex];
if (IsOccupiedUnlocked(nextSite, occupancy))
{
HoldForOccupiedUnlocked(now, nextSite, "进站");
return;
}
MoveOccupancyUnlocked(occupancy, CurrentSiteId, nextSite);
_holdingForStop = false;
_index = nextIndex;
ArriveUnlocked(nextSite, isStart: false);
}
private bool IsOccupiedUnlocked(int siteId, Dictionary occupancy)
{
if (_map == null || occupancy == null || !_map.SingleOccupancy(siteId))
{
return false;
}
return occupancy.TryGetValue(siteId, out var occupier) && occupier != VehicleCode;
}
private void OccupyUnlocked(Dictionary occupancy, int siteId)
{
if (occupancy == null || siteId <= 0 || _map == null || !_map.SingleOccupancy(siteId))
{
return;
}
occupancy[siteId] = VehicleCode;
}
private void MoveOccupancyUnlocked(Dictionary occupancy, int fromSiteId, int toSiteId)
{
if (occupancy == null)
{
return;
}
if (fromSiteId > 0 &&
occupancy.TryGetValue(fromSiteId, out var occupier) &&
occupier == VehicleCode)
{
occupancy.Remove(fromSiteId);
}
OccupyUnlocked(occupancy, toSiteId);
}
private void HoldForOccupiedUnlocked(DateTime now, int occupiedSiteId, string action)
{
_holdingForStop = true;
State = MagCarSimProtocol.StateRunning;
_waitingRelease = false;
_nextHopAt = now.AddMilliseconds(200);
if ((now - _lastHoldLogAt).TotalMilliseconds >= 5000)
{
_lastHoldLogAt = now;
Log($"停止/交管点 {occupiedSiteId} 已有车,{action}受阻,保持站点 {CurrentSiteId} 运行中");
}
}
private int IntervalOrDefault()
{
var value = Config.IntervalMs > 0 ? Config.IntervalMs : _hostConfig.DefaultIntervalMs;
return Math.Max(200, value);
}
private byte[] BuildStatusUnlocked(byte command)
{
return MagCarSimProtocol.BuildStatus(
command,
VehicleCode,
Node,
State,
Charge,
Current,
Voltage,
Speed,
Angle,
Task,
Lift,
Roll);
}
private void Log(string message)
{
MagCarSimLog.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] [{Name}/{VehicleCode}] {message}");
}
}
}