| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 |
- using IACommService4CSharp;
- using PlcCom;
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.ComponentModel.Design;
- using System.Diagnostics;
- using System.Drawing.Design;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using System.Windows.Forms;
- using System.Xml.Linq;
- using static PlcComponent.PlcDataMonitor;
- namespace PlcComponent
- {
- //public delegate void DataTriggered(object sender, MonitorEventArgs e);
- public partial class PlcDataMonitor :Component, IMultiOutputComponent
- {
- [Browsable(false)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public IPlcComProtocol Protocol { get; set; }
- [Category("PLC")]
- [Description("添加数据集合地址入口")]
- [Browsable(false)]
- [Editor(typeof(NodeTypeEditor), typeof(UITypeEditor))]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public Node NodeToAdd
- {
- get => nodeToAdd;
- set
- {
- if (value != null && !readNodes.Contains(value))
- {
- nodeToAdd = value;
- readNodes.Add(nodeToAdd);
- ConfigReadNodes();
- }
- }
- }
- private Node nodeToAdd;
- [Category("PLC")]
- [Description("数据集合,请不要调用Add或者Remove方法")]
- [Browsable(false)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- [Editor(typeof(NodesCollectionEditor), typeof(UITypeEditor))]
- public List<Node> ReadNodes
- {
- get => readNodes;
- set
- {
- readNodes = value;
- ConfigReadNodes();
- }
- }
- private List<Node> readNodes;
- public Dictionary<string, object> NodeStringValuePairs;
- [Category("PLC")]
- [Description("数据更新周期ms")]
- [DefaultValue(100)]
- public int ReadInterval { get => readInterval; set{ readInterval = value < 10 ? 10 : value; } }
- private int readInterval=100;
- [Category("PLC")]
- [Description("是否启用")]
- [DefaultValue(true)]
- public bool BackgroundRefreshEnable { get; set; } = true;
- [Category("PLC")]
- [Description("数据监控集合")]
- [Editor(typeof(PlcDataMonitorEditor), typeof(UITypeEditor))]
- public List<MonitorRule> MonitorRules
- {
- get => monitorRules;
- set
- {
- monitorRules=value;
- readNodes.Clear();
- List<MonitorRule> rulesNeedToRemove=new List<MonitorRule>();
- foreach (var item in monitorRules)
- {
- //NodeToAdd = item.Node;
- if (item.Node != null && !readNodes.Contains(item.Node))
- {
- readNodes.Add(item.Node);
- }
- else if(readNodes.Contains(item.Node))
- {
- rulesNeedToRemove.Add(item);//剔除重复的
- }
- }
- if (rulesNeedToRemove.Count > 0)
- {
- foreach (var item in rulesNeedToRemove)
- monitorRules.Remove(item);
- }
- ConfigReadNodes();
- }
- }
- private List<MonitorRule> monitorRules;
- [Category("PLC")]
- [Description("是否检测有和指定值相同")]
- [DefaultValue(true)]
- public bool CheckAnyEqualsTargetValue { get; set; } = true;
- public bool HaveDataEqualsTargetValue { get;private set; }
-
- // 新增:报警/事件专用事件
- [Description("监控的某个寄存器被触发时")]
- public event Action<object, List<MonitorEventArgs>> OnDataTriggered;
- [Description("监控的某个寄存器触发被清除时")]
- public event Action<object, List<MonitorEventArgs>> OnDataCleared;
- [Description("监控的所有寄存器都不满足触发条件时的事件")]
- public event Action<object> OnAllDataCleared;
- [Description("监控的所有寄存器的值都刷新过后")]
- [Browsable(true)]
- public event Action<object, List<MonitorRule>> OnAllDataRefreshed;
- [Description("有监控的值和目标值相同时")]
- [Browsable(true)]
- public event Action<object, List<MonitorRule>> OnDatasEqualTarget;
- /// <summary>
- /// Occurs when reading nodes from the PLC fails.
- /// </summary>
- /// <remarks>This event is triggered when an attempt to read nodes from the PLC does not succeed.
- /// Subscribers can use this event to handle errors or retry logic.</remarks>
- public event Action<IPlcBindableComponent, List<Node>> ReadNodesFailed;
- public class MonitorEventArgs : EventArgs
- {
- public MonitorRule Rule { get; set; }
- public object CurrentValue { get; set; }
- public DateTime Timestamp { get; set; }
- public string Message => Rule?.Message;
- }
- public PlcDataMonitor()
- {
- InitializeComponent();
- readNodes = new List<Node>();
- monitorRules = new List<MonitorRule>();
- }
- public PlcDataMonitor(IContainer container)
- {
- container.Add(this);
- InitializeComponent();
- readNodes = new List<Node>();
- monitorRules = new List<MonitorRule>();
- }
- private void ConfigReadNodes()
- {
- NodeStringValuePairs = new Dictionary<string, object>();
- foreach (var node in readNodes)
- {
- if (node != null)
- {
- if (!string.IsNullOrEmpty(node.Value))
- {
- NodeStringValuePairs.Add(node.Value, null);
- }
- }
- }
- }
- public List<Node> RemoveInvalidNodes()
- {
- var nodesToRemove = new List<Node>();
- List<string> strings = ReadNodes.Select(n => n.Value).ToList();
- try
- {
- var rt = Protocol.ReadValue(strings, out List<object> results);
- if (rt == 0)
- {
- for (int i = 0; i < strings.Count; i++)
- {
- if (results[i] == null)
- {
- nodesToRemove.Add(ReadNodes[i]);
- }
- }
- }
- }
- catch (Exception)
- {
- }
- foreach (var item in nodesToRemove)
- {
- ReadNodes.Remove(item);
- //ReadOneNodeFailed.Invoke(c, item, 0);
- }
- if (nodesToRemove.Count > 0)
- {
- var itemsToRemove = new HashSet<Node>(nodesToRemove);
- MonitorRules = MonitorRules.Where(b => !itemsToRemove.Contains(b.Node)).ToList();
- ReadNodesFailed?.Invoke(this, nodesToRemove);
- }
- return nodesToRemove;
- }
- public event RegisterValueChangedHandler OnPLCRegisterValueChanged;
- public event ReadRegisterFailedHandler OnReadRegisterFailed;
- public event UpdateComponentStateFailedHandler OnUpdateComponentStateFailed;
- private bool isReadNodesCached = false;
- private List<string> addresses=new List<string>();
- private List<string> CacheReadNodes()
- {
- if(!isReadNodesCached)
- {
- addresses = ReadNodes.Select(n => n.Value).ToList();
- isReadNodesCached = true;
- }
- return addresses;
- }
- public List<object> Read()
- {
- if (Protocol != null && ReadNodes != null)
- {
- CacheReadNodes();
- var rt = Protocol.ReadValue(addresses, out List<object> results);
- if (rt == 0)
- {
- UpdateValues(ReadNodes, results);
- return results;
- }
- else
- {
- OnReadRegisterFailed?.Invoke(this, ReadNodes[0], rt);
- }
- }
- return null;
- }
- private Dictionary<Node, MonitorRule> _ruleDictCache;
- private bool _isCacheValid = false;
- private void EnsureRuleDict()
- {
- if (!_isCacheValid)
- {
- _ruleDictCache = MonitorRules.ToDictionary(r => r.Node, r => r);
- _isCacheValid = true;
- }
- }
- List<MonitorRule> dataEqualsTargetRules = new List<MonitorRule>();
- List<MonitorEventArgs> triggeredData = new List<MonitorEventArgs>();
- List<MonitorEventArgs> clearedData = new List<MonitorEventArgs>();
- public void UpdateValues(List<Node> nodes, List<object> values)
- {
- EnsureRuleDict(); //缓存字典
-
- bool haveDataEqualsTarget = false;
- dataEqualsTargetRules.Clear();
- triggeredData.Clear();
- clearedData.Clear();
- for (int i = 0; i < nodes.Count; i++)
- {
- // 1. 原有值变化通知
- if (NodeStringValuePairs.TryGetValue(nodes[i].Value, out var pair))
- {
- if (!object.Equals(pair, values[i]))
- {
- NodeStringValuePairs[nodes[i].Value] = values[i];
- OnPLCRegisterValueChanged?.Invoke(this, ReadNodes[i], values[i]);
- }
- }
-
- // 2. 监控值的变化通知
- if (_ruleDictCache.TryGetValue(nodes[i], out var rule))
- {
- rule.CurrentValue = values[i];
- if (CheckAnyEqualsTargetValue && object.Equals(rule.TargetValue, values[i]))
- {
- haveDataEqualsTarget = true;
- dataEqualsTargetRules.Add(rule);
- }
- var result = CheckAndTrigger(rule);
- if (result== CompareResult.Rising)
- {
- triggeredData.Add(new MonitorEventArgs
- {
- Rule = rule,
- CurrentValue = rule.CurrentValue,
- Timestamp = DateTime.Now
- });
- }
- else if (result== CompareResult.Falling)
- {
- clearedData.Add(new MonitorEventArgs
- {
- Rule = rule,
- CurrentValue = rule.CurrentValue,
- Timestamp = DateTime.Now
- });
- }
- }
- }
- if(triggeredData.Count>0)
- {
- OnDataTriggered?.Invoke(this, triggeredData);
- }
- if (clearedData.Count > 0)
- {
- OnDataCleared?.Invoke(this, clearedData);
- }
- if (HaveDataEqualsTargetValue && !haveDataEqualsTarget)
- {
- OnAllDataCleared?.Invoke(this);
- }
- HaveDataEqualsTargetValue = haveDataEqualsTarget;
- if(haveDataEqualsTarget)
- {
- OnDatasEqualTarget?.Invoke(this, dataEqualsTargetRules);
- }
- OnAllDataRefreshed?.Invoke(this, MonitorRules);
- }
- private Dictionary<Node, (object value, DateTime timestamp)> lastStableValues = new Dictionary<Node, (object value, DateTime timestamp)>();
- private CompareResult CheckAndTrigger(MonitorRule rule)
- {
- int rt=0;
- object x=rule.CurrentValue, y=null;
- CompareResult result= rule.LatestCompareResult;
- if (rule.CompareType == CompareType.ToTarget)
- {
- y = rule.TargetValue;
- if (!rule.TargetIsBoolFalse)
- {
- rt = Comparer<object>.Default.Compare(x, y);
- if (rule.HaveComparedOnce)
- {
- if (rt > 0)
- result = CompareResult.Rising;
- else if (rt < 0)
- result = CompareResult.Falling;
- if (result != rule.LatestCompareResult)
- {
- rule.LatestCompareResult=result;
- return result;
- }
- else
- return CompareResult.NoChange;
- }
- else
- {
- rule.HaveComparedOnce = true;
- if (rule.FirstRefreshTrigger)
- {
- if (rt >= 0)
- result = CompareResult.Rising;
- else
- result = CompareResult.Falling;
- rule.LatestCompareResult = result;
- if (result != rule.LatestCompareResult)
- {
- rule.LatestCompareResult = result;
- return result;
- }
- else
- return CompareResult.None;
- }
- else
- {
- rule.LatestCompareResult = CompareResult.None;
- return CompareResult.None;
- }
- }
- }
- else//目标是bool类型,且是false
- {
- rt = Comparer<object>.Default.Compare(x, y);
- if (rule.HaveComparedOnce)
- {
- if (rt < 0)
- result = CompareResult.Rising;//由于y永远是false,故不可能达到这一步。
- else if (rt > 0)
- result = CompareResult.Falling;
- if (result != rule.LatestCompareResult)
- {
- rule.LatestCompareResult = result;
- return result;
- }
- else
- return CompareResult.NoChange;
- }
- else
- {
- rule.HaveComparedOnce = true;
- if (rule.FirstRefreshTrigger)
- {
- if (rt <= 0)
- result = CompareResult.Rising;
- if (result != rule.LatestCompareResult)
- {
- rule.LatestCompareResult = result;
- return result;
- }
- else
- return CompareResult.NoChange;
- }
- return CompareResult.None;
- }
- }
- }
- else if (rule.CompareType == CompareType.ToPreviewValue)
- {
- // 获取上一次值
- if (lastStableValues.TryGetValue(rule.Node, out var lastValue))
- {
- y = lastValue.value;
- }
- else if (rule.FirstRefreshTrigger)
- {
- y = GetDefaultValue(rule.CurrentValue);
- }
- lastStableValues[rule.Node] = (x, DateTime.Now);
- if (y != null)
- {
- rt = Comparer<object>.Default.Compare(x, y);
- if (rt > 0)
- result = CompareResult.Rising;
- else if (rt == 0)
- result = CompareResult.NoChange;
- else
- result = CompareResult.Falling;
- return result;
- }
- else
- {
- return CompareResult.None;
- }
- }
- else
- return CompareResult.None;
- }
- static object GetDefaultValue(object boxedValue)
- {
- if (boxedValue == null)
- throw new ArgumentNullException(nameof(boxedValue));
- Type t = boxedValue.GetType();
- if (!t.IsValueType)
- throw new ArgumentException("Only value types are supported.");
- return Activator.CreateInstance(t);
- }
- }
- public enum MonitorType
- {
- RisingEdge,
- FallingEdge,
- RisingOrFallingEdge,
- SameValue,
- }
- [Serializable]
- public class MonitorRule
- {
- private object targetValue = true;
- [Description("读取地址")]
- [Editor(typeof(NodeTypeEditor), typeof(UITypeEditor))]
- public Node Node { get; set; }
- [Description("监控变量类型")]
- [DefaultValue(MonitorType.RisingOrFallingEdge)]
- public MonitorType MonitorType { get; set; } = MonitorType.RisingOrFallingEdge;
- [Description("当前值")]
- public object CurrentValue { get; set; }
- [Description("数据比较方式")]
- [DefaultValue(CompareType.ToTarget)]
- public CompareType CompareType { get; set; }
- public bool HaveComparedOnce { get; set; }
- public CompareResult LatestCompareResult { get; set; } = CompareResult.None;
- [Description("当CompareType=ToTarget 时所要对比的值")]
- [DefaultValue(true)]
- [TypeConverter(typeof(StringConverter))]
- public object TargetValue { get => targetValue; set { targetValue = value;if (targetValue is bool b) TargetIsBoolFalse = !b; } }
- [Description("第一次满足上升或者下降沿时是否触发事件")]
- [DefaultValue(true)]
- public bool FirstRefreshTrigger { get; set; } = true; // 第一次满足条件是否触发
- [Description("触发事件的消息")]
- public string Message { get; set; }
- internal bool TargetIsBoolFalse { get; set; }
- }
- public enum CompareType
- {
- /// <summary>
- /// 和目标值比较
- /// </summary>
- ToTarget,
- /// <summary>
- /// 和前一次数据比较
- /// </summary>
- ToPreviewValue
- }
- public enum CompareResult
- {
- None,
- NoChange,
- Rising,
- Falling
- }
- }
|