新增信号交互进程插件
This commit is contained in:
@@ -5,9 +5,17 @@ using StandardScene.Signal.Model;
|
||||
|
||||
namespace StandardScene.Signal.Plc
|
||||
{
|
||||
/// <summary>读写字节转换与四套自动离站,对齐反编译 PLCManager。</summary>
|
||||
/// <summary>
|
||||
/// PLC 字节含义转换,以及按机构类型自动推进离站状态机。对齐反编译 <c>PLCManager</c>。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 由 <see cref="PlcStationWorker"/> 每 200ms 在已持有 <see cref="PlcStationRuntime.Sync"/> 时调用。
|
||||
/// 写字节含义见 <see cref="SignalWriteValue"/>:无状态 → 进入中 → 到位 → 离开中 → 已离开 → 清零。
|
||||
/// 「已离开」可由本侧写成功,或 PLC 回读写值变成已离开(对方清过)触发清空。
|
||||
/// </remarks>
|
||||
public static class PlcStationLogic
|
||||
{
|
||||
/// <summary>PLC 读字节 → 调度侧枚举。仅 1=允许进入、2=允许离开,其余当无状态。</summary>
|
||||
public static SignalReadValue ConvertRead(byte val) => val switch
|
||||
{
|
||||
1 => SignalReadValue.允许进入,
|
||||
@@ -15,6 +23,7 @@ namespace StandardScene.Signal.Plc
|
||||
_ => SignalReadValue.无状态
|
||||
};
|
||||
|
||||
/// <summary>PLC 写地址回读字节 → 调度侧枚举。1~4 对应进入中/到位/离开中/已离开。</summary>
|
||||
public static SignalWriteValue ConvertWrite(byte val) => val switch
|
||||
{
|
||||
1 => SignalWriteValue.进入中,
|
||||
@@ -24,6 +33,10 @@ namespace StandardScene.Signal.Plc
|
||||
_ => SignalWriteValue.无状态
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 判断站点是否落在机构配置的包含列表里。
|
||||
/// 列表支持逗号、分号、空格分隔,与 JSON 里 <c>JgInContains</c> / <c>JgContains</c> 一致。
|
||||
/// </summary>
|
||||
public static bool ContainsSite(string csv, int siteId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(csv) || siteId <= 0)
|
||||
@@ -32,6 +45,7 @@ namespace StandardScene.Signal.Plc
|
||||
.Any(s => int.TryParse(s.Trim(), out var n) && n == siteId);
|
||||
}
|
||||
|
||||
/// <summary>按 <see cref="PlcStationModel.LineType"/> 分发四套离站逻辑;未知类型不处理。</summary>
|
||||
public static void AutoLeave(PlcStationRuntime rt)
|
||||
{
|
||||
if (rt?.Config == null) return;
|
||||
@@ -52,6 +66,10 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 离站完成:写字节清无状态、清空机构内车号。
|
||||
/// 若仍挂着 RequestInAgvNo(上线途中 PLC 已报已离开),一并清掉以免卡死下一辆。
|
||||
/// </summary>
|
||||
private static void ClearLeft(PlcStationRuntime rt, bool fromReadback)
|
||||
{
|
||||
rt.WriteSignal = SignalWriteValue.无状态;
|
||||
@@ -67,6 +85,12 @@ namespace StandardScene.Signal.Plc
|
||||
Diagnosis.Log($"{rt.Config.JgName} 已离开,清空信号", "JGControl", true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上线机构:进站过程由扫车 RequestIn/Arrived 启动,离站由本方法跟车位推进。
|
||||
/// 进入中且车已离开 JgInContains → 视为到位;
|
||||
/// 到位且车在跑 → 离开中;
|
||||
/// 离开中且车已不在 JgContains → 已离开。
|
||||
/// </summary>
|
||||
private static void AutoLeaveUpLine(PlcStationRuntime rt)
|
||||
{
|
||||
if (rt.WriteSuccess && rt.WriteSignal == SignalWriteValue.已离开)
|
||||
@@ -100,6 +124,11 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下线机构:车已在机构内(InAgvNo),等允许离开后升销钉、停充、启动,写离开中;
|
||||
/// 车离开 JgContains 后写已离开。
|
||||
/// <paramref name="pin2"/> 为 true 时双牵引,需销钉1+2都到位。
|
||||
/// </summary>
|
||||
private static void AutoLeaveDownLine(PlcStationRuntime rt, bool pin2)
|
||||
{
|
||||
if (rt.WriteSuccess && rt.WriteSignal == SignalWriteValue.已离开)
|
||||
@@ -144,6 +173,10 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待通行机构:无销钉,允许离开且停在 JgPointId 时停充并启动,写离开中;
|
||||
/// 离开包含地标后写已离开。
|
||||
/// </summary>
|
||||
private static void AutoLeaveWaitSit(PlcStationRuntime rt)
|
||||
{
|
||||
if (rt.WriteSuccess && rt.WriteSignal == SignalWriteValue.已离开)
|
||||
|
||||
@@ -4,7 +4,14 @@ using StandardScene.Signal.Model;
|
||||
|
||||
namespace StandardScene.Signal.Plc
|
||||
{
|
||||
/// <summary>机构运行态。写信号赋值会清 WriteSuccess,驱动 Worker 重写 PLC。</summary>
|
||||
/// <summary>
|
||||
/// 单个机构的运行态(不进 JSON)。扫车线程改写目标写字节,PLC Worker 负责真正下发。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 赋值 <see cref="WriteSignal"/> 会把 <see cref="WriteSuccess"/> 置 false,下一拍 Worker 发现
|
||||
/// 目标与回读不一致就会重写 PLC。回读与目标一致后再标成功,AutoLeave 才继续往下一状态走。
|
||||
/// 所有读写应在 <see cref="Sync"/> 内进行。
|
||||
/// </remarks>
|
||||
public sealed class PlcStationRuntime
|
||||
{
|
||||
private SignalWriteValue _writeSignal;
|
||||
@@ -16,11 +23,13 @@ namespace StandardScene.Signal.Plc
|
||||
Config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
/// <summary>本机构静态配置(IP、DB 地址、地标、机构类型)。</summary>
|
||||
public PlcStationModel Config { get; }
|
||||
|
||||
/// <summary>扫车线程与 PLC Worker 共用运行态,写入时加此锁。</summary>
|
||||
public object Sync { get; } = new object();
|
||||
|
||||
/// <summary>PLC→调度:允许进入 / 允许离开 / 无状态。变化时打诊断。</summary>
|
||||
public SignalReadValue ReadSignal
|
||||
{
|
||||
get => _readSignal;
|
||||
@@ -32,6 +41,9 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调度→PLC 的目标写字节。赋值即视为尚未写成功,驱动 Worker 重写。
|
||||
/// </summary>
|
||||
public SignalWriteValue WriteSignal
|
||||
{
|
||||
get => _writeSignal;
|
||||
@@ -44,6 +56,7 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>从写地址回读到的当前值,用来确认 PLC 是否已吃进目标字节。</summary>
|
||||
public SignalWriteValue ReadWriteSignal
|
||||
{
|
||||
get => _readWriteSignal;
|
||||
@@ -55,13 +68,25 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>已报到在机构内的 AGV 号;0 表示没有。Arrived / 上线自动到位 时写入。</summary>
|
||||
public int InAgvNo { get; set; }
|
||||
|
||||
/// <summary>已获准进入、正在往机构开的 AGV 号;到位或离站清空后为 0。</summary>
|
||||
public int RequestInAgvNo { get; set; }
|
||||
|
||||
/// <summary>最近一拍 PLC 读写是否成功。心跳或读写失败会立刻变 false。</summary>
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
/// <summary>目标写字节是否已与 PLC 回读一致。改 WriteSignal 时自动清掉。</summary>
|
||||
public bool WriteSuccess { get; set; }
|
||||
|
||||
/// <summary>内存侧心跳翻转标志,供监视面板看到 Worker 在跑;真正写 PLC 的是 HeartDb 位翻转。</summary>
|
||||
public bool Heart { get; set; }
|
||||
|
||||
/// <summary>最近一次断线或周期异常原因,连接正常时清空。</summary>
|
||||
public string LastError { get; set; } = "";
|
||||
|
||||
/// <summary>拷贝一份给 MissionStatus / 监视列表,避免 UI 直接绑运行态。</summary>
|
||||
public PlcStationSnapshot ToSnapshot() => new PlcStationSnapshot
|
||||
{
|
||||
JgName = Config.JgName,
|
||||
|
||||
@@ -9,6 +9,15 @@ namespace StandardScene.Signal.Plc
|
||||
/// 单机构 200ms 循环:心跳、读允许进出、回读写值、按需写入、自动离站。
|
||||
/// 对齐反编译 <c>PLCManager.ExcuteThreadMethod</c>。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 一拍顺序固定,失败则本拍标断线并 return,避免用脏数据推进状态机:
|
||||
/// 1. 翻转内存心跳标志;若配置了 HeartDb,读当前位再写反码(PLC 侧看翻转判断调度活着)。
|
||||
/// 2. 读 ReadDb → <see cref="PlcStationRuntime.ReadSignal"/>。
|
||||
/// 3. 回读 WriteDb → ReadWriteSignal;与目标 WriteSignal 一致则 WriteSuccess=true。
|
||||
/// 4. 目标与回读不一致且尚未成功时写入目标字节。
|
||||
/// 5. <see cref="PlcStationLogic.AutoLeave"/>。
|
||||
/// 读写均在 <see cref="PlcStationRuntime.Sync"/> 内,与扫车 Handler 互斥。
|
||||
/// </remarks>
|
||||
public sealed class PlcStationWorker : IDisposable
|
||||
{
|
||||
private readonly SiemensPlcSession _session;
|
||||
@@ -16,6 +25,8 @@ namespace StandardScene.Signal.Plc
|
||||
private readonly Action _onTick;
|
||||
private CancellationTokenSource _cts;
|
||||
private Thread _thread;
|
||||
|
||||
/// <summary>断线日志节流起点,避免 200ms 打满诊断。</summary>
|
||||
private DateTime _lastFailLog = DateTime.MinValue;
|
||||
|
||||
public PlcStationWorker(PlcStationRuntime runtime, SiemensPlcSession session, int pollMs, Action onTick = null)
|
||||
@@ -26,8 +37,10 @@ namespace StandardScene.Signal.Plc
|
||||
_onTick = onTick;
|
||||
}
|
||||
|
||||
/// <summary>本机构运行态,扫车线程按 JgName 查找后加 Sync 读写。</summary>
|
||||
public PlcStationRuntime Runtime { get; }
|
||||
|
||||
/// <summary>启动后台线程。重复 Start 若仍存活则忽略。</summary>
|
||||
public void Start()
|
||||
{
|
||||
if (_thread != null && _thread.IsAlive) return;
|
||||
@@ -41,6 +54,7 @@ namespace StandardScene.Signal.Plc
|
||||
Diagnosis.Log($"{Runtime.Config.JgName} Worker 已启动 → {Runtime.Config.Ip}:{Runtime.Config.Port}", "PLC", true);
|
||||
}
|
||||
|
||||
/// <summary>取消循环、最多等 2s、关闭 S7 连接并标断线。</summary>
|
||||
public void Stop()
|
||||
{
|
||||
try { _cts?.Cancel(); }
|
||||
@@ -80,6 +94,7 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>单拍 PLC 交互。心跳地址为空则跳过心跳;读写地址为空则跳过对应步骤。</summary>
|
||||
internal void Tick()
|
||||
{
|
||||
var cfg = Runtime.Config;
|
||||
@@ -145,6 +160,7 @@ namespace StandardScene.Signal.Plc
|
||||
LogFail($"{Runtime.Config.JgName} {reason}");
|
||||
}
|
||||
|
||||
/// <summary>同一机构失败日志最少间隔 5 秒。</summary>
|
||||
private void LogFail(string msg)
|
||||
{
|
||||
if ((DateTime.Now - _lastFailLog).TotalSeconds < 5) return;
|
||||
|
||||
@@ -5,9 +5,17 @@ using IoTClient.Common.Enums;
|
||||
|
||||
namespace StandardScene.Signal.Plc
|
||||
{
|
||||
/// <summary>西门子 S7 长连接。地址兼容 Hsl 的 DB1.0 与 IoTClient 的 DB1.DBB0。</summary>
|
||||
/// <summary>
|
||||
/// 西门子 S7 长连接封装(IoTClient)。一个机构一个实例,由对应 Worker 独占。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 字节地址兼容老 Hsl 写法 <c>DB1.0</c> 与 IoTClient 写法 <c>DB1.DBB0</c>;
|
||||
/// 心跳位地址保持原样(如 <c>DB1.DBX2.0</c>),不做 DBB 转换。
|
||||
/// 连接失败时读/写返回 false,由 Worker 标断线,不在这里抛给扫车线程。
|
||||
/// </remarks>
|
||||
public sealed class SiemensPlcSession : IDisposable
|
||||
{
|
||||
/// <summary>匹配 Hsl 风格「DB块号.字节偏移」,捕获组 1=DB,2=偏移。</summary>
|
||||
private static readonly Regex BareDbByte = new Regex(@"^DB(\d+)\.(\d+)$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private readonly SiemensVersion _version;
|
||||
@@ -17,6 +25,10 @@ namespace StandardScene.Signal.Plc
|
||||
private SiemensClient _client;
|
||||
private readonly object _sync = new object();
|
||||
|
||||
/// <param name="version">进程字段 PlcVersion,如 S7_1500。</param>
|
||||
/// <param name="ip">机构 IP。</param>
|
||||
/// <param name="port">S7 端口,通常 102。</param>
|
||||
/// <param name="slot">机架槽号,S7-1500 常见 1。</param>
|
||||
public SiemensPlcSession(string version, string ip, int port, int slot)
|
||||
{
|
||||
_version = ParseVersion(version);
|
||||
@@ -25,6 +37,7 @@ namespace StandardScene.Signal.Plc
|
||||
_slot = (byte)Math.Max(0, slot);
|
||||
}
|
||||
|
||||
/// <summary>当前客户端是否已 Open 成功。未创建客户端时为 false。</summary>
|
||||
public bool Connected
|
||||
{
|
||||
get
|
||||
@@ -33,6 +46,7 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>惰性创建客户端并 Open。已连接则直接 true;失败返回 false 不抛。</summary>
|
||||
public bool EnsureOpen()
|
||||
{
|
||||
lock (_sync)
|
||||
@@ -53,6 +67,7 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>读一字节。地址会先规范化;未连接或失败时 value=0 且返回 false。</summary>
|
||||
public bool TryReadByte(string address, out byte value)
|
||||
{
|
||||
value = 0;
|
||||
@@ -72,6 +87,7 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>写一字节(进入中/到位等)。失败返回 false。</summary>
|
||||
public bool TryWriteByte(string address, byte value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(address) || !EnsureOpen())
|
||||
@@ -87,6 +103,7 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>读心跳位。地址不做 DBB 转换。</summary>
|
||||
public bool TryReadBool(string address, out bool value)
|
||||
{
|
||||
value = false;
|
||||
@@ -106,6 +123,7 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>写心跳位(通常写当前值的反码,让 PLC 看到翻转)。</summary>
|
||||
public bool TryWriteBool(string address, bool value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(address) || !EnsureOpen())
|
||||
@@ -121,6 +139,7 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>关闭并丢弃客户端,下次 EnsureOpen 会新建。</summary>
|
||||
public void Close()
|
||||
{
|
||||
lock (_sync)
|
||||
@@ -133,6 +152,9 @@ namespace StandardScene.Signal.Plc
|
||||
|
||||
public void Dispose() => Close();
|
||||
|
||||
/// <summary>
|
||||
/// <c>DB1.0</c> → <c>DB1.DBB0</c>;已是 DBB/DBW/DBX 的地址原样返回。
|
||||
/// </summary>
|
||||
internal static string NormalizeByteAddress(string address)
|
||||
{
|
||||
var addr = address.Trim();
|
||||
@@ -140,6 +162,7 @@ namespace StandardScene.Signal.Plc
|
||||
return m.Success ? $"DB{m.Groups[1].Value}.DBB{m.Groups[2].Value}" : addr;
|
||||
}
|
||||
|
||||
/// <summary>空或无法解析时默认 S7_1500。允许把 S7-1500 写成带连字符。</summary>
|
||||
internal static SiemensVersion ParseVersion(string version)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(version))
|
||||
|
||||
@@ -12,11 +12,16 @@ using StandardScene.Signal.Site;
|
||||
namespace StandardScene.Signal.Plc
|
||||
{
|
||||
/// <summary>
|
||||
/// 不引用 Magnetic.dll:按方法名反射 Start/Stop/牵引棒。
|
||||
/// MagCar / Mag2Car 有对应 MethodMember 时生效;没有则视为动作已完成。
|
||||
/// 信号插件访问 AGV / 站点的适配层。不引用 Magnetic.dll,按方法名反射启停和牵引棒。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// MagCar / Mag2Car 上有对应 MethodMember(StartMag2Car、SetPin1Up 等)时生效;
|
||||
/// 没有 <c>IsActionFinish</c> 则视为动作已完成,避免非磁导航车卡在销钉等待。
|
||||
/// 启停回退顺序:StartMag2Car / StartMagCar → SendStart(0)。
|
||||
/// </remarks>
|
||||
public static class SignalCarAdapter
|
||||
{
|
||||
/// <summary>当前内核里的全部车;SimpleLib 异常时返回空,避免扫车线程崩掉。</summary>
|
||||
public static IEnumerable<Car> All()
|
||||
{
|
||||
try
|
||||
@@ -29,12 +34,17 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>按车号取车;id≤0 返回 null。</summary>
|
||||
public static Car Find(int id)
|
||||
{
|
||||
if (id <= 0) return null;
|
||||
return SimpleLib.GetCar(id) as Car;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前站点:优先 <c>siteID</c>,否则 <c>GetLastSite()</c>。
|
||||
/// 握手 / 放行 / 限流都用这个号去对 JSON 里的 Sit。
|
||||
/// </summary>
|
||||
public static int PositionId(Car car)
|
||||
{
|
||||
if (car == null) return 0;
|
||||
@@ -50,6 +60,7 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>读车 status.enums["State"],没有则空串。</summary>
|
||||
public static string GetState(Car car)
|
||||
{
|
||||
if (car?.status?.enums != null &&
|
||||
@@ -59,6 +70,7 @@ namespace StandardScene.Signal.Plc
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <summary>State 为「运行中」或 Running。</summary>
|
||||
public static bool IsMoving(Car car)
|
||||
{
|
||||
var state = GetState(car);
|
||||
@@ -66,7 +78,10 @@ namespace StandardScene.Signal.Plc
|
||||
string.Equals(state, "Running", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>对齐反编译 RunStatus==停止:非运行中即视为停稳,含充电中。</summary>
|
||||
/// <summary>
|
||||
/// 对齐反编译 <c>RunStatus==停止</c>:非运行中即视为可握手,含充电中 / 停止中 / WaitPass。
|
||||
/// 充电点必须把「充电中」算停稳,否则 TimedCharging 不会进 Handler。
|
||||
/// </summary>
|
||||
public static bool IsStopped(Car car)
|
||||
{
|
||||
if (car == null) return false;
|
||||
@@ -80,6 +95,7 @@ namespace StandardScene.Signal.Plc
|
||||
return !IsRunning(car);
|
||||
}
|
||||
|
||||
/// <summary>正在充电(中英文 State)。</summary>
|
||||
public static bool IsCharging(Car car)
|
||||
{
|
||||
var state = GetState(car);
|
||||
@@ -87,6 +103,10 @@ namespace StandardScene.Signal.Plc
|
||||
string.Equals(state, "Charging", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 车仍占资源:运行中,或有 pendingLocks,或 tags 含 occupied。
|
||||
/// 上线机构「到位后开始离开」用这个判断,比单纯看 State 更早切到离开中。
|
||||
/// </summary>
|
||||
public static bool IsRunning(Car car)
|
||||
{
|
||||
if (car == null) return false;
|
||||
@@ -98,6 +118,7 @@ namespace StandardScene.Signal.Plc
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>电量百分比,读 enums["Soc"];解析失败当 0。</summary>
|
||||
public static double Soc(Car car)
|
||||
{
|
||||
if (car?.status?.enums != null &&
|
||||
@@ -107,6 +128,7 @@ namespace StandardScene.Signal.Plc
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>限流用的车组:fields["group"] 或 ["GroupID"]。</summary>
|
||||
public static string GroupId(Car car)
|
||||
{
|
||||
if (car?.fields == null) return "";
|
||||
@@ -117,6 +139,7 @@ namespace StandardScene.Signal.Plc
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <summary>当前停在给定站点集合上的全部车(按 PositionId)。</summary>
|
||||
public static IReadOnlyList<Car> CarsAt(IEnumerable<int> siteIds)
|
||||
{
|
||||
var set = new HashSet<int>(siteIds ?? Array.Empty<int>());
|
||||
@@ -125,12 +148,14 @@ namespace StandardScene.Signal.Plc
|
||||
return All().Where(c => set.Contains(PositionId(c))).ToList();
|
||||
}
|
||||
|
||||
/// <summary>当前停在单个站点上的车。下线 RequestIn 用 JgPointId 查机构内是否已有车。</summary>
|
||||
public static IReadOnlyList<Car> CarsAt(int siteId)
|
||||
{
|
||||
if (siteId <= 0) return Array.Empty<Car>();
|
||||
return CarsAt(new[] { siteId });
|
||||
}
|
||||
|
||||
/// <summary>读站点 fields 指定键。站点不存在或值为空返回 false。</summary>
|
||||
public static bool TryReadSiteField(int siteId, string key, out string value)
|
||||
{
|
||||
value = "";
|
||||
@@ -152,6 +177,10 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 地图点是否自带 SignalStop(SignalType 字段存在且非空)。
|
||||
/// SignalParams 可缺省。有类型则扫车不再查 handshake-points.json。
|
||||
/// </summary>
|
||||
public static bool TryReadSignalStop(int siteId, out string signalType, out string signalParams)
|
||||
{
|
||||
signalType = "";
|
||||
@@ -162,6 +191,10 @@ namespace StandardScene.Signal.Plc
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 已充电秒数。优先反射 GetChargeTime(int/double/TimeSpan);
|
||||
/// 没有则从首次看到充电中的时刻算,离开充电态时清字典。
|
||||
/// </summary>
|
||||
public static int ChargeSeconds(Car car, IDictionary<int, DateTime> startedAt)
|
||||
{
|
||||
if (car == null) return 0;
|
||||
@@ -191,6 +224,7 @@ namespace StandardScene.Signal.Plc
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>放行:Mag2 → Mag → SendStart(0)。</summary>
|
||||
public static void Start(Car car)
|
||||
{
|
||||
if (car == null) return;
|
||||
@@ -199,6 +233,7 @@ namespace StandardScene.Signal.Plc
|
||||
TryInvoke(car, "SendStart", new object[] { (ushort)0 });
|
||||
}
|
||||
|
||||
/// <summary>急停:Mag2 → Mag → SendStop(0)。机构未允许进入时调用。</summary>
|
||||
public static void Stop(Car car)
|
||||
{
|
||||
if (car == null) return;
|
||||
@@ -207,12 +242,17 @@ namespace StandardScene.Signal.Plc
|
||||
TryInvoke(car, "SendStop", new object[] { (ushort)0 });
|
||||
}
|
||||
|
||||
/// <summary>停充。握手放行前通常先停充再启动。</summary>
|
||||
public static void StopCharge(Car car)
|
||||
{
|
||||
if (car == null) return;
|
||||
TryInvoke(car, "StopCharge");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 销钉/停充等动作是否完成。车类型没有 IsActionFinish 时返回 true(非磁导航不阻塞)。
|
||||
/// 反射异常也当完成,避免把产线卡死。
|
||||
/// </summary>
|
||||
public static bool IsActionFinish(Car car, string action)
|
||||
{
|
||||
if (car == null) return false;
|
||||
@@ -231,6 +271,7 @@ namespace StandardScene.Signal.Plc
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>下发动作:先按动作名直接调 MethodMember,没有再 ImmediateCommand(action)。</summary>
|
||||
public static void Control(Car car, string action)
|
||||
{
|
||||
if (car == null || string.IsNullOrWhiteSpace(action)) return;
|
||||
@@ -238,6 +279,10 @@ namespace StandardScene.Signal.Plc
|
||||
TryInvoke(car, "ImmediateCommand", new object[] { action });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按名称找实例方法。无参与带参分开匹配,重载参数个数不对就跳过。
|
||||
/// 调用抛错返回 false,让上层试下一个名字。
|
||||
/// </summary>
|
||||
private static bool TryInvoke(object target, string name, object[] args = null)
|
||||
{
|
||||
var type = target.GetType();
|
||||
|
||||
Reference in New Issue
Block a user