using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using LessokajiWeaverUtilities.MagicAttributes; using LessokajiWeaverUtilities.Utilities; using Newtonsoft.Json; using Simple3.RCS; using Simple3.RCS.CarTypes; using SimpleCore; using SimpleCore.Library; using SimpleCore.PropType; using SimpleCore.Traffic; using StandardScene.Utils; namespace StandardScene.ExtendDevice.Door { //todo:2026.1.21 1,门控制器的通信参数配置移动到具体门控制器类中管理,实现对不同通信类型的支持 [MissionType(Name = "门控进程")] [I18N.DocumentTranslation(Name = "DoorMission", locale = "en")] public class DoorMission : Mission { private const string DataFileName = "DoorConfig.json"; private string _dataFilePath; /// /// 当前所有门控制器实例列表 /// private List _doorControllers = new List(); /// /// 用于管理异步循环的取消令牌源 /// private CancellationTokenSource _cancellationTokenSource; /// /// 保存监控配置与状态的后台任务 /// private Task _configTask; private Task _stateTask; /// /// 同步锁,用于保护门控制器列表的并发访问 /// private readonly object _syncLock = new object(); /// /// 门区域内的小车记录:(ControllerIndex, DoorIndex) -> List /// [JsonIgnore] private readonly Dictionary<(int ControllerIndex, int DoorIndex), List> carsInAreas = new Dictionary<(int ControllerIndex, int DoorIndex), List>(); /// /// 需要打开的门:(ControllerIndex, DoorIndex) -> bool /// [JsonIgnore] private readonly Dictionary<(int ControllerIndex, int DoorIndex), bool> _needOpen = new Dictionary<(int ControllerIndex, int DoorIndex), bool>(); /// /// 手动控制请求:(ControllerIndex, DoorIndex) -> ManualControlRequest /// [JsonIgnore] private readonly Dictionary<(int ControllerIndex, int DoorIndex), ManualControlRequest> _manualRequests = new Dictionary<(int ControllerIndex, int DoorIndex), ManualControlRequest>(); private const int DefaultManualHoldSeconds = 10; public enum ControlSource { Auto = 0, Manual = 1 } private class ManualControlRequest { public bool Target { get; set; } public DateTime ExpireAt { get; set; } } public class DoorControlStatus { public bool Target { get; set; } public ControlSource Source { get; set; } public double? ManualRemainingSeconds { get; set; } public IReadOnlyList CarsInArea { get; set; } = Array.Empty(); } /// /// 监控界面快照项(线程安全快照,避免UI直接枚举可变字典) /// public class DoorMonitorSnapshotItem { public int ControllerIndex { get; set; } public int DoorIndex { get; set; } public DoorState State { get; set; } public bool Target { get; set; } public ControlSource Source { get; set; } public double? ManualRemainingSeconds { get; set; } public IReadOnlyList CarsInArea { get; set; } = Array.Empty(); public ushort ControlAddress { get; set; } public ushort OpenStatusAddress { get; set; } } [MethodMember(Name = "启动进程")] [I18N.DocumentTranslation(Name = "Start Mission", locale = "en")] public override void Execute() { // 设置数据文件路径 _dataFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DataFileName); // 如果已经启动,先停止之前的循环 StopInternalAsync().GetAwaiter().GetResult(); TrafficControl.BeforeLock += BeforeLockEvent; TrafficControl.AfterLeave += AfterLeaveEvent; TrafficControl.OnLockAcquired += OnLockAcquiredEvent; // 启动时立即加载一次配置,避免监控界面在第一次轮询前无数据 try { var configControllers = LoadDoorControllerConfig(); SyncDoorControllers(configControllers); } catch (Exception ex) { Diagnosis.Log($"启动时加载门控制器配置失败: {ExceptionFormatter.FormatEx(ex)}", "DoorMission", true); } // 创建新的取消令牌源 _cancellationTokenSource = new CancellationTokenSource(); // 启动异步循环 var token = _cancellationTokenSource.Token; _configTask = Task.Run(async () => await MonitorDoorConfigAsync(token), token); _stateTask = Task.Run(async () => await MonitorDoorLogicAsync(token), token); status.status = "Running"; } /// /// 停止监控任务 /// [MethodMember(Name = "停止进程")] [I18N.DocumentTranslation(Name = "Stop Mission", locale = "en")] public void Stop() { StopInternalAsync().GetAwaiter().GetResult(); status.status = "/"; } /// /// 取消并释放当前的取消令牌源 /// private async Task StopInternalAsync() { // 取消事件订阅 TrafficControl.BeforeLock -= BeforeLockEvent; TrafficControl.AfterLeave -= AfterLeaveEvent; TrafficControl.OnLockAcquired -= OnLockAcquiredEvent; var cts = Interlocked.Exchange(ref _cancellationTokenSource, null); var configTask = Interlocked.Exchange(ref _configTask, null); var stateTask = Interlocked.Exchange(ref _stateTask, null); if (cts == null && configTask == null && stateTask == null) { return; } try { cts?.Cancel(); } catch (ObjectDisposedException) { // 已释放,忽略 } var runningTasks = new[] { configTask, stateTask } .Where(t => t != null) .ToArray(); if (runningTasks.Length > 0) { var aggregateTask = Task.WhenAll(runningTasks); var timeoutTask = Task.Delay(TimeSpan.FromSeconds(5)); var completedTask = await Task.WhenAny(aggregateTask, timeoutTask).ConfigureAwait(false); if (completedTask == timeoutTask) { Diagnosis.Log("停止门控任务超时", "DoorMission", true); } else { try { await aggregateTask.ConfigureAwait(false); } catch (Exception ex) { Diagnosis.Log($"停止门控任务时发生异常: {ExceptionFormatter.FormatEx(ex)}", "DoorMission", true); } } } cts?.Dispose(); DisconnectAllDoorControllers(); } /// /// 断开所有门控制器连接 /// private void DisconnectAllDoorControllers() { List snapshot; lock (_syncLock) { snapshot = _doorControllers.ToList(); } foreach (var controller in snapshot) { try { controller.Disconnect(); } catch (Exception ex) { Diagnosis.Log($"停止门控制器失败: Index={controller.Index}, Error={ExceptionFormatter.FormatEx(ex)}", "DoorMission", true); } } } /// /// 异步监控门控制器配置文件 /// private async Task MonitorDoorConfigAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { try { // 读取配置文件 var configControllers = LoadDoorControllerConfig(); // 同步门控制器列表 SyncDoorControllers(configControllers); // 等待10秒 await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken); } catch (OperationCanceledException) { // 正常取消,退出循环 break; } catch (Exception ex) { Diagnosis.Log($"门控制器配置监控错误: {ExceptionFormatter.FormatEx(ex)}", "DoorMission", true); // 发生错误时等待5秒后重试 try { await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); } catch (OperationCanceledException) { break; } } } } /// /// 监控门控逻辑 /// private async Task MonitorDoorLogicAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { try { lock (_syncLock) { foreach (var controller in _doorControllers) { foreach (var doorConfig in controller.DoorConfigs.Values) { var doorIndex = doorConfig.Index; var key = (controller.Index, doorIndex); var needOpen = _needOpen.TryGetValue(key, out var open) && open; // 自动目标:有车或需要打开 var hasCarsInArea = carsInAreas.TryGetValue(key, out var cars) && cars.Count > 0; var autoTarget = needOpen || hasCarsInArea; // 手动请求仲裁:优先级 Manual > Auto,手动过期后自动恢复 bool finalTarget = autoTarget; if (_manualRequests.TryGetValue(key, out var manual)) { if (manual.ExpireAt <= DateTime.Now) { _manualRequests.Remove(key); } else { finalTarget = manual.Target; } } // 仅更新门控制器中的目标控制字段,不直接进行通信 controller.SetDoorControlTarget(doorIndex, finalTarget); } } } await Task.Delay(TimeSpan.FromMilliseconds(500), cancellationToken); } catch (OperationCanceledException) { // 正常取消,退出循环 break; } catch (Exception ex) { Diagnosis.Log($"门控逻辑监控错误: {ExceptionFormatter.FormatEx(ex)}", "DoorMission", true); // 发生错误时等待5秒后重试 try { await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); } catch (OperationCanceledException) { break; } } } } /// /// 加载门控制器配置文件 /// private List LoadDoorControllerConfig() { try { if (File.Exists(_dataFilePath)) { var jsonContent = File.ReadAllText(_dataFilePath, Encoding.UTF8); if (!string.IsNullOrWhiteSpace(jsonContent)) { var controllers = jsonContent.JsonTo>(); return controllers ?? new List(); } } } catch (Exception ex) { Diagnosis.Log($"加载门控制器配置文件失败: {ExceptionFormatter.FormatEx(ex)}", "DoorMission", true); } return new List(); } /// /// 同步门控制器列表,根据配置文件进行增删改 /// private void SyncDoorControllers(List configControllers) { var controllersToAdd = new List(); lock (_syncLock) { // 创建配置中的门控制器索引字典 var configDict = configControllers.ToDictionary(c => c.Index); // 创建当前门控制器索引字典 var currentDict = _doorControllers.ToDictionary(c => c.Index); // 1. 删除:在配置中不存在的门控制器 var toRemove = _doorControllers.Where(c => !configDict.ContainsKey(c.Index)).ToList(); foreach (var controller in toRemove) { try { controller.Disconnect(); _doorControllers.Remove(controller); Diagnosis.Post($"删除门控制器: Index={controller.Index}, IP={controller.Ip}", "DoorMission", true); } catch (Exception ex) { Diagnosis.Log($"删除门控制器失败: {ExceptionFormatter.FormatEx(ex)}", "DoorMission", true); } } // 2. 添加和修改:遍历配置中的门控制器 foreach (var configController in configControllers) { if (currentDict.TryGetValue(configController.Index, out var existingController)) { // 修改:检查是否需要更新 if (ShouldUpdateDoorController(existingController, configController)) { try { UpdateDoorController(existingController, configController); Diagnosis.Post($"更新门控制器: Index={configController.Index}, IP={configController.Ip}, Type={configController.Type}", "DoorMission", true); } catch (Exception ex) { Diagnosis.Log($"更新门控制器失败: {ExceptionFormatter.FormatEx(ex)}", "DoorMission", true); } } } else { controllersToAdd.Add(configController); } } } foreach (var configController in controllersToAdd) { try { var newController = CreateDoorControllerInstance(configController); if (newController != null) { lock (_syncLock) { _doorControllers.Add(newController); } Diagnosis.Post($"添加门控制器: Index={configController.Index}, IP={configController.Ip}, Type={configController.Type}", "DoorMission", true); } else { Diagnosis.Log($"无法创建门控制器实例: Index={configController.Index}, Type={configController.Type}", "DoorMission", true); } } catch (Exception ex) { Diagnosis.Log($"添加门控制器失败: {ExceptionFormatter.FormatEx(ex)}", "DoorMission", true); } } } /// /// 获取门控制器类型的 DoorTypeAttribute.Name /// private string GetDoorTypeName(Type controllerType) { if (controllerType == null) { return string.Empty; } var attr = controllerType.GetCustomAttribute(); return attr?.Name ?? controllerType.Name; } /// /// 判断是否需要更新门控制器 /// private bool ShouldUpdateDoorController(BasicDoorController existingController, DoorControllerModel configController) { // 检查基本属性是否变更 var existingTypeName = GetDoorTypeName(existingController.GetType()); if (existingController.Ip != configController.Ip || existingController.Port != configController.Port || existingTypeName != configController.Type) { return true; } // 检查门配置信息是否变更 return HasDoorConfigsChanged(existingController, configController); } /// /// 检查门配置信息是否变更 /// private bool HasDoorConfigsChanged(BasicDoorController existingController, DoorControllerModel configController) { var configDoors = configController.Doors ?? new List(); var configDict = configDoors.ToDictionary(d => d.Index); var existingDict = existingController.DoorConfigs; // 检查门数量是否变化 if (existingDict.Count != configDict.Count) { return true; } // 检查每个门的配置是否变化 foreach (var configDoor in configDoors) { if (!existingDict.TryGetValue(configDoor.Index, out var existingDoor)) { // 新增了门 return true; } // 检查门配置是否变化 if (existingDoor.ControlAddress != configDoor.ControlAddress || existingDoor.OpenStatusAddress != configDoor.OpenStatusAddress) { return true; } } // 检查是否有门被删除 foreach (var existingKey in existingDict.Keys) { if (!configDict.ContainsKey(existingKey)) { return true; } } return false; } /// /// 更新门控制器属性 /// private void UpdateDoorController(BasicDoorController controller, DoorControllerModel configController) { // 如果类型改变,需要重新创建实例 var existingTypeName = GetDoorTypeName(controller.GetType()); if (existingTypeName != configController.Type) { // 断开旧连接 controller.Disconnect(); // 从列表中移除 _doorControllers.Remove(controller); // 创建新实例 var newController = CreateDoorControllerInstance(configController); if (newController != null) { _doorControllers.Add(newController); } } else { // 只更新属性 bool needReconnect = controller.Ip != configController.Ip || controller.Port != configController.Port; controller.Ip = configController.Ip; controller.Port = configController.Port; // 更新门配置信息 controller.UpdateDoorConfigs(configController.Doors); // 初始化门状态(基于配置中的门索引) var doorIndices = configController.Doors?.Select(d => d.Index).ToList() ?? new List(); controller.InitializeDoors(doorIndices); // 如果IP或端口改变,需要重新连接 if (needReconnect) { controller.Disconnect(); controller.Connect(); } } } /// /// 根据 DoorTypeAttribute.Name 获取类型 /// private Type GetControllerTypeByDoorTypeName(string doorTypeName) { if (string.IsNullOrWhiteSpace(doorTypeName)) { return null; } try { // 在插件/宿主环境下 GetExecutingAssembly 可能不是 StandardScene.dll // 这里固定从门控制器基类所在程序集查找 // 跨程序集发现:门控制器具体类型可能位于卫星插件 dll(StandardScene.Devices.Door), // 用内核同款全域类型发现替代仅扫基类所在程序集。 var allControllerTypes = Simple3.Utils.UiTypeDiscovery.AllTypes() .Where(t => t.IsClass && !t.IsAbstract && t.Namespace == typeof(BasicDoorController).Namespace && t.IsSubclassOf(typeof(BasicDoorController)) && t.IsDefined(typeof(DoorTypeAttribute), false)); foreach (var type in allControllerTypes) { var attr = type.GetCustomAttribute(); if (attr != null && attr.Name == doorTypeName) { return type; } } return null; } catch (Exception ex) { Diagnosis.Log($"获取门控制器类型失败: DoorTypeName={doorTypeName}, Error={ExceptionFormatter.FormatEx(ex)}", "DoorMission", true); return null; } } /// /// 通过 DoorTypeAttribute.Name 创建门控制器实例 /// private BasicDoorController CreateDoorControllerInstance(DoorControllerModel configController) { if (string.IsNullOrWhiteSpace(configController.Type)) { return null; } try { // 通过 DoorTypeAttribute.Name 查找类型 var controllerType = GetControllerTypeByDoorTypeName(configController.Type); if (controllerType == null) { Diagnosis.Log($"未找到门控制器类型: {configController.Type}", "DoorMission", true); return null; } // 使用反射创建实例 var instance = (BasicDoorController)Activator.CreateInstance(controllerType); // 设置属性 instance.Index = configController.Index; instance.Ip = configController.Ip; instance.Port = configController.Port; // 初始化门配置信息 instance.InitializeDoorConfigs(configController.Doors); // 初始化门状态(基于配置中的门索引) var doorIndices = configController.Doors?.Select(d => d.Index).ToList() ?? new List(); instance.InitializeDoors(doorIndices); // 自动连接 instance.Connect(); return instance; } catch (Exception ex) { Diagnosis.Log($"创建门控制器实例失败: Type={configController.Type}, Error={ExceptionFormatter.FormatEx(ex)}", "DoorMission", true); return null; } } /// /// 获取当前所有门控制器实例(只读) /// public IReadOnlyList GetDoorControllers() { lock (_syncLock) { return _doorControllers.ToList().AsReadOnly(); } } /// /// 获取用于监控界面的门状态快照 /// public IReadOnlyList GetDoorMonitorSnapshot() { lock (_syncLock) { var snapshot = new List(); var now = DateTime.Now; foreach (var controller in _doorControllers) { var doorConfigs = controller.DoorConfigs?.Values?.ToList() ?? new List(); foreach (var doorConfig in doorConfigs) { var key = (controller.Index, doorConfig.Index); var cars = carsInAreas.TryGetValue(key, out var list) && list != null ? list.ToList().AsReadOnly() : new List().AsReadOnly(); var source = ControlSource.Auto; double? manualRemaining = null; bool target = false; if (_manualRequests.TryGetValue(key, out var manual)) { if (manual.ExpireAt > now) { source = ControlSource.Manual; target = manual.Target; manualRemaining = (manual.ExpireAt - now).TotalSeconds; } else { _manualRequests.Remove(key); } } if (source == ControlSource.Auto) { var needOpen = _needOpen.TryGetValue(key, out var open) && open; target = needOpen || cars.Count > 0; } snapshot.Add(new DoorMonitorSnapshotItem { ControllerIndex = controller.Index, DoorIndex = doorConfig.Index, State = controller.GetDoorState(doorConfig.Index), Target = target, Source = source, ManualRemainingSeconds = manualRemaining, CarsInArea = cars, ControlAddress = doorConfig.ControlAddress, OpenStatusAddress = doorConfig.OpenStatusAddress }); } } return snapshot.AsReadOnly(); } } /// /// 解析门标识符字符串(格式:控制器索引.门索引) /// /// 门标识符字符串,格式:控制器索引.门索引 /// 如果解析成功返回(控制器索引, 门索引),否则返回null private (int ControllerIndex, int DoorIndex)? ParseDoorIdentifier(string doorIdentifier) { if (string.IsNullOrWhiteSpace(doorIdentifier)) { return null; } var parts = doorIdentifier.Split('.'); if (parts.Length != 2) { return null; } if (int.TryParse(parts[0].Trim(), out int controllerIndex) && int.TryParse(parts[1].Trim(), out int doorIndex)) { return (controllerIndex, doorIndex); } return null; } /// /// 读取门状态 /// /// 门控制器索引 /// 门索引 /// true=打开,false=关闭 public bool GetDoorState(int controllerIndex, int doorIndex) { lock (_syncLock) { var controller = _doorControllers.FirstOrDefault(c => c.Index == controllerIndex); if (controller != null && controller.DoorConfigs.ContainsKey(doorIndex)) { // 只访问控制器中的门状态字段,不直接进行通信 var state = controller.GetDoorState(doorIndex); return state == DoorState.Open; } } return false; } /// /// 写入门控制状态 /// /// 门控制器索引 /// 门索引 /// true=打开,false=关闭 public void SetDoorControlTarget(int controllerIndex, int doorIndex, bool open) { lock (_syncLock) { var controller = _doorControllers.FirstOrDefault(c => c.Index == controllerIndex); if (controller != null && controller.DoorConfigs.ContainsKey(doorIndex)) { // 仅设置门的目标控制状态,由门控制器内部线程负责实际通信 controller.SetDoorControlTarget(doorIndex, open); } } } /// /// 设置手动控制目标,支持保持时间,车辆占用时禁止手动关闭 /// /// 控制器索引 /// 门索引 /// 目标开关 /// 手动保持秒数,默认10秒 /// 成功返回true;当占用且尝试关闭时返回false public bool SetManualDoorControl(int controllerIndex, int doorIndex, bool open, int? holdSeconds = null) { lock (_syncLock) { // 车辆占用时禁止手动关闭 var key = (controllerIndex, doorIndex); if (!open && carsInAreas.TryGetValue(key, out var cars) && cars.Count > 0) { return false; } var seconds = holdSeconds.GetValueOrDefault(DefaultManualHoldSeconds); _manualRequests[key] = new ManualControlRequest { Target = open, ExpireAt = DateTime.Now.AddSeconds(seconds) }; return true; } } /// /// 清除手动控制,恢复自动 /// public void ClearManualDoorControl(int controllerIndex, int doorIndex) { lock (_syncLock) { var key = (controllerIndex, doorIndex); _manualRequests.Remove(key); } } /// /// 清空车辆占用记录 /// public void ClearCarsInArea(int controllerIndex, int doorIndex) { lock (_syncLock) { var key = (controllerIndex, doorIndex); if (carsInAreas.ContainsKey(key)) { carsInAreas[key].Clear(); } } } /// /// 获取门的控制状态(目标、来源、手动剩余时间、占用车辆) /// public DoorControlStatus GetDoorControlStatus(int controllerIndex, int doorIndex) { lock (_syncLock) { var key = (controllerIndex, doorIndex); // 车辆占用 IReadOnlyList cars; if (carsInAreas.TryGetValue(key, out var list) && list != null) { cars = list.ToList().AsReadOnly(); } else { cars = new List().AsReadOnly(); } // 当前目标来源与剩余 var source = ControlSource.Auto; double? remaining = null; bool target = false; if (_manualRequests.TryGetValue(key, out var manual)) { if (manual.ExpireAt > DateTime.Now) { source = ControlSource.Manual; target = manual.Target; remaining = (manual.ExpireAt - DateTime.Now).TotalSeconds; } else { _manualRequests.Remove(key); } } if (source == ControlSource.Auto) { var needOpen = _needOpen.TryGetValue(key, out var open) && open; var hasCars = cars.Count > 0; target = needOpen || hasCars; } return new DoorControlStatus { Target = target, Source = source, ManualRemainingSeconds = remaining, CarsInArea = cars }; } } /// /// 获取门的车辆占用情况 /// /// 门控制器索引 /// 门索引 /// 车辆ID列表,如果没有车辆则返回空列表 public IReadOnlyList GetCarsInArea(int controllerIndex, int doorIndex) { lock (_syncLock) { var key = (controllerIndex, doorIndex); if (carsInAreas.TryGetValue(key, out var cars) && cars != null) { return cars.ToList().AsReadOnly(); } return new List().AsReadOnly(); } } private bool BeforeLockEvent(AbstractCar car, int siteId) { var site = SimpleLib.GetSite(siteId); if (!site.fields.TryGetValue("EnterDoor", out string fieldStr)) return true; var lastSiteId = car.status.holdingLocks.LastOrDefault(); var lastSite = SimpleLib.GetSite(lastSiteId); if (lastSite == null || !lastSite.fields.TryGetValue("PreEnterDoor",out var value)) return true; if (value != fieldStr) return true; var doorInfo = ParseDoorIdentifier(fieldStr); if (!doorInfo.HasValue) return true; var (controllerIndex, doorIndex) = doorInfo.Value; lock (_syncLock) { _needOpen[(controllerIndex, doorIndex)] = true; return GetDoorState(controllerIndex, doorIndex); } } private void AfterLeaveEvent(AbstractCar car, int siteId) { var site = SimpleLib.GetSite(siteId); if (!site.fields.TryGetValue("LeaveDoor", out string fieldStr)) return; var carSiteId = car.status.holdingLocks.FirstOrDefault(); var carSite = SimpleLib.GetSite(carSiteId); if (carSite == null || !carSite.fields.TryGetValue("RearLeaveDoor",out var value)) return; if (value != fieldStr) return; var doorInfo = ParseDoorIdentifier(fieldStr); if (!doorInfo.HasValue) return; var (controllerIndex, doorIndex) = doorInfo.Value; var key = (controllerIndex, doorIndex); lock (_syncLock) { if (carsInAreas.TryGetValue(key, out var list)) { list.Remove(car.id); } } } private void OnLockAcquiredEvent(AbstractCar car, int siteId) { var site = SimpleLib.GetSite(siteId); if (!site.fields.TryGetValue("EnterDoor", out string fieldStr)) return; if(car.status.holdingLocks.Length<2) return; var preSiteId = car.status.holdingLocks[car.status.holdingLocks.Length-2]; var preSite = SimpleLib.GetSite(preSiteId); if (preSite == null || !preSite.fields.TryGetValue("PreEnterDoor", out var value)) return ; if (value != fieldStr) return ; var doorInfo = ParseDoorIdentifier(fieldStr); if (!doorInfo.HasValue) return; var (controllerIndex, doorIndex) = doorInfo.Value; var key = (controllerIndex, doorIndex); lock (_syncLock) { if (!carsInAreas.TryGetValue(key, out var list)) { list = new List(); carsInAreas[key] = list; } if (!list.Contains(car.id)) { list.Add(car.id); } _needOpen[key] = false; } } /// /// 打开门控制器管理界面 /// [MethodMember(Name = "打开管理界面")] [I18N.DocumentTranslation(Name = "Open Manager", locale = "en")] public static void OpenViewer() { DoorManager.OpenViewer(); } /// /// 打开门控监控界面 /// [MethodMember(Name = "打开监控界面")] [I18N.DocumentTranslation(Name = "Open Monitor", locale = "en")] public static void OpenMonitor() { try { DoorMonitor.Open(); } catch (Exception ex) { CycleUiHelper.Alert("错误", $"打开门控监控界面失败: {ex.Message}"); } } } }