对齐 FairyView 宿主命名。环线/充电/按钮/门进程的 Stop 改为 override,避免隐藏基类;Kiva.ForceStop 改为 override。 Co-authored-by: Cursor <cursoragent@cursor.com>
1025 lines
39 KiB
C#
1025 lines
39 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// 当前所有门控制器实例列表
|
||
/// </summary>
|
||
private List<BasicDoorController> _doorControllers = new List<BasicDoorController>();
|
||
|
||
/// <summary>
|
||
/// 用于管理异步循环的取消令牌源
|
||
/// </summary>
|
||
private CancellationTokenSource _cancellationTokenSource;
|
||
|
||
/// <summary>
|
||
/// 保存监控配置与状态的后台任务
|
||
/// </summary>
|
||
private Task _configTask;
|
||
private Task _stateTask;
|
||
|
||
/// <summary>
|
||
/// 同步锁,用于保护门控制器列表的并发访问
|
||
/// </summary>
|
||
private readonly object _syncLock = new object();
|
||
|
||
/// <summary>
|
||
/// 门区域内的小车记录:(ControllerIndex, DoorIndex) -> List<CarId>
|
||
/// </summary>
|
||
[JsonIgnore] private readonly Dictionary<(int ControllerIndex, int DoorIndex), List<int>> carsInAreas = new Dictionary<(int ControllerIndex, int DoorIndex), List<int>>();
|
||
|
||
/// <summary>
|
||
/// 需要打开的门:(ControllerIndex, DoorIndex) -> bool
|
||
/// </summary>
|
||
[JsonIgnore] private readonly Dictionary<(int ControllerIndex, int DoorIndex), bool> _needOpen = new Dictionary<(int ControllerIndex, int DoorIndex), bool>();
|
||
|
||
/// <summary>
|
||
/// 手动控制请求:(ControllerIndex, DoorIndex) -> ManualControlRequest
|
||
/// </summary>
|
||
[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<int> CarsInArea { get; set; } = Array.Empty<int>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 监控界面快照项(线程安全快照,避免UI直接枚举可变字典)
|
||
/// </summary>
|
||
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<int> CarsInArea { get; set; } = Array.Empty<int>();
|
||
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";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止监控任务
|
||
/// </summary>
|
||
[MethodMember(Name = "停止进程")]
|
||
[I18N.DocumentTranslation(Name = "Stop Mission", locale = "en")]
|
||
public override void Stop()
|
||
{
|
||
StopInternalAsync().GetAwaiter().GetResult();
|
||
status.status = "/";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取消并释放当前的取消令牌源
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 断开所有门控制器连接
|
||
/// </summary>
|
||
private void DisconnectAllDoorControllers()
|
||
{
|
||
List<BasicDoorController> 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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步监控门控制器配置文件
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 监控门控逻辑
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载门控制器配置文件
|
||
/// </summary>
|
||
private List<DoorControllerModel> LoadDoorControllerConfig()
|
||
{
|
||
try
|
||
{
|
||
if (File.Exists(_dataFilePath))
|
||
{
|
||
var jsonContent = File.ReadAllText(_dataFilePath, Encoding.UTF8);
|
||
if (!string.IsNullOrWhiteSpace(jsonContent))
|
||
{
|
||
var controllers = jsonContent.JsonTo<List<DoorControllerModel>>();
|
||
return controllers ?? new List<DoorControllerModel>();
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Diagnosis.Log($"加载门控制器配置文件失败: {ExceptionFormatter.FormatEx(ex)}", "DoorMission", true);
|
||
}
|
||
|
||
return new List<DoorControllerModel>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步门控制器列表,根据配置文件进行增删改
|
||
/// </summary>
|
||
private void SyncDoorControllers(List<DoorControllerModel> configControllers)
|
||
{
|
||
var controllersToAdd = new List<DoorControllerModel>();
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取门控制器类型的 DoorTypeAttribute.Name
|
||
/// </summary>
|
||
private string GetDoorTypeName(Type controllerType)
|
||
{
|
||
if (controllerType == null)
|
||
{
|
||
return string.Empty;
|
||
}
|
||
|
||
var attr = controllerType.GetCustomAttribute<DoorTypeAttribute>();
|
||
return attr?.Name ?? controllerType.Name;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断是否需要更新门控制器
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查门配置信息是否变更
|
||
/// </summary>
|
||
private bool HasDoorConfigsChanged(BasicDoorController existingController, DoorControllerModel configController)
|
||
{
|
||
var configDoors = configController.Doors ?? new List<DoorModel>();
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新门控制器属性
|
||
/// </summary>
|
||
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<int>();
|
||
controller.InitializeDoors(doorIndices);
|
||
|
||
// 如果IP或端口改变,需要重新连接
|
||
if (needReconnect)
|
||
{
|
||
controller.Disconnect();
|
||
controller.Connect();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据 DoorTypeAttribute.Name 获取类型
|
||
/// </summary>
|
||
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<DoorTypeAttribute>();
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过 DoorTypeAttribute.Name 创建门控制器实例
|
||
/// </summary>
|
||
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<int>();
|
||
instance.InitializeDoors(doorIndices);
|
||
|
||
// 自动连接
|
||
instance.Connect();
|
||
|
||
return instance;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Diagnosis.Log($"创建门控制器实例失败: Type={configController.Type}, Error={ExceptionFormatter.FormatEx(ex)}", "DoorMission", true);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前所有门控制器实例(只读)
|
||
/// </summary>
|
||
public IReadOnlyList<BasicDoorController> GetDoorControllers()
|
||
{
|
||
lock (_syncLock)
|
||
{
|
||
return _doorControllers.ToList().AsReadOnly();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取用于监控界面的门状态快照
|
||
/// </summary>
|
||
public IReadOnlyList<DoorMonitorSnapshotItem> GetDoorMonitorSnapshot()
|
||
{
|
||
lock (_syncLock)
|
||
{
|
||
var snapshot = new List<DoorMonitorSnapshotItem>();
|
||
var now = DateTime.Now;
|
||
|
||
foreach (var controller in _doorControllers)
|
||
{
|
||
var doorConfigs = controller.DoorConfigs?.Values?.ToList() ?? new List<DoorModel>();
|
||
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<int>().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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析门标识符字符串(格式:控制器索引.门索引)
|
||
/// </summary>
|
||
/// <param name="doorIdentifier">门标识符字符串,格式:控制器索引.门索引</param>
|
||
/// <returns>如果解析成功返回(控制器索引, 门索引),否则返回null</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取门状态
|
||
/// </summary>
|
||
/// <param name="controllerIndex">门控制器索引</param>
|
||
/// <param name="doorIndex">门索引</param>
|
||
/// <returns>true=打开,false=关闭</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 写入门控制状态
|
||
/// </summary>
|
||
/// <param name="controllerIndex">门控制器索引</param>
|
||
/// <param name="doorIndex">门索引</param>
|
||
/// <param name="open">true=打开,false=关闭</param>
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置手动控制目标,支持保持时间,车辆占用时禁止手动关闭
|
||
/// </summary>
|
||
/// <param name="controllerIndex">控制器索引</param>
|
||
/// <param name="doorIndex">门索引</param>
|
||
/// <param name="open">目标开关</param>
|
||
/// <param name="holdSeconds">手动保持秒数,默认10秒</param>
|
||
/// <returns>成功返回true;当占用且尝试关闭时返回false</returns>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除手动控制,恢复自动
|
||
/// </summary>
|
||
public void ClearManualDoorControl(int controllerIndex, int doorIndex)
|
||
{
|
||
lock (_syncLock)
|
||
{
|
||
var key = (controllerIndex, doorIndex);
|
||
_manualRequests.Remove(key);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空车辆占用记录
|
||
/// </summary>
|
||
public void ClearCarsInArea(int controllerIndex, int doorIndex)
|
||
{
|
||
lock (_syncLock)
|
||
{
|
||
var key = (controllerIndex, doorIndex);
|
||
if (carsInAreas.ContainsKey(key))
|
||
{
|
||
carsInAreas[key].Clear();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取门的控制状态(目标、来源、手动剩余时间、占用车辆)
|
||
/// </summary>
|
||
public DoorControlStatus GetDoorControlStatus(int controllerIndex, int doorIndex)
|
||
{
|
||
lock (_syncLock)
|
||
{
|
||
var key = (controllerIndex, doorIndex);
|
||
|
||
// 车辆占用
|
||
IReadOnlyList<int> cars;
|
||
if (carsInAreas.TryGetValue(key, out var list) && list != null)
|
||
{
|
||
cars = list.ToList().AsReadOnly();
|
||
}
|
||
else
|
||
{
|
||
cars = new List<int>().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
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取门的车辆占用情况
|
||
/// </summary>
|
||
/// <param name="controllerIndex">门控制器索引</param>
|
||
/// <param name="doorIndex">门索引</param>
|
||
/// <returns>车辆ID列表,如果没有车辆则返回空列表</returns>
|
||
public IReadOnlyList<int> 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<int>().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<int>();
|
||
carsInAreas[key] = list;
|
||
}
|
||
if (!list.Contains(car.id))
|
||
{
|
||
list.Add(car.id);
|
||
}
|
||
_needOpen[key] = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开门控制器管理界面
|
||
/// </summary>
|
||
[MethodMember(Name = "打开管理界面")]
|
||
[I18N.DocumentTranslation(Name = "Open Manager", locale = "en")]
|
||
public static void OpenViewer()
|
||
{
|
||
DoorManager.OpenViewer();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开门控监控界面
|
||
/// </summary>
|
||
[MethodMember(Name = "打开监控界面")]
|
||
[I18N.DocumentTranslation(Name = "Open Monitor", locale = "en")]
|
||
public static void OpenMonitor()
|
||
{
|
||
try
|
||
{
|
||
DoorMonitor.Open();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
FairyUiHelper.Alert("错误", $"打开门控监控界面失败: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
}
|