Files
黄兆尉andCursor e075bc0d7d refactor: CycleUiHelper 重命名为 FairyUiHelper
对齐 FairyView 宿主命名。环线/充电/按钮/门进程的 Stop 改为 override,避免隐藏基类;Kiva.ForceStop 改为 override。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 22:49:07 +08:00

720 lines
28 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Simple3.RCS;
using Simple3.RCS.CarTypes;
using Simple3.CADTools;
using Simple3.Props;
using Simple3.UI;
using SimpleCore;
using SimpleCore.Compiler;
using SimpleCore.Library;
using SimpleCore.PropType;
using SimpleCore.Traffic;
using StandardScene.InterLock;
using StandardScene.Model;
using StandardScene.Utils;
using static StandardScene.Chained.ChainedDeliveryMission;
namespace StandardScene
{
/// <summary>
/// 插件本地化设置
/// </summary>
public class CustomOperationsBeforeLoading
{
public static void Set()
{
///死锁提醒
TrafficControl.OnDeadLock = (loopingCar) =>
{
try
{
var cars = string.Join(",", loopingCar.Select(p => $"{p.id}"));
var msg = $"小车:{cars}间发生死锁" +
$"请及时人工介入处理!!!!";
FairyUiHelper.Alert("死锁提醒", msg);
}
catch { }
};
}
}
public class NoReflectionApi : Attribute
{
}
public class ReflectionApiWithParameter : Attribute
{
public string name;
public string desc;
public string Hint;
}
public static class Commons
{
public static VehicleStatus GetVehicleStatus(Car car)
{
if(car is DummyCar && car.lstatus.Contains("上线"))
return VehicleStatus.Normal;
if (car.lstatus.Contains("正常但未初始化")||car.lstatus.Contains("Normal but not initialized"))
return VehicleStatus.NeedInit;
if (car.lstatus.Contains("正常") || car.lstatus.Contains("Normal")||car.lstatus.Contains("上线"))
return VehicleStatus.Normal;
if (car.lstatus.Contains("自动驾驶系统失联") || car.lstatus.Contains("Autonomous driving system lost"))
return VehicleStatus.Offline;
return VehicleStatus.Unknown;
}
/// <summary>
/// 获取车辆状态
/// </summary>
/// <param name="car">车辆</param>
/// <param name="key">状态-键</param>
/// <returns>状态-值</returns>
public static string GetCarStatus(Car car, string key)
{
string value = "0";
if (car == null) return value;
if (car.status.enums.TryGetValue(key, out var valueStr))
value = valueStr;
return value;
}
/// <summary>
/// 添加标签
/// </summary>
/// <param name="item"></param>
/// <param name="tag"></param>
/// <param name="value"></param>
public static void AddOrUpdateTag<T>(T item, string tag, string value) where T : TagSet
{
if (item.Contains(tag))
item.Remove(tag);
item.Add(tag, value);
}
/// <summary>
/// 删除标签
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="item"></param>
/// <param name="tag"></param>
public static void DeleteTag<T>(T item, string tag) where T : TagSet
{
if (item.Contains(tag))
{
item.Remove(tag);
}
}
/// <summary>
/// 清空标签
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="item"></param>
public static void ClearTags<T>(T item) where T : TagSet
{
item.Clear();
}
/// <summary>
/// 添加或更新字段
/// </summary>
/// <param name="car"></param>
/// <param name="field"></param>
/// <param name="value"></param>
public static void AddOrUpdateCarField(Car car, string field, string value)
{
if (car.fields.ContainsKey(field))
{
car.fields.Remove(field);
car.fields.Add(field, value);
}
else
car.fields.Add(field, value);
}
/// <summary>
/// 删除字段
/// </summary>
/// <param name="car"></param>
/// <param name="field"></param>
public static void DeleteCarField(Car car, string field)
{
if (car.fields.ContainsKey(field))
{
car.fields.Remove(field);
}
}
/// <summary>
/// 清空字段
/// </summary>
/// <param name="car"></param>
public static void ClearCarFields(Car car)
{
car.fields.Clear();
}
/// <summary>
/// 添加或更新site的字段
/// </summary>
/// <param name="site"></param>
/// <param name="field"></param>
/// <param name="value"></param>
public static void AddOrUpdateSiteField(Site site, string field, string value)
{
if (site.fields.ContainsKey(field))
{
site.fields.Remove(field);
site.fields.Add(field, value);
}
else
site.fields.Add(field, value);
}
/// <summary>
/// 删除site的字段
/// </summary>
/// <param name="site"></param>
/// <param name="field"></param>
public static void DeleteSiteField(Site site, string field)
{
if (site.fields.ContainsKey(field))
{
site.fields.Remove(field);
}
}
/// <summary>
/// 清空site的字段
/// </summary>
/// <param name="site"></param>
public static void ClearSiteFields(Site site)
{
site.fields.Clear();
}
/// <summary>
/// 检查给定字符串是否为合法的 HTTP/HTTPS URL(至少包含协议与主机)。
/// </summary>
/// <param name="url">待校验的 URL 字符串</param>
/// <returns>true 表示 URL 合法</returns>
public static bool IsValidHttpUrl(string url)
{
if (string.IsNullOrWhiteSpace(url)) return false;
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) return false;
if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps) return false;
if (string.IsNullOrEmpty(uri.Host)) return false;
return true;
}
public static double CarValue(Car car, string key)
{
if (car.fields.TryGetValue("electricCurrent", out var strelectricCurrent))
return double.Parse(strelectricCurrent);
if (key == "Soc" && (car.name.Contains("模拟") || car.name.ToLower().Contains("sim")))
{
if (car.fields.TryGetValue("Soc", out var strSoc))
return double.Parse(strSoc);
return 100;
}
var value = "0";
if (car.status.enums.TryGetValue(key, out var valueStr))
value = valueStr;
return double.Parse(value);
}
/// <summary>
/// 添加或更新mission的字段
/// </summary>
/// <param name="mission"></param>
/// <param name="field"></param>
/// <param name="value"></param>
public static void AddOrUpdateMissionField(Mission mission, string field, string value)
{
if (mission.fields.ContainsKey(field))
{
mission.fields.Remove(field);
mission.fields.Add(field, value);
}
else
mission.fields.Add(field, value);
}
/// <summary>
/// 删除mission的字段
/// </summary>
/// <param name="mission"></param>
/// <param name="field"></param>
/// <returns></returns>
public static void DeleteMissionField(Mission mission, string field)
{
if (mission.fields.ContainsKey(field))
mission.fields.Remove(field);
}
public static List<AbstractCar> GetOnlineCars()
{
return SimpleLib.GetAllCars().Where(c => c.GetLastSite() != -1 && Commons.GetVehicleStatus((Car)c)== VehicleStatus.Normal).ToList();
}
public static bool IsSceneSiteByCode(int[] siteCodes)
{
try
{
foreach (var siteCode in siteCodes)
{
var site = SimpleLib.GetAllSites().FirstOrDefault(s => s.id == siteCode);
if (site == null) return false;
}
}
catch (Exception ex)
{
Console.WriteLine($@"IsSceneSite 校验报错=>{ex.Message}");
return false;
}
return true;
}
public static readonly object PlanSession = new object();
public static int SelectCar(AbstractCar car, bool enableGoingStandbyCar = false, bool needCharge = false, bool checkHoldCar = false)
{
var carHaveCoordination = (Car)car;
var conditions = car.GetLastSite() != -1 && GetVehicleStatus((Car)car)== VehicleStatus.Normal&&
!car.tags.Contains("changePriority") &&
!car.tags.Contains("priority") && !car.tags.Contains("occupied")
&& !car.tags.Contains("agvOffline")
&& car.status.pendingLocks.Length == 0 && !car.tags.Contains("deliver") &&//pendingLocks临时增加解决车接多任务问题
(!car.tags.Contains("shouldCharge") || needCharge)
&& !car.tags.Contains("currentWorkStep") && !car.fields.ContainsKey("StopAccept") && car.tags.Contains("Online"); //巡航任务未完成的车
// Mag2Car 联调:UDP Online 即可调度,不强制 lstatus=上线(模拟器常报 state=0 未准备)
var isMag2Car = string.Equals(car.GetType().Name, "Mag2Car", StringComparison.Ordinal);
if (car.name.Contains("模拟") || isMag2Car)
{
conditions = car.GetLastSite() != -1 &&
(!car.tags.Contains("holdCar") || !checkHoldCar) && !car.tags.Contains("occupied")
&& (!isMag2Car || car.tags.Contains("Online"))
&& car.status.pendingLocks.Length == 0 && !car.tags.Contains("deliver") &&//pendingLocks临时增加解决车接多任务问题
(!car.tags.Contains("shouldCharge") || needCharge)
&& !car.tags.Contains("currentWorkStep") && !car.fields.ContainsKey("StopAccept"); //巡航任务未完成的车
}
//((carHaveCoordination.haveCoordination || car.name.Contains("模拟")) && (!car.tags.Contains("shouldCharge") || needCharge));
if (conditions) return 1;
if (enableGoingStandbyCar && car.tags.TryGetValue("dest", out var dst) &&
int.TryParse(dst, out var dstId) &&
SimpleLib.GetSite(dstId).fields.ContainsKey(GetGiveWayType((Car)car))) return 1;
return -1;
}
private static string GetGiveWayType(Car car)
{
if (car.fields.ContainsKey("mover")) return "standbyMover";
if (car.fields.ContainsKey("loader")) return "standbyLoader";
return "giveWay";
}
public static bool SiteHaveTask(Site site)
{
var siteHaveTask = SimpleLib.GetAllCars().ToList().FindAll(o =>
{
if (o.status.pendingLocks.Length > 0)
{
if (o.status.pendingLocks.Contains(site.id))
{
return true;
}
}
if (o.status.holdingLocks.Length > 0)
{
if (o.status.holdingLocks.Contains(site.id))
{
return true;
}
}
return false;
}).Count != 0;
return siteHaveTask;
}
public static SegmentPlan GetNearestPlan(Car car, Func<Site, bool> test, int srcId = -1, bool disableConflict = false)
{
var curRouteLength = float.MaxValue;
SegmentPlan targetPlan = null;
foreach (var site in SimpleLib.GetAllSites())
{
if (!test(site)) continue;
bool occupied = false;
foreach (var cc in SimpleLib.GetAllCars())
{
if (cc != car)
{
if (cc.tags.Contains("dest") && cc.tags.IsEqual("dest", site.id.ToString()) ||
cc.status.holdingLocks.Contains(site.id))
occupied = true;
}
}
if (occupied) continue;
var mPlan = new SegmentPlan { usingCar = car };
if (disableConflict)
mPlan.fields["forbid_cross"] = "false";
try
{
var newRouteLength = mPlan.FindRoute(
SimpleLib.GetSite(srcId == -1 ? car.GetLastSite() : srcId),
SimpleLib.GetSite(site.id));
if (newRouteLength < curRouteLength)
{
curRouteLength = newRouteLength;
targetPlan = mPlan;
}
}
catch
{
// ignored
}
}
return targetPlan;
}
public static void NearestTask(List<Delivery> deliveries, List<Delivery> runningDeliveries = null)
{
try
{
var curW = float.MaxValue;
var priority = int.MinValue;
Delivery priorityNextDelivery = null;
Delivery deliveryTask = null;
//车不是空闲车,但这个任务结束的时候离另一个将要执行的任务很近。
//(通常来说是同一个工序的,除非单纯地返空托(或路线规划失败,跳别的任务了未按优先级执行)在仓库)
// 车空闲
foreach (var car in SimpleLib.GetAllCars().OfType<Car>()) //找到那个车 距离待执行任务最近
{
DeleteTag(car.tags, "changePriority");
//车的状态是OK的
if (car.GetLastSite() == -1 || car.tags.Contains("agvOffline") || car.tags.Contains("occupied")) continue;
//车已经被分配过任务了
if (deliveries.FirstOrDefault(t => t.UsingCar == car) != null) continue;
int dst = 0;
#region ObsoleteCode
//if (car.tags.ContainsKey("occupied")) //说明是正在执行任务的车
//{
// if (car.tags.ContainsKey("dest")
// && deliverys.Where(d => d.usingCar != null && d.usingCar == car) == null //并且这个车没有需要等待的任务执行的任务
// //还存在一种情况车被选了但中途有高优先级的任务进来
// )
// {
// //这个车正在执行的任务
// dst = int.Parse(car.tags["dest"]);
// }
// else
// {
// //这个车已经有需要等待的任务了。
// continue;
// }
//}
//else //车空闲
//{
// dst = car.GetLastSite();
//}
//无效
#endregion
dst = car.GetLastSite();
if (dst == -1)
{
//输出日志 变更dst;
dst = car.status.holdingLocks.First();
Diagnosis.Post($"调度小车{car.name}:{car.id}的GetLastSite 为 -1 。变更 holdingLocks.First() 为小车初始点{dst}");
}
//空闲的车 //如果有多个那个离的最近
foreach (var delivery in deliveries.Where(d => d.UsingCar == null))
{
//车是否能接到该任务。
if (car.fields.ContainsKey("group") && !string.IsNullOrEmpty(car.fields["group"]) && !car.fields["group"].Contains(delivery.Group))
continue;
var mPlan = new SegmentPlan { usingCar = car };
try
{
var myW = mPlan.FindRoute(
SimpleLib.GetSite(dst),
SimpleLib.GetSite(delivery.Src));
//找到最近的,
if (myW < curW)
{
curW = myW;
//找到优先级最高的。且距离最近
if (delivery.Priority >= priority)
{
priorityNextDelivery = delivery;
priority = delivery.Priority;
Diagnosis.Post($"{priorityNextDelivery.Id}/{priorityNextDelivery.Group}/{priority}", " delivery.priority");
}
//找到距离最近的。
Delivery nextDelivery = delivery;
if (nextDelivery != priorityNextDelivery) //并且这两个任务不同 优先级高的比较远
{
if (runningDeliveries != null)
{
//优先级高的先执行
Delivery deliveryRunning = null; // runningDeliveries.Where(d => d.group == priorityNextDelivery.group).FirstOrDefault();
//有车正在执行优先级高的任务。有的话就不让车过去
if (deliveryRunning != null) //true
{
Diagnosis.Post($"{deliveryRunning.Id}/{deliveryRunning.UsingCar?.id}/" +
$"{deliveryRunning.Group}/{deliveryRunning.Priority}", "deliveryRunning");
deliveryTask = nextDelivery;
}
else
{
deliveryTask = priorityNextDelivery;
Diagnosis.Post($"{priorityNextDelivery.Id}/{priorityNextDelivery.UsingCar?.id}/" +
$"{priorityNextDelivery.Group}/{priorityNextDelivery.Priority}", "priorityNextDelivery1");
}
}
else
{
deliveryTask = priorityNextDelivery;
Diagnosis.Post($"{priorityNextDelivery.Id}/{priorityNextDelivery.UsingCar?.id}/" +
$"{priorityNextDelivery.Group}/{priorityNextDelivery.Priority}", "priorityNextDelivery2");
}
}
else
{
deliveryTask = nextDelivery;
Diagnosis.Post($"{nextDelivery.Id}/{nextDelivery.UsingCar?.id}/" +
$"{nextDelivery.Group}/{nextDelivery.Priority}", "nextDelivery");
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message + ex.ToString());
}
}
if (deliveryTask != null)
{
deliveryTask.UsingCar = car;
deliveryTask.Priority = 50;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message + ex.ToString());
}
}
public static async Task GoSite(AbstractCar car, Site targetSite, int step, string action = "/", bool reverse = false)
{
try
{
var plan = new SegmentPlan
{
usingCar = car,
fields =
{
["reverse"] = reverse.ToString(),
["action"] = action,
["allow_destination_on_route"] = "true"
}
};
/* if (stations!=null && stations.Count()>=1)
{
var finishLeftPtlSites = SimpleLib.GetAllSites().Where(site => site.name.Contains("TaskStation")).ToList();
foreach (var site in finishLeftPtlSites)
{
var stationCode = site.name.Split('-')[1];
if (stations.Contains(stationCode))
{
site.fields.Add("askInfons1", "1");
}
}
}*/
plan.FindRoute(SimpleLib.GetSite(car.GetLastSite()), targetSite);
//plan.HintNotEnding();
var program = plan.Compile("move");
car.tags.Add("occupied", $"go{targetSite.id}");
Console.WriteLine($">>Script:{program.script}");
var tsk = program.Queue();
/* if (stations != null && stations.Count() >= 1)
{
var finishLeftPtlSites = SimpleLib.GetAllSites().Where(site => site.name.Contains("TaskStation")).ToList();
foreach (var site in finishLeftPtlSites)
{
var stationCode = site.name.Split('-')[1];
if (stations.Contains(stationCode))
{
site.fields.Remove("askInfons1");
}
}
}*/
await tsk;
car.tags.Remove("occupied");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message + "重新执行");
Thread.Sleep(3000);
}
}
public static SegmentPlan GenerateEscapePlan(SegmentPlan dstPlan, Func<Site, bool> escapeSiteCondition = null)
{
var curW = float.MaxValue;
var car = dstPlan.usingCar;
var escapedSites = SimpleLib.GetAllCars().Where(cc => cc.status.escape.Length > 0)
.Select(cc => cc.status.escape.Last()).ToHashSet();
SegmentPlan planEsc = null;
foreach (var site in SimpleLib.GetAllSites())
{
if (site.id == dstPlan.Destination.id || site.id == dstPlan.Source.id) continue;
if (escapedSites.Contains(site.id)) continue;
if (escapeSiteCondition != null && !escapeSiteCondition(site)) continue;
bool occupied = false;
foreach (var cc in SimpleLib.GetAllCars())
{
if (cc != car)
{
if (cc.status.holdingLocks.Contains(site.id) ||
(cc.tags.Contains("dest") && cc.tags["dest"] == site.id.ToString())) occupied = true;
}
}
if (occupied) continue;
TryGeneratePlan(dstPlan.usingCar, dstPlan.Destination, site, ref curW, ref planEsc);
}
return planEsc;
}
private static void TryGeneratePlan(AbstractCar car, Site srcSite, Site dstSite, ref float curW, ref SegmentPlan generatedPlan)
{
var mPlan = new SegmentPlan { usingCar = car, findLoop = false, fields = new Dictionary<string, string>() };
mPlan.fields["forbid_cross"] = "false";
try
{
var actuallyFindRoute = false;
var myw = TryGetWeight(srcSite, dstSite);
if (myw < 0)
{
myw = mPlan.FindRoute(srcSite, dstSite);
AddWeight(srcSite, dstSite, myw);
actuallyFindRoute = true;
}
if (myw < curW)
{
if (!actuallyFindRoute) mPlan.FindRoute(srcSite, dstSite);
curW = myw;
generatedPlan = mPlan;
}
}
catch
{
// ignored
}
}
public static Dictionary<(Site, Site), float> weightDictionary = new();
public static float TryGetWeight(Site s1, Site s2)
{
lock (weightDictionary)
{
if (weightDictionary.TryGetValue((s1, s2), out var w)) return w;
if (weightDictionary.TryGetValue((s2, s1), out var ww)) return ww;
}
return -1;
}
public static void AddWeight(Site s1, Site s2, float w)
{
lock (weightDictionary) weightDictionary[(s1, s2)] = w;
}
public static SegmentPlan SimpleToNearestPlan(AbstractCar car, Func<Site, bool> test, int srcID = -1)
{
var curw = float.MaxValue;
SegmentPlan targetPlan = null;
var srcSite = SimpleLib.GetSite(srcID == -1 ? car.GetLastSite() : srcID);
foreach (var site in SimpleLib.GetAllSites())
{
if (!test(site)) continue;
if (site.id == srcSite.id) continue;
bool occupied = false;
foreach (var cc in SimpleLib.GetAllCars())
{
// if (cc != car)//当前
{
if (cc.status.holdingLocks.Contains(site.id) ||
(cc.tags.Contains("dest") && cc.tags["dest"] == site.id.ToString())) occupied = true;
}
}
if (occupied) continue;
TryGeneratePlan(car, srcSite, site, ref curw, ref targetPlan);
}
return targetPlan;
}
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private static bool ShowConsole = false;
public static void ShowConsoleFun()
{
ShowConsole = !ShowConsole;
var handle = GetConsoleWindow();
int n = ShowConsole ? 0 : 5;
Console.WriteLine(n);
ShowWindow(handle, n);
}
public static void HideConsoleFun()
{
var handle = GetConsoleWindow();
ShowWindow(handle, 0);
}
}
}