0%

OPC DA开发常用接口规范介绍-OPCDAAuto自动化接口

OPCDA自动化接口是OPC基金会组织为了方便并统一OPCDA客户端开发而发布的一个接口协议集。自动化接口中共定义了6类对象:OPCServer对象、OPCBrowser对象、OPCGroups对象、OPCGroup对象、OPCItems对象、OPCItem对象, 接下来分别对这些对象的主要功能进行说明。

OPCServer对象

客户端可以通过OPCServer对象连接至OPC主机, 用于获取OPC主机信息、创建和操作Group与Item对象。OPCServer只有一个无参构造函数, 它的主要方法及使用如下:

GetOPCServers(object Node)

获取到OPC主机上的OPC服务列表, 参数为空时默认会获取本机服务列表

1
2
3
4
5
6
7
8
9
10
string Host = "192.168.2.53";
OPCServer LocalOpcServer = new OPCServer(); //OPCServer无参构造函数
//object ServerList = LocalOpcServer.GetOPCServers(); //获取本机服务列表
//object ServerList = LocalOpcServer.GetOPCServers(null); //获取本机服务列表
Array ServerList = LocalOpcServer.GetOPCServers(Host) as Array; //获取主机192.168.1.2上的OPC服务列表

foreach (string Server in ServerList)
{
Console.WriteLine("取得主机{0} OPC服务:{1}", Host, Server);
}

Connect(string ProgID, object Node)

连接主机Node上Program ID为ProgID的OPC服务

1
LocalOpcServer.Connect(ServerList.GetValue(1).ToString(), Host);

QueryAvailableLocaleIDs()

获取OPC主机区域ID列表

1
2
3
4
5
Array LocaleIDs = LocalOpcServer.QueryAvailableLocaleIDs() as Array;
for(int i = 1; i <= LocaleIDs.Length; i++)
{
Console.WriteLine($"QueryAvailableLocaleIDs result index[{i}] value={LocaleIDs.GetValue(i)}");
}

QueryAvailableProperties(string ItemID, out int Count, out Array PropertyIDs, out Array Descriptions, out Array DataTypes)

获取ItemID对应的属性列表

1
2
3
4
5
6
7
8
9
int Count;
Array PropertyIDs;
Array Descriptions;
Array DataTypes;
LocalOpcServer.QueryAvailableProperties("Random.Int4", out Count, out PropertyIDs, out Descriptions, out DataTypes);
for (int i = 1; i <= PropertyIDs.Length; i++)
{
Console.WriteLine($"QueryAvailableProperties result index[{i}] PropertyID={PropertyIDs.GetValue(i)} Description={Descriptions.GetValue(i)} DataType={Descriptions.GetValue(i)}");
}

GetItemProperties(string ItemID, int Count, ref Array PropertyIDs, out Array PropertyValues, out Array Errors)

根据属性列表查询对应ItemID属性值

1
2
3
4
5
6
7
8
9
10
List<int> InPropertyIDs = new List<int>();
InPropertyIDs.Add(0); //必须添加, 否则报错
InPropertyIDs.Add(4);
Array PropertyValues;
Array Errors;
LocalOpcServer.GetItemProperties("Random.Int4", 1, InPropertyIDs.ToArray(), out PropertyValues, out Errors);
for (int i = 1; i <= PropertyValues.Length; i++)
{
Console.WriteLine($"GetItemProperties result index[{i}] value={PropertyValues.GetValue(i)}");
}

GetErrorString(int ErrorCode)

根据ErrorCode获取文字描述

1
2
3
4
foreach (int error in Errors)
{
Console.WriteLine(LocalOpcServer.GetErrorString(error));
}

CreateBrowser()

创建OPCBrowser, 通过OPCBrowser对象可获得OPC服务器的节点信息

1
OPCBrowser LocalOPCbrowser = LocalOpcServer.CreateBrowser();

Disconnect()

断开与OPC主机的连接

1
LocalOpcServer.Disconnect();

完整示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
string Host = "192.168.2.53";
OPCServer LocalOpcServer = new OPCServer(); //OPCServer无参构造函数
//object ServerList = LocalOpcServer.GetOPCServers(); //获取本机服务列表
//object ServerList = LocalOpcServer.GetOPCServers(null); //获取本机服务列表
Array ServerList = LocalOpcServer.GetOPCServers(Host) as Array; //获取主机192.168.1.2上的OPC服务列表

