Files
6098/Cowain.Bake.Communication/PLC/PLC_OpcUaClient1.cs

716 lines
28 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<ITrigService>(); //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<UInt16>(node.Address + node.VarName, 1);
if (!result.IsSuccess)
{
LogHelper.Instance.Warn($"MachineId:{Storages[0].StationId}:写心跳节点失败");
}
}
}, cts.Token);
}
public void Stop()
{
cts.Cancel();
}
/// <summary>
/// 连接服务器结束后马上浏览根节点
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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(); //订阅节点
LogHelper.Instance.Fatal($"{ConnectString},订阅节点");
}
}
catch (Exception exception)
{
IsConnect = false;
LogHelper.Instance.Error($"订阅失败!,{ConnectString},{exception}");
}
}
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);
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();
}
/// <summary>
/// 把地址拆开x=3;11 OPC是节点可以不要
/// new用于隐藏方法它调用的方法来自于申明的类
/// </summary>
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<string> nodes = new List<string>();
List<Variable> 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<dynamic>(param);
this.ConnectString = d.ConnectString;
}
//public byte[] TToByte<T>(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<T> Read<T>(string tag)
{
OperateResult<T> operateResult = new OperateResult<T>()
{
IsSuccess = false,
};
if (!opcUaClient.Connected)
{
return operateResult;
}
operateResult.Content = opcUaClient.ReadNode<T>(tag);
if (null != operateResult.Content)
{
operateResult.IsSuccess = true;
}
return operateResult;
}
/// <summary>
/// 读多个节点,返回的数据类型相同 item.OPCNodes, item.AddressType, (item.VariableList[0].ArrayLength > 1) ? true : false
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="nodes"></param>
/// <param name="len"></param>
/// <returns></returns>
public OperateResult<bool> ReadNode(StorageArea storage)
{
//opcUaClient.ReadNode 读单个未知数据类型
//opcUaClient.ReadNode<> 读单个已知数据类型; 也可以是个数组地址,返回一个数组(同类型)
//opcUaClient.ReadNodes 读多个节点数据,可以是多个不同类型的数据;一个节点一个Object,返回object列表,1对1的关系
//opcUaClient.ReadNodes<> 读多个节点数据,同一类型,返回一个数组
//MethodInfo mi = null;
bool result;
OperateResult<bool> operateResult = new OperateResult<bool>()
{
IsSuccess = false,
Content = false,
};
result = Read(storage);
operateResult.IsSuccess = result;
operateResult.Content = result;
return operateResult;
}
public override List<DataValue> Reads(NodeId[] nodeIds)
{
return opcUaClient.ReadNodes(nodeIds);
}
public bool Read(StorageArea storage)
{
int index = 0;
List<NodeId> nodeIds = new List<NodeId>();
foreach (var item in storage.OPCNodes)
{
nodeIds.Add(new NodeId(item));
}
//------------------------------------------- add by lsm 20250925
//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.Error($"读取失败:{item}");
// //_unityContainer.Resolve<LogService>().AddLog($"PLC_OpcUaClient:Read:读取节点数据失败,{node.VarName},{node.VarDesc}", E_LogType.Info.ToString());
// }
// else
// {
// int kkk = 0;
// }
//}
//LogHelper.Instance.Debug($"-----end----------");
//-------------------------------------------
if (0 == nodeIds.Count
|| Global.AppExit
|| !opcUaClient.Connected)
{
return true;
}
try
{
List<DataValue> 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<LogService>().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<bool> 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<T> GetValue<T>(string paramName)
{
string msg = "";
OperateResult<T> result = new OperateResult<T>()
{
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<T> GetValue<T>(string paramName, int stationId, int number = 1)
{
string msg = "";
OperateResult<T> result = new OperateResult<T>()
{
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<LogService>().AddLog($"PLC_OpcUaClient:写数据失败,节点 = {tags},值={values}", E_LogType.Error.ToString());
}
return result;
}
//T:可以为数组
public override OperateResult Write<T>(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<T>(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<LogService>().AddLog($"PLC_OpcUaClient:Write:写数据失败,节点 = {address},值={data},失败原因:" + ex.Message, E_LogType.Error.ToString());
}
return result;
}
/// <summary>
/// 订阅节点//AddSubscription单线程回调模型。因此只要你的回调方法没有主动开线程就是串行执行。
/// </summary>
private void SubscribeNodes()
{
string nodeAddr = "";
List<Variable> SubInfoNodes = (from v in VariableList
orderby v.Index
where v.TrigEnable == true //按读的数据类型分组 炉子有42个信号
select v).ToList();
try
{
foreach (var node in SubInfoNodes)
{
nodeAddr = $"{node.Address}{node.VarName}";
opcUaClient.RemoveSubscription(node.Id.ToString());
opcUaClient.AddSubscription(node.Id.ToString(), nodeAddr, SubCallback); //NotificationReceived 事件处理器,当数据变化时,这个处理器会被异步调用
}
}
catch (Exception ex)
{
LogHelper.Instance.Fatal($"订阅时报错,{nodeAddr},{ConnectString},{ex.Message}.");
_unityContainer.Resolve<LogService>().AddLog($"PLC_OpcUaClient:订阅时报错,{nodeAddr},{ConnectString},{ex.Message}.", E_LogType.Debug.ToString());
}
}
/// <summary>
/// 执行订阅的事件(方法)
/// </summary>
/// <param name="key">node.TrigJson</param>
/// <param name="monitoredItem">节点的值</param>
/// <param name="args"></param>
private void SubCallback(string key, MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args)
{
int Id = 0;
try
{
lock (_objLock)
{
Id = int.Parse(key);
if (0 == Id) //OPC服务退出会触发这里
{
LogHelper.Instance.Fatal("OPC触发异常!");
return;
}
// 如果有多个的订阅值都关联了当前的方法可以通过key和monitoredItem来区分
MonitoredItemNotification notification = args.NotificationValue as MonitoredItemNotification;
Variable node = VariableDic[Id]; //索引,之前是遍历
if (null == notification || null == notification.Value
|| null == notification.Value.WrappedValue.Value) //key
{
LogHelper.Instance.Fatal($"触发节点编号;{node.Id},{node.VarDesc},读取数据失败!");
return;
}
if (null == node)
{
LogHelper.Instance.GetCurrentClassWarn("必须对应唯一的一个Write节点");
return;
}
//foreach (var value in monitoredItem.DequeueValues())
//{
//}
//LogHelper.Instance.Info($"1-----SubCallback---回调:{Id}-----{monitoredItem.StartNodeId.ToString()}------信号,线程ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); //此为多线程
_unityContainer.Resolve<PLCBlockingCollection>().MsgBlock.Add(new BlockData(notification.Value, node));
//_trigService.RecvTrigInfo(notification.Value, node);
}
}
catch (Exception e)
{
LogHelper.Instance.Info($"OPC 触发解析出错,{key}{e.Message}");
_unityContainer.Resolve<LogService>().AddLog($"PLC_OpcUaClient:OPC 触发解析出错,{key}{e.Message}", E_LogType.Debug.ToString());
}
}
}
}