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 ReadNodes { get => readNodes; set { readNodes = value; ConfigReadNodes(); } } private List readNodes; public Dictionary 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 MonitorRules { get => monitorRules; set { monitorRules=value; readNodes.Clear(); List rulesNeedToRemove=new List(); 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 monitorRules; [Category("PLC")] [Description("是否检测有和指定值相同")] [DefaultValue(true)] public bool CheckAnyEqualsTargetValue { get; set; } = true; public bool HaveDataEqualsTargetValue { get;private set; } // 新增:报警/事件专用事件 [Description("监控的某个寄存器被触发时")] public event Action> OnDataTriggered; [Description("监控的某个寄存器触发被清除时")] public event Action> OnDataCleared; [Description("监控的所有寄存器都不满足触发条件时的事件")] public event Action OnAllDataCleared; [Description("监控的所有寄存器的值都刷新过后")] [Browsable(true)] public event Action> OnAllDataRefreshed; [Description("有监控的值和目标值相同时")] [Browsable(true)] public event Action> OnDatasEqualTarget; /// /// Occurs when reading nodes from the PLC fails. /// /// 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. public event Action> 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(); monitorRules = new List(); } public PlcDataMonitor(IContainer container) { container.Add(this); InitializeComponent(); readNodes = new List(); monitorRules = new List(); } private void ConfigReadNodes() { NodeStringValuePairs = new Dictionary(); foreach (var node in readNodes) { if (node != null) { if (!string.IsNullOrEmpty(node.Value)) { NodeStringValuePairs.Add(node.Value, null); } } } } public List RemoveInvalidNodes() { var nodesToRemove = new List(); List strings = ReadNodes.Select(n => n.Value).ToList(); try { var rt = Protocol.ReadValue(strings, out List 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(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 addresses=new List(); private List CacheReadNodes() { if(!isReadNodesCached) { addresses = ReadNodes.Select(n => n.Value).ToList(); isReadNodesCached = true; } return addresses; } public List Read() { if (Protocol != null && ReadNodes != null) { CacheReadNodes(); var rt = Protocol.ReadValue(addresses, out List results); if (rt == 0) { UpdateValues(ReadNodes, results); return results; } else { OnReadRegisterFailed?.Invoke(this, ReadNodes[0], rt); } } return null; } private Dictionary _ruleDictCache; private bool _isCacheValid = false; private void EnsureRuleDict() { if (!_isCacheValid) { _ruleDictCache = MonitorRules.ToDictionary(r => r.Node, r => r); _isCacheValid = true; } } List dataEqualsTargetRules = new List(); List triggeredData = new List(); List clearedData = new List(); public void UpdateValues(List nodes, List 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 lastStableValues = new Dictionary(); 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.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.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.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 { /// /// 和目标值比较 /// ToTarget, /// /// 和前一次数据比较 /// ToPreviewValue } public enum CompareResult { None, NoChange, Rising, Falling } }