foreach (string Server in ServerList)
{
Console.WriteLine("取得主机{0} OPC服务:{1}", Host, Server);
}

LocalOpcServer.Connect(ServerList.GetValue(1).ToString(), Host);

Array LocaleIDs = LocalOpcServer.QueryAvailableLocaleIDs() as Array;
for(int i = 1; i <= LocaleIDs.Length; i++)
{
Console.WriteLine($"QueryAvailableLocaleIDs result index[{i}] value={LocaleIDs.GetValue(i)}");
}

int Count;
Array PropertyIDs;
Array Descriptions;
Array DataTypes;
LocalOpcServer.QueryAvailableProperties("Random.Int4", out Count, out PropertyIDs, out Descriptions, out DataTypes);
for (int i = 1; i <= PropertyIDs.Length; i++)
{
Console.WriteLine($"QueryAvailableProperties result index[{i}] PropertyID={PropertyIDs.GetValue(i)} Description={Descriptions.GetValue(i)} DataType={Descriptions.GetValue(i)}");
}

List<int> InPropertyIDs = new List<int>();
InPropertyIDs.Add(0); //必须添加, 否则报错
InPropertyIDs.Add(4);
Array PropertyValues;
Array Errors;
LocalOpcServer.GetItemProperties("Random.Int4", 1, InPropertyIDs.ToArray(), out PropertyValues, out Errors);
for (int i = 1; i <= PropertyValues.Length; i++)
{
Console.WriteLine($"GetItemProperties result index[{i}] value={PropertyValues.GetValue(i)}");
}

// GetErrorString获取错误代码描述
foreach (int error in Errors)
{
Console.WriteLine(LocalOpcServer.GetErrorString(error));
}

OPCBrowser LocalOPCbrowser = LocalOpcServer.CreateBrowser();

LocalOpcServer.Disconnect();

OPCBrowser对象

OPCBrowser是OPC Server用来存储分支和节点信息的树形结构, 常用方法如下:

1
2
3
4
5
6
7
void ShowBranches() \\获取当前分支的子分支
void ShowLeafs(object Flat) \\获取叶子叶子节点, 当Flat=True时, 会以扁平的方式返回当前分支下的所有节点信息(包含子节点)
void MoveUp() \\向上移动一个层级
void MoveToRoot() \\移动至树顶(根)
void MoveDown(string Branch) \\向下移动至指定Branch分支
string GetItemID(string Leaf) \\获取节点ID
dynamic GetAccessPaths(string ItemID) \\获取节点的访问路径

构建树形结构的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
OPCBrowser LocalOPCbrowser = LocalOpcServer.CreateBrowser();
ShowOpcTagsRecurcive(LocalOPCbrowser, "");

//递归显示树形结构
private static void ShowOpcTagsRecurcive(OPCBrowser Browser, string prefix)
{
Browser.ShowBranches();

foreach (string item in Browser)
{
Console.WriteLine($"{prefix} branch {Browser.GetItemID(item)}");
Browser.MoveDown(item.ToString());
ShowOpcTagsRecurcive(Browser, prefix + "\t");
Browser.MoveUp();
}

Browser.ShowLeafs();

foreach (string item in Browser)
{

Console.WriteLine($"{prefix} leaf {Browser.GetItemID(item)}");
}

}

OPCGroups对象

OPCGroups是OPCGroup对象的集合, 通过OPCGroups可以获取、添加、删除OPCGroup对象, 另外还可以通过OPCGroups的属性对OPCGroup做一些默认设置。

常用方法

1
2
3
4
5
OPCGroup Item(object ItemSpecifier) //根据指定的参数获取OPCGroup
OPCGroup Add(object Name) //根据指定的名称添加OPCGroup
OPCGroup GetOPCGroup(object ItemSpecifier) //根据指定的参数获取OPCGroup
void RemoveAll() //移除所有OPCGroup
void Remove(object ItemSpecifier) //根据名称移除指定的OPCGroup

常用属性

1
2
3
4
DefaultGroupIsActive //默认是否激活
DefaultGroupUpdateRate //默认更新频率
DefaultGroupDeadBand //默认死区, 变化量超过死区后将会触发DataChange事件
DefaultGroupTimeBias //默认时间偏差

