Files
StandardSence/StandardScene.Signal/Plc/PlcStationLogic.cs
T

217 lines
9.5 KiB
C#

using System;
using System.Linq;
using SimpleCore.Library;
using StandardScene.Signal.Model;
namespace StandardScene.Signal.Plc
{
/// <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.允许进入,
2 => SignalReadValue.允许离开,
_ => SignalReadValue.无状态
};
/// <summary>PLC 写地址回读字节 → 调度侧枚举。1~4 对应进入中/到位/离开中/已离开。</summary>
public static SignalWriteValue ConvertWrite(byte val) => val switch
{
1 => SignalWriteValue.进入中,
2 => SignalWriteValue.到位,
3 => SignalWriteValue.离开中,
4 => SignalWriteValue.已离开,
_ => SignalWriteValue.无状态
};
/// <summary>
/// 判断站点是否落在机构配置的包含列表里。
/// 列表支持逗号、分号、空格分隔,与 JSON 里 <c>JgInContains</c> / <c>JgContains</c> 一致。
/// </summary>
public static bool ContainsSite(string csv, int siteId)
{
if (string.IsNullOrWhiteSpace(csv) || siteId <= 0)
return false;
return csv.Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.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;
switch (rt.Config.LineType)
{
case SignalLineType.上线机构:
AutoLeaveUpLine(rt);
break;
case SignalLineType.单牵引下线机构:
AutoLeaveDownLine(rt, pin2: false);
break;
case SignalLineType.双牵引下线机构:
AutoLeaveDownLine(rt, pin2: true);
break;
case SignalLineType.等待通行机构:
AutoLeaveWaitSit(rt);
break;
}
}
/// <summary>
/// 离站完成:写字节清无状态、清空机构内车号。
/// 若仍挂着 RequestInAgvNo(上线途中 PLC 已报已离开),一并清掉以免卡死下一辆。
/// </summary>
private static void ClearLeft(PlcStationRuntime rt, bool fromReadback)
{
rt.WriteSignal = SignalWriteValue.无状态;
rt.InAgvNo = 0;
if (rt.RequestInAgvNo > 0)
{
Diagnosis.Log(
$"{rt.Config.JgName} {(fromReadback ? "回读已离开" : "写已离开成功")},清空请求进入车 {rt.RequestInAgvNo}",
"JGControl", true);
rt.RequestInAgvNo = 0;
}
else
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.已离开)
ClearLeft(rt, fromReadback: false);
if (rt.ReadWriteSignal == SignalWriteValue.已离开)
ClearLeft(rt, fromReadback: true);
if (rt.WriteSuccess && rt.WriteSignal == SignalWriteValue.进入中)
{
var car = SignalCarAdapter.Find(rt.RequestInAgvNo);
if (car == null) return;
if (!ContainsSite(rt.Config.JgInContains, SignalCarAdapter.PositionId(car)))
{
rt.WriteSignal = SignalWriteValue.到位;
rt.InAgvNo = car.id;
rt.RequestInAgvNo = 0;
Diagnosis.Log($"AGV{car.id} 已到达机构 {rt.Config.JgName}", "JGControl", true);
}
}
else if (rt.WriteSuccess && rt.WriteSignal == SignalWriteValue.到位)
{
var car = SignalCarAdapter.Find(rt.InAgvNo);
if (car == null || SignalCarAdapter.IsRunning(car))
rt.WriteSignal = SignalWriteValue.离开中;
}
else if (rt.WriteSuccess && rt.WriteSignal == SignalWriteValue.离开中)
{
var car = SignalCarAdapter.Find(rt.InAgvNo);
if (car == null || !ContainsSite(rt.Config.JgContains, SignalCarAdapter.PositionId(car)))
rt.WriteSignal = SignalWriteValue.已离开;
}
}
/// <summary>
/// 下线机构:车已在机构内(InAgvNo),等允许离开后升销钉、停充、启动,写离开中;
/// 车离开 JgContains 后写已离开。
/// <paramref name="pin2"/> 为 true 时双牵引,需销钉1+2都到位。
/// </summary>
private static void AutoLeaveDownLine(PlcStationRuntime rt, bool pin2)
{
if (rt.WriteSuccess && rt.WriteSignal == SignalWriteValue.已离开)
{
ClearLeft(rt, fromReadback: false);
return;
}
if (rt.ReadWriteSignal == SignalWriteValue.已离开)
ClearLeft(rt, fromReadback: true);
if (rt.InAgvNo == 0) return;
var car = SignalCarAdapter.Find(rt.InAgvNo);
if (car == null) return;
var pos = SignalCarAdapter.PositionId(car);
if (rt.ReadSignal == SignalReadValue.允许离开 && pos == rt.Config.JgPointId)
{
if (rt.WriteSignal == SignalWriteValue.离开中) return;
var pinOk = SignalCarAdapter.IsActionFinish(car, "SetPin1Up")
&& (!pin2 || SignalCarAdapter.IsActionFinish(car, "SetPin2Up"))
&& SignalCarAdapter.IsActionFinish(car, "StopCharge");
if (pinOk)
{
SignalCarAdapter.StopCharge(car);
SignalCarAdapter.Start(car);
rt.WriteSignal = SignalWriteValue.离开中;
Diagnosis.Log($"{rt.Config.JgName} 允许离开,通知 AGV{car.id} 启动,写离开中", "JGControl", true);
}
else
{
SignalCarAdapter.Control(car, "SetPin1Up");
if (pin2) SignalCarAdapter.Control(car, "SetPin2Up");
SignalCarAdapter.Control(car, "StopCharge");
}
}
else if (!string.IsNullOrEmpty(rt.Config.JgContains)
&& !ContainsSite(rt.Config.JgContains, pos)
&& rt.ReadWriteSignal != SignalWriteValue.已离开)
{
rt.WriteSignal = SignalWriteValue.已离开;
}
}
/// <summary>
/// 等待通行机构:无销钉,允许离开且停在 JgPointId 时停充并启动,写离开中;
/// 离开包含地标后写已离开。
/// </summary>
private static void AutoLeaveWaitSit(PlcStationRuntime rt)
{
if (rt.WriteSuccess && rt.WriteSignal == SignalWriteValue.已离开)
{
ClearLeft(rt, fromReadback: false);
return;
}
if (rt.ReadWriteSignal == SignalWriteValue.已离开)
ClearLeft(rt, fromReadback: true);
if (rt.InAgvNo == 0) return;
var car = SignalCarAdapter.Find(rt.InAgvNo);
if (car == null) return;
var pos = SignalCarAdapter.PositionId(car);
if (rt.ReadSignal == SignalReadValue.允许离开 && pos == rt.Config.JgPointId)
{
if (rt.WriteSignal == SignalWriteValue.离开中) return;
if (SignalCarAdapter.IsActionFinish(car, "StopCharge"))
{
SignalCarAdapter.Start(car);
rt.WriteSignal = SignalWriteValue.离开中;
Diagnosis.Log($"{rt.Config.JgName} 允许离开,通知 AGV{car.id} 启动,写离开中", "JGControl", true);
}
else
SignalCarAdapter.Control(car, "StopCharge");
}
else if (!string.IsNullOrEmpty(rt.Config.JgContains)
&& !ContainsSite(rt.Config.JgContains, pos)
&& rt.ReadWriteSignal != SignalWriteValue.已离开)
{
rt.WriteSignal = SignalWriteValue.已离开;
}
}
}
}