CommunicationManager.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using Newtonsoft.Json;
  7. using Newtonsoft.Json.Serialization;
  8. using TeamAAS.Communication.Attributes;
  9. using TeamAAS.Communication.Base;
  10. using TeamAAS.Communication.Interfaces;
  11. using TeamAAS.Communication.Models;
  12. using TeamAAS.Communication.PLCs;
  13. namespace TeamAAS.Communication
  14. {
  15. /// <summary>
  16. /// 通讯设备统一管理器。
  17. /// 整合设备管理、类型注册(反射)、连接操作、PLC读写和配置持久化。
  18. /// 同时作为懒汉单例使用:<c>CommunicationManager.Instance</c>。
  19. /// </summary>
  20. public class CommunicationManager : IDisposable
  21. {
  22. #region 单例
  23. private static readonly Lazy<CommunicationManager> _instance =
  24. new Lazy<CommunicationManager>(() => new CommunicationManager(), isThreadSafe: true);
  25. /// <summary>
  26. /// 懒汉单例入口。首次访问时初始化,线程安全。
  27. /// </summary>
  28. public static CommunicationManager Instance => _instance.Value;
  29. #endregion
  30. private readonly object _sync = new object();
  31. private readonly Dictionary<Guid, ICommunication> _devices = new Dictionary<Guid, ICommunication>();
  32. #region 类型注册(反射扫描)
  33. private static readonly Lazy<List<CommunicationTypeInfo>> _types =
  34. new Lazy<List<CommunicationTypeInfo>>(Scan);
  35. public IReadOnlyList<CommunicationTypeInfo> GetAvailableTypes() => _types.Value;
  36. public CommunicationTypeInfo GetTypeInfo(string typeKey) =>
  37. string.IsNullOrWhiteSpace(typeKey) ? null : _types.Value.FirstOrDefault(t => t.TypeKey == typeKey);
  38. public IReadOnlyList<CommunicationTypeInfo> GetPlcTypes() =>
  39. _types.Value.Where(t => typeof(PlcCommunicationBase).IsAssignableFrom(t.Type)).ToList();
  40. public IReadOnlyList<CommunicationTypeInfo> GetBasicTypes() =>
  41. _types.Value.Where(t => string.Equals(t.Category, "基础通讯", StringComparison.Ordinal)).ToList();
  42. private static List<CommunicationTypeInfo> Scan()
  43. {
  44. var result = new List<CommunicationTypeInfo>();
  45. // 通用模块扫描:收集全部 ICommunication 实现(含 Runtime\Plugins 里的插件 DLL),
  46. // 插件项目新增的通讯设备类型无需修改主程序即可被发现。
  47. foreach (var type in TeamAAS.Modularity.AssemblyScanner.FindImplementations<ICommunication>())
  48. {
  49. var attr = type.GetCustomAttribute<CommunicationAttribute>();
  50. if (attr == null) continue;
  51. result.Add(new CommunicationTypeInfo
  52. {
  53. TypeKey = type.FullName,
  54. DisplayName = attr.DisplayName,
  55. Category = attr.Category,
  56. Description = attr.Description,
  57. Type = type,
  58. });
  59. }
  60. return result
  61. .GroupBy(t => t.TypeKey)
  62. .Select(g => g.First())
  63. .OrderBy(t => t.Category)
  64. .ThenBy(t => t.DisplayName)
  65. .ToList();
  66. }
  67. #endregion
  68. #region 设备列表
  69. public IReadOnlyList<ICommunication> Devices
  70. {
  71. get { lock (_sync) return _devices.Values.OrderBy(d => d.Index).ToList(); }
  72. }
  73. public int Count
  74. {
  75. get { lock (_sync) return _devices.Count; }
  76. }
  77. #endregion
  78. #region 创建与注册
  79. public ICommunication Create(string typeKey, Guid id, string name, int index)
  80. {
  81. if (string.IsNullOrWhiteSpace(typeKey))
  82. throw new ArgumentException("TypeKey 不能为空。", nameof(typeKey));
  83. var typeInfo = GetTypeInfo(typeKey);
  84. if (typeInfo == null)
  85. throw new InvalidOperationException($"未找到通讯设备类型: {typeKey}");
  86. var device = (ICommunication)Activator.CreateInstance(typeInfo.Type);
  87. device.Id = id;
  88. device.Index = index;
  89. device.Name = name;
  90. Register(device);
  91. return device;
  92. }
  93. public void Register(ICommunication device)
  94. {
  95. if (device == null) throw new ArgumentNullException(nameof(device));
  96. ICommunication replaced = null;
  97. lock (_sync)
  98. {
  99. if (_devices.TryGetValue(device.Id, out replaced)
  100. && ReferenceEquals(replaced, device))
  101. {
  102. return;
  103. }
  104. _devices[device.Id] = device;
  105. }
  106. // 替换同一 ID 的设备时,先从注册表移除旧实例,再在锁外释放底层连接资源,
  107. // 避免 Dispose/Disconnect 回调反向访问管理器造成锁重入。
  108. if (replaced != null)
  109. {
  110. try { replaced.Disconnect(); } catch { }
  111. try { replaced.Dispose(); } catch { }
  112. }
  113. }
  114. #endregion
  115. #region 设备获取
  116. public ICommunication Get(Guid id)
  117. {
  118. lock (_sync) { _devices.TryGetValue(id, out var device); return device; }
  119. }
  120. public bool TryGet(Guid id, out ICommunication device)
  121. {
  122. lock (_sync) { return _devices.TryGetValue(id, out device); }
  123. }
  124. public ICommunication GetByName(string name) => Devices.FirstOrDefault(d => d.Name == name);
  125. public bool Remove(Guid id)
  126. {
  127. ICommunication device;
  128. lock (_sync)
  129. {
  130. if (!_devices.TryGetValue(id, out device)) return false;
  131. _devices.Remove(id);
  132. }
  133. try { device.Disconnect(); } catch { }
  134. device.Dispose();
  135. return true;
  136. }
  137. #endregion
  138. #region 便捷查询
  139. public IReadOnlyList<ICommunication> GetAll() => Devices;
  140. public IReadOnlyList<ICommunication> GetConnected() => Devices.Where(d => d.IsConnected).ToList();
  141. public IReadOnlyList<ICommunication> GetDisconnected() => Devices.Where(d => !d.IsConnected).ToList();
  142. public IReadOnlyList<ICommunication> GetTcpClients() => Devices.Where(d => d is Devices.TcpClientCommunication).ToList();
  143. public IReadOnlyList<ICommunication> GetTcpServers() => Devices.Where(d => d is Devices.TcpServerCommunication).ToList();
  144. public IReadOnlyList<ICommunication> GetSerials() => Devices.Where(d => d is Devices.SerialCommunication).ToList();
  145. public IReadOnlyList<ICommunication> GetUdps() => Devices.Where(d => d is Devices.UdpCommunication).ToList();
  146. public IReadOnlyList<ICommunication> GetWebs() => Devices.Where(d => d is Devices.WebCommunication).ToList();
  147. #endregion
  148. #region PLC 查询
  149. public IReadOnlyList<ICommunication> GetAllPlcModules() => Devices.Where(d => d is PlcCommunicationBase).ToList();
  150. public IReadOnlyList<ICommunication> GetConnectedPlcModules() => GetAllPlcModules().Where(p => p.IsConnected).ToList();
  151. public IReadOnlyList<ICommunication> GetPlcByProtocol(string typeKey) => Devices.Where(d => d is PlcCommunicationBase && d.TypeKey == typeKey).ToList();
  152. #endregion
  153. #region 连接操作
  154. public void Connect(Guid id)
  155. {
  156. var device = Get(id);
  157. if (device != null) device.Connect();
  158. }
  159. public void Disconnect(Guid id)
  160. {
  161. var device = Get(id);
  162. if (device != null) device.Disconnect();
  163. }
  164. public void ConnectAll()
  165. {
  166. foreach (var device in Devices) try { device.Connect(); } catch { }
  167. }
  168. public void DisconnectAll()
  169. {
  170. foreach (var device in Devices) try { device.Disconnect(); } catch { }
  171. }
  172. public void ConnectAllPlc()
  173. {
  174. foreach (var plc in GetAllPlcModules()) try { plc.Connect(); } catch { }
  175. }
  176. public void DisconnectAllPlc()
  177. {
  178. foreach (var plc in GetAllPlcModules()) try { plc.Disconnect(); } catch { }
  179. }
  180. #endregion
  181. #region PLC 读写便捷方法
  182. public object ReadPlcValue(Guid plcId, string address)
  183. {
  184. var plc = Get(plcId);
  185. if (plc == null) throw new InvalidOperationException($"PLC 设备未找到: {plcId}");
  186. return plc.ReadValue(address);
  187. }
  188. public void WritePlcValue(Guid plcId, string address, object value)
  189. {
  190. var plc = Get(plcId);
  191. if (plc == null) throw new InvalidOperationException($"PLC 设备未找到: {plcId}");
  192. plc.WriteValue(address, value);
  193. }
  194. public async System.Threading.Tasks.Task<object> ReadPlcValueAsync(Guid plcId, string address)
  195. {
  196. var plc = Get(plcId);
  197. if (plc == null) throw new InvalidOperationException($"PLC 设备未找到: {plcId}");
  198. return await plc.ReadValueAsync(address);
  199. }
  200. public async System.Threading.Tasks.Task WritePlcValueAsync(Guid plcId, string address, object value)
  201. {
  202. var plc = Get(plcId);
  203. if (plc == null) throw new InvalidOperationException($"PLC 设备未找到: {plcId}");
  204. await plc.WriteValueAsync(address, value);
  205. }
  206. #endregion
  207. #region 清理
  208. public void Clear()
  209. {
  210. List<ICommunication> all;
  211. lock (_sync) { all = _devices.Values.ToList(); _devices.Clear(); }
  212. foreach (var device in all) { try { device.Disconnect(); } catch { } device.Dispose(); }
  213. }
  214. public void Dispose() { Clear(); }
  215. #endregion
  216. #region 配置持久化(Newtonsoft.Json)
  217. public static string DefaultConfigPath
  218. {
  219. get
  220. {
  221. return Path.Combine(TeamAAS.PathHelper.ConfigDirectory, "Communications.json");
  222. }
  223. }
  224. private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings
  225. {
  226. TypeNameHandling = TypeNameHandling.All,
  227. Formatting = Formatting.Indented,
  228. ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
  229. ContractResolver = new CamelCasePropertyNamesContractResolver(),
  230. DefaultValueHandling = DefaultValueHandling.Populate,
  231. };
  232. public bool SaveConfig(string path = null)
  233. {
  234. path = path ?? DefaultConfigPath;
  235. var all = new List<ICommunication>();
  236. lock (_sync) { all.AddRange(_devices.Values.OrderBy(d => d.Index)); }
  237. return TeamAAS.JsonFileStore.Save(path, all, JsonSettings);
  238. }
  239. public void LoadConfig(string path = null)
  240. {
  241. path = path ?? DefaultConfigPath;
  242. if (!File.Exists(path)) return;
  243. var devices = TeamAAS.JsonFileStore.Load<List<ICommunication>>(path, JsonSettings);
  244. Clear();
  245. if (devices != null)
  246. {
  247. foreach (var d in devices.Where(x => x != null))
  248. {
  249. Register(d);
  250. }
  251. }
  252. }
  253. #endregion
  254. }
  255. }