using LessokajiWeaverUtilities.Utilities; using Newtonsoft.Json; using SimpleLite.RCS; using SimpleLite.Props; using SimpleCore; using SimpleCore.Library; using SimpleCore.PropType; using StandardScene; using StandardScene.Scheduler.FassEvent; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using SimpleLite.RCS.CarTypes; namespace StandardScene.Scheduler { /// /// FASS 车辆事件调度进程。 /// /// 移植自 fass/FASS.Scheduler/Services/Events/EventCarService.cs: /// 按 JSON 配置定时轮询全场车辆,匹配触发条件后执行交管/PLC/电量等条件判断, /// 最终对 MagCar 下发 Start(放行)或 Stop(停车)。 /// /// /// 使用方式:在场景中添加本 Mission →「启动进程」→ 默认加载 Configs\EventCar\FG2512073.json。 /// /// [MissionType(Name = "FASS车辆事件进程", editor = typeof(EventCarMission))] [I18N.DocumentTranslation(Name = "FASS Event Car Mission", locale = "en")] public class EventCarMission : Mission { /// EventCar JSON 配置文件路径(相对程序目录或绝对路径)。 [FieldMember] public string EventConfigPath = "Config\\EventCar\\traffic.json"; /// 主循环轮询间隔(毫秒),对应 FASS TimerTick 频率,默认 500ms。 [FieldMember] public int TickIntervalMs = 500; /// 与下料站/安全信号交互用的西门子 PLC IP。 [FieldMember] public string PlcIpAddress = "127.0.0.1"; /// PLC 端口,S7 默认 102。 [FieldMember] public int PlcPort = 102; /// PLC 型号字符串,如 S7_1500、S7_300,传给 IoTClient SiemensClient。 [FieldMember] public string PlcVersion = "S7_1500"; /// 是否启用 ME11 下料站 PLC 信号交互(false 时 requestStaion/InplaceStaion 等直接返回 true)。 [FieldMember] public bool IsEnableMEll = true; /// 充电完成放行默认电量阈值(%),可被事件内 ChargeStart 参数覆盖。 [FieldMember] public double ChargeStartThreshold = 93; [JsonIgnore] private bool _started; [JsonIgnore] private Thread _workerThread; [JsonIgnore] private FassEventCarHandler _handler; /// 当前生效的车辆事件配置列表(TriggerSource=Car 且 TriggerEnable=true)。 [JsonIgnore] private List _eventCars = new List(); /// 记录每辆车上一周期的 CurrentNode,用于 PrevNode 触发与地标变化时清除任务防重复标记。 [JsonIgnore] private readonly Dictionary _prevNodeByCarId = new Dictionary(); /// 进程运行状态扩展字段。 public class EventCarMissionStatus : MissionStatus { /// 主循环已执行次数。 public int TickCount { get; set; } /// 已加载的事件规则条数。 public int LoadedEventCount { get; set; } /// 实际加载的配置文件绝对/解析路径。 public string ConfigPath { get; set; } } public override MissionStatus status { get; set; } = new EventCarMissionStatus(); public static Mission Create() { return new EventCarMission(); } /// /// 启动后台轮询线程:加载配置 → 创建 Handler → 周期性 ProcessAllCars。 /// [MethodMember(Name = "启动进程", Description = "加载 FASS EventCar 配置并开始轮询车辆事件")] public override void Execute() { if (_started) { status.status = "已在运行"; return; } try { ReloadConfig(); } catch (Exception ex) { status.status = "配置加载失败"; Diagnosis.Post($"FASS车辆事件进程配置加载失败:{ExceptionFormatter.FormatEx(ex)}", "FassEventCar", true); throw; } _handler = new FassEventCarHandler(this); _started = true; status.status = "运行中"; _workerThread = new Thread(WorkerLoop) { IsBackground = true, Name = "EventCarMission" }; _workerThread.Start(); Diagnosis.Post($"FASS车辆事件进程已启动,配置数={_eventCars.Count}", "FassEventCar", true); } /// 热加载 JSON 配置,运行中也可调用。 [MethodMember(Name = "重新加载配置", Description = "重新读取 EventCar JSON 配置")] public void ReloadConfig() { var path = ResolveConfigPath(); _eventCars = FassEventConfigLoader.Load(path) .Where(item => item.TriggerEnable && string.Equals(item.TriggerSource, "Car", StringComparison.OrdinalIgnoreCase)) .ToList(); var missionStatus = (EventCarMissionStatus)status; missionStatus.LoadedEventCount = _eventCars.Count; missionStatus.ConfigPath = path; status.status = _started ? "运行中" : "配置已加载"; Diagnosis.Post($"FASS EventCar 配置已加载:{path},事件数={_eventCars.Count}", "FassEventCar", true); } /// 停止轮询并等待工作线程结束(最多 2 秒)。 [MethodMember(Name = "停止进程", Description = "停止 FASS 车辆事件轮询")] public void Stop() { _started = false; try { _workerThread?.Join(2000); } catch { } status.status = "已停止"; Diagnosis.Post("FASS车辆事件进程已停止", "FassEventCar", true); } /// /// 后台主循环:每 TickIntervalMs 扫描一次全场车辆。 /// 单轮异常不会终止进程,仅写诊断日志。 /// private void WorkerLoop() { while (_started) { try { ProcessAllCars(); var missionStatus = (EventCarMissionStatus)status; missionStatus.TickCount++; status.status = $"运行中:{missionStatus.TickCount}"; } catch (Exception ex) { Diagnosis.Post($"FASS车辆事件进程循环异常:{ExceptionFormatter.FormatEx(ex)}", "FassEventCar", true); } Thread.Sleep(Math.Max(100, TickIntervalMs)); } } /// /// 遍历所有 Car,构建快照并异步调用 Handler(与 FASS Task.Run 行为一致)。 /// /// 跳过 status 为空或 usage.refreshing=false 的车辆(未参与调度刷新的车不处理)。 /// 当地标变化时清除 ,允许在新地标重新触发事件。 /// /// private void ProcessAllCars() { foreach (var car in SimpleLib.GetAllCars().OfType()) { if (car.status == null) { continue; } var usage = car.status.usage?.Get(); if (usage != null && usage.refreshing == false) { continue; } var currentNode = FassEventCarSnapshotFactory.GetCurrentNodeCode(car); _prevNodeByCarId.TryGetValue(car.id, out var prevNode); var snapshot = FassEventCarSnapshotFactory.Create(car, prevNode, FassEventCarSnapshotFactory.GetNextNodeCode(car)); Task.Run(() => _handler.HandleCarTimerTick(car, snapshot, _eventCars)); if (!string.Equals(prevNode, currentNode, StringComparison.OrdinalIgnoreCase)) { _prevNodeByCarId[car.id] = currentNode; ClearTaskTags(car); } } } /// 车辆换地标后删除防重复执行标签,使同一规则可在新站点再次触发。 private static void ClearTaskTags(Car car) { if (car.tags == null) { return; } Commons.DeleteTag(car.tags, FassEventCarHandler.ActiveTaskTag); } /// /// 解析配置文件路径:绝对路径直接用;相对路径依次尝试 BaseDirectory、CurrentDirectory、上级目录。 /// private string ResolveConfigPath() { if (Path.IsPathRooted(EventConfigPath)) { return EventConfigPath; } var candidates = new[] { Path.Combine(AppDomain.CurrentDomain.BaseDirectory, EventConfigPath), Path.Combine(Directory.GetCurrentDirectory(), EventConfigPath), Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", EventConfigPath)) }; return candidates.FirstOrDefault(File.Exists) ?? candidates[0]; } } }