using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using CommonUsage; using LessokajiWeaverUtilities.Utilities; using Newtonsoft.Json; using SimpleLite; using SimpleLite.RCS; using SimpleLite.RCS.CarTypes; using SimpleCore; using SimpleCore.Library; using SimpleCore.PropType; using StandardScene.Chained; using StandardScene.ChargeStationType; using StandardScene.Model; namespace StandardScene.Charge { /// /// 标准充电进程状态 /// public class StandardChargeMissionStatus : AbstractChargeMissionStatus { /// /// 是否屏蔽充电桩交互(true=屏蔽,false=允许) /// public bool ShieldInterLock = false; } /// /// 标准充电进程 /// 负责充电桩的初始化、通讯管理和充电业务逻辑处理 /// [MissionType(Name = "充电进程", editor = typeof(StandardChargeMission))] [I18N.DocumentTranslation(Name = "Charge Mission", locale = "en")] public class StandardChargeMission : AbstractChargeLogiceMission { #region 字段和属性 /// /// 充电站字典 Key=站点ID, Value=充电站对象 /// [JsonIgnore] public Dictionary ChargeStations; /// /// 进程状态 /// public override MissionStatus status { get; set; } = new StandardChargeMissionStatus(); /// /// 进程是否已启动 /// [JsonIgnore] public bool myStarted = false; /// /// 时间同步服务是否已启动 /// [JsonIgnore] public bool tsStarted = false; /// /// 充电处理线程 /// [JsonIgnore] private Thread ChargeThread; /// /// 当前正在充电的车辆 Key=车辆ID, Value=充电次数 /// [JsonIgnore] public Dictionary inChargeCar = new(); /// /// 上一次充电的车辆记录 /// [JsonIgnore] public Dictionary lastinChargeCar = new(); /// /// 充电开始时间 Key=车辆ID, Value=开始时间 /// [JsonIgnore] public Dictionary BeginTime = new(); /// /// 充电条件状态 /// [JsonIgnore] public Dictionary Condition = new(); /// /// 上一次充电条件状态 /// [JsonIgnore] public Dictionary lastCondition = new(); /// /// 充电超时时间(小时) /// [JsonIgnore] public double outtimeOfCharge = 0.5; /// /// UDP通讯服务 /// [JsonIgnore] public ChargeUdpService UdpService; #endregion #region 辅助方法 /// /// 获取最低电量车辆的SOC值 /// 用于充电策略判断,找到系统中电量最低的空闲车辆 /// /// 当前车辆 /// 所有充电站点列表 /// 最低电量值 public override float LowerCarSoc(AbstractCar car, List allChargeSite) { try { // 查找符合条件的最低电量车辆: // 1. 车辆在有效站点上(GetLastSite != -1) // 2. 车辆有坐标信息(haveCoordination) // 3. 车辆未被占用(!occupied) // 4. 车辆未在充电(!charging) // 5. 车辆不在充电站点上 // 6. 车辆在线 var lowCar = SimpleLib.GetAllCars() .OfType() .ToList() .FindAll(p => p.GetLastSite() != -1 && p.haveCoordination && !p.tags.Contains("occupied") && !p.tags.Contains("charging") && !allChargeSite.Contains(SimpleLib.GetSite(p.GetLastSite())) && IsOnlineCar((Car)p)) .OrderBy(p => Commons.CarValue(p, "Soc")) .FirstOrDefault(); // 如果没有找到符合条件的车辆,返回当前车辆的电量 if (lowCar == null) return (float)Commons.CarValue((Car)car, "Soc"); return (float)Commons.CarValue(lowCar, "Soc"); } catch (Exception e) { Diagnosis.Post($"获取最低电量车辆失败: {e.Message}", "error"); return (float)0; } } /// /// 获取车辆的充电类型 /// 根据车辆字段判断是FRLD还是MuXing类型 /// /// 车辆对象 /// 充电类型字符串 public override string GetChargeType(AbstractCar car) { if (car is null) { return ""; } // 优先判断FRLD类型 if (car.fields.ContainsKey("FRLD")) return "FRLD"; // 其次判断MuXing类型 if (car.fields.ContainsKey("MuXing")) return "MuXing"; // 默认返回MuXing return "MuXing"; } /// /// 判断车辆是否在线 /// 通过车辆是否在有效站点上来判断 /// /// 车辆对象 /// true=在线, false=离线 public override bool IsOnlineCar(Car car) { return car.GetLastSite() != -1; } /// /// 车辆到达站点时的处理 /// 记录车辆到达充电站点的日志 /// /// 车辆对象 /// 站点对象 public override void ArriveAction(Car car, Site site) { // 判断是否为充电站点 if (site.fields.TryGetValue("Charge", out var strStationId)) { // 记录到达日志(模拟车辆除外) if (!car.name.Contains("模拟")) { Diagnosis.Post($"{car.name}({car.id})到达充电站点{site.id}"); } } else { Diagnosis.Post($"arrived, site{site.id} is not charge site"); } } /// /// 车辆离开站点时的处理 /// 记录车辆离开充电站点的日志 /// /// 车辆对象 /// 站点对象 public override void LeaveAction(Car car, Site site) { // 判断是否为充电站点 if (site.fields.TryGetValue("Charge", out var strStationId)) { // 记录离开日志(模拟车辆除外) if (!car.name.Contains("模拟")) { Diagnosis.Post($"{car.name}({car.id})离开充电站点{site.id}"); } } else { Diagnosis.Post($"left, site{site.id} is not charge site"); } } /// /// 站点筛选器 /// 判断站点是否为充电站点 /// /// 站点ID /// true=充电站点, false=非充电站点 public override bool SiteFilter(int siteId) { // 如果未屏蔽充电桩交互,返回false if (((StandardChargeMissionStatus)status).ShieldInterLock) { return false; } // 检查站点字段中是否包含"Charge"关键字 var site = SimpleLib.GetSite(siteId); return site.fields.Keys.ToList().Any(p => p.Contains("Charge")); } #endregion #region 核心业务方法 /// /// 启动充电进程 /// 初始化充电站、创建通讯连接、启动充电业务循环 /// [MethodMember(Name = "启动进程", Description = "开始处理充电维护进程")] public override void Execute() { // 防止重复启动 if (myStarted) { MessageBox.Show("充电进程已启动,不可重复启动"); return; } status.status = "已启动"; myStarted = true; int iteration = 0; ChargeStations = new Dictionary(); // 创建充电处理线程 ChargeThread = new Thread(() => { ChargeStations = new Dictionary(); while (true) { try { if (status.status.Contains("已停止")) { break; } var shieldInterLock = ((StandardChargeMissionStatus)status).ShieldInterLock; // ==================== 步骤1: 初始化充电站 ==================== // 从充电桩管理配置中获取所有充电桩配置 var allStationConfigs = ChargeStationHelper.GetAllStationConfigs(); // 遍历所有充电桩配置,创建充电站实例 foreach (var stationConfig in allStationConfigs) { // 1.1 检查充电桩是否启用 if (!stationConfig.Enabled) { Diagnosis.Log($"ChargeStation[{stationConfig.StationId}] is disabled, skip"); continue; } // 1.2 验证站点ID if (!stationConfig.SiteId.HasValue || stationConfig.SiteId.Value <= 0) { Diagnosis.Log($"ChargeStation[{stationConfig.StationId}] has invalid SiteId, skip"); continue; } int siteId = stationConfig.SiteId.Value; // 1.4 验证IP地址格式 if (string.IsNullOrWhiteSpace(stationConfig.IpAddress) || !IPAddress.TryParse(stationConfig.IpAddress, out var ipAddress)) { Diagnosis.Log($"ERR:ChargeStation[{stationConfig.StationId}] IP[{stationConfig.IpAddress}] is invalid"); continue; } // 1.5 验证端口范围 int port = stationConfig.Port; if (port <= 0 || port > 65535) { Diagnosis.Log($"ERR:ChargeStation[{stationConfig.StationId}] Port[{port}] is invalid"); continue; } // 1.3 检查站点是否已存在,以及IP/端口是否变更 if (ChargeStations.ContainsKey(siteId)) { var existingStation = ChargeStations[siteId]; // 检查IP或端口是否变更 if (existingStation.Ip != ipAddress.ToString() || existingStation.Port != port) { Diagnosis.Log($"ChargeStation[{stationConfig.StationId}] IP/Port changed from [{existingStation.Ip}:{existingStation.Port}] to [{ipAddress}:{port}], recreating connection..."); // 先关闭旧连接 try { existingStation.CloseCommunication(); } catch (Exception ex) { Diagnosis.Log($"WARN:ChargeStation[{stationConfig.StationId}] failed to close old connection: {ex.Message}"); } // 更新IP和端口 existingStation.Ip = ipAddress.ToString(); existingStation.Port = port; // 重新创建通讯连接 try { existingStation.CreateCommunication(ipAddress, port); Diagnosis.Log($"ChargeStation[{stationConfig.StationId}] connection recreated successfully"); } catch (Exception ex) { Diagnosis.Log($"ERR:ChargeStation[{stationConfig.StationId}] failed to recreate connection: {ex.Message}"); } } // 站点已存在且IP/端口未变更,跳过 continue; } // 1.6 根据充电桩类型创建对应的充电站对象 string chargeTypeString = GetChargeTypeString(stationConfig.Type); // 跨程序集解析:充电桩具体类型可能位于卫星插件 dll(StandardScene.Devices.Charge), // 不能再用 Type.GetType(简单名,仅当前程序集)。改用内核同款全域类型发现。 string chargeTypeFullName = "StandardScene.ChargeStationType." + chargeTypeString; Type type = SimpleLite.Utils.UiTypeDiscovery.AllTypes() .FirstOrDefault(t => t.FullName == chargeTypeFullName); if (type == null) { Diagnosis.Log($"ERR:ChargeStation[{stationConfig.StationId}] Type[{chargeTypeString}] not found"); continue; } // 1.7 创建充电站实例并配置基本信息 object chargeStation = Activator.CreateInstance(type); ((AbstractChargeStation)chargeStation).SiteId = siteId; ((AbstractChargeStation)chargeStation).Ip = ipAddress.ToString(); ((AbstractChargeStation)chargeStation).Port = port; // 1.8 设置通讯类型(从配置读取,默认TCP) if (stationConfig.Type == ChargeStationType.FRLDShort) { ((AbstractChargeStation)chargeStation).CommunicationType = "UDP"; } else { ((AbstractChargeStation)chargeStation).CommunicationType = stationConfig.CommunicationType; } //((AbstractChargeStation)chargeStation).CommunicationType = // string.IsNullOrWhiteSpace(stationConfig.CommunicationType) // ? "UDP" // : stationConfig.CommunicationType.ToUpper(); // 1.9 创建通讯连接 ((AbstractChargeStation)chargeStation).CreateCommunication(ipAddress, port); // 1.10 添加到充电站字典 ChargeStations.Add(siteId, (AbstractChargeStation)chargeStation); Diagnosis.Log($"ChargeStation ADD: StationId[{stationConfig.StationId}] SiteId[{siteId}] IP[{ipAddress}:{port}] Type[{chargeTypeString}] Comm[{((AbstractChargeStation)chargeStation).CommunicationType}]"); } // ==================== 步骤2: 初始化UDP服务 ==================== // 如果有任意充电桩使用UDP通讯,则创建UDP服务 if (ChargeStations.Any(c => c.Value.CommunicationType == "UDP")) { UdpService ??= new ChargeUdpService(); } status.status = $"已启动-循环{iteration++}"; // ==================== 步骤3: 充电业务处理循环 ==================== foreach (var item in ChargeStations.Keys.ToArray()) //移除配置 { var chargeStationSetting = ChargeStationHelper.GetStationBySiteId(item); if (chargeStationSetting == null) { try { ChargeStations[item].CloseCommunication(); } catch (Exception ex) { Diagnosis.Log($"WARN:ChargeStationHelper [{item}] failed to close old connection: {ex.Message}"); } ChargeStations.Remove(item); } } //S站点存在配置里没有的,需要移除配置 var sites = SimpleLib.GetAllSites().Where(s => s.fields.ContainsKey("Charge")); foreach (var item in sites) { if (!ChargeStations.Keys.Contains(item.id)) { //移除站点的配置 item.fields.Remove("Charge"); item.fields.Remove("setVoltage"); item.fields.Remove("setElectricCurrent"); item.fields.Remove("group"); Diagnosis.Post($"Charge {item.name}-{item.id} 未在充电管理配置移除参数"); item.name = "NoName"; //并关闭对应的连接 } } // 遍历所有已添加的充电站,处理充电业务逻辑 foreach (var chargeStationEntry in ChargeStations) { var openCharge = 0; int siteId = chargeStationEntry.Key; var chargeStation = chargeStationEntry.Value; var site = SimpleLib.GetSite(siteId); if (site == null) { Console.WriteLine($"从地图中未获取到站点的信息 siteId {siteId}"); continue; } var chargeStationSetting = ChargeStationHelper.GetStationBySiteId(siteId); if (!chargeStationSetting.Enabled) { continue; } //绑定group 添加charge if (site != null) { site.name = chargeStationSetting.Name; site.fields["Charge"] = "True"; site.fields["setVoltage"] = chargeStationSetting.SetVoltage.ToString("0.0"); site.fields["setElectricCurrent"] = chargeStationSetting.SetElectricCurrent.ToString("0.0"); if (chargeStationSetting.Enabled) { site.fields["group"] = chargeStationSetting.GroupCarType.ToString(); } else { site.fields["group"] = "禁停"; } } // 3.1 设置站点访问权限(默认允许进入和离开) if (chargeStationSetting.ChargeMethod == ChargeMethodType.Side) { SetAllowEnter(siteId, chargeStationSetting.ShieldSiteMechanismStatus || chargeStationSetting.MechanismStatus == MechanismStatus.Retracted); SetAllowExit(siteId, chargeStationSetting.ShieldSiteMechanismStatus || chargeStationSetting.MechanismStatus == MechanismStatus.Retracted); SetAcknowledgeLeave(siteId, true); } else { SetAllowEnter(siteId, true); SetAllowExit(siteId, true); SetAcknowledgeLeave(siteId, true); } // 3.2 查找当前在充电站点的车辆 // 条件:车辆在站点上 或 正在获取站点锁 或 持有站点锁 var car = SimpleLib .GetAllCars() .FirstOrDefault(c => c.GetLastSite() == siteId || c.status.aquiringLock == siteId || c.status.holdingLocks.Contains(siteId) ); // 3.3 如果找到车辆,处理充电逻辑 if (car != null) { // 3.3.1 检查车辆状态是否正常 if (Commons.GetVehicleStatus((Car)car) != VehicleStatus.Normal&&car.fields.ContainsKey("SkipStatus")) { continue; } // 3.3.2 判断车辆是否正在充电 var charging = car.tags.Contains("charging"); // 3.3.3 检查充电条件 // 条件:未被占用 && 未获取其他锁 && 正在充电标记 if (!car.tags.Contains("occupied") && car.status.holdingLocks.Length == 1 && car.status.pendingLocks.Length == 0 && charging) { openCharge = 1; // 允许充电 //// 3.3.4 安全检查:验证叉齿是否升起 //var actualLiftPillar = 1; // 默认已升起 //if (car.status.enums.TryGetValue("actualLiftPillar", out var liftPillar)) //{ // actualLiftPillar = Convert.ToInt32(liftPillar); //} //// 如果叉齿未升起,禁止充电 //if (actualLiftPillar != 1) //{ // openCharge = 0; // Console.WriteLine($"{car.id} 小车不满足充电的安全条件 叉齿未抬升"); // // TODO: 触发报警,通知人员处理 //} } } // 3.3.5 发送充电指令(如果未屏蔽充电桩交互) if (!shieldInterLock) { Diagnosis.Log($"向充电站[{siteId}]发送充电指令, 车辆[{car?.id}], 指令[{openCharge}]","Charge",true); chargeStation.SendToChargeStation(openCharge, (Car)car, site); } } } catch (Exception ex) { Diagnosis.Post( $"StandardChargeMission Error: {ExceptionFormatter.FormatEx(ex)}", "error" ); } // 等待500ms后进行下一次循环 Thread.Sleep(500); } }) { Name = "StandardChargeMission", IsBackground = true }; // 启动充电处理线程 ChargeThread.Start(); // ==================== 步骤4: 启动站点禁用状态上传任务 ==================== // 定期将禁用站点信息上传到迷毂系统 Task.Factory.StartNew(() => { HttpPostData httpPostData = new HttpPostData(); while (true) { try { if (status.status.Contains("已停止")) { break; } // 4.1 获取所有标记为"unavailable"的站点 var sites = SimpleLib .GetAllSites() .Where(site => site.tags.Contains("unavailable")) .ToList(); // 4.2 收集所有需要禁用的站点ID HashSet disabledSites = new HashSet(); foreach (var site in sites) { // 添加当前站点 disabledSites.Add(site.id); // 4.3 检查并添加关联的必须释放站点(mustFree) if (site.fields.ContainsKey("mustFree") && site.mustFree != null && site.mustFree.Length > 0) { for (int i = 0; i < site.mustFree.Length; i++) { disabledSites.Add(site.mustFree[i]); } } } // 4.4 上传禁用站点列表到迷毂系统 Diagnosis.Log($"向迷毂提供禁用站点,共 {disabledSites.Count} 个", "siteIsEnable", true); httpPostData.UploadListNode(disabledSites); // 等待500ms后进行下一次上传 Thread.Sleep(500); } catch (Exception e) { Diagnosis.Post($"上传禁用站点失败: {ExceptionFormatter.FormatEx(e)}", "禁用站点"); } } }, TaskCreationOptions.LongRunning); // 调用基类Execute方法 base.Execute(); } /// /// 关闭充电进程 /// 停止所有相关线程 /// [MethodMember(Name = "关闭进程", Description = "关闭充电进程")] public void Stop() { try { started = false; myStarted = false; // 先置停止状态,循环体检测到“已停止”后会自行 break status.status = "已停止"; // 协作式停止:等待工作线程在下一次循环检测标志后退出(不再使用 .NET8 已不支持的 Thread.Abort) myThread?.Join(2000); ChargeThread?.Join(2000); Diagnosis.Log("充电进程已停止"); foreach (var item in ChargeStations.Values) { item.CloseCommunication(); Diagnosis.Post($"充电进程已停止,关闭充电通讯连接{item.Ip}-{item.Port}"); } } catch (Exception ex) { Diagnosis.Post($"充电进程已停止 {ex.ToString()}"); } } /// /// 切换充电桩交互屏蔽状态 /// true=屏蔽交互,false=允许交互 /// [MethodMember(Name = "切换充电桩交互状态", Description = "屏蔽/允许充电桩交互")] public void ShieldInterLock() { var currentStatus = ((StandardChargeMissionStatus)status).ShieldInterLock; ((StandardChargeMissionStatus)status).ShieldInterLock = !currentStatus; string statusText = ((StandardChargeMissionStatus)status).ShieldInterLock ? "已屏蔽" : "已允许"; Console.WriteLine($"充电桩交互状态: {statusText}"); Diagnosis.Log($"充电桩交互状态切换为: {statusText}"); } /// /// 打开充电桩管理界面 /// 用于配置和监控充电桩 /// [MethodMember(Name = "打开充电桩管理界面", Description = "打开充电桩管理界面")] public void OpenManagementWindow() { ChargeStationHelper.OpenManagementWindow(); } #endregion #region 辅助工具方法 /// /// 将充电桩类型枚举转换为类型字符串 /// 用于通过反射创建对应的充电站对象 /// /// 充电桩类型枚举 /// 充电站类名 private string GetChargeTypeString(ChargeStationType type) { switch (type) { case ChargeStationType.FRLDTall: return "FLChargeStation"; case ChargeStationType.FRLDShort: return "PCBChargeStation"; case ChargeStationType.MuXing: return "MuXingChargeStation"; default: // 默认使用FRLD矮款充电桩 return "PCBChargeStation"; } } /// /// 给车辆下发充电任务 /// 为测试或调试用途,手动给车辆添加shouldCharge标签 /// [MethodMember(Name = "小车下发充电任务", Description = "给小车下发充电任务")] public void addtagshuldcharge() { // 查找第一个在有效站点上的车辆 var car = (Car)SimpleLib.GetAllCars() .ToList() .Find(c => c.GetLastSite() != -1); if (car != null) { // 添加shouldCharge标签 Commons.AddOrUpdateTag(car.tags, "shouldCharge", "true"); Diagnosis.Log($"已为车辆 {car.id} 添加充电任务标签"); } else { Diagnosis.Log("未找到在有效站点上的车辆"); } } #endregion } }