编辑
2025-09-30
C#
00

目录

摘要
正文
服务
KepServer配制

摘要

UA全称是unified architecture(统一架构)。为了应对标准化和跨平台的趋势,为了更好地推广OPC,OPC基金会近些年在之前OPC成功应用的基础上推出了一个新的OPC标准-OPC UA。OPC UA接口协议包含了之前的 A&E, DA,OPC XML DA or HDA,只使用一个地址空间就能访问之前所有的对象,而且不受WINDOWS平台限制,因为它是从传输层以上来定义的,导致了灵活性和安全性比之前的OPC都提升了。

正文

opc ua支持c/s模式,同时也支持类似mqtt的发布订阅模式,通常各种设备作为opc ua的服务端提供各种服务。

image.png

image.png

信息模型(Information model)

OPC UA 使用了对象(objects)作为过程系统表示数据和活动的基础。对象包含了变量,事件和方法,它们通过引用(reference)来互相连接。

OPC UA 信息模型是节点的网络(Network of Node),或者称为结构化图(graph),由节点(node)和引用(References)组成,这种结构图称之为OPC UA 的地址空间。这种图形结构可以描述各种各样的结构化信息(对象)。

地址空间要点:

  • 地址空间是用来给服务器提供标准方式,以向客户端表示对象。
  • 地址空间的实现途径是使用对象模型,通过变量和方法的对象,以及表达关系的对象。
  • 地址空间中模型的元素被称为节点,为节点分配节点类来代表对象模型的元素。
  • 对象及其组件在地址空间中表示为节点的集合,节点由属性描述并由引用相连。
  • OPC UA建模的基本在于节点和节点间的引用。

对象模型

image.png

节点模型

image.png

opc ua定义了8种类型的节点

对象(Object)

对象类型(Object Type)

变量(Variable)

变量类型(Variable Type)

方法(Method)

视图(View)

引用(Reference)

数据类型(Data Type)

节点模型要点

  • 节点根据用途分属于不同的节点类别(NodeClass),一些表示实例(/Root/Objects),一些表示类型(/Root/Types)。
  • 节点类依据属性和引用来定义。OPC UA规范定义的节点类称为地址空间的元数据,地址空间中每个节点都是这些节点类的实例。
  • 节点是节点类的实例,属性和引用是节点的基本组件。
  • 属性(Attribute)用于描述节点,不同的节点类别有不同的属性(属性集)。节点类的定义中包括属性的定义,因此属性不包括在地址空间中。
  • 引用(Reference)表示节点间的关系。引用被定义为引用类型节点的实例,存在于地址空间中。
