using Newtonsoft.Json; using SimpleLite; using SimpleLite.RCS; using SimpleLite.RCS.CarTypes; using SimpleLite.RCS.Signal; using SimpleLite.CADTools; using SimpleLite.Props; using SimpleLite.UI; using SimpleCore; using SimpleCore.Compiler; using SimpleCore.Library; using SimpleCore.PropType; using StandardScene.Model; using StandardScene; using StandardScene.Utils; using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; namespace StandardScene.Chained { /// /// 任务类型枚举 /// public enum TaskType { 移动 = 1, // 移动任务(当前未使用) 搬运 = 2, // 搬运任务 装车 = 3, // 装车任务 卸车 = 4, // 卸车任务 } /// /// 运输任务类 /// 继承自AbstractDelivery,实现具体的运输任务功能 /// /// public class TransportDelivery : AbstractChainedDeliveryMission.AbstractDelivery public class TransportDelivery : ChainedDeliveryMission.Delivery { /// 任务类型(枚举值) public TaskType Type; /// 任务类型(字符串形式) public string TaskType; /// 使用的小车名称 public string UsingCarName = "/"; /// 物料信息 public string Material = ""; /// 占车站点 public string HoldCarSite = ""; /// 阶段任务代码(用于标记阶段任务) public string MGTaskCode = ""; /// 任务ID字符串(用于标记阶段任务,可包含多个任务ID,用逗号分隔) public string TaskIdsString = ""; /// /// 构造函数 /// 注册任务生命周期回调函数 /// public TransportDelivery() { // 注册任务开始回调 OnStart += d => { TransportOnStart((TransportDelivery)d); }; // 注册取货完成回调 DoneFetch += d => { TransportDoneFetch((TransportDelivery)d); }; // 注册放货完成回调 DonePut += d => { TransportDonePut((TransportDelivery)d); }; // 注册任务完成回调 DoneMission += d => { TransportDoneMission((TransportDelivery)d); }; } /// /// 任务开始时的回调处理 /// /// 运输任务对象 private void TransportOnStart(TransportDelivery d) { Diagnosis.Post($"{d.TaskId}___{d.Src}->{d.Dst}: step-onStart"); } /// /// 取货完成时的回调处理 /// /// 运输任务对象 private void TransportDoneFetch(TransportDelivery d) { Diagnosis.Post($"{d.TaskId}___{d.Src}->{d.Dst}: step-doneFetch"); } /// /// 放货完成时的回调处理 /// /// 运输任务对象 private void TransportDonePut(TransportDelivery d) { Diagnosis.Post($"{d.TaskId}___{d.Src}->{d.Dst}: step-donePut"); } /// /// 任务完成时的回调处理 /// /// 运输任务对象 private void TransportDoneMission(TransportDelivery d) { Diagnosis.Post($"{d.TaskId}___{d.Src}->{d.Dst}: step-doneMission"); } } /// /// 搬运任务进程类 /// 继承自AbstractChainedDeliveryMission,实现具体的搬运任务调度 /// [MissionType(Name = "搬运任务进程", editor = typeof(TransportMission))] internal class TransportMission : ChainedDeliveryMission { /// 是否启用就近任务策略("1"=启用,"0"=禁用) private static readonly string NearestTask = "1"; /// 任务查看器窗口对象 private DeliveryViewer dv; public TransportMission() { // 确保回调静态类已完成注册 TransportDeliveryCallbacks.EnsureInitialized(); // 根据配置的 MissionCallbackURL 设置回调地址(若有) var p = Param; if (p != null && !string.IsNullOrWhiteSpace(p.MissionCallbackURL)) { if (Commons.IsValidHttpUrl(p.MissionCallbackURL)) { _lastCallbackUrl = p.MissionCallbackURL; TransportDeliveryCallbacks.ConfigureCallbackUrl(_lastCallbackUrl); } else { Diagnosis.Log($"无效的 MissionCallbackURL 配置: {p.MissionCallbackURL}", "TransportMission"); } } } public class TransportMissionParam { /// /// 递送任务状态回调地址。 /// public string MissionCallbackURL; } public TransportMissionParam Param => StringDictConvert.Convert(fields); private readonly bool _onDisplay = true; private string _lastCallbackUrl; protected override Delivery CreateDeliveryFromSnapshot(DeliveryStateSnapshot snap) { var usingCar = SimpleLib.GetCar(snap.UsingCarId); if (snap.UsingCarId != -1 && usingCar == null) { Diagnosis.Post($"CreateDeliveryFromSnapshot[{snap.Id}] failed:usingCar[{snap.UsingCarId}] not found"); return null; } if (usingCar != null && usingCar is not Car) { Diagnosis.Post($"CreateDeliveryFromSnapshot[{snap.Id}] skipped:usingCar[{snap.UsingCarId}] is not Car type"); return null; } var d = new TransportDelivery { Id = snap.Id, TaskId = snap.TaskId ?? string.Empty, Src = snap.Src, Dst = snap.Dst, SkipFetch = snap.SkipFetch, SkipPut = snap.SkipPut, Putting = snap.Putting, CreateTime = snap.CreateTime, StartTime = snap.StartTime, FinishTime = snap.FinishTime, CarType = snap.CarType, UsingCar = (Car)usingCar }; if (usingCar != null && string.Equals(snap.Status, "putting", StringComparison.OrdinalIgnoreCase) && !CheckCarLoaded(usingCar)) { d.Error = true; } // 恢复回调配置与事件绑定 d.ReportOnStarted = snap.ReportOnStarted; d.ReportOnFetched = snap.ReportOnFetched; d.ReportOnPut = snap.ReportOnPut; d.ReportOnFinished = snap.ReportOnFinished; d.ReportOnFailed = snap.ReportOnFailed; d.ReportOnTerminated = snap.ReportOnTerminated; d.OnStartCallbackKeys = snap.OnStartCallbackKeys ?? new List(); d.DoneFetchCallbackKeys = snap.DoneFetchCallbackKeys ?? new List(); d.DonePutCallbackKeys = snap.DonePutCallbackKeys ?? new List(); d.DoneMissionCallbackKeys = snap.DoneMissionCallbackKeys ?? new List(); d.FailedCallbackKeys = snap.FailedCallbackKeys ?? new List(); d.OnTerminatedCallbackKeys = snap.OnTerminatedCallbackKeys ?? new List(); // 统一通过 Attacher 挂载所有回调 DeliveryCallbackAttacher.AttachAll(d); return d; } protected override bool CheckCarLoaded(AbstractCar car) { if(car is DummyCar) return true; var carLoaded = Commons.GetCarStatus((Car)car, "Loaded"); if (!string.IsNullOrEmpty(carLoaded) && bool.TryParse(carLoaded, out var result)) return result; return false; } /// /// 执行方法(启动任务调度进程) /// 启动基类的任务调度循环,并启动状态显示线程 /// public override void Execute() { base.Execute(); // 调用基类Execute方法,启动任务调度循环 //HideSimpleConsole(); // 隐藏控制台窗口 // 启动状态显示线程,实时显示小车状态和任务执行情况 new Thread(() => { while (true) { Thread.Sleep(100); // 每100ms更新一次显示 var painter = SimpleMonitor.getPainter("TransportMissionPainter"); painter.clear(); if (!_onDisplay) continue; // 如果未启用显示,跳过 // 检查并更新任务状态回调 URL(如果配置发生变化) var p = Param; var currentUrl = p?.MissionCallbackURL; if (!string.IsNullOrWhiteSpace(currentUrl) && currentUrl != _lastCallbackUrl) { // 使用 Commons 中的统一 URL 校验逻辑 if (Commons.IsValidHttpUrl(currentUrl)) { TransportDeliveryCallbacks.ConfigureCallbackUrl(currentUrl); _lastCallbackUrl = currentUrl; } else { Diagnosis.Log($"无效的 MissionCallbackURL 配置: {currentUrl}", "TransportMission"); } } var onMissionCnt = 0; // 正在执行任务的小车数量 var carStr = $"{string.Join("\n", SimpleLib.GetAllCars().Select(cc => { var status = ""; if (cc.tags.TryGetValue("occupied", out var occupiedStr)) { onMissionCnt++; status = occupiedStr; if (cc.tags.TryGetValue("idle", out var _)) { Diagnosis.Post($"strange idle tag, {cc.name}({cc.id})"); cc.tags.Remove("idle"); } } else if (cc.tags.TryGetValue("idle", out var idleStr)) status = DateTime.TryParse(idleStr, out var idleTime) ? $"idle:{idleTime}" : $"idle:{idleStr}"; return $"{cc.name}({cc.id})\t{status}"; }))}"; //var waitingMissions = GetDeliveries() // .Where(dd => dd.GetStatus() == DeliveryStatus.Waiting).ToList(); painter.drawTextFixed( $"小车:\t开动率:{onMissionCnt}/{SimpleLib.GetAllCars().Length}\n{carStr}\n\n", new SolidBrush(Color.Black), VirtualPainter.DrawPosition.LeftTop, Color.AliceBlue); painter = null; } }).Start(); } /// /// 变更任务优先级(重写基类方法) /// 根据配置决定是否执行就近任务策略 /// public override void ChangePriority() { try { if (NearestTask == "1") // NearestTask 等于1 执行就近原则 { // 就近任务执行(当前被注释,未启用) // NearestTaskExecute(); } else { // 如果未启用就近原则,移除所有小车的就近选车标记 foreach (var car in SimpleLib.GetAllCars().OfType()) { Commons.DeleteTag(car.tags, "changePriority"); } } } catch (Exception ex) { Console.WriteLine("同一工序就近执行就近任务执行:" + ex.ToString()); } } /// /// 一键初始化所有小车 /// 将状态为"正常"或"上线"且未初始化的小车进行重置 /// [MethodMember(Name = "一键初始化")] public void ResetAll() { foreach (var car in SimpleLib.GetAllCars().OfType().Where(c => c.lstatus.Contains("正常") || c.lstatus.Contains("上线"))) { // 如果小车未初始化(GetLastSite返回-1),执行重置 if (car.GetLastSite() == -1) ((Car)car).Reset(); } } /// /// 一键上线所有小车 /// 将状态为"正常"或"上线"且已初始化的小车标记为在线状态 /// [MethodMember(Name = "一键上线")] public void OnlineAll() { foreach (var car in SimpleLib.GetAllCars().OfType().Where(c => c.lstatus.Contains("正常") || c.lstatus.Contains("上线"))) { // 如果小车已初始化,标记为在线 if (car.GetLastSite() != -1) { Commons.AddOrUpdateTag(car.tags, "Online", "true"); } } } /// /// 查看任务列表界面 /// 打开任务查看器窗口,显示所有任务的状态 /// [MethodMember(Name = "查看任务", Description = "显示界面")] public void Print() { dv = new DeliveryViewer(); dv.Show(); } /// /// 手动创建单取货任务 /// 通过UI交互选择小车和取货点,创建仅取货任务(跳过取货步骤,小车从当前位置移动到取货点) /// [MethodMember(Name = "单取货", Description = "增加并排队搬运任务链")] public async void ManualEnqueueFetch() { try { G.pushStatus("选择小车"); var selected = SimpleMonitor.selected.ToArray(); if (selected.Length == 0) { CycleUiHelper.Alert("提示", "请先选择需要控制的小车!"); return; } var obj = selected[0]; if (obj is Car car) { G.pushStatus("选择取货位"); // 等待用户在UI中选择取货点 var pt1 = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true }); // 创建运输任务:从小车当前位置到选择的取货点 var d = new TransportDelivery { CarType = "Car", Src = car.GetLastSite(), // 起始点为小车当前位置 Dst = pt1.site, // 目标点为选择的取货点 SkipFetch = true, // 跳过取货步骤(因为小车已经在起始点) UsingCar = car, // 指定使用的小车 PutPlanInfo = new() { { "action", "fetch" } }, // 路径动作设置为fetch }; G.pushStatus($"排序了一个{car.name}搬运任务{d.Id}: {car.GetLastSite()} -> {d.Dst}"); Enqueue(d); } else { CycleUiHelper.Alert("提示", "请选择需要控制的小车!"); } } catch { G.pushStatus($"结束任务链"); } } //写一个冒泡排序的算法,按照距离排序 [MethodMember(Name = "切换后台显示")] public void SwitchBackgroundDisplay() { ShowSimpleConsole(); } /// /// 手动创建单放货任务 /// 通过UI交互选择小车和放货点,创建仅放货任务(小车从当前位置移动到放货点并放货) /// [MethodMember(Name = "单放货", Description = "增加并排队搬运任务链")] public async void ManualEnqueuePut() { try { G.pushStatus("选择小车"); var selected = SimpleMonitor.selected.ToArray(); if (selected.Length == 0) { CycleUiHelper.Alert("提示", "请先选择需要控制的小车!"); return; } var obj = selected[0]; if (obj is Car car) { G.pushStatus("选择取货位"); // 等待用户在UI中选择放货点(注释写的是"取货位"但实际是放货点) var pt1 = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true }); var srcSite = SimpleLib.GetSite(pt1.site); // 创建运输任务:从小车当前位置到选择的放货点 var d = new TransportDelivery { CarType = "Car", Src = car.GetLastSite(), // 起始点为小车当前位置 Dst = pt1.site, // 目标点为选择的放货点 UsingCar = car, // 指定使用的小车 SkipFetch = true, // 跳过取货步骤 PutPlanInfo = new() { { "action", "put" } }, // 路径动作设置为put }; G.pushStatus($"排序了一个{car.name}搬运任务{d.Id}: {srcSite.id} -> {d.Dst}"); Enqueue(d); } else { CycleUiHelper.Alert("提示", "请选择需要控制的小车!"); } } catch { G.pushStatus($"结束任务链"); } } /// /// 手动创建完整的取放货任务 /// 通过UI交互选择取货点和放货点,创建完整的搬运任务(取货->放货) /// [MethodMember(Name = "取放货", Description = "增加并排队搬运任务链")] public async void ManualEnqueue() { try { G.pushStatus("选择取货位"); // 等待用户在UI中选择取货点 var pt1 = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true }); var srcSite = SimpleLib.GetSite(pt1.site); G.pushStatus("选择放货位"); // 等待用户在UI中选择放货点 var pt2 = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true }); var dstSite = SimpleLib.GetSite(pt2.site); // 创建完整的运输任务:从取货点到放货点 var d = new TransportDelivery { CarType = "Car", Src = srcSite.id, // 取货点 Dst = dstSite.id, // 放货点 TaskId = $"ManualTask-{DateTime.Now}" // 生成任务ID }; G.pushStatus($"排序了一个叉车搬运任务{d.Id}: {srcSite.id} -> {dstSite.id}"); Enqueue(d); } catch { G.pushStatus($"结束任务链"); } } /// /// 手动创建移动任务(去某地) /// 通过UI交互选择目标站点,创建移动任务(起点和终点相同,只移动不放货) /// [MethodMember(Name = "去某地", Description = "去某地")] public async void ManualEnqueueGo() { try { G.pushStatus("选择前往的站点"); // 等待用户在UI中选择目标站点 var pt1 = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true }); var srcSite = SimpleLib.GetSite(pt1.site); // 创建移动任务:起点和终点相同(只移动到目标点,不执行取放货操作) var d = new TransportDelivery { CarType = "Car", Src = pt1.site, // 起点(实际不会取货) Dst = pt1.site, // 终点(实际不会放货) TaskId = $"ManualTask-{DateTime.Now}", SrcFindLoop = false, // 起点不查找回路 DstFindLoop = false // 终点不查找回路 }; G.pushStatus($"排序了一个叉车搬运任务{d.Id}: {srcSite.id} -> {srcSite.id}"); Enqueue(d); } catch { G.pushStatus($"结束任务链"); } } /// /// 供按钮盒进程反射调用:ButtonMission.ExecuteButtonActionInternal 反射调用本方法后, /// 由 HandleMethodResult 解析返回值,驱动 OnActionExecuted 成功/失败反馈。 /// 返回 true 表示已入队;false 表示站点无效。若 Enqueue 抛错,由按钮进程捕获后同样视为失败。 /// 按钮配置示例:TriggerMission=TransportMission,TriggerMethod=EnqueueTransportByButton,TriggerMethodParams=取货站点ID,放货站点ID /// /// 取货站点 ID /// 放货站点 ID public bool EnqueueTransportByButton(int srcSiteId, int dstSiteId) { if (SimpleLib.GetSite(srcSiteId) == null || SimpleLib.GetSite(dstSiteId) == null) { Diagnosis.Log($"按钮排队搬运失败:站点不存在 src={srcSiteId}, dst={dstSiteId}", "TransportMission", true); return false; } var d = new TransportDelivery { CarType = "Car", Src = srcSiteId, Dst = dstSiteId, TaskId = $"Button-{DateTime.Now:yyyyMMddHHmmssfff}" }; Enqueue(d); Diagnosis.Post($"按钮排队搬运 {d.Id}: {srcSiteId} -> {dstSiteId}"); return true; } // ========== 控制台窗口显示控制 ========== /// Windows API:获取控制台窗口句柄 [DllImport("kernel32.dll")] static extern IntPtr GetConsoleWindow(); /// Windows API:显示/隐藏窗口 [DllImport("user32.dll")] static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); /// 控制台显示状态标志 private static bool ShowConsole = false; /// /// 切换控制台窗口显示/隐藏 /// public static void ShowSimpleConsole() { ShowConsole = !ShowConsole; // 切换显示状态 var handle = GetConsoleWindow(); int n = ShowConsole ? 0 : 5; // 0=显示,5=隐藏 Console.WriteLine(n); ShowWindow(handle, n); } /// /// 隐藏控制台窗口 /// public static void HideSimpleConsole() { var handle = GetConsoleWindow(); ShowWindow(handle, 0); // 0表示隐藏窗口 } } }