using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using TeamAAS.Communication.Attributes; using TeamAAS.Communication.Base; using TeamAAS.Communication.Interfaces; using TeamAAS.Communication.Models; using TeamAAS.Communication.PLCs; namespace TeamAAS.Communication { /// /// 通讯设备统一管理器。 /// 整合设备管理、类型注册(反射)、连接操作、PLC读写和配置持久化。 /// 同时作为懒汉单例使用:CommunicationManager.Instance。 /// public class CommunicationManager : IDisposable { #region 单例 private static readonly Lazy _instance = new Lazy(() => new CommunicationManager(), isThreadSafe: true); /// /// 懒汉单例入口。首次访问时初始化,线程安全。 /// public static CommunicationManager Instance => _instance.Value; #endregion private readonly object _sync = new object(); private readonly Dictionary _devices = new Dictionary(); #region 类型注册(反射扫描) private static readonly Lazy> _types = new Lazy>(Scan); public IReadOnlyList GetAvailableTypes() => _types.Value; public CommunicationTypeInfo GetTypeInfo(string typeKey) => string.IsNullOrWhiteSpace(typeKey) ? null : _types.Value.FirstOrDefault(t => t.TypeKey == typeKey); public IReadOnlyList GetPlcTypes() => _types.Value.Where(t => typeof(PlcCommunicationBase).IsAssignableFrom(t.Type)).ToList(); public IReadOnlyList GetBasicTypes() => _types.Value.Where(t => string.Equals(t.Category, "基础通讯", StringComparison.Ordinal)).ToList(); private static List Scan() { var result = new List(); // 通用模块扫描:收集全部 ICommunication 实现(含 Runtime\Plugins 里的插件 DLL), // 插件项目新增的通讯设备类型无需修改主程序即可被发现。 foreach (var type in TeamAAS.Modularity.AssemblyScanner.FindImplementations()) { var attr = type.GetCustomAttribute(); if (attr == null) continue; result.Add(new CommunicationTypeInfo { TypeKey = type.FullName, DisplayName = attr.DisplayName, Category = attr.Category, Description = attr.Description, Type = type, }); } return result .GroupBy(t => t.TypeKey) .Select(g => g.First()) .OrderBy(t => t.Category) .ThenBy(t => t.DisplayName) .ToList(); } #endregion #region 设备列表 public IReadOnlyList Devices { get { lock (_sync) return _devices.Values.OrderBy(d => d.Index).ToList(); } } public int Count { get { lock (_sync) return _devices.Count; } } #endregion #region 创建与注册 public ICommunication Create(string typeKey, Guid id, string name, int index) { if (string.IsNullOrWhiteSpace(typeKey)) throw new ArgumentException("TypeKey 不能为空。", nameof(typeKey)); var typeInfo = GetTypeInfo(typeKey); if (typeInfo == null) throw new InvalidOperationException($"未找到通讯设备类型: {typeKey}"); var device = (ICommunication)Activator.CreateInstance(typeInfo.Type); device.Id = id; device.Index = index; device.Name = name; Register(device); return device; } public void Register(ICommunication device) { if (device == null) throw new ArgumentNullException(nameof(device)); ICommunication replaced = null; lock (_sync) { if (_devices.TryGetValue(device.Id, out replaced) && ReferenceEquals(replaced, device)) { return; } _devices[device.Id] = device; } // 替换同一 ID 的设备时,先从注册表移除旧实例,再在锁外释放底层连接资源, // 避免 Dispose/Disconnect 回调反向访问管理器造成锁重入。 if (replaced != null) { try { replaced.Disconnect(); } catch { } try { replaced.Dispose(); } catch { } } } #endregion #region 设备获取 public ICommunication Get(Guid id) { lock (_sync) { _devices.TryGetValue(id, out var device); return device; } } public bool TryGet(Guid id, out ICommunication device) { lock (_sync) { return _devices.TryGetValue(id, out device); } } public ICommunication GetByName(string name) => Devices.FirstOrDefault(d => d.Name == name); public bool Remove(Guid id) { ICommunication device; lock (_sync) { if (!_devices.TryGetValue(id, out device)) return false; _devices.Remove(id); } try { device.Disconnect(); } catch { } device.Dispose(); return true; } #endregion #region 便捷查询 public IReadOnlyList GetAll() => Devices; public IReadOnlyList GetConnected() => Devices.Where(d => d.IsConnected).ToList(); public IReadOnlyList GetDisconnected() => Devices.Where(d => !d.IsConnected).ToList(); public IReadOnlyList GetTcpClients() => Devices.Where(d => d is Devices.TcpClientCommunication).ToList(); public IReadOnlyList GetTcpServers() => Devices.Where(d => d is Devices.TcpServerCommunication).ToList(); public IReadOnlyList GetSerials() => Devices.Where(d => d is Devices.SerialCommunication).ToList(); public IReadOnlyList GetUdps() => Devices.Where(d => d is Devices.UdpCommunication).ToList(); public IReadOnlyList GetWebs() => Devices.Where(d => d is Devices.WebCommunication).ToList(); #endregion #region PLC 查询 public IReadOnlyList GetAllPlcModules() => Devices.Where(d => d is PlcCommunicationBase).ToList(); public IReadOnlyList GetConnectedPlcModules() => GetAllPlcModules().Where(p => p.IsConnected).ToList(); public IReadOnlyList GetPlcByProtocol(string typeKey) => Devices.Where(d => d is PlcCommunicationBase && d.TypeKey == typeKey).ToList(); #endregion #region 连接操作 public void Connect(Guid id) { var device = Get(id); if (device != null) device.Connect(); } public void Disconnect(Guid id) { var device = Get(id); if (device != null) device.Disconnect(); } public void ConnectAll() { foreach (var device in Devices) try { device.Connect(); } catch { } } public void DisconnectAll() { foreach (var device in Devices) try { device.Disconnect(); } catch { } } public void ConnectAllPlc() { foreach (var plc in GetAllPlcModules()) try { plc.Connect(); } catch { } } public void DisconnectAllPlc() { foreach (var plc in GetAllPlcModules()) try { plc.Disconnect(); } catch { } } #endregion #region PLC 读写便捷方法 public object ReadPlcValue(Guid plcId, string address) { var plc = Get(plcId); if (plc == null) throw new InvalidOperationException($"PLC 设备未找到: {plcId}"); return plc.ReadValue(address); } public void WritePlcValue(Guid plcId, string address, object value) { var plc = Get(plcId); if (plc == null) throw new InvalidOperationException($"PLC 设备未找到: {plcId}"); plc.WriteValue(address, value); } public async System.Threading.Tasks.Task ReadPlcValueAsync(Guid plcId, string address) { var plc = Get(plcId); if (plc == null) throw new InvalidOperationException($"PLC 设备未找到: {plcId}"); return await plc.ReadValueAsync(address); } public async System.Threading.Tasks.Task WritePlcValueAsync(Guid plcId, string address, object value) { var plc = Get(plcId); if (plc == null) throw new InvalidOperationException($"PLC 设备未找到: {plcId}"); await plc.WriteValueAsync(address, value); } #endregion #region 清理 public void Clear() { List all; lock (_sync) { all = _devices.Values.ToList(); _devices.Clear(); } foreach (var device in all) { try { device.Disconnect(); } catch { } device.Dispose(); } } public void Dispose() { Clear(); } #endregion #region 配置持久化(Newtonsoft.Json) public static string DefaultConfigPath { get { return Path.Combine(TeamAAS.PathHelper.ConfigDirectory, "Communications.json"); } } private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented, ReferenceLoopHandling = ReferenceLoopHandling.Ignore, ContractResolver = new CamelCasePropertyNamesContractResolver(), DefaultValueHandling = DefaultValueHandling.Populate, }; public bool SaveConfig(string path = null) { path = path ?? DefaultConfigPath; var all = new List(); lock (_sync) { all.AddRange(_devices.Values.OrderBy(d => d.Index)); } return TeamAAS.JsonFileStore.Save(path, all, JsonSettings); } public void LoadConfig(string path = null) { path = path ?? DefaultConfigPath; if (!File.Exists(path)) return; var devices = TeamAAS.JsonFileStore.Load>(path, JsonSettings); Clear(); if (devices != null) { foreach (var d in devices.Where(x => x != null)) { Register(d); } } } #endregion } }