init commit

This commit is contained in:
zhaowei.huang
2026-06-14 11:19:15 +08:00
parent e79a3815a5
commit c8e540d272
174 changed files with 60830 additions and 39 deletions
@@ -0,0 +1,599 @@
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 System.Windows.Forms;
using Microsoft.Win32.SafeHandles;
namespace StandardScene.Chained
{
/// <summary>
/// 任务类型枚举
/// </summary>
public enum TaskType
{
= 1, // 移动任务(当前未使用)
= 2, // 搬运任务
= 3, // 装车任务
= 4, // 卸车任务
}
/// <summary>
/// 运输任务类
/// 继承自AbstractDelivery,实现具体的运输任务功能
/// </summary>
/// public class TransportDelivery : AbstractChainedDeliveryMission.AbstractDelivery
public class TransportDelivery : ChainedDeliveryMission.Delivery
{
/// <summary>任务类型(枚举值)</summary>
public TaskType Type;
/// <summary>任务类型(字符串形式)</summary>
public string TaskType;
/// <summary>使用的小车名称</summary>
public string UsingCarName = "/";
/// <summary>物料信息</summary>
public string Material = "";
/// <summary>占车站点</summary>
public string HoldCarSite = "";
/// <summary>阶段任务代码(用于标记阶段任务)</summary>
public string MGTaskCode = "";
/// <summary>任务ID字符串(用于标记阶段任务,可包含多个任务ID,用逗号分隔)</summary>
public string TaskIdsString = "";
/// <summary>
/// 构造函数
/// 注册任务生命周期回调函数
/// </summary>
public TransportDelivery()
{
// 注册任务开始回调
OnStart += d => { TransportOnStart((TransportDelivery)d); };
// 注册取货完成回调
DoneFetch += d => { TransportDoneFetch((TransportDelivery)d); };
// 注册放货完成回调
DonePut += d => { TransportDonePut((TransportDelivery)d); };
// 注册任务完成回调
DoneMission += d => { TransportDoneMission((TransportDelivery)d); };
}
/// <summary>
/// 任务开始时的回调处理
/// </summary>
/// <param name="d">运输任务对象</param>
private void TransportOnStart(TransportDelivery d)
{
Diagnosis.Post($"{d.TaskId}___{d.Src}->{d.Dst}: step-onStart");
}
/// <summary>
/// 取货完成时的回调处理
/// </summary>
/// <param name="d">运输任务对象</param>
private void TransportDoneFetch(TransportDelivery d)
{
Diagnosis.Post($"{d.TaskId}___{d.Src}->{d.Dst}: step-doneFetch");
}
/// <summary>
/// 放货完成时的回调处理
/// </summary>
/// <param name="d">运输任务对象</param>
private void TransportDonePut(TransportDelivery d)
{
Diagnosis.Post($"{d.TaskId}___{d.Src}->{d.Dst}: step-donePut");
}
/// <summary>
/// 任务完成时的回调处理
/// </summary>
/// <param name="d">运输任务对象</param>
private void TransportDoneMission(TransportDelivery d)
{
Diagnosis.Post($"{d.TaskId}___{d.Src}->{d.Dst}: step-doneMission");
}
}
/// <summary>
/// 搬运任务进程类
/// 继承自AbstractChainedDeliveryMission,实现具体的搬运任务调度
/// </summary>
[MissionType(Name = "搬运任务进程", editor = typeof(TransportMission))]
internal class TransportMission : ChainedDeliveryMission
{
/// <summary>是否启用就近任务策略("1"=启用,"0"=禁用)</summary>
private static readonly string NearestTask = "1";
/// <summary>任务查看器窗口对象</summary>
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
{
/// <summary>
/// 递送任务状态回调地址。
/// </summary>
public string MissionCallbackURL;
}
public TransportMissionParam Param => StringDictConvert<TransportMissionParam>.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<string>();
d.DoneFetchCallbackKeys = snap.DoneFetchCallbackKeys ?? new List<string>();
d.DonePutCallbackKeys = snap.DonePutCallbackKeys ?? new List<string>();
d.DoneMissionCallbackKeys = snap.DoneMissionCallbackKeys ?? new List<string>();
d.FailedCallbackKeys = snap.FailedCallbackKeys ?? new List<string>();
d.OnTerminatedCallbackKeys = snap.OnTerminatedCallbackKeys ?? new List<string>();
// 统一通过 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;
}
/// <summary>
/// 执行方法(启动任务调度进程)
/// 启动基类的任务调度循环,并启动状态显示线程
/// </summary>
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();
}
/// <summary>
/// 变更任务优先级(重写基类方法)
/// 根据配置决定是否执行就近任务策略
/// </summary>
public override void ChangePriority()
{
try
{
if (NearestTask == "1") // NearestTask 等于1 执行就近原则
{
// 就近任务执行(当前被注释,未启用)
// NearestTaskExecute();
}
else
{
// 如果未启用就近原则,移除所有小车的就近选车标记
foreach (var car in SimpleLib.GetAllCars().OfType<Car>())
{
Commons.DeleteTag(car.tags, "changePriority");
}
}
}
catch (Exception ex)
{
Console.WriteLine("同一工序就近执行就近任务执行:" + ex.ToString());
}
}
/// <summary>
/// 一键初始化所有小车
/// 将状态为"正常"或"上线"且未初始化的小车进行重置
/// </summary>
[MethodMember(Name = "一键初始化")]
public void ResetAll()
{
foreach (var car in SimpleLib.GetAllCars().OfType<Car>().Where(c => c.lstatus.Contains("正常") || c.lstatus.Contains("上线")))
{
// 如果小车未初始化(GetLastSite返回-1),执行重置
if (car.GetLastSite() == -1)
((Car)car).Reset();
}
}
/// <summary>
/// 一键上线所有小车
/// 将状态为"正常"或"上线"且已初始化的小车标记为在线状态
/// </summary>
[MethodMember(Name = "一键上线")]
public void OnlineAll()
{
foreach (var car in SimpleLib.GetAllCars().OfType<Car>().Where(c => c.lstatus.Contains("正常") || c.lstatus.Contains("上线")))
{
// 如果小车已初始化,标记为在线
if (car.GetLastSite() != -1)
{
Commons.AddOrUpdateTag(car.tags, "Online", "true");
}
}
}
/// <summary>
/// 查看任务列表界面
/// 打开任务查看器窗口,显示所有任务的状态
/// </summary>
[MethodMember(Name = "查看任务", Description = "显示界面")]
public void Print()
{
dv = new DeliveryViewer();
dv.Show();
}
/// <summary>
/// 手动创建单取货任务
/// 通过UI交互选择小车和取货点,创建仅取货任务(跳过取货步骤,小车从当前位置移动到取货点)
/// </summary>
[MethodMember(Name = "单取货", Description = "增加并排队搬运任务链")]
public async void ManualEnqueueFetch()
{
try
{
G.pushStatus("选择小车");
var selected = SimpleMonitor.selected.ToArray();
if (selected.Length == 0) { MessageBox.Show("请先选择需要控制的小车!"); 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
{
MessageBox.Show("请选择需要控制的小车!");
}
}
catch
{
G.pushStatus($"结束任务链");
}
}
//写一个冒泡排序的算法,按照距离排序
[MethodMember(Name = "切换后台显示")]
public void SwitchBackgroundDisplay()
{
ShowSimpleConsole();
}
/// <summary>
/// 手动创建单放货任务
/// 通过UI交互选择小车和放货点,创建仅放货任务(小车从当前位置移动到放货点并放货)
/// </summary>
[MethodMember(Name = "单放货", Description = "增加并排队搬运任务链")]
public async void ManualEnqueuePut()
{
try
{
G.pushStatus("选择小车");
var selected = SimpleMonitor.selected.ToArray();
if (selected.Length == 0) { MessageBox.Show("请先选择需要控制的小车!"); 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
{
MessageBox.Show("请选择需要控制的小车!");
}
}
catch
{
G.pushStatus($"结束任务链");
}
}
/// <summary>
/// 手动创建完整的取放货任务
/// 通过UI交互选择取货点和放货点,创建完整的搬运任务(取货->放货)
/// </summary>
[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($"结束任务链");
}
}
/// <summary>
/// 手动创建移动任务(去某地)
/// 通过UI交互选择目标站点,创建移动任务(起点和终点相同,只移动不放货)
/// </summary>
[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($"结束任务链");
}
}
/// <summary>
/// 供按钮盒进程反射调用:ButtonMission.ExecuteButtonActionInternal 反射调用本方法后,
/// 由 HandleMethodResult 解析返回值,驱动 OnActionExecuted 成功/失败反馈。
/// 返回 true 表示已入队;false 表示站点无效。若 Enqueue 抛错,由按钮进程捕获后同样视为失败。
/// 按钮配置示例:TriggerMission=TransportMissionTriggerMethod=EnqueueTransportByButtonTriggerMethodParams=取货站点ID,放货站点ID
/// </summary>
/// <param name="srcSiteId">取货站点 ID</param>
/// <param name="dstSiteId">放货站点 ID</param>
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;
}
// ========== 控制台窗口显示控制 ==========
/// <summary>Windows API:获取控制台窗口句柄</summary>
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
/// <summary>Windows API:显示/隐藏窗口</summary>
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
/// <summary>控制台显示状态标志</summary>
private static bool ShowConsole = false;
/// <summary>
/// 切换控制台窗口显示/隐藏
/// </summary>
public static void ShowSimpleConsole()
{
ShowConsole = !ShowConsole; // 切换显示状态
var handle = GetConsoleWindow();
int n = ShowConsole ? 0 : 5; // 0=显示,5=隐藏
Console.WriteLine(n);
ShowWindow(handle, n);
}
/// <summary>
/// 隐藏控制台窗口
/// </summary>
public static void HideSimpleConsole()
{
var handle = GetConsoleWindow();
ShowWindow(handle, 0); // 0表示隐藏窗口
}
}
}