代码示例

1
2
3
4
5
6
7
8
9
10
11
OPCGroups LocalGroups = LocalOpcServer.OPCGroups;
LocalGroups.DefaultGroupIsActive = true;
LocalGroups.DefaultGroupDeadband = 0;
LocalGroups.DefaultGroupUpdateRate = 1000;
LocalGroups.DefaultGroupTimeBias = 5000;

OPCGroup t = LocalGroups.Add("Test");
OPCGroup test = LocalGroups.Item("Test");
OPCGroup Test = LocalGroups.GetOPCGroup("Test");
LocalGroups.Remove("Test");
LocalGroups.RemoveAll();

OPCGroup对象

OPCGroup对象是编程中最常用的对象之一, 它为客户端读写OPCItem提供了同步异步方法, 对于OPCGroups上设置的默认属性也提供了属性进行覆盖。由于对节点数据的读写涉及到OPCGroup、OPCItems和OPCItem, 所以示例代码将在介绍完OPCItem后一起提供。

常用方法

1
2
3
4
5
6
7
8
9
10
11
SyncRead(short Source, int NumItems, ref Array ServerHandles, out Array Values, out Array Errors, out object Qualities, out object TimeStamps) //同步读方法, 其中参数Source只能是OPCAutomation.OPCDataSource.OPCCache或OPCAutomation.OPCDataSource.OPCDevice

SyncWrite(int NumItems, ref Array ServerHandles, ref Array Values, out Array Errors) //同步写方法

AsyncRead(int NumItems, ref Array ServerHandles, out Array Errors, int TransactionID, out int CancelID) //异步读方法

AsyncWrite(int NumItems, ref Array ServerHandles, ref Array Values, out Array Errors, int TransactionID, out int CancelID) //异步写方法

AsyncRefresh(short Source, int TransactionID, out int CancelID) //异步刷新, 其中参数Source只能是OPCAutomation.OPCDataSource.OPCCache或OPCAutomation.OPCDataSource.OPCDevice

AsyncCancel(int CancelID) //异步取消

常用属性

1
2
3
4
5
6
7
8
9
10
Name //获取OPCGroup名称
IsPublic //是否为Public
IsActive //是否激活
IsSubscribed //是否订阅, 设置为true时才会触发DataChange事件
ClientHandle //客户端句柄
ServerHandle //服务端句柄
TimeBias //时间偏移, 对应OPCGroups对象的DefaultGroupDeadBand
DeadBand //死区, 对应OPCGroups对象的DefaultGroupDeadBand
UpdateRate //更新频率, 对应OPCGroups对象的DefaultGroupUpdateRate
OPCItems //获取OPCItems对象

事件

1
2
3
4
DataChange //数据变化事件, 当组内任何OPCItem数据或数据质量发生变化时会触发此事件
AsyncReadComplete //异步读结束事件, 对应AsyncRead方法
AsyncWriteComplete //异步写结束事件, 对应AsyncWrite方法
AsyncCancelComplete //异步取消结束事件, 对应AsyncCancel方法

OPCItems对象

OPCItems对象也是编程中最常用的对象之一, 它提供了添加和删除OPCItem对象的接口。

常用方法

1
2
3
4
5
6
7
8
9
Item(object ItemSpecifier) //根据名称获取OPCItem对象

OPCItem GetOPCItem(int ServerHandle) //根据ServerHandle获取OPCItem对象

OPCItem AddItem(string ItemID, int ClientHandle) //添加OPCItem对象

AddItems(int NumItems, ref Array ItemIDs, ref Array ClientHandles, out Array ServerHandles, out Array Errors, object RequestedDataTypes, object AccessPaths) //批量添加OPCItem对象

Remove(int NumItems, ref Array ServerHandles, out Array Errors) //批量移除OPCItem对象

常用属性

1
Count //获取OPCItems对象所拥有的OPCItem数量

OPCItem对象

OPCItem为服务端存储的数据的载体, 它包含节点的值, 数据通信质量及时间戳。

常用方法

1
2
Read(short Source, out object Value, out object Quality, out object TimeStamp) \\读取数据
void Write(object Value) //写值

常用属性