属性数据类型说明
NodeIdNodeId在OPC UA服务器内唯一确定的一个节点,并且在OPC UA服务器中定位该节点
NodeClassInt32该节点的类型(上面列出的8种之一)
BrowseNameQualifiedName浏览OPC UA服务器事定义的节点。它是非本地化的
DisplayNameLocalizedText包含节点的名字,用来在用户接口中显示名字,本地化
DescriptionLocalizedText本地化的描述(可选)
WriteMaskUint32指示哪个节点属性是可写的,即可被OPC UA客户端修改(可选)
UserWriteMaskUint32指示哪个节点属性可以被当前连接到服务器上的用户修改(可选

引用模型

image.png

节点(nodes) : 共计有8种节点(对象,对象类型,变量,变量类型,视图,方法,引用,数据类型)

image.png

服务

服务可以看成是OPC UA服务器提供的API集合,OPC UA与定义了37个标准服务,常用的服务有:

  • 读写服务

可以获取和修改服务器指定节点指定属性的值

  • 调用服务

执行服务器上指定节点的方法

  • 订阅数据变化和订阅事件

可以监控服务器数据的变化

KepServer配制

右键OPC UA配制

image.png

输入管理员姓名与密码

image.png

主要配置以上信息

如果需要通过账号访问,需要设置中用户管理器下添加用户信息。

image.png

如果需要匿名登录,一定要配置以下信息

image.png

连接OPCUA Server

C#
private async void btnConnect_Click(object sender, EventArgs e) { m_OpcUaClient = new OpcUaClient(); try { m_OpcUaClient.UserIdentity = new UserIdentity(new AnonymousIdentityToken()); //m_OpcUaClient.UserIdentity = new UserIdentity("opcuser", "123"); m_OpcUaClient.OpcStatusChange += M_OpcUaClient_OpcStatusChange; await m_OpcUaClient.ConnectServer("opc.tcp://IDIO-002:49320"); this.Text = "连接成功!"; } catch (Exception ex) { this.Text = "连接失败!"; } } bool isConnected = false; /// <summary> /// Opc状态事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void M_OpcUaClient_OpcStatusChange(object sender, OpcUaStatusEventArgs e) { if (m_OpcUaClient != null) { if (e.Error) { isConnected = false; return; } else { isConnected = true; } } }

读取信息

C#
private void btnRead_Click(object sender, EventArgs e) { DataValue dataValue = m_OpcUaClient.ReadNode(new NodeId("ns=2;s=Chanel1.Device1.B1")); uPanel1.Value = dataValue.Value.ToString(); uPanel1.Title ="B1"; dataValue = m_OpcUaClient.ReadNode(new NodeId("ns=2;s=Chanel1.Device1.B2")); uPanel2.Value = dataValue.Value.ToString(); uPanel2.Title = "B2"; dataValue = m_OpcUaClient.ReadNode(new NodeId("ns=2;s=Chanel1.Device1.B3")); uPanel3.Value = dataValue.Value.ToString(); uPanel3.Title = "B3"; dataValue = m_OpcUaClient.ReadNode(new NodeId("ns=2;s=Chanel1.Device1.F1")); uPanel4.Value = dataValue.Value.ToString(); uPanel4.Title = "F1"; dataValue = m_OpcUaClient.ReadNode(new NodeId("ns=2;s=Chanel1.Device1.I1")); uPanel5.Value = dataValue.Value.ToString(); uPanel5.Title = "I1"; dataValue = m_OpcUaClient.ReadNode(new NodeId("ns=2;s=Chanel1.Device1.L1")); uPanel6.Value = dataValue.Value.ToString(); uPanel6.Title = "L1"; dataValue = m_OpcUaClient.ReadNode(new NodeId("ns=2;s=Chanel1.Device1.S1")); uPanel7.Value = dataValue.Value.ToString(); uPanel7.Title = "S1"; dataValue = m_OpcUaClient.ReadNode(new NodeId("ns=2;s=Chanel1.Device1.S2")); uPanel8.Value = dataValue.Value.ToString(); uPanel8.Title = "S2"; }

批量读取

C#
private void btnBatchRead_Click(object sender, EventArgs e) { List<NodeId> nodeIds = new List<NodeId>(); nodeIds.Add(new NodeId("ns=2;s=Chanel1.Device1.B1")); nodeIds.Add(new NodeId("ns=2;s=Chanel1.Device1.B2")); nodeIds.Add(new NodeId("ns=2;s=Chanel1.Device1.B3")); nodeIds.Add(new NodeId("ns=2;s=Chanel1.Device1.F1")); nodeIds.Add(new NodeId("ns=2;s=Chanel1.Device1.I1")); nodeIds.Add(new NodeId("ns=2;s=Chanel1.Device1.L1")); nodeIds.Add(new NodeId("ns=2;s=Chanel1.Device1.S1")); nodeIds.Add(new NodeId("ns=2;s=Chanel1.Device1.S2")); // dataValues按顺序定义的值,每个值里面需要重新判断类型 List<DataValue> dataValues = m_OpcUaClient.ReadNodes(nodeIds.ToArray()); // 然后遍历你的数据信息 int idx = 0; foreach (var dataValue in dataValues) { // 获取你的实际的数据 object value = dataValue.WrappedValue.Value == null ? "" : dataValue.WrappedValue.Value; switch (idx) { case 0: uPanel1.Value = value.ToString(); break; case 1: uPanel2.Value = value.ToString(); break; case 2: uPanel3.Value = value.ToString(); break; case 3: uPanel4.Value = value.ToString(); break; case 4: uPanel5.Value = value.ToString(); break; case 5: uPanel6.Value = value.ToString(); break; case 6: uPanel7.Value = value.ToString(); break; case 7: uPanel8.Value = value.ToString(); break; default: break; } idx++; } }

同类型数据读取

C#
private void btnBatchSameRead_Click(object sender, EventArgs e) { // 如果你批量读取的值的类型都是一样的,比如bool,那么有简便的方式 List<string> tags = new List<string>(); tags.Add("ns=2;s=Chanel1.Device1.B1"); tags.Add("ns=2;s=Chanel1.Device1.B2"); tags.Add("ns=2;s=Chanel1.Device1.B3"); // 按照顺序定义的值 List<bool> values = m_OpcUaClient.ReadNodes<bool>(tags.ToArray()); uPanel1.Value = values[0].ToString(); uPanel2.Value = values[1].ToString(); uPanel3.Value = values[2].ToString(); }

异步读

C#
private async void btnAsyncRead_Click(object sender, EventArgs e) { uPanel1.Value = (await m_OpcUaClient.ReadNodeAsync<bool>("ns=2;s=Chanel1.Device1.B1")).ToString(); uPanel2.Value = (await m_OpcUaClient.ReadNodeAsync<bool>("ns=2;s=Chanel1.Device1.B2")).ToString(); uPanel3.Value = (await m_OpcUaClient.ReadNodeAsync<bool>("ns=2;s=Chanel1.Device1.B3")).ToString(); uPanel4.Value = (await m_OpcUaClient.ReadNodeAsync<float>("ns=2;s=Chanel1.Device1.F1")).ToString(); uPanel5.Value = (await m_OpcUaClient.ReadNodeAsync<short>("ns=2;s=Chanel1.Device1.I1")).ToString(); uPanel6.Value = (await m_OpcUaClient.ReadNodeAsync<Int32>("ns=2;s=Chanel1.Device1.L1")).ToString(); uPanel7.Value = (await m_OpcUaClient.ReadNodeAsync<string>("ns=2;s=Chanel1.Device1.S1")).ToString(); uPanel8.Value = (await m_OpcUaClient.ReadNodeAsync<string>("ns=2;s=Chanel1.Device1.S2")).ToString(); }

异步写

C#
m_OpcUaClient.WriteNode<string>("ns=2;s=Chanel1.Device1.S2", arg1);

批量写

C#
private void btnBatchWrite_Click(object sender, EventArgs e) { bool issuccess = m_OpcUaClient.WriteNodes(new string[] { "ns=2;s=Chanel1.Device1.S1", "ns=2;s=Chanel1.Device1.S2" }, new object[] { "A0001", "A0002" }); }

单个节点侦听

C#
private void btnListen_Click(object sender, EventArgs e) { m_OpcUaClient.AddSubscription("KeyL1", "ns=2;s=Chanel1.Device1.L1", MonitorCallback); } private void MonitorCallback(string key, MonitoredItem arg2, MonitoredItemNotificationEventArgs arg3) { if (InvokeRequired) { Invoke(new Action<string, MonitoredItem, MonitoredItemNotificationEventArgs>(MonitorCallback), key, arg2, arg3); return; } // 如果有多个的订阅值都关联了当前的方法,可以通过key和monitoredItem来区分 MonitoredItemNotification notification = arg3.NotificationValue as MonitoredItemNotification; if (notification != null) { uPanel6.Value = notification.Value.WrappedValue.Value.ToString(); } }

image.png

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!