| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- 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
- {
- /// <summary>
- /// 通讯设备统一管理器。
- /// 整合设备管理、类型注册(反射)、连接操作、PLC读写和配置持久化。
- /// 同时作为懒汉单例使用:<c>CommunicationManager.Instance</c>。
- /// </summary>
- public class CommunicationManager : IDisposable
- {
- #region 单例
- private static readonly Lazy<CommunicationManager> _instance =
- new Lazy<CommunicationManager>(() => new CommunicationManager(), isThreadSafe: true);
- /// <summary>
- /// 懒汉单例入口。首次访问时初始化,线程安全。
- /// </summary>
- public static CommunicationManager Instance => _instance.Value;
- #endregion
- private readonly object _sync = new object();
- private readonly Dictionary<Guid, ICommunication> _devices = new Dictionary<Guid, ICommunication>();
- #region 类型注册(反射扫描)
- private static readonly Lazy<List<CommunicationTypeInfo>> _types =
- new Lazy<List<CommunicationTypeInfo>>(Scan);
- public IReadOnlyList<CommunicationTypeInfo> GetAvailableTypes() => _types.Value;
- public CommunicationTypeInfo GetTypeInfo(string typeKey) =>
- string.IsNullOrWhiteSpace(typeKey) ? null : _types.Value.FirstOrDefault(t => t.TypeKey == typeKey);
- public IReadOnlyList<CommunicationTypeInfo> GetPlcTypes() =>
- _types.Value.Where(t => typeof(PlcCommunicationBase).IsAssignableFrom(t.Type)).ToList();
- public IReadOnlyList<CommunicationTypeInfo> GetBasicTypes() =>
- _types.Value.Where(t => string.Equals(t.Category, "基础通讯", StringComparison.Ordinal)).ToList();
- private static List<CommunicationTypeInfo> Scan()
- {
- var result = new List<CommunicationTypeInfo>();
- // 通用模块扫描:收集全部 ICommunication 实现(含 Runtime\Plugins 里的插件 DLL),
- // 插件项目新增的通讯设备类型无需修改主程序即可被发现。
- foreach (var type in TeamAAS.Modularity.AssemblyScanner.FindImplementations<ICommunication>())
- {
- var attr = type.GetCustomAttribute<CommunicationAttribute>();
- 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<ICommunication> 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<ICommunication> GetAll() => Devices;
- public IReadOnlyList<ICommunication> GetConnected() => Devices.Where(d => d.IsConnected).ToList();
- public IReadOnlyList<ICommunication> GetDisconnected() => Devices.Where(d => !d.IsConnected).ToList();
- public IReadOnlyList<ICommunication> GetTcpClients() => Devices.Where(d => d is Devices.TcpClientCommunication).ToList();
- public IReadOnlyList<ICommunication> GetTcpServers() => Devices.Where(d => d is Devices.TcpServerCommunication).ToList();
- public IReadOnlyList<ICommunication> GetSerials() => Devices.Where(d => d is Devices.SerialCommunication).ToList();
- public IReadOnlyList<ICommunication> GetUdps() => Devices.Where(d => d is Devices.UdpCommunication).ToList();
- public IReadOnlyList<ICommunication> GetWebs() => Devices.Where(d => d is Devices.WebCommunication).ToList();
- #endregion
- #region PLC 查询
- public IReadOnlyList<ICommunication> GetAllPlcModules() => Devices.Where(d => d is PlcCommunicationBase).ToList();
- public IReadOnlyList<ICommunication> GetConnectedPlcModules() => GetAllPlcModules().Where(p => p.IsConnected).ToList();
- public IReadOnlyList<ICommunication> 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<object> 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<ICommunication> 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<ICommunication>();
- 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<List<ICommunication>>(path, JsonSettings);
- Clear();
- if (devices != null)
- {
- foreach (var d in devices.Where(x => x != null))
- {
- Register(d);
- }
- }
- }
- #endregion
- }
- }
|