1
2
3
4
5
6
7
ClientHandle //客户端句柄
ServerHandle //服务端句柄
AccessPath //访问路径
ItemID //ID
Value //值
Quality //数据质量
TimeStamp //更新时间戳

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
OPCGroup LocalGroup = LocalGroups.Add("LocalGroups");
LocalGroup.IsActive = true;
LocalGroup.IsSubscribed = true;
LocalGroup.UpdateRate = 10000; //每十秒刷新一次
LocalGroup.DataChange += Group_DataChange; //事件
LocalGroup.AsyncWriteComplete += LocalGroup_AsyncWriteComplete; //数据变化事件
LocalGroup.AsyncReadComplete += LocalGroup_AsyncReadComplete; //异步读结束事件

ClientHandleItemIDPairs.Add(0, "");
ClientHandleItemIDPairs.Add(1, "Random.Int4");
ClientHandleItemIDPairs.Add(2, "Random.Int2");

try
{
Array ServerHandles;
Array Errors;
int CancelID;
//批量添加监控节点
LocalGroup.OPCItems.AddItems(2, ClientHandleItemIDPairs.Values.ToArray(), ClientHandleItemIDPairs.Keys.ToArray(), out ServerHandles, out Errors);
//添加单个监控节点
OPCItem SingleItem = LocalGroup.OPCItems.AddItem("Write Only.String", 3);
ClientHandleItemIDPairs.Add(3, "Write Only.String");

for (int i = 1; i <= ServerHandles.Length; i++)
{
Console.WriteLine($"Added Item: {LocalGroup.OPCItems.GetOPCItem((int)ServerHandles.GetValue(i)).ItemID}, Error: {LocalOpcServer.GetErrorString((int)Errors.GetValue(i))}");
}

Array outValues;
object outQualities;
object outTimestamps;



//同步读
LocalGroup.SyncRead((short)OPCAutomation.OPCDataSource.OPCDevice, ServerHandles.Length, ServerHandles, out outValues, out Errors, out outQualities, out outTimestamps);
for (int i = 1; i <= ServerHandles.Length; i++)
{

Console.WriteLine($"SyncRead Item {LocalGroup.OPCItems.GetOPCItem((int)ServerHandles.GetValue(i)).ItemID} Value: {outValues.GetValue(i)}, Quality: {(outQualities as Array).GetValue(i)}, Timestamp: {(outTimestamps as Array).GetValue(i)}, Error: {LocalOpcServer.GetErrorString((int)Errors.GetValue(i))}");
}

//此处添加的0和""是必须的,否则出错!
int[] WriteServerHandles = new[] { 0, SingleItem.ServerHandle };
object[] WriteValues = new object[] { "", "Hello" };
//同步写
LocalGroup.SyncWrite(1, WriteServerHandles.ToArray(), WriteValues, out Errors);

//异步读, TransactionID == 1
LocalGroup.AsyncRead(ServerHandles.Length, ServerHandles, out Errors, 1, out CancelID);
//异步写, TransactionID == 2
LocalGroup.AsyncWrite(1, WriteServerHandles.ToArray(), WriteValues, out Errors, 2, out CancelID);
//异步刷新, TransactionID == 3
LocalGroup.AsyncRefresh((short)OPCAutomation.OPCDataSource.OPCDevice, 3, out CancelID);
}
catch (Exception ex)
{

MessageBox.Show($"错误信息{ex.Message} {ex.StackTrace}", "错误");
}

private void LocalGroup_AsyncWriteComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array Errors)
{
if (TransactionID == 2)
{
for (int i = 1; i <= NumItems; i++)
{
Console.WriteLine($"Async Write {ClientHandleItemIDPairs[(int)ClientHandles.GetValue(i)]} Complete, Error: {LocalOpcServer.GetErrorString((int)Errors.GetValue(i))}");
}
}
}
private void LocalGroup_AsyncReadComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps, ref Array Errors)
{
if (TransactionID == 1)
{
for (int i = 1; i <= NumItems; i++)
{
Console.WriteLine($"Async Read {ClientHandleItemIDPairs[(int)ClientHandles.GetValue(i)]} Complete, Value: {ItemValues.GetValue(i)}, Quality: {GetQualityString((int)Qualities.GetValue(i))}, TimeStamps: {((DateTime)TimeStamps.GetValue(i)).ToString("yyyy-MM-dd HH:mm:ss")}, Error: {LocalOpcServer.GetErrorString((int)Errors.GetValue(i))}");
}
}
}

