using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using SimpleCore; using SimpleCore.PropType; using SimpleLite.RCS; using SimpleLite.RCS.CarTypes; using StandardScene.Signal.Site; namespace StandardScene.Signal.Plc { /// /// 信号插件访问 AGV / 站点的适配层。不引用 Magnetic.dll,按方法名反射启停和牵引棒。 /// /// /// MagCar / Mag2Car 上有对应 MethodMember(StartMag2Car、SetPin1Up 等)时生效; /// 没有 IsActionFinish 则视为动作已完成,避免非磁导航车卡在销钉等待。 /// 启停回退顺序:StartMag2Car / StartMagCar → SendStart(0)。 /// public static class SignalCarAdapter { /// 当前内核里的全部车;SimpleLib 异常时返回空,避免扫车线程崩掉。 public static IEnumerable All() { try { return SimpleLib.GetAllCars().OfType(); } catch { return Array.Empty(); } } /// 按车号取车;id≤0 返回 null。 public static Car Find(int id) { if (id <= 0) return null; return SimpleLib.GetCar(id) as Car; } /// /// 当前站点:优先 siteID,否则 GetLastSite()。 /// 握手 / 放行 / 限流都用这个号去对 JSON 里的 Sit。 /// public static int PositionId(Car car) { if (car == null) return 0; try { if (car.siteID > 0) return car.siteID; var last = car.GetLastSite(); return last > 0 ? last : 0; } catch { return 0; } } /// 读车 status.enums["State"],没有则空串。 public static string GetState(Car car) { if (car?.status?.enums != null && car.status.enums.TryGetValue("State", out var state) && !string.IsNullOrWhiteSpace(state)) return state; return ""; } /// State 为「运行中」或 Running。 public static bool IsMoving(Car car) { var state = GetState(car); return state == "运行中" || string.Equals(state, "Running", StringComparison.OrdinalIgnoreCase); } /// /// 对齐反编译 RunStatus==停止:非运行中即视为可握手,含充电中 / 停止中 / WaitPass。 /// 充电点必须把「充电中」算停稳,否则 TimedCharging 不会进 Handler。 /// public static bool IsStopped(Car car) { if (car == null) return false; if (IsMoving(car)) return false; var state = GetState(car); if (state == "停止中" || state == "充电中" || string.Equals(state, "Stopping", StringComparison.OrdinalIgnoreCase) || string.Equals(state, "Charging", StringComparison.OrdinalIgnoreCase) || string.Equals(state, "WaitPass", StringComparison.OrdinalIgnoreCase)) return true; return !IsRunning(car); } /// 正在充电(中英文 State)。 public static bool IsCharging(Car car) { var state = GetState(car); return state == "充电中" || string.Equals(state, "Charging", StringComparison.OrdinalIgnoreCase); } /// /// 车仍占资源:运行中,或有 pendingLocks,或 tags 含 occupied。 /// 上线机构「到位后开始离开」用这个判断,比单纯看 State 更早切到离开中。 /// public static bool IsRunning(Car car) { if (car == null) return false; if (IsMoving(car)) return true; if (car.status?.pendingLocks != null && car.status.pendingLocks.Length > 0) return true; if (car.tags != null && car.tags.Contains("occupied")) return true; return false; } /// 电量百分比,读 enums["Soc"];解析失败当 0。 public static double Soc(Car car) { if (car?.status?.enums != null && car.status.enums.TryGetValue("Soc", out var soc) && double.TryParse(soc, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) return value; return 0; } /// 限流用的车组:fields["group"] 或 ["GroupID"]。 public static string GroupId(Car car) { if (car?.fields == null) return ""; if (car.fields.TryGetValue("group", out var g) && !string.IsNullOrWhiteSpace(g)) return g; if (car.fields.TryGetValue("GroupID", out var g2) && !string.IsNullOrWhiteSpace(g2)) return g2; return ""; } /// 当前停在给定站点集合上的全部车(按 PositionId)。 public static IReadOnlyList CarsAt(IEnumerable siteIds) { var set = new HashSet(siteIds ?? Array.Empty()); if (set.Count == 0) return Array.Empty(); return All().Where(c => set.Contains(PositionId(c))).ToList(); } /// 当前停在单个站点上的车。下线 RequestIn 用 JgPointId 查机构内是否已有车。 public static IReadOnlyList CarsAt(int siteId) { if (siteId <= 0) return Array.Empty(); return CarsAt(new[] { siteId }); } /// 读站点 fields 指定键。站点不存在或值为空返回 false。 public static bool TryReadSiteField(int siteId, string key, out string value) { value = ""; if (siteId <= 0 || string.IsNullOrWhiteSpace(key)) return false; try { var site = SimpleLib.GetSite(siteId); if (site?.fields == null) return false; if (!site.fields.TryGetValue(key, out var raw) || string.IsNullOrWhiteSpace(raw)) return false; value = raw; return true; } catch { return false; } } /// /// 地图点是否自带 SignalStop(SignalType 字段存在且非空)。 /// SignalParams 可缺省。有类型则扫车不再查 handshake-points.json。 /// public static bool TryReadSignalStop(int siteId, out string signalType, out string signalParams) { signalType = ""; signalParams = ""; if (!TryReadSiteField(siteId, SignalSiteFields.SignalType, out signalType)) return false; TryReadSiteField(siteId, SignalSiteFields.SignalParams, out signalParams); return true; } /// /// 已充电秒数。优先反射 GetChargeTime(int/double/TimeSpan); /// 没有则从首次看到充电中的时刻算,离开充电态时清字典。 /// public static int ChargeSeconds(Car car, IDictionary startedAt) { if (car == null) return 0; var mi = car.GetType().GetMethod("GetChargeTime", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (mi != null) { try { var r = mi.Invoke(car, null); if (r is int i) return i; if (r is double d) return (int)d; if (r is TimeSpan ts) return (int)ts.TotalSeconds; } catch { } } if (IsCharging(car)) { if (!startedAt.ContainsKey(car.id)) startedAt[car.id] = DateTime.Now; return (int)(DateTime.Now - startedAt[car.id]).TotalSeconds; } startedAt.Remove(car.id); return 0; } /// 类型名恰好为 MagCar(不含 Mag2Car)。磁条交管只放行这种车。 public static bool IsMagCar(Car car) { if (car == null) return false; return string.Equals(car.GetType().Name, "MagCar", StringComparison.Ordinal); } /// 放行:Mag2 → Mag → SendStart(0)。 public static void Start(Car car) { if (car == null) return; if (TryInvoke(car, "StartMag2Car") || TryInvoke(car, "StartMagCar")) return; TryInvoke(car, "SendStart", new object[] { (ushort)0 }); } /// 仅 MagCar 发 FASS 1.0 启动(0x01)。不调 StartMag2Car。 public static void StartMagCar(Car car) { if (!IsMagCar(car)) return; if (TryInvoke(car, "StartMagCar")) return; TryInvoke(car, "SendStart", new object[] { (ushort)0 }); } /// 急停:Mag2 → Mag → SendStop(0)。机构未允许进入时调用。 public static void Stop(Car car) { if (car == null) return; if (TryInvoke(car, "StopMag2Car") || TryInvoke(car, "StopMagCar")) return; TryInvoke(car, "SendStop", new object[] { (ushort)0 }); } /// 停充。握手放行前通常先停充再启动。 public static void StopCharge(Car car) { if (car == null) return; TryInvoke(car, "StopCharge"); } /// /// 销钉/停充等动作是否完成。车类型没有 IsActionFinish 时返回 true(非磁导航不阻塞)。 /// 反射异常也当完成,避免把产线卡死。 /// public static bool IsActionFinish(Car car, string action) { if (car == null) return false; if (string.IsNullOrWhiteSpace(action)) return true; var mi = car.GetType().GetMethod("IsActionFinish", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (mi == null) return true; try { var r = mi.Invoke(car, new object[] { action }); return r is true; } catch { return true; } } /// 下发动作:先按动作名直接调 MethodMember,没有再 ImmediateCommand(action)。 public static void Control(Car car, string action) { if (car == null || string.IsNullOrWhiteSpace(action)) return; if (TryInvoke(car, action)) return; TryInvoke(car, "ImmediateCommand", new object[] { action }); } /// /// 按名称找实例方法。无参与带参分开匹配,重载参数个数不对就跳过。 /// 调用抛错返回 false,让上层试下一个名字。 /// private static bool TryInvoke(object target, string name, object[] args = null) { var type = target.GetType(); var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Where(m => string.Equals(m.Name, name, StringComparison.Ordinal)); foreach (var mi in methods) { var ps = mi.GetParameters(); if (args == null) { if (ps.Length != 0) continue; try { mi.Invoke(target, null); return true; } catch { return false; } } if (ps.Length != args.Length) continue; try { mi.Invoke(target, args); return true; } catch { return false; } } return false; } } }