using System; using System.Linq; using SimpleCore.Library; using StandardScene.Signal.Model; namespace StandardScene.Signal.Plc { /// /// PLC 字节含义转换,以及按机构类型自动推进离站状态机。对齐反编译 PLCManager。 /// /// /// 由 每 200ms 在已持有 时调用。 /// 写字节含义见 :无状态 → 进入中 → 到位 → 离开中 → 已离开 → 清零。 /// 「已离开」可由本侧写成功,或 PLC 回读写值变成已离开(对方清过)触发清空。 /// public static class PlcStationLogic { /// PLC 读字节 → 调度侧枚举。仅 1=允许进入、2=允许离开,其余当无状态。 public static SignalReadValue ConvertRead(byte val) => val switch { 1 => SignalReadValue.允许进入, 2 => SignalReadValue.允许离开, _ => SignalReadValue.无状态 }; /// PLC 写地址回读字节 → 调度侧枚举。1~4 对应进入中/到位/离开中/已离开。 public static SignalWriteValue ConvertWrite(byte val) => val switch { 1 => SignalWriteValue.进入中, 2 => SignalWriteValue.到位, 3 => SignalWriteValue.离开中, 4 => SignalWriteValue.已离开, _ => SignalWriteValue.无状态 }; /// /// 判断站点是否落在机构配置的包含列表里。 /// 列表支持逗号、分号、空格分隔,与 JSON 里 JgInContains / JgContains 一致。 /// 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); } /// 分发四套离站逻辑;未知类型不处理。 public static void AutoLeave(PlcStationRuntime rt) { if (rt?.Station == null || rt.Dock == null) return; switch (rt.Station.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; } } /// /// 离站完成:写字节清无状态、清空机构内车号。 /// 若仍挂着 RequestInAgvNo(上线途中 PLC 已报已离开),一并清掉以免卡死下一辆。 /// private static void ClearLeft(PlcStationRuntime rt, bool fromReadback) { rt.WriteSignal = SignalWriteValue.无状态; rt.InAgvNo = 0; if (rt.RequestInAgvNo > 0) { Diagnosis.Log( $"{rt.DisplayName} {(fromReadback ? "回读已离开" : "写已离开成功")},清空请求进入车 {rt.RequestInAgvNo}", "JGControl", true); rt.RequestInAgvNo = 0; } else Diagnosis.Log($"{rt.DisplayName} 已离开,清空信号", "JGControl", true); } /// /// 上线机构:进站过程由扫车 RequestIn/Arrived 启动,离站由本方法跟车位推进。 /// 进入中且车已离开 JgInContains → 视为到位; /// 到位且车在跑 → 离开中; /// 离开中且车已不在 JgContains → 已离开。 /// 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.Dock.JgInContains, SignalCarAdapter.PositionId(car))) { rt.WriteSignal = SignalWriteValue.到位; rt.InAgvNo = car.id; rt.RequestInAgvNo = 0; Diagnosis.Log($"AGV{car.id} 已到达机构 {rt.DisplayName}", "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.Dock.JgContains, SignalCarAdapter.PositionId(car))) rt.WriteSignal = SignalWriteValue.已离开; } } /// /// 下线机构:车已在机构内(InAgvNo),等允许离开后升销钉、停充、启动,写离开中; /// 车离开 JgContains 后写已离开。 /// 为 true 时双牵引,需销钉1+2都到位。 /// 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.Dock.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.DisplayName} 允许离开,通知 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.Dock.JgContains) && !ContainsSite(rt.Dock.JgContains, pos) && rt.ReadWriteSignal != SignalWriteValue.已离开) { rt.WriteSignal = SignalWriteValue.已离开; } } /// /// 等待通行机构:无销钉,允许离开且停在 JgPointId 时停充并启动,写离开中; /// 离开包含地标后写已离开。 /// 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.Dock.JgPointId) { if (rt.WriteSignal == SignalWriteValue.离开中) return; if (SignalCarAdapter.IsActionFinish(car, "StopCharge")) { SignalCarAdapter.Start(car); rt.WriteSignal = SignalWriteValue.离开中; Diagnosis.Log($"{rt.DisplayName} 允许离开,通知 AGV{car.id} 启动,写离开中", "JGControl", true); } else SignalCarAdapter.Control(car, "StopCharge"); } else if (!string.IsNullOrEmpty(rt.Dock.JgContains) && !ContainsSite(rt.Dock.JgContains, pos) && rt.ReadWriteSignal != SignalWriteValue.已离开) { rt.WriteSignal = SignalWriteValue.已离开; } } } }