using Newtonsoft.Json; using SimpleLite; using SimpleLite.RCS; using SimpleLite.RCS.CarTypes; using SimpleLite.CADTools; using SimpleLite.Props; using SimpleLite.UI; using SimpleCore; using SimpleCore.Compiler; using SimpleCore.Library; using SimpleCore.PropType; using StandardScene.Chained; using StandardScene.InterLock; using StandardScene.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using StandardScene.Model; using static StandardScene.Chained.ChainedDeliveryMission; namespace StandardScene.Charge { #region Enums and Status Classes /// /// 充电决策枚举 /// public enum ChargeDecision { MustCharge, AvailableToTask, NoCharge, } /// /// 充电任务状态类 /// public class AbstractChargeMissionStatus : MissionStatus { #region 运行时状态 /// 空闲充电桩数量 public int freeChargeCount; /// 当前最低电量车辆的SOC public float lowerSocCar; /// 空闲车辆比例 public double freeCarPercent; #endregion #region 电量阈值配置 /// 必须充电电量阈值(低于此值必须充电) public double mustChargeSoc; /// 空闲充电电量阈值 public double idleChargeSoc; /// 任务可用电量阈值(高于此值可执行任务) public double taskAvailableSoc; /// 允许打断充电的最低电量 public double allowInterruptSoc; /// 满电阈值 public double fullChargeSoc; #endregion #region 时间配置 /// 空闲后触发充电的等待秒数 public double idleChargeSeconds; /// 必须充电的最短秒数 public double mustChargeSeconds; /// 空闲后触发回待命的等待秒数 public double idleSeconds; /// 补电保持分钟数(充到100%后继续保持) public double topUpMinutes = 5; #endregion #region 策略开关 /// 最小允许空闲车去充电的任务数 public int minAllowFreeCarToChargeTaskCnt; /// 允许充电数量 public int allowChargeCnt; /// 是否允许打断充电去执行任务 public bool allowInterruptTask; /// 是否使用最低电量优先充电 public bool useLowerSocForCharge; /// 低电量车辆换充阈值差 public int lowPowerCarSwapChangeThresholdDelta = 15; /// 是否启用充电异常检测 public bool enableErrorChargeDetection = false; /// 是否使用充电站过滤器 public bool useChargeSiteFilter = false; #endregion } /// /// 单个车辆处理过程中的上下文信息 /// public class CarChargeContext { /// 当前处理的车辆 public Car Car { get; set; } /// 当前电量百分比 public double Soc { get; set; } /// 当前充电电流 public double ElectricCurrent { get; set; } /// 是否必须充电 public bool MustCharge { get; set; } /// 是否应该去充电 public bool ShouldGoCharge { get; set; } /// 是否应该去待命点 public bool ShouldGoStandby { get; set; } /// 车辆是否在待命点 public bool CarInStandbySite { get; set; } /// 车辆是否在充电站 public bool CarInChargeSite { get; set; } /// 充电后是否可执行任务 public bool ChargingTaskAvailable { get; set; } /// 等待执行的任务数量 public int WaitingMissions { get; set; } /// 空闲开始时间 public DateTime IdleTime { get; set; } /// 目标类型:1充电,2待命 public int TargetType { get; set; } /// 目标路径规划 public SegmentPlan TargetPlan { get; set; } } #endregion /// /// 抽象充电任务基类 /// 负责管理车辆的自动充电和待命调度 /// public class AbstractChargeLogiceMission : AbstractInterlockMission { #region Constants /// 主循环间隔(毫秒) protected const int LOOP_INTERVAL_MS = 500; /// 必须充电触发延迟(毫秒) protected const int MUST_CHARGE_TRIGGER_DELAY_MS = 5000; /// 目标类型:充电 protected const int TARGET_TYPE_CHARGE = 1; /// 目标类型:待命 protected const int TARGET_TYPE_STANDBY = 2; /// 最大不可达站点记录数 protected const int MAX_UNDELIVERABLE_SITES = 5; /// 触发任务打断的最小等待任务数 protected const int MAX_WAIT_WAITMISSION = 1; /// 补电相关标签名称 private static readonly string[] TopUpTags = { "topup", "topupInProgress", "topupStart", "topupEnd", "topupMinutes", "topupRequested" }; #endregion #region Fields and Properties public override MissionStatus status { get; set; } = new AbstractChargeMissionStatus(); [JsonIgnore] private protected Thread myThread; [JsonIgnore] public Func chargeStrategy = null; [JsonIgnore] public bool started = false; [JsonIgnore] private int _interruptingCarId = -1; [JsonIgnore] private int _bestInterruptionCandidateId = -1; [JsonIgnore] private readonly object _interruptLock = new object(); /// 获取状态对象的类型转换 protected AbstractChargeMissionStatus AcmStatus => (AbstractChargeMissionStatus)status; #endregion #region Virtual Methods - 扩展点 /// /// 小车到达充电站或待命点时的动作 /// public virtual void ArriveAction(Car car, Site site) { } /// /// 小车离开充电站或待命点时的动作 /// public virtual void LeaveAction(Car car, Site site) { } /// /// 根据小车获取对应车所允许停靠的休息点类型 /// public virtual string GetStandbyType(AbstractCar car) => "standby"; /// /// 根据小车获取对应车所允许停靠的充电点类型 /// public virtual string GetChargeType(AbstractCar car) => "Charge"; /// /// 获取小车对应车型待执行的任务数量 /// public virtual int WaitingMissions(AbstractCar car) { var waitingDeliveries = SimpleProject.proj.Missions .OfType() .First() .GetDeliveries() .FindAll(p => p.GetStatus() == DeliveryStatus.Waiting); return waitingDeliveries.Count; } /// /// 额外的对充电站的筛选要求(如在线状态) /// public virtual bool ChargeSiteFilter(Site site) => true; /// /// 获取不在充电的最低电量车辆的SOC值 /// public virtual float LowerCarSoc(AbstractCar car, List allChargeSite) => 0; /// /// 判断车辆是否在线 /// public virtual bool IsOnlineCar(Car car) => false; /// /// 是否禁止小车回待命点或充电点 /// public virtual bool ForbidBackToStandbyOrCharge(AbstractCar car) => false; /// /// 验证车辆是否可以处理 /// protected virtual bool ValidateCarForProcessing(Car car) { if (Commons.GetVehicleStatus(car)!= VehicleStatus.Normal) return false; if (car.GetLastSite() == -1) return false; if (car.fields.ContainsKey("arm")) return false; return true; } /// /// 判断充电桩是否异常 /// protected virtual bool IsChargePointError(Car car, int chargeSiteId) { return car != null && Commons.CarValue(car, "electricCurrent") < 0; } #endregion #region Mission Execution - 主执行逻辑 [MethodMember(Name = "启动进程", Description = "开始处理充电维护进程")] public override void Execute() { status.status = "已启动"; if (started) return; started = true; myThread = new Thread(ChargeLoop) { Name = "AbstractChargeMission" }; myThread.Start(); } /// /// 充电主循环 /// private void ChargeLoop() { while (started) { Thread.Sleep(LOOP_INTERVAL_MS); try { UpdateMissionParameters(); // 按电量从低到高处理所有车辆 var orderedCars = SimpleLib.GetAllCars() .OfType() .OrderBy(cc => Commons.CarValue(cc, "Soc")); foreach (var car in orderedCars) { var allChargeSites = GetChargeSitesForCar(car); var chargingCarCount = CalculateGlobalStats(allChargeSites); ProcessSingleCar(car, allChargeSites, chargingCarCount); } } catch (Exception e) { Diagnosis.Log($"chargeMission error:{ExceptionFormatter.FormatEx(e)}", "error", true); } } } /// /// 获取车辆可用的充电站点列表 /// private List GetChargeSitesForCar(Car car) { var chargeType = GetChargeType(car); var allChargeSites = SimpleLib.GetAllSites() .Where(p => p.fields.TryGetValue("group", out var group) && chargeType.Contains(group)) .ToList(); if (AcmStatus.useChargeSiteFilter) { allChargeSites = allChargeSites.Where(ChargeSiteFilter).ToList(); } return allChargeSites; } #endregion #region Top-Up (补电) 功能 [MethodMember(Name = "补电功能", Description = "将指定小车发送去补电(到100%并保持若干分钟)")] public void StartTopUp() { try { G.pushStatus("选择小车"); var obj = SimpleMonitor.selected.ToArray()[0]; if (obj is Car car) { if (car == null) { Diagnosis.Post($"StartTopUp failed: car not found", "StartTopUpCharge", true); return; } Commons.AddOrUpdateTag(car.tags, "topup", "requested"); Commons.AddOrUpdateTag(car.tags, "shouldCharge", "true"); Diagnosis.Log($"StartTopUp: car {car.name}({car.id}) requested top-up", "StartTopUpCharge", true); } } catch (Exception ex) { Diagnosis.Log($"StartTopUp error: {ExceptionFormatter.FormatEx(ex)}", "error", true); } } /// /// 测试Block功能(内部调试用) /// public void TestBlock() { G.pushStatus("选择小车"); var obj = SimpleMonitor.selected.ToArray()[0]; if (obj is Car car && car != null) { CommandToBlock(car.id); } } /// /// 处理补电生命周期 /// private void HandleTopUpLifecycle(CarChargeContext context) { var car = context.Car; // 如果没有补电相关标签,直接返回 if (!IsInTopUpMode(car)) return; // 补电请求但未到充电站,确保shouldCharge存在 if (car.tags.Contains("topup") && !context.CarInChargeSite) { Commons.AddOrUpdateTag(car.tags, "shouldCharge", "true"); return; } // 已到充电站且处于补电模式 if (car.tags.Contains("topup") && context.CarInChargeSite) { HandleTopUpAtChargeSite(context); } } /// /// 处理车辆在充电站时的补电逻辑 /// private void HandleTopUpAtChargeSite(CarChargeContext context) { var car = context.Car; // 电量达到100%且未开始计时,启动补电计时器 if (context.Soc >= 100 && !car.tags.Contains("topupInProgress")) { StartTopUpTimer(car); } // 补电进行中,检查是否完成 if (car.tags.Contains("topupInProgress")) { CheckTopUpCompletion(car, context.Soc); } } /// /// 启动补电计时器 /// private void StartTopUpTimer(Car car) { double minutes = AcmStatus.topUpMinutes; if (car.tags.TryGetValue("topupMinutes", out var mStr) && double.TryParse(mStr, out var mVal) && mVal > 0) { minutes = mVal; } var start = DateTime.Now; var end = start.AddMinutes(minutes); Commons.AddOrUpdateTag(car.tags, "topupStart", start.ToString("o")); Commons.AddOrUpdateTag(car.tags, "topupEnd", end.ToString("o")); Commons.AddOrUpdateTag(car.tags, "topupInProgress", "true"); Diagnosis.Log($"TopUp started for car {car.name}({car.id}), until {end}", "Charge", true); } /// /// 检查补电是否完成 /// private void CheckTopUpCompletion(Car car, double soc) { if (!car.tags.TryGetValue("topupEnd", out var endStr) || !DateTime.TryParse(endStr, out var endTime)) { return; } if (DateTime.Now >= endTime && soc >= 100) { ClearTopUpTags(car); Commons.DeleteTag(car.tags, "shouldCharge"); Commons.AddOrUpdateTag(car.tags, "mustToStandby", "true"); Diagnosis.Log($"TopUp completed for car {car.name}({car.id})", "Charge", true); } } /// /// 清理所有补电相关标签 /// private void ClearTopUpTags(Car car) { foreach (var tag in TopUpTags) { Commons.DeleteTag(car.tags, tag); } } /// /// 判断车辆是否处于补电模式 /// private bool IsInTopUpMode(Car car) { return car.tags.Contains("topup") || car.tags.Contains("topupInProgress"); } #endregion #region Parameter Management - 参数管理 private void UpdateMissionParameters() { try { // 从充电策略配置服务读取参数 var config = ChargeStationHelper.GetChargeStrategyConfig(); if (config != null) { // SOC 相关参数 AcmStatus.mustChargeSoc = config.MustChargeSoc; AcmStatus.idleChargeSoc = config.IdleChargeSoc; AcmStatus.taskAvailableSoc = config.TaskAvailableSoc; AcmStatus.fullChargeSoc = config.FullChargeSoc; AcmStatus.allowInterruptSoc = config.AllowInterruptSoc; // 时间相关参数 AcmStatus.idleChargeSeconds = config.IdleChargeSeconds; AcmStatus.idleSeconds = config.IdleSeconds; AcmStatus.mustChargeSeconds = config.MustChargeSeconds; AcmStatus.topUpMinutes = config.TopUpMinutes; // 任务相关参数 AcmStatus.minAllowFreeCarToChargeTaskCnt = config.MinAllowFreeCarToChargeTaskCnt; // 开关参数 AcmStatus.allowInterruptTask = config.AllowInterruptTask; AcmStatus.useLowerSocForCharge = config.UseLowerSocForCharge; AcmStatus.enableErrorChargeDetection = config.EnableErrorChargeDetection; AcmStatus.useChargeSiteFilter = config.UseChargeSiteFilter; //Diagnosis.Log("充电策略参数已从配置文件加载"); } else { // 配置为空,使用旧的方式(从 fields 读取) LoadParametersFromFields(); } } catch (Exception ex) { Diagnosis.Log($"加载充电策略参数失败,使用fields或默认值: {ex.Message}"); // 出错时使用旧的方式(从 fields 读取) LoadParametersFromFields(); } } /// /// 从 fields 字典加载参数(兼容旧方式) /// private void LoadParametersFromFields() { AcmStatus.mustChargeSoc = ParseDouble("mustChargeSoc", 20); AcmStatus.idleChargeSoc = ParseDouble("idleChargeSoc", 90); AcmStatus.taskAvailableSoc = ParseDouble("taskAvailableSoc", 60); AcmStatus.fullChargeSoc = ParseDouble("fullChargeSoc", 90); AcmStatus.allowInterruptSoc = ParseDouble("allowInterruptSoc", 45); AcmStatus.idleChargeSeconds = ParseDouble("idleChargeSeconds", 30); AcmStatus.idleSeconds = ParseDouble("idleSeconds", 5); AcmStatus.mustChargeSeconds = ParseDouble("mustChargeSeconds", 60); AcmStatus.topUpMinutes = ParseDouble("topUpMinutes", 5); AcmStatus.minAllowFreeCarToChargeTaskCnt = ParseInt("minAllowFreeCarToChargeTaskNub", 0); AcmStatus.allowInterruptTask = ParseBool("allowInterruptTask", false); AcmStatus.useLowerSocForCharge = ParseBool("useLowerSocForCharge", true); AcmStatus.enableErrorChargeDetection = ParseBool("enableErrorChargeDetection", false); AcmStatus.useChargeSiteFilter = ParseBool("useChargeSiteFilter", false); } private double ParseDouble(string key, double defaultValue) { return fields.TryGetValue(key, out var str) && double.TryParse(str, out var val) ? val : defaultValue; } private int ParseInt(string key, int defaultValue) { return fields.TryGetValue(key, out var str) && int.TryParse(str, out var val) ? val : defaultValue; } private bool ParseBool(string key, bool defaultValue) { return fields.TryGetValue(key, out var str) && bool.TryParse(str, out var val) ? val : defaultValue; } #endregion #region Global Stats - 全局统计 private int CalculateGlobalStats(List allChargeSites) { var validCars = SimpleLib.GetAllCars().Where(p => p.GetLastSite() != -1).ToList(); int chargingCarCount = 0; int freeCarCount = 0; int onlineCarCount = 0; foreach (var car in validCars) { if (IsCarOccupyingChargeSite(car, allChargeSites)) chargingCarCount++; if (car.tags.Contains("idle") && car.status.pendingLocks.Length == 0) freeCarCount++; if (IsOnlineCar((Car)car)) onlineCarCount++; } if (onlineCarCount != 0) AcmStatus.freeCarPercent = (float)freeCarCount / onlineCarCount; return chargingCarCount; } private bool IsCarOccupyingChargeSite(AbstractCar car, List allChargeSites) { return allChargeSites.Any(site => car.status.holdingLocks.Contains(site.id) || car.status.pendingLocks.Contains(site.id)); } #endregion #region Car Processing - 单车处理主流程 private void ProcessSingleCar(Car car, List allChargeSites, int chargingCarCount) { if (!ValidateCarForProcessing(car)) return; var context = BuildCarContext(car, allChargeSites, chargingCarCount); // 清理待命标签 if (context.CarInStandbySite && car.tags.Contains("mustToStandby")) Commons.DeleteTag(car.tags, "mustToStandby"); // 任务打断检查 HandleTaskInterruption(context); // 前置条件检查 if (IsCarBusy(car) || ForbidBackToStandbyOrCharge(car)) return; // 必须充电逻辑 HandleMustChargeLogic(context); // 更新空闲和充电状态 if (!UpdateIdleAndChargeStatus(context)) return; // 处理补电生命周期 HandleTopUpLifecycle(context); // 决定充电或回待命 DecideChargeOrStandby(context); // 规划路径并执行 PlanAndExecuteMovement(context, allChargeSites); } private CarChargeContext BuildCarContext(Car car, List allChargeSites, int chargingCarCount) { var soc = Commons.CarValue(car, "Soc"); var electricCurrent = Commons.CarValue(car, "ElectricCurrent"); var carSiteId = car.GetLastSite(); var context = new CarChargeContext { Car = car, Soc = soc, ElectricCurrent = electricCurrent, MustCharge = soc < AcmStatus.mustChargeSoc, WaitingMissions = WaitingMissions(car), CarInStandbySite = SimpleLib.GetAllSites() .Any(p => p.fields.ContainsKey("standby") && p.id == carSiteId), CarInChargeSite = car.status.holdingLocks.Length == 1 && SimpleLib.GetAllSites().Any(p => p.fields.ContainsKey("Charge") && p.id == car.status.holdingLocks.First()) }; // 更新全局状态 AcmStatus.freeChargeCount = allChargeSites.Count - chargingCarCount; AcmStatus.lowerSocCar = LowerCarSoc(car, allChargeSites); if (car.tags.TryGetValue("idle", out var strIdleTime)) context.IdleTime = DateTime.Parse(strIdleTime); return context; } private bool IsCarBusy(Car car) { return car.tags.Contains("occupied") || car.tags.Contains("blocking") || car.status.pendingLocks.Length != 0; } #endregion #region Charging Logic - 充电逻辑 /// /// 处理任务打断逻辑 /// private void HandleTaskInterruption(CarChargeContext context) { lock (_interruptLock) { // 清理过时的打断持有者 CleanupStaleInterruptHolder(); var occupiedValue = GetOccupiedTagValue(context.Car); var isMaintenanceTask = IsMaintenanceTask(occupiedValue); // 判断是否可以考虑触发打断 var canConsiderTrigger = CanConsiderInterrupt(context, isMaintenanceTask); // 评选最佳候选车 if (canConsiderTrigger) { SelectBestInterruptCandidate(context); } // 只有最佳候选车才触发打断 bool isBestCandidate = context.Car.id == _bestInterruptionCandidateId; LadderLogic.TriggerOnce(canConsiderTrigger && isBestCandidate, 5000, () => { ExecuteInterrupt(context, occupiedValue); }, context.Car.id); } } /// /// 清理过时的打断持有者 /// private void CleanupStaleInterruptHolder() { if (_interruptingCarId == -1) return; var holder = SimpleLib.GetAllCars().OfType().FirstOrDefault(c => c.id == _interruptingCarId); if (holder == null) { _interruptingCarId = -1; return; } var holderOccupied = GetOccupiedTagValue(holder); if (!IsMaintenanceTask(holderOccupied)) { _interruptingCarId = -1; } } /// /// 判断是否为维护任务(充电或待命) /// private bool IsMaintenanceTask(string occupiedValue) { return occupiedValue.Contains("toCharge") || occupiedValue.Contains("toStandby"); } /// /// 判断是否可以考虑触发打断 /// private bool CanConsiderInterrupt(CarChargeContext context, bool isMaintenanceTask) { return context.WaitingMissions > 0 && context.Soc > AcmStatus.allowInterruptSoc && FreeCarNub() == 0 && AcmStatus.allowInterruptTask && isMaintenanceTask && _interruptingCarId == -1; } /// /// 评选最佳打断候选车 /// private void SelectBestInterruptCandidate(CarChargeContext context) { // 获取所有满足打断条件的车辆 var candidates = SimpleLib.GetAllCars().OfType() .Where(c => { var cOccupied = GetOccupiedTagValue(c); var cSoc = Commons.CarValue(c, "Soc"); return IsMaintenanceTask(cOccupied) && cSoc > AcmStatus.allowInterruptSoc; }).ToList(); if (!candidates.Any()) return; // 获取第一个等待任务 var firstMission = SimpleProject.proj.Missions.OfType() .First() .GetDeliveries() .FindAll(p => p.GetStatus() == DeliveryStatus.Waiting) .OrderBy(p => p.CreateTime) .FirstOrDefault(); Car bestCandidate; if (firstMission != null) { // 优先选择距离任务起点最近的车,其次选择电量最高的车 bestCandidate = candidates .OrderBy(c => CalculateRouteLength(c, ((TransportDelivery)firstMission).Src)) .ThenByDescending(c => Commons.CarValue(c, "Soc")) .FirstOrDefault(); } else { // 仅按电量排序 bestCandidate = candidates .OrderByDescending(c => Commons.CarValue(c, "Soc")).FirstOrDefault(); } if (bestCandidate != null) { _bestInterruptionCandidateId = bestCandidate.id; Diagnosis.Post($"更新最佳候选车ID:{bestCandidate.id}", "CommandToBlock", true); } } /// /// 计算车辆到目标站点的路径长度 /// private float CalculateRouteLength(Car car, int targetSiteId) { var mPlan = new SegmentPlan { usingCar = car }; try { var routeLength = mPlan.FindRoute( SimpleLib.GetSite(car.GetLastSite()), SimpleLib.GetSite(targetSiteId)); Diagnosis.Post($"当前尝试打断车{car.id}距离任务起点{mPlan.Destination.id}距离:{routeLength}", "CommandToBlock", true); return routeLength; } catch { return float.MaxValue; } } /// /// 执行打断操作 /// private void ExecuteInterrupt(CarChargeContext context, string occupiedValue) { lock (_interruptLock) { if (_interruptingCarId == -1) { _interruptingCarId = context.Car.id; Diagnosis.Post($"小车:{context.Car.name},触发打断任务,missions:{context.WaitingMissions}," + $"isOccupied:{occupiedValue},soc:{context.Soc},freecar:{FreeCarNub()}", "CommandToBlock", true); CommandToBlock(context.Car.id); _bestInterruptionCandidateId = -1; } else { Diagnosis.Post($"跳过打断:已有车辆({_interruptingCarId})正在被打断,当前尝试车:{context.Car.id}", "CommandToBlock", true); } } } /// /// 处理必须充电逻辑 /// private void HandleMustChargeLogic(CarChargeContext context) { LadderLogic.TriggerOnce( context.MustCharge && !context.Car.tags.Contains("shouldCharge"), MUST_CHARGE_TRIGGER_DELAY_MS, () => { Diagnosis.Log($"小车:{context.Car.name},必须充电,当前电量:{context.Soc}", "Charge", true); Commons.AddOrUpdateTag(context.Car.tags, "shouldCharge", "true"); }, context.Car.id); } /// /// 更新空闲和充电状态 /// private bool UpdateIdleAndChargeStatus(CarChargeContext context) { var car = context.Car; lock (Commons.PlanSession) { if (car.tags.Contains("occupied") || car.tags.Contains("blocking")) return false; if (!car.tags.Contains("idle")) car.tags.Add("idle", DateTime.Now.ToString()); // 如果正在补电,即使电量满了也不应自动去待命 if (context.Soc > AcmStatus.fullChargeSoc && !IsInTopUpMode(car)) context.ShouldGoStandby = true; } if (!car.tags.TryGetValue("idle", out var strIdleTime)) return false; context.IdleTime = DateTime.Parse(strIdleTime); // 清理充电标记 if (car.tags.Contains("charging") && !context.CarInChargeSite) car.tags.Remove("charging"); // 判断是否需要充电 var carFree = context.Soc <= AcmStatus.taskAvailableSoc && AcmStatus.freeChargeCount > 0; var idleCharge = (context.Soc < AcmStatus.idleChargeSoc || carFree) && (DateTime.Now - context.IdleTime).TotalSeconds > AcmStatus.idleChargeSeconds; UpdateShouldChargeTag(context, idleCharge); return true; } /// /// 更新shouldCharge标签 /// private void UpdateShouldChargeTag(CarChargeContext context, bool idleCharge) { var car = context.Car; if ((context.MustCharge || (idleCharge && context.WaitingMissions <= AcmStatus.minAllowFreeCarToChargeTaskCnt)) && !context.ShouldGoStandby) { bool lowerSocOk = (AcmStatus.useLowerSocForCharge && context.Soc <= AcmStatus.lowerSocCar) || !AcmStatus.useLowerSocForCharge; if (((context.MustCharge || idleCharge) && lowerSocOk) && AcmStatus.freeChargeCount > 0 && context.Soc < AcmStatus.fullChargeSoc) { Commons.AddOrUpdateTag(car.tags, "shouldCharge", "true"); Diagnosis.Log($"小车:{car.name},需求充电,当前电量:{context.Soc},lowerSocCar:{AcmStatus.lowerSocCar}," + $"mustCharge:{context.MustCharge},idleCharge:{idleCharge},waitingMissions:{context.WaitingMissions}", "Charge", true); } else { Commons.DeleteTag(car.tags, "shouldCharge"); } } } #endregion #region Decision Making - 决策逻辑 /// /// 决定充电或回待命 /// private void DecideChargeOrStandby(CarChargeContext context) { var car = context.Car; lock (Commons.PlanSession) { var carSiteId = car.GetLastSite(); if (carSiteId == -1 || car.tags.Contains("occupied") || car.tags.Contains("blocking")) return; // 更新充电状态 UpdateChargingTag(context); // 检查充电异常 ErrorChargeTriggerToStandby(context); // 清除shouldCharge标记 if ((context.ChargingTaskAvailable || (context.WaitingMissions > 0 && context.Soc > AcmStatus.taskAvailableSoc && !car.tags.Contains("charging"))) && car.tags.Contains("shouldCharge")) { Commons.DeleteTag(car.tags, "shouldCharge"); } // 检查是否可以离开充电站执行任务 CheckCanLeaveForTask(context); // 决定去充电 if (car.tags.Contains("shouldCharge") && !CarInChargeSite(car, carSiteId)) { context.ShouldGoCharge = true; Diagnosis.Log($"selected car:{car.name}|{car.id} to charge", "Charge", true); } // 决定去待命 if (!context.ShouldGoCharge) { CheckShouldGoStandby(context, carSiteId); } } } /// /// 更新充电标签 /// private void UpdateChargingTag(CarChargeContext context) { var car = context.Car; var isTopUping = IsInTopUpMode(car); if (context.CarInChargeSite) { if (context.Soc <= AcmStatus.fullChargeSoc || isTopUping) { if (!car.tags.Contains("charging")) car.tags.Add("charging", DateTime.Now.ToString()); } else if (!isTopUping) { if (car.tags.Contains("charging")) car.tags.Remove("charging"); context.ChargingTaskAvailable = true; } } } /// /// 检查是否可以离开充电站执行任务 /// private void CheckCanLeaveForTask(CarChargeContext context) { var car = context.Car; var canInterruptForTask = context.WaitingMissions > MAX_WAIT_WAITMISSION && context.Soc > AcmStatus.allowInterruptSoc; if (context.Soc > AcmStatus.taskAvailableSoc && car.tags.Contains("charging")) { var onlyOneCarCharge = SimpleLib.GetAllCars().Count(p => p.GetLastSite() != -1) == 1; var canLeaveButStillCharge = (context.Soc - AcmStatus.lowerSocCar) <= AcmStatus.lowPowerCarSwapChangeThresholdDelta && (context.WaitingMissions <= AcmStatus.minAllowFreeCarToChargeTaskCnt || context.Soc <= AcmStatus.lowerSocCar || onlyOneCarCharge || (context.WaitingMissions == 0 && context.Soc < AcmStatus.fullChargeSoc)); // 补电模式不允许离开 if (IsInTopUpMode(car)) return; // 满足任务打断条件或满足原有离开条件 if (canInterruptForTask || (car.tags.TryGetValue("charging", out var cTime) && DateTime.Parse(cTime).AddSeconds(AcmStatus.mustChargeSeconds) < DateTime.Now && !canLeaveButStillCharge)) { car.tags.Remove("charging"); context.ChargingTaskAvailable = true; Commons.AddOrUpdateTag(car.tags, "mustToStandby", "true"); Commons.DeleteTag(car.tags, "shouldCharge"); if (canInterruptForTask) { Diagnosis.Log($"小车:{car.name},因有任务且电量{context.Soc}高于{AcmStatus.allowInterruptSoc},中断充电去执行任务", "Charge", true); } } } } /// /// 检查是否应该去待命点 /// private void CheckShouldGoStandby(CarChargeContext context, int carSiteId) { var car = context.Car; var standbyType = GetStandbyType(car); bool isBlocked = SimpleLib.GetAllCars() .Any(c => c.status.pendingLocks.Length != 0 && c.id != car.id && c.status.pendingLocks.Contains(car.GetLastSite())); if (SimpleLib.GetSite(carSiteId).fields.ContainsKey(standbyType) && !isBlocked) return; if ((DateTime.Now - context.IdleTime).TotalSeconds < AcmStatus.idleSeconds) return; context.ShouldGoStandby = true; } /// /// 充电异常触发回待命点 /// private void ErrorChargeTriggerToStandby(CarChargeContext context) { if (!AcmStatus.enableErrorChargeDetection || !context.CarInChargeSite) return; var currentChargeSite = context.Car.status.holdingLocks.First(); bool isChargeError = IsChargePointError(context.Car, currentChargeSite); LadderLogic.TriggerOnce(isChargeError, 10000, () => { HandleChargeError(context, currentChargeSite); }, context.Car.id); } /// /// 处理充电异常 /// private void HandleChargeError(CarChargeContext context, int currentChargeSite) { Commons.DeleteTag(context.Car.tags, "shouldCharge"); if (context.Car.tags.Contains("charging")) context.Car.tags.Remove("charging"); if (AcmStatus.freeChargeCount == 0) { Commons.AddOrUpdateTag(context.Car.tags, "mustToStandby", "true"); context.ShouldGoStandby = true; Diagnosis.Log($"小车:{context.Car.name},充电桩{currentChargeSite}异常,无空闲充电桩,返回待命点", "ChargeError", true); } else { context.ShouldGoCharge = true; Commons.AddOrUpdateTag(context.Car.tags, "mustToAnotherChargeSite", $"{currentChargeSite}"); context.ShouldGoStandby = false; Diagnosis.Log($"小车:{context.Car.name},充电桩{currentChargeSite}异常,切换到其他充电桩", "ChargeError", true); } } /// /// 判断当前站点是否是其他小车pendingLocks的终点,若是则需要避让 /// private bool CheckPendingEndpointConflict(int siteId, Car currentCar) { if (siteId == -1) return false; foreach (var other in SimpleLib.GetAllCars()) { if (other.id == currentCar.id) continue; var pending = other.status.pendingLocks.Contains(siteId); var holding = other.status.holdingLocks.Contains(siteId); if (holding || pending) { return true; } if (other.tags.TryGetValue("conflictTask", out string conflictTask)) { if (conflictTask.Contains(siteId.ToString())) { return true; } } } return false; } #endregion #region Movement Planning - 移动规划 /// /// 规划路径并执行移动 /// private void PlanAndExecuteMovement(CarChargeContext context, List allChargeSites) { var car = context.Car; var carCharging = allChargeSites.Contains(SimpleLib.GetSite(car.GetLastSite())); // 规划去充电站 if (ShouldPlanToCharge(context)) { context.TargetPlan = PlanToChargeSite(context); if (context.TargetPlan != null) { car.tags.Add("occupied", "toCharge"); context.TargetType = TARGET_TYPE_CHARGE; } } // 规划去待命点 if (context.TargetPlan == null && ShouldPlanToStandby(context, carCharging)) { context.TargetPlan = PlanToStandbySite(context); if (context.TargetPlan != null) { car.tags.Add("occupied", "toStandby"); context.TargetType = TARGET_TYPE_STANDBY; } } if (context.TargetPlan == null) return; ExecuteMovement(context); } private bool ShouldPlanToCharge(CarChargeContext context) { var car = context.Car; return (context.ShouldGoCharge || (car.tags.ContainsKey("mustToAnotherChargeSite") && car.tags.ContainsKey("shouldCharge"))) && Commons.SelectCar(car, false, true, true) != -1 && !car.tags.Contains("mustToStandby"); } private bool ShouldPlanToStandby(CarChargeContext context, bool carCharging) { var car = context.Car; return context.ShouldGoStandby && (car.tags.Contains("mustToStandby") || !carCharging || (carCharging && context.ChargingTaskAvailable)) && Commons.SelectCar(car, checkHoldCar: true) != -1; } private SegmentPlan PlanToChargeSite(CarChargeContext context) { var car = context.Car; return Commons.GetNearestPlan(car, site => { if (IsSiteOccupied(site)) return false; bool mustToAnotherChargeSite = car.tags.TryGetValue("mustToAnotherChargeSite", out var chargingSite) && int.Parse(chargingSite) == site.id; return site.fields.ContainsKey("group") && site.fields.ContainsKey("Charge") && GetChargeType(car).Contains(site.fields["group"]) && ((!mustToAnotherChargeSite && context.CarInChargeSite) || !context.CarInChargeSite); }); } private SegmentPlan PlanToStandbySite(CarChargeContext context) { var car = context.Car; return Commons.SimpleToNearestPlan(car, site => { if (IsSiteOccupied(site)) return false; if (IsUndeliverableSite(car, site.id)) return false; var isConflict= CheckPendingEndpointConflict(car.status.holdingLocks.First(),car) &&(site.fields.ContainsKey("standby")|| site.fields.ContainsKey("giveWay")); return ((site.fields.ContainsKey("standby") || isConflict)) && !site.tags.Contains("unavailable") && !context.CarInStandbySite; }); } /// /// 执行移动 /// private void ExecuteMovement(CarChargeContext context) { var car = context.Car; var targetPlan = context.TargetPlan; CarProgram program; try { program = targetPlan.Compile( $"{car.name}({car.id}) maintenance. charging?{context.ShouldGoCharge}--{targetPlan.Destination.id}"); } catch (Exception ex) { HandleCarFailure(car, ex); if (!context.ShouldGoCharge) AddUndeliverableSite(car, targetPlan.Destination.id); return; } Task.Run(async () => { try { if (context.TargetType == TARGET_TYPE_STANDBY && car.tags.Contains("shouldCharge")) LeaveAction(car, targetPlan.Source); car.tags.Add("dest", $"{targetPlan.Destination.id}"); await program.Queue(); CleanupAfterMovement(car, targetPlan.Destination.id); } catch (Exception e) { Diagnosis.Post($"fail to standby/charge exception:{ExceptionFormatter.FormatEx(e)}"); HandleCarFailure(car); } }); } private void CleanupAfterMovement(Car car, int destinationId) { Commons.DeleteTag(car.tags, "mustToAnotherChargeSite"); Commons.DeleteTag(car.tags, "undeliverableSite"); Commons.DeleteTag(car.tags, "mustToStandby"); car.siteID = destinationId; lock (Commons.PlanSession) car.tags.Remove("occupied"); } #endregion #region Helper Methods - 辅助方法 /// /// 获取occupied标签值 /// public string GetOccupiedTagValue(Car car) { return car.tags.TryGetValue("occupied", out var occupiedValue) ? occupiedValue : ""; } /// /// 处理车辆失败 /// private void HandleCarFailure(Car car, Exception ex = null) { Diagnosis.Post($"car{car.id} failed going standby/charge, {ExceptionFormatter.FormatEx(ex)}"); lock (Commons.PlanSession) { Commons.DeleteTag(car.tags, "occupied"); car.tags.Add("idle", DateTime.Now.ToString()); } } /// /// 获取空闲车辆数量 /// private int FreeCarNub() { return SimpleLib.GetAllCars() .Count(p => !p.tags.Contains("occupied") && p.status.holdingLocks.Length == 1 && p.status.pendingLocks.Length == 0 && !p.tags.ContainsKey("charging") && p.tags.Contains("idle")); } /// /// 判断车辆是否在充电站 /// private bool CarInChargeSite(Car car, int carSiteId) { var site = SimpleLib.GetSite(carSiteId); if (!site.fields.ContainsKey("group")) return false; if (!GetChargeType(car).Contains(site.fields["group"])) return false; if (!site.fields.ContainsKey("Charge")) return false; return true; } /// /// 判断站点是否被占用 /// private bool IsSiteOccupied(Site site) { return SimpleLib.GetAllCars().Any(o => o.status.pendingLocks.Contains(site.id) || o.status.holdingLocks.Contains(site.id)); } /// /// 判断站点是否不可达 /// private bool IsUndeliverableSite(Car car, int siteId) { if (!car.tags.TryGetValue("undeliverableSite", out var sites)) return false; var siteList = sites.Split(','); if (siteList.Length > MAX_UNDELIVERABLE_SITES) Commons.DeleteTag(car.tags, "undeliverableSite"); foreach (var s in siteList) { if (int.TryParse(s, out var id) && id == siteId) { Diagnosis.Log($"{car.name}休息回滚发去待命点,sites[{sites}]失败,undeliverableSite,更换下个站点", "UndeliverableSite", specialFolder: true); return true; } } return false; } /// /// 添加不可达站点记录 /// private void AddUndeliverableSite(Car car, int siteId) { if (car.tags.TryGetValue("undeliverableSite", out var undeliverableSite)) Commons.AddOrUpdateTag(car.tags, "undeliverableSite", $"{undeliverableSite},{siteId}"); else Commons.AddOrUpdateTag(car.tags, "undeliverableSite", siteId.ToString()); } #endregion } }