PlcDataMonitor.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. using IACommService4CSharp;
  2. using PlcCom;
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.ComponentModel;
  7. using System.ComponentModel.Design;
  8. using System.Diagnostics;
  9. using System.Drawing.Design;
  10. using System.Linq;
  11. using System.Text;
  12. using System.Threading.Tasks;
  13. using System.Windows.Forms;
  14. using System.Xml.Linq;
  15. using static PlcComponent.PlcDataMonitor;
  16. namespace PlcComponent
  17. {
  18. //public delegate void DataTriggered(object sender, MonitorEventArgs e);
  19. public partial class PlcDataMonitor :Component, IMultiOutputComponent
  20. {
  21. [Browsable(false)]
  22. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  23. public IPlcComProtocol Protocol { get; set; }
  24. [Category("PLC")]
  25. [Description("添加数据集合地址入口")]
  26. [Browsable(false)]
  27. [Editor(typeof(NodeTypeEditor), typeof(UITypeEditor))]
  28. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  29. public Node NodeToAdd
  30. {
  31. get => nodeToAdd;
  32. set
  33. {
  34. if (value != null && !readNodes.Contains(value))
  35. {
  36. nodeToAdd = value;
  37. readNodes.Add(nodeToAdd);
  38. ConfigReadNodes();
  39. }
  40. }
  41. }
  42. private Node nodeToAdd;
  43. [Category("PLC")]
  44. [Description("数据集合,请不要调用Add或者Remove方法")]
  45. [Browsable(false)]
  46. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  47. [Editor(typeof(NodesCollectionEditor), typeof(UITypeEditor))]
  48. public List<Node> ReadNodes
  49. {
  50. get => readNodes;
  51. set
  52. {
  53. readNodes = value;
  54. ConfigReadNodes();
  55. }
  56. }
  57. private List<Node> readNodes;
  58. public Dictionary<string, object> NodeStringValuePairs;
  59. [Category("PLC")]
  60. [Description("数据更新周期ms")]
  61. [DefaultValue(100)]
  62. public int ReadInterval { get => readInterval; set{ readInterval = value < 10 ? 10 : value; } }
  63. private int readInterval=100;
  64. [Category("PLC")]
  65. [Description("是否启用")]
  66. [DefaultValue(true)]
  67. public bool BackgroundRefreshEnable { get; set; } = true;
  68. [Category("PLC")]
  69. [Description("数据监控集合")]
  70. [Editor(typeof(PlcDataMonitorEditor), typeof(UITypeEditor))]
  71. public List<MonitorRule> MonitorRules
  72. {
  73. get => monitorRules;
  74. set
  75. {
  76. monitorRules=value;
  77. readNodes.Clear();
  78. List<MonitorRule> rulesNeedToRemove=new List<MonitorRule>();
  79. foreach (var item in monitorRules)
  80. {
  81. //NodeToAdd = item.Node;
  82. if (item.Node != null && !readNodes.Contains(item.Node))
  83. {
  84. readNodes.Add(item.Node);
  85. }
  86. else if(readNodes.Contains(item.Node))
  87. {
  88. rulesNeedToRemove.Add(item);//剔除重复的
  89. }
  90. }
  91. if (rulesNeedToRemove.Count > 0)
  92. {
  93. foreach (var item in rulesNeedToRemove)
  94. monitorRules.Remove(item);
  95. }
  96. ConfigReadNodes();
  97. }
  98. }
  99. private List<MonitorRule> monitorRules;
  100. [Category("PLC")]
  101. [Description("是否检测有和指定值相同")]
  102. [DefaultValue(true)]
  103. public bool CheckAnyEqualsTargetValue { get; set; } = true;
  104. public bool HaveDataEqualsTargetValue { get;private set; }
  105. public int RefreshCount;
  106. public TimeSpan RefreshTimeElapsed;
  107. // 新增:报警/事件专用事件
  108. [Description("寄存器的一些值变化时")]
  109. public event Action<List<(Node node, object value)>> OnMonitoredValuesChanged;
  110. [Description("监控的某个寄存器被触发时")]
  111. public event Action<object, List<MonitorEventArgs>> OnDataTriggered;
  112. [Description("监控的某个寄存器触发被清除时")]
  113. public event Action<object, List<MonitorEventArgs>> OnDataCleared;
  114. [Description("监控的所有寄存器都不满足触发条件时的事件")]
  115. public event Action<object> OnAllDataCleared;
  116. [Description("监控的所有寄存器的值都刷新过后")]
  117. [Browsable(true)]
  118. public event Action<object, List<MonitorRule>> OnAllDataRefreshed;
  119. [Description("有监控的值和目标值相同时")]
  120. [Browsable(true)]
  121. public event Action<object, List<MonitorRule>> OnDatasEqualTarget;
  122. /// <summary>
  123. /// Occurs when reading nodes from the PLC fails.
  124. /// </summary>
  125. /// <remarks>This event is triggered when an attempt to read nodes from the PLC does not succeed.
  126. /// Subscribers can use this event to handle errors or retry logic.</remarks>
  127. public event Action<IPlcBindableComponent, List<Node>> ReadNodesFailed;
  128. public class MonitorEventArgs : EventArgs
  129. {
  130. public MonitorRule Rule { get; set; }
  131. public object CurrentValue { get; set; }
  132. public DateTime Timestamp { get; set; }
  133. public string Message => Rule?.Message;
  134. }
  135. public PlcDataMonitor()
  136. {
  137. InitializeComponent();
  138. readNodes = new List<Node>();
  139. monitorRules = new List<MonitorRule>();
  140. }
  141. public PlcDataMonitor(IContainer container)
  142. {
  143. container.Add(this);
  144. InitializeComponent();
  145. readNodes = new List<Node>();
  146. monitorRules = new List<MonitorRule>();
  147. }
  148. private void ConfigReadNodes()
  149. {
  150. NodeStringValuePairs = new Dictionary<string, object>();
  151. foreach (var node in readNodes)
  152. {
  153. if (node != null)
  154. {
  155. if (!string.IsNullOrEmpty(node.Value))
  156. {
  157. NodeStringValuePairs.Add(node.Value, null);
  158. }
  159. }
  160. }
  161. }
  162. public List<Node> RemoveInvalidNodes()
  163. {
  164. var nodesToRemove = new List<Node>();
  165. List<string> strings = ReadNodes.Select(n => n.Value).ToList();
  166. try
  167. {
  168. var rt = Protocol.ReadValue(strings, out List<object> results);
  169. if (rt == 0)
  170. {
  171. for (int i = 0; i < strings.Count; i++)
  172. {
  173. if (results[i] == null)
  174. {
  175. nodesToRemove.Add(ReadNodes[i]);
  176. }
  177. }
  178. }
  179. }
  180. catch (Exception)
  181. {
  182. }
  183. foreach (var item in nodesToRemove)
  184. {
  185. ReadNodes.Remove(item);
  186. //ReadOneNodeFailed.Invoke(c, item, 0);
  187. }
  188. if (nodesToRemove.Count > 0)
  189. {
  190. var itemsToRemove = new HashSet<Node>(nodesToRemove);
  191. MonitorRules = MonitorRules.Where(b => !itemsToRemove.Contains(b.Node)).ToList();
  192. ReadNodesFailed?.Invoke(this, nodesToRemove);
  193. }
  194. return nodesToRemove;
  195. }
  196. public event RegisterValueChangedHandler OnPLCRegisterValueChanged;
  197. public event ReadRegisterFailedHandler OnReadRegisterFailed;
  198. public event UpdateComponentStateFailedHandler OnUpdateComponentStateFailed;
  199. private bool isReadNodesCached = false;
  200. private List<string> addresses=new List<string>();
  201. private List<string> CacheReadNodes()
  202. {
  203. if(!isReadNodesCached)
  204. {
  205. addresses = ReadNodes.Select(n => n.Value).ToList();
  206. isReadNodesCached = true;
  207. }
  208. return addresses;
  209. }
  210. public List<object> Read()
  211. {
  212. var sw=Stopwatch.StartNew();
  213. if (Protocol != null && ReadNodes != null)
  214. {
  215. CacheReadNodes();
  216. var rt = Protocol.ReadValue(addresses, out List<object> results);
  217. if (rt == 0)
  218. {
  219. UpdateValues(ReadNodes, results);
  220. RefreshTimeElapsed = sw.Elapsed;
  221. return results;
  222. }
  223. else
  224. {
  225. OnReadRegisterFailed?.Invoke(this, ReadNodes[0], rt);
  226. }
  227. }
  228. return null;
  229. }
  230. private Dictionary<Node, MonitorRule> _ruleDictCache;
  231. private bool _isCacheValid = false;
  232. private void EnsureRuleDict()
  233. {
  234. if (!_isCacheValid)
  235. {
  236. _ruleDictCache = MonitorRules.ToDictionary(r => r.Node, r => r);
  237. _isCacheValid = true;
  238. }
  239. }
  240. List<(Node, object)> changedMonitoredValues = new List<(Node, object)>();
  241. List<MonitorRule> dataEqualsTargetRules = new List<MonitorRule>();
  242. List<MonitorEventArgs> triggeredData = new List<MonitorEventArgs>();
  243. List<MonitorEventArgs> clearedData = new List<MonitorEventArgs>();
  244. public void UpdateValues(List<Node> nodes, List<object> values)
  245. {
  246. EnsureRuleDict(); //缓存字典
  247. bool haveDataEqualsTarget = false;
  248. changedMonitoredValues.Clear();
  249. dataEqualsTargetRules.Clear();
  250. triggeredData.Clear();
  251. clearedData.Clear();
  252. for (int i = 0; i < nodes.Count; i++)
  253. {
  254. // 1. 原有值变化通知
  255. if (NodeStringValuePairs.TryGetValue(nodes[i].Value, out var pair))
  256. {
  257. if (!object.Equals(pair, values[i]))
  258. {
  259. NodeStringValuePairs[nodes[i].Value] = values[i];
  260. OnPLCRegisterValueChanged?.Invoke(this, ReadNodes[i], values[i]);
  261. changedMonitoredValues.Add((ReadNodes[i], values[i]));
  262. }
  263. }
  264. // 2. 监控值的变化通知
  265. if (_ruleDictCache.TryGetValue(nodes[i], out var rule))
  266. {
  267. rule.CurrentValue = values[i];
  268. if (CheckAnyEqualsTargetValue && object.Equals(rule.TargetValue, values[i]))
  269. {
  270. haveDataEqualsTarget = true;
  271. dataEqualsTargetRules.Add(rule);
  272. }
  273. var result = CheckAndTrigger(rule);
  274. if (result== CompareResult.Rising)
  275. {
  276. triggeredData.Add(new MonitorEventArgs
  277. {
  278. Rule = rule,
  279. CurrentValue = rule.CurrentValue,
  280. Timestamp = DateTime.Now
  281. });
  282. }
  283. else if (result== CompareResult.Falling)
  284. {
  285. clearedData.Add(new MonitorEventArgs
  286. {
  287. Rule = rule,
  288. CurrentValue = rule.CurrentValue,
  289. Timestamp = DateTime.Now
  290. });
  291. }
  292. }
  293. }
  294. if(changedMonitoredValues.Count>0)
  295. {
  296. OnMonitoredValuesChanged?.Invoke(changedMonitoredValues);
  297. }
  298. if(triggeredData.Count>0)
  299. {
  300. OnDataTriggered?.Invoke(this, triggeredData);
  301. }
  302. if (clearedData.Count > 0)
  303. {
  304. OnDataCleared?.Invoke(this, clearedData);
  305. }
  306. if (HaveDataEqualsTargetValue && !haveDataEqualsTarget)
  307. {
  308. OnAllDataCleared?.Invoke(this);
  309. }
  310. HaveDataEqualsTargetValue = haveDataEqualsTarget;
  311. if(haveDataEqualsTarget)
  312. {
  313. OnDatasEqualTarget?.Invoke(this, dataEqualsTargetRules);
  314. }
  315. OnAllDataRefreshed?.Invoke(this, MonitorRules);
  316. RefreshCount++;
  317. }
  318. private Dictionary<Node, (object value, DateTime timestamp)> lastStableValues = new Dictionary<Node, (object value, DateTime timestamp)>();
  319. private CompareResult CheckAndTrigger(MonitorRule rule)
  320. {
  321. int rt=0;
  322. object x=rule.CurrentValue, y=null;
  323. CompareResult result;
  324. if (rule.CompareType == CompareType.ToTarget)
  325. {
  326. y = rule.TargetValue;
  327. bool e = object.Equals(x, y);
  328. if (rule.HaveComparedOnce)
  329. {
  330. if (e)
  331. {
  332. if (rule.LastObjectEqual)
  333. result = CompareResult.NoChange;
  334. else
  335. result = CompareResult.Rising;
  336. }
  337. else
  338. {
  339. if (rule.LastObjectEqual)
  340. result = CompareResult.Falling;
  341. else
  342. result = CompareResult.NoChange;
  343. }
  344. }
  345. else
  346. {
  347. if (e)
  348. result = CompareResult.Rising;
  349. else
  350. result = CompareResult.None;
  351. rule.HaveComparedOnce = true;
  352. }
  353. rule.LastObjectEqual = e;
  354. return result;
  355. }
  356. else if (rule.CompareType == CompareType.ToPreviewValue)
  357. {
  358. // 获取上一次值
  359. if (lastStableValues.TryGetValue(rule.Node, out var lastValue))
  360. {
  361. y = lastValue.value;
  362. }
  363. else if (rule.FirstRefreshTrigger)
  364. {
  365. y = GetDefaultValue(rule.CurrentValue);
  366. }
  367. lastStableValues[rule.Node] = (x, DateTime.Now);
  368. if (y != null)
  369. {
  370. rt = Comparer<object>.Default.Compare(x, y);
  371. if (rt > 0)
  372. result = CompareResult.Rising;
  373. else if (rt == 0)
  374. result = CompareResult.NoChange;
  375. else
  376. result = CompareResult.Falling;
  377. return result;
  378. }
  379. else
  380. {
  381. return CompareResult.None;
  382. }
  383. }
  384. else
  385. return CompareResult.None;
  386. }
  387. static object GetDefaultValue(object boxedValue)
  388. {
  389. if (boxedValue == null)
  390. throw new ArgumentNullException(nameof(boxedValue));
  391. Type t = boxedValue.GetType();
  392. if (!t.IsValueType)
  393. throw new ArgumentException("Only value types are supported.");
  394. return Activator.CreateInstance(t);
  395. }
  396. }
  397. public enum MonitorType
  398. {
  399. RisingEdge,
  400. FallingEdge,
  401. RisingOrFallingEdge,
  402. SameValue,
  403. }
  404. [Serializable]
  405. public class MonitorRule
  406. {
  407. private object targetValue = true;
  408. [Description("读取地址")]
  409. [Editor(typeof(NodeTypeEditor), typeof(UITypeEditor))]
  410. public Node Node { get; set; }
  411. [Description("监控变量类型")]
  412. [DefaultValue(MonitorType.RisingOrFallingEdge)]
  413. public MonitorType MonitorType { get; set; } = MonitorType.RisingOrFallingEdge;
  414. [Description("当前值")]
  415. public object CurrentValue { get; set; }
  416. [Description("数据比较方式")]
  417. [DefaultValue(CompareType.ToTarget)]
  418. public CompareType CompareType { get; set; }
  419. public bool HaveComparedOnce { get; set; }
  420. public bool LastObjectEqual { get; set; }
  421. [Description("当CompareType=ToTarget 时所要对比的值")]
  422. [DefaultValue(true)]
  423. [TypeConverter(typeof(StringConverter))]
  424. public object TargetValue { get => targetValue; set { targetValue = value;if (targetValue is bool b) TargetIsBoolFalse = !b; } }
  425. [Description("第一次满足上升或者下降沿时是否触发事件")]
  426. [DefaultValue(true)]
  427. public bool FirstRefreshTrigger { get; set; } = true; // 第一次满足条件是否触发
  428. [Description("触发事件的消息")]
  429. public string Message { get; set; }
  430. internal bool TargetIsBoolFalse { get; set; }
  431. }
  432. public enum CompareType
  433. {
  434. /// <summary>
  435. /// 和目标值比较
  436. /// </summary>
  437. ToTarget,
  438. /// <summary>
  439. /// 和前一次数据比较
  440. /// </summary>
  441. ToPreviewValue
  442. }
  443. public enum CompareResult
  444. {
  445. None,
  446. NoChange,
  447. Rising,
  448. Falling
  449. }
  450. }