private void Group_DataChange(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps)
{
for (int i = 1; i <= NumItems; i++)
{
Console.WriteLine($"DataChange {ClientHandleItemIDPairs[(int)ClientHandles.GetValue(i)]} Complete, Value: {ItemValues.GetValue(i)}, Quality: {GetQualityString((int)Qualities.GetValue(i))}, TimeStamps: {((DateTime)TimeStamps.GetValue(i)).ToString("yyyy-MM-dd HH:mm:ss")}");
}
}

public static string GetQualityString(int QualityCode)
{
switch (QualityCode)
{
case 0: return "Bad";
case 1: return "Bad, Low Limited";
case 2: return "Bad, High Limited";
case 3: return "Bad, Constant";
case 4: return "Bad, Configuration Error";
case 5: return "Bad, Configuration Error, Low Limited";
case 6: return "Bad, Configuration Error, High Limited";
case 7: return "Bad, Configuration Error, Constant";
case 8: return "Bad, Not Connected";
case 9: return "Bad, Not Connected, Low Limited";
case 10: return "Bad, Not Connected, High Limited";
case 11: return "Bad, Not Connected, Constant";
case 12: return "Bad, Device Failure";
case 13: return "Bad, Device Failure, Low Limited";
case 14: return "Bad, Device Failure, High Limited";
case 15: return "Bad, Device Failure, Constant";
case 16: return "Bad, Sensor Failure";
case 17: return "Bad, Sensor Failure, Low Limited";
case 18: return "Bad, Sensor Failure, High Limited";
case 19: return "Bad, Sensor Failure, Constant";
case 20: return "Bad, Last Known Value";
case 21: return "Bad, Last Known Value, Low Limited";
case 22: return "Bad, Last Known Value, High Limited";
case 23: return "Bad, Last Known Value, Constant";
case 24: return "Bad, Comm Failure";
case 25: return "Bad, Comm Failure, Low Limited";
case 26: return "Bad, Comm Failure, High Limited";
case 27: return "Bad, Comm Failure, Constant";
case 28: return "Bad, Out of Service";
case 29: return "Bad, Out of Service, Low Limited";
case 30: return "Bad, Out of Service, High Limited";
case 31: return "Bad, Out of Service, Constant";
case 32: return "Bad, Waiting for Initial Data";
case 33: return "Bad, Waiting for Initial Data, Low Limited";
case 34: return "Bad, Waiting for Initial Data, High Limited";
case 35: return "Bad, Waiting for Initial Data, Constant";
case 64: return "Uncertain";
case 65: return "Uncertain, Low Limited";
case 66: return "Uncertain, High Limited";
case 67: return "Uncertain, Constant";
case 68: return "Uncertain, Last Usable Value";
case 69: return "Uncertain, Last Usable Value, Low Limited";
case 70: return "Uncertain, Last Usable Value, High Limited";
case 71: return "Uncertain, Last Usable Value, Constant";
case 80: return "Uncertain, Sensor Not Accurate";
case 81: return "Uncertain, Sensor Not Accurate, Low Limited";
case 82: return "Uncertain, Sensor Not Accurate, High Limited";
case 83: return "Uncertain, Sensor Not Accurate, Constant";
case 84: return "Uncertain, Engineering Units Exceeded";
case 85: return "Uncertain, Engineering Units Exceeded, Low Limited";
case 86: return "Uncertain, Engineering Units Exceeded, High Limited";
case 87: return "Uncertain, Engineering Units Exceeded, Constant";
case 88: return "Uncertain, Sub-Normal";
case 89: return "Uncertain, Sub-Normal, Low Limited";
case 90: return "Uncertain, Sub-Normal, High Limited";
case 91: return "Uncertain, Sub-Normal, Constant";
case 192: return "Good";
case 193: return "Good, Low Limited";
case 194: return "Good, High Limited";
case 195: return "Good, Constant";
case 216: return "Good, Local Override";
case 217: return "Good, Local Override, Low Limited";
case 218: return "Good, Local Override, High Limited";
case 219: return "Good, Local Override, Constant";
default:
return "INVALIDE QUALITY CODE!";
}
}