using Cowain.Bake.BLL; using Cowain.Bake.Common; using Cowain.Bake.Common.Core; using Cowain.Bake.Common.Enums; using Cowain.Bake.Common.Interface; using Cowain.Bake.Model.Entity; using Cowain.Bake.Model.Models; using HslCommunication; using Newtonsoft.Json; using Opc.Ua; using Opc.Ua.Client; using OpcUaHelper; using Prism.Services.Dialogs; using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Unity; namespace Cowain.Bake.Communication.PLC { public class PLC_OpcUaClient : PLCBase { readonly static object _objLock = new object(); readonly CancellationTokenSource cts = new CancellationTokenSource(); bool isSubscribeNodes = false; public OpcUaClient opcUaClient { get; set; } ITrigService _trigService { get; set; } public PLC_OpcUaClient(IUnityContainer unityContainer, IDialogService dialogService) : base(unityContainer, dialogService) { opcUaClient = new OpcUaClient(); OPC = opcUaClient; opcUaClient.ConnectComplete += OpcUaClient_ConnectComplete; opcUaClient.KeepAliveComplete += OpcUaClient_KeepAliveComplete; opcUaClient.ReconnectStarting += OpcUaClient_ReconnectStarting; opcUaClient.ReconnectComplete += OpcUaClient_ReconnectComplete; opcUaClient.OpcStatusChange += OpcUaClient_OpcStatusChange; _trigService = _unityContainer.Resolve(); //MachinePLCService //Start(); } ~PLC_OpcUaClient() { Close(); Stop(); } public void Start() { Task.Run(async () => { while (!cts.Token.IsCancellationRequested) { if (Global.AppExit) { return; } await Task.Delay(Global.HEARTBEAT_INTERVAL_TIME); if (null == opcUaClient || !opcUaClient.Connected || !IsConnect) { continue; } var node = (from secondaryList in Storages from item in secondaryList.VariableList where item.ParamName == EStoveSignal.Heartbeat.ToString() orderby item.Number select item).FirstOrDefault(); if (null == node) { LogHelper.Instance.Warn("查询心跳节点失败"); continue; } OperateResult result = Write(node.Address + node.VarName, 1); if (!result.IsSuccess) { LogHelper.Instance.Warn($"MachineId:{Storages[0].StationId}:写心跳节点失败"); } } }, cts.Token); } public void Stop() { cts.Cancel(); } /// /// 连接服务器结束后马上浏览根节点 /// /// /// private void OpcUaClient_ConnectComplete(object sender, EventArgs e) { try { opcUaClient = (OpcUaClient)sender; //2.重连不成功 OPC = opcUaClient; IsConnect = opcUaClient.Connected; if (!isSubscribeNodes && IsConnect && !Global.AppExit) { opcUaClient.RemoveAllSubscription(); isSubscribeNodes = true; //SubscribeNodes(); //订阅节点 SubscriptionNodes(); CheckAddress(); //add by lsm 20260203 LogHelper.Instance.Fatal($"{ConnectString},订阅节点"); } } catch (Exception exception) { IsConnect = false; LogHelper.Instance.Error($"订阅失败!,{ConnectString},{exception}"); } } void CheckAddress() { foreach (var storage in Storages) { foreach (var item in storage.OPCNodes) { DataValue values1 = opcUaClient.ReadNode(item); if (values1 == null || null == values1.Value || null == values1.WrappedValue.Value || !StatusCode.IsGood(values1.StatusCode)) { LogHelper.Instance.Debug($"{ConnectString},读取失败:{item}"); } } } } private void OpcUaClient_OpcStatusChange(object sender, OpcUaStatusEventArgs e) { try { if (e.Error) //1.重连不成功 { IsConnect = false; LogHelper.Instance.Warn($"OPC UA客户端重连不成功,{e.Text}"); } if (!Regex.IsMatch(e.Text, "Connected")) { LogHelper.Instance.Info($"本OPC UA客户端的终极事件,{IpAddress},{e.Text}"); } } catch (Exception ex) { LogHelper.Instance.Error($"OpcUaClient_OpcStatusChange:" + ex.Message); //throw; } //if (Regex.IsMatch(e.Text, "Reconnecting")) //验证无效 //{ // opcUaClient.Session.Reconnect(); //} //LogHelper.Instance.Info("本OPC UA客户端的终极事件,当客户端的状态变更都会触发,包括了连接,重连,断开,状态激活:" + e.ToString()); } private void OpcUaClient_ReconnectComplete(object sender, EventArgs e) { LogHelper.Instance.Info("重新连接到服务器的时候触发"); } private void OpcUaClient_ReconnectStarting(object sender, EventArgs e) { LogHelper.Instance.Info($"开始重新连接到服务器的时候触发,{IpAddress}"); } private void OpcUaClient_KeepAliveComplete(object sender, EventArgs e) { //LogHelper.Instance.Debug("ua客户端每隔5秒会与服务器进行通讯验证,每次验证都会触发该方法"); } public async override void Connect() { try { if (null != opcUaClient.Session) { //如果session没问题,能关掉,为什么要重连呢,230606 Close(); } //var timeouttask = Task.Delay(3000); //var completedTask = await Task.WhenAny(opcUaClient.ConnectServer(ConnectString), timeouttask); opcUaClient.UserIdentity = new UserIdentity(new AnonymousIdentityToken()); await opcUaClient.ConnectServer(ConnectString); opcUaClient.ReconnectPeriod = 20000; //OPC UA 客户端在与服务器断开连接后尝试重新连接的时间间隔,是一个表示时间间隔的数值,以毫秒为单位 if (null != opcUaClient.Session) { opcUaClient.Session.OperationTimeout = 5000; // 执行操作时的超时时间。它指定了客户端等待服务器响应的最长时间,以毫秒为单位 } LogHelper.Instance.Fatal($"连接OPCUA服务器,{ConnectString}"); //if (completedTask == timeouttask) //{ // LogHelper.Instance.Error("连接OPCUA服务器超时!"); //} } catch (Exception ex) { LogHelper.Instance.Error($"连接OPCUA服务器失败!Connect:{ConnectString},{ex.Message}"); } } public override void Close() { if (null != opcUaClient.Session) { IsConnect = false; isSubscribeNodes = false; opcUaClient?.RemoveAllSubscription(); opcUaClient?.Disconnect(); LogHelper.Instance.Fatal($"Close:{ConnectString},订阅删除"); } } public override void StartRead() { base.StartRead(); } /// /// 把地址拆开,x=3;11, OPC是节点,可以不要 /// new用于隐藏方法,它调用的方法来自于申明的类 /// public override void AnalysisAddress() { } public override void GetStorageArea() { Storages.Clear(); var query = (from v in VariableList //Regex.Replace(v.VarType, @"\[.*\]", string.Empty) orderby v.Id // Convert.ToInt16(v.Address), v.ReadLength group v by new { v.StationId, v.VarType } //new {v.DeviceId, v.VarType} //按读的数据类型分组 Regex.Replace(v.VarType, @"\[.*\]", string.Empty into a where a.Count() > 0 orderby a.Key.StationId select a).ToList(); foreach (var g in query) { //var first = g.First(); var totalLengthQuery = (from a in g //同类型,同大小的分一组,单个和数组各一组, orderby a.ArrayLength group a by a.ArrayLength > 1 into b //a.VarType.Substring(a.VarType.Length - 1) select new { VarList = b, Id = b.First().Id, StationId = b.First().StationId, AddressType = b.First().VarType, //StartAddress = b.First().ReadLength, //这样第一次偏移永远是0 GroupReadLength = b.Sum(t => t.ArrayLength) }).ToList(); totalLengthQuery.ForEach(x => { List nodes = new List(); List variables = x.VarList.ToList(); var readNode = variables.Where(ns => ns.OperType != (int)EOperTypePLC.Writable).ToList(); foreach (var variable in readNode) { nodes.Add($"{variable.Address}{variable.VarName}"); } Storages.Add(new StorageArea() { Id = x.Id, StationId = x.StationId, AddressType = x.AddressType, //StartAddress = x.StartAddress.ToString(), OPCNodes = nodes, Len = x.GroupReadLength, //如何判断是不是数组 VariableList = variables }); }); } } public override void GetJsonParam(string param) { dynamic d = JsonConvert.DeserializeObject(param); this.ConnectString = d.ConnectString; } //public byte[] TToByte(T[] value) //{ // string typeName = value.GetType().FullName.Replace("[]", string.Empty); // Type type = Type.GetType(typeName); //System.UInt16 // int size = Marshal.SizeOf(type); // byte[] result = new byte[value.Length * size]; // Buffer.BlockCopy(value, 0, result, 0, result.Length); // return result; //} public override OperateResult Read(string tag) { OperateResult operateResult = new OperateResult() { IsSuccess = false, }; if (!opcUaClient.Connected) { return operateResult; } operateResult.Content = opcUaClient.ReadNode(tag); if (null != operateResult.Content) { operateResult.IsSuccess = true; } return operateResult; } /// /// 读多个节点,返回的数据类型相同 item.OPCNodes, item.AddressType, (item.VariableList[0].ArrayLength > 1) ? true : false /// /// /// /// /// public OperateResult ReadNode(StorageArea storage) { //opcUaClient.ReadNode 读单个未知数据类型 //opcUaClient.ReadNode<> 读单个已知数据类型; 也可以是个数组地址,返回一个数组(同类型) //opcUaClient.ReadNodes 读多个节点数据,可以是多个不同类型的数据;一个节点,一个Object,返回object列表,1对1的关系 //opcUaClient.ReadNodes<> 读多个节点数据,同一类型,返回一个数组 //MethodInfo mi = null; bool result; OperateResult operateResult = new OperateResult() { IsSuccess = false, Content = false, }; result = Read(storage); operateResult.IsSuccess = result; operateResult.Content = result; return operateResult; } public override List Reads(NodeId[] nodeIds) { return opcUaClient.ReadNodes(nodeIds); } public bool Read(StorageArea storage) { int index = 0; List nodeIds = new List(); foreach (var item in storage.OPCNodes) { nodeIds.Add(new NodeId(item)); } if (0 == nodeIds.Count || Global.AppExit || !opcUaClient.Connected) { return true; } try { List values = opcUaClient.ReadNodes(nodeIds.ToArray()); var readNode = storage.VariableList.Where(ns => ns.OperType != (int)EOperTypePLC.Writable).ToList(); foreach (var node in readNode) { if (values[index] == null || null == values[index].Value || null == values[index].WrappedValue.Value || !StatusCode.IsGood(values[index].StatusCode)) { node.Quality = false; //LogHelper.Instance.Error($"读取节点数据失败,{node.VarName},{node.VarDesc}"); //_unityContainer.Resolve().AddLog($"PLC_OpcUaClient:Read:读取节点数据失败,{node.VarName},{node.VarDesc}", E_LogType.Info.ToString()); return false; } else { node.CurValue = values[index].WrappedValue.Value; node.Value = ShowValue(values[index]); node.Quality = true; } index++; } } catch (Exception ex) { string json = JsonConvert.SerializeObject(storage.OPCNodes.ToArray()); LogHelper.Instance.Fatal($"读取节点,DeviceId:【{json}】报错," + ex.Message); return false; } return true; } public object ShowValue(DataValue value) { if (value.WrappedValue.TypeInfo.ValueRank == -1) { return value.WrappedValue.Value; } if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.Int32) { return string.Join("|", (int[])value.WrappedValue.Value); // 最终值 } else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.UInt32) { return string.Join("|", (uint[])value.WrappedValue.Value); // 数组的情况参照上面的例子 } else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.Float) { return string.Join("|", (float[])value.WrappedValue.Value); // 数组的情况参照上面的例子 } else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.Boolean) { return string.Join("|", (bool[])value.WrappedValue.Value); // 数组的情况参照上面的例子 } else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.UInt16) { return string.Join("|", (UInt16[])value.WrappedValue.Value); // 数组的情况参照上面的例子 } else if (value.WrappedValue.TypeInfo.BuiltInType == Opc.Ua.BuiltInType.Int16) { return string.Join("|", (Int16[])value.WrappedValue.Value); // 数组的情况参照上面的例子 } else { return "None"; } } public override void ReadStorageArea() { bool readError = false; OperateResult read; foreach (var item in Storages) { for (int i = 0; i < Global.MAX_READS; i++) { if (Global.AppExit) { return; } read = ReadNode(item); if (read.IsSuccess) { IsConnect = true; readError = false; break; } else { foreach (var val in item.VariableList) { val.Quality = false; } //LogHelper.Instance.Error($"{ConnectString},ErrorCode:{read.ErrorCode},读错误:" + read.Message); } readError = true; //由于此处是不停的根据表中的配置刷新数据的,OPC UA建立security channel的成本很高,所以必须限制此处的时间 //给其他线程的读写留出空余时间 Thread.Sleep(199); } Thread.Sleep(199); if (readError) { //230605 //IsConnect = false; //break; } } // 触发 GC GC.Collect(); GC.WaitForPendingFinalizers(); } //上下料 public override OperateResult GetValue(string paramName) { string msg = ""; OperateResult result = new OperateResult() { IsSuccess = false, }; Variable node = (from storage in Storages from item in storage.VariableList where item.ParamName == paramName select item).FirstOrDefault(); if (null == node || null == node.CurValue) { msg = $"获取内存PLC数据失败,paramName:{paramName}"; result.Message = msg; return result; } else { result.Content = (T)Convert.ChangeType(node.CurValue, typeof(T)); result.IsSuccess = true; return result; } } public override Variable GetVariable(string paramName, int machineId, int number = 0) { Variable node = (from storage in Storages from item in storage.VariableList where storage.StationId == machineId && item.ParamName == paramName && item.Number == number select item).FirstOrDefault(); if (null == node) { LogHelper.Instance.GetCurrentClassError("没有找到这个节点信息"); } return node; } public override OperateResult GetValue(string paramName, int stationId, int number = 1) { string msg = ""; OperateResult result = new OperateResult() { IsSuccess = false, }; Variable node = (from storage in Storages from item in storage.VariableList where storage.StationId == stationId && item.ParamName == paramName && item.Number == number select item).FirstOrDefault(); if (null == node || null == node.CurValue) { LogHelper.Instance.Error($"没有节点信息:{paramName},工站:{stationId},number:{number}"); msg = $"获取内存PLC数据失败,addr:{node.Address}{node.VarName},number:{number},{stationId}"; result.Message = msg; return result; } else { result.Content = (T)Convert.ChangeType(node.CurValue, typeof(T)); result.IsSuccess = true; return result; } } //可以发送多个地址 public override OperateResult Writes(string[] tags, object[] values, int maxCount = Global.MAX_READS) { OperateResult result = new OperateResult() { IsSuccess = false }; try { for (int i = 0; i < maxCount; i++) { result.IsSuccess = opcUaClient.WriteNodes(tags, values); //现在不能写,因为 if (result.IsSuccess) { break; } Thread.Sleep(100); } //写不成功就断开链接暂时屏蔽,针对OPC UA,因为重建session需要成本太大 //230605 //IsConnect = false; //出异常再捕获,写不成功暂时不捕获 //LogHelper.Instance.Info($"写数据失败,节点 = {tags.ToString()},值={values.ToString()}"); } catch (Exception ex) { LogHelper.Instance.Error($"写数据失败,节点 = {tags},值={values},失败原因:{ex}"); _unityContainer.Resolve().AddLog($"PLC_OpcUaClient:写数据失败,节点 = {tags},值={values}", E_LogType.Error.ToString()); } return result; } //T:可以为数组 public override OperateResult Write(string address, T data, int maxCount = Global.MAX_READS) { OperateResult result = new OperateResult() { IsSuccess = false }; try { for (int i = 0; i < maxCount; i++) { if (!opcUaClient.Connected) { return result; } result.IsSuccess = opcUaClient.WriteNode(address, data); //现在不能写,因为 if (result.IsSuccess) { break; //return result; } Thread.Sleep(199); } //230605 //IsConnect = false; //LogHelper.Instance.Info($"写数据失败,节点 = {address},值={data.ToString()}"); } catch (Exception ex) { LogHelper.Instance.Error($"写数据失败,节点 = {address},值={data},失败原因:{ex}"); _unityContainer.Resolve().AddLog($"PLC_OpcUaClient:Write:写数据失败,节点 = {address},值={data},失败原因:" + ex.Message, E_LogType.Error.ToString()); } return result; } public void SubscriptionNodes() { List SubInfoNodes = (from v in VariableList orderby v.Index where v.TrigEnable == true //按读的数据类型分组 炉子有42个信号 select v).ToList(); foreach (var item in SubInfoNodes) { string vkey = $"{item.Id}"; string nodeId = $"{item.Address}{item.VarName}"; SingleNodeIdDatasSubscription(vkey, nodeId, (key, monitoredItem, args) => { if (vkey == key) { MonitoredItemNotification notification = args.NotificationValue as MonitoredItemNotification; if (notification != null) { item.Quality = StatusCode.IsGood(notification.Value.StatusCode); if (item.Quality) { _unityContainer.Resolve().MsgBlock.Add(new BlockData(notification.Value, item)); } } } }); } } /// /// 单节点数据订阅 /// /// 订阅的关键字(必须唯一) /// 节点:"ns=3;s=\"test\".\"Static_1\"" /// 数据订阅的回调方法 public void SingleNodeIdDatasSubscription(string key, string nodeId, Action callback) { if (opcUaClient == null) return; try { opcUaClient.AddSubscription(key, nodeId, callback); } catch (Exception ex) { LogHelper.Instance.Error($"订阅节点异常:{nodeId},Msg={ex.Message}"); } } } }