摘要:本文将带你了解C#编程之实现与西门子SIMATIC NET OPC DA通讯,希望本文对大家学C#/.Net有所帮助。
OPC是Object Linking and Embedding(OLE)forProcess Control的缩写,它是微软公司的对象链接和嵌入技术在过程控制方面的应用。OPC以OLE/COM/DCOM技术为基础,采用客户/服务器模式,为工业自动化软件面向对象的开发提供了统一的标准,这个标准定义了应用Microsoft操作系统在基于PC的客户机之间交换自动化实时数据的方法,采用这项标准后,硬件开发商将取代软件开发商为自己的硬件产品开发统一的OPC接口程序,而软件开发者可免除开发驱动程序的工作,充分发挥自己的特长,把更多的精力投入到其核心产品的开发上。
SimaticNet是西门子全集成自动化系统中的一个重要组成部分,它为完善的工业自动化控制系统的通讯提供部件和网络,同时提供多个OPCServer,为数据的外部访问提供接口,本文主要以OPC.SimaticNET为例说明。
90年代OPC基金会开发了一系列的通讯接口比如 Data Access (DA), Alarm & Events (A&E), Historical Data Access (HDA) and Data eXchange (DX),统称传统OPC。今天主要使用的OPC DA通讯方式,这个在1995年左右还是很流行的方法,最近几年OPC Foundation又开发了新的 OPC Unified Architecture (UA) 标准,更好的适应了工业4.0。关于传统OPC和OPC UA的区别,后面会单独来说。
许多OPC服务器,包括OPC.SimaticNet,是在COM平台开发的,从而对于基于.NET框架下的C#语言,作为客户端程序语言访问OPCServer,需要解决两个平台间无缝迁移的问题。OPC基金会对会员提供了OpcRcw动态链接库,OPC NET COM 包装器和OPC NET API,将OPC复杂的规范封状成简单易用的C#类 ,可以比较容易地实现数据访问。
OPC主要包含两种接口:CUSTOM标准接口和OLE自动化标准接口,自定义接口是服务商必须提供的,而自动化接口则是可选的。
自定义接口是一组COM接口,主要用于采用C++语言的应用程序开发;
自动化接口是一组OLE接口,主要用于采用VB,DELPHI,Excel等基于脚本编程语言的应用程序开发。本文是使用C#通过自动化接口来实现的,也是最简单的方式。
首先必须了解的是OPC服务器的对象模型:
程序中涉及到的重要方法和属性比较多,解释下几个容易搞混的:
OPCItem 对象的属性ServerHandle,只读属性,服务器提供给Item的句柄,通过此句柄,Client可以定位到此Item,来对此Item进行后续的操作,比如移动删除;
OPCItem 对象的属性ClientHandle,可读可写属性,客户端分配给Item的句柄,这个句柄可以手动设置,也可由.NET随机选取的,不需要我们来设置,并且每次运行时,这
个句柄都不同,类似于TCP scoket通讯中的Client端分配的端口号。Server端必须指定端口号,Client端随机生成,每次都不一样。
OPCGroup 对象的属性的IsSubscribed,可读可写属性,Group的IsSubscribed为True,此Group才能开始接受服务器的数据属性,此Group才能被订阅。
OPCGroup 对象的事件DataChange (TransactionID As Long, NumItems As Long, ClientHandles() As Long,ItemValues() As Variant, Qualities() As Long, TimeStamps() As Date)需要注意的是NumItems参数是每次事件触发时Group中实际发生数据变化的Item的数量,而不是整个Group里的Items.
OPCGroup 对象的属性UpdateRate,可读可写属性,规定了数据刷新的周期,单位milliseconds.注意的是,不是设定多少ms,实际就是多少,比如给定53ms,OPC server会就近选择50ms.有区间划分的。
源程序如下:
[csharp] view plain copy
1. using System;
2. using System.Collections.Generic;
3. using System.ComponentModel;
4. using System.Data;
5. using System.Drawing;
6. using System.Text;
7. using System.Windows.Forms;
8.
9. using System.Net;
10. using OPCAutomation;
11.
12.
13. namespace OpcClient
14. {
15. public partial class OpcClient : Form
16. {
17. public OpcClient()
18. {
19. InitializeComponent();
20. }
21.
22. #region 私有变量
23. private String strHostIP;
24. private String strHostName;
25. private Boolean opc_connected;
26.
27. private OPCServer LocalServer;
28. private OPCGroups myGroups;
29. private OPCGroup myGroup;
30. private OPCGroup myGroup1;
31. private OPCItems myItems;
32. private OPCItems myItems1;
33. private OPCItem myItem;
34. int itmHandleClient = 0; /// 客户端句柄
35. int itmHandleServer = 0; /// 服务端句柄
36. //**wfx
37. private OPCItem [] myItemArray;
38. private OPCItem[] myItemArray1;
39.
40. #endregion
41.
42. #region 私有方法
43. /// <summary>
44. /// 连接OPC服务器
45. /// </summary>
46. /// <param name="remoteServerIP">OPCServerIP</param>
47. /// <param name="remoteServerName">OPCServer名称</param>
48. private bool ConnectRemoteServer(string remoteServerIP, string remoteServerName)
49. {
50. try
51. {
52. LocalServer.Connect(remoteServerName, remoteServerIP);
53.
54. if (LocalServer.ServerState == (int)OPCServerState.OPCRunning)
55. {
56. lblState.Text = "已连接到:" + "\r\n" + LocalServer.ServerName + "\r\n";
57. //显示服务器信息
58. lblState.Text = lblState.Text + "开始时间:" + "\r\n" + LocalServer.StartTime.ToString() + "\r\n";
59. lblState.Text = lblState.Text + "版本:" + LocalServer.MajorVersion.ToString() + "." + LocalServer.MinorVersion.ToString() + "." + LocalServer.BuildNumber.ToString();
60. }
61. else
62. {
63. //这里你可以根据返回的状态来自定义显示信息,请查看自动化接口API文档
64. lblState.Text = "状态:" + LocalServer.ServerState.ToString() + "\r\n";
65.
66. }
67. }
68. catch (Exception err)
69. {
70. MessageBox.Show("连接远程服务器出现错误:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
71. return false;
72. }
73. return true;
74. }
75.
76. /// <summary>
77. /// 每当项数据有变化时执行的事件
78. /// </summary>
79. /// <param name="TransactionID">处理ID</param>
80. /// <param name="NumItems">项个数</param>
81. /// <param name="ClientHandles">项客户端句柄</param>
82. /// <param name="ItemValues">TAG值</param>
83. /// <param name="Qualities">品质</param>
84. /// <param name="TimeStamps">时间戳</param>
85. void myGroup_DataChange(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps)
86. {
87. //为了测试,所以加了控制台的输出,来查看事物ID号
88. Console.WriteLine("********"+TransactionID.ToString()+NumItems.ToString()+"*********");//第二次进来后为啥变成1了,之前都是4个
89. //**wfx
90. /*
91. for (int i = 1; i <= NumItems; i++)
92. {
93. this.txtValue.Text = ItemValues.GetValue(i).ToString();
94. this.txtQuality.Text = Qualities.GetValue(i).ToString();
95. this.txtTime.Text = TimeStamps.GetValue(i).ToString();
96. }
97. */
98. //**wfx
99. TextBox[] tb = new TextBox[4];
100. tb[0] = textBox1;
101. tb[1] = textBox2;
102. tb[2] = textBox3;
103. tb[3] = textBox4;
104. //ClientHandles.GetValue(i);
105. for (int i = 1; i <= NumItems;i++ )
106. {
107. tb[(int)ClientHandles.GetValue(i)-1].Text = ((float)ItemValues.GetValue(i)).ToString("0.00");
108. }
109. }
110.
111. /// <summary>
112. /// 写入TAG值时执行的事件
113. /// </summary>
114. /// <param name="TransactionID"></param>
115. /// <param name="NumItems"></param>
116. /// <param name="ClientHandles"></param>
117. /// <param name="Errors"></param>
118. void myGroup_AsyncWriteComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array Errors)
119. {
120. lblState.Text = "";
121. for (int i = 1; i <= NumItems; i++)
122. {
123. lblState.Text += "TransactionID:" + TransactionID.ToString() + "\r\n" + "ClientHandle:" + ClientHandles.GetValue(i).ToString() + "\r\n" + "ErrorValue: " + Errors.GetValue(i).ToString() + "\r\n";
124. }
125. }
126.
127. /// <summary>
128. /// 创建组
129. /// </summary>
130. private bool CreateGroup()
131. {
132. try
133. {
134. myGroups = LocalServer.OPCGroups;
135. myGroup = myGroups.Add("OpcDotNetGroup");
136. myGroup1 = myGroups.Add("OpcDotNetGroup1");
137. SetDefaultGroupProperty();
138. myItems = myGroup.OPCItems;
139. myItems1 = myGroup1.OPCItems;
140. //**wfx
141. myItemArray =new OPCItem [16];
142. myItemArray1 = new OPCItem[16];
143. //**add items
144. //DA格式 S7:[S7 connection_1]MX3.1
145. //S7:[S7 connection_1]DB1,X3.0
146. //OPC UA格式 S7:S7 connection_1.db5.68,r
147. //S7:S7 connection_1.db1.20,x7
148. myItemArray[0] = myItems.AddItem("S7:[S7 connection_1]db5,real68", 1);//Z轴
149. myItemArray[1] = myItems.AddItem("S7:[S7 connection_1]db1,real50", 2);//刮刀
150. myItemArray[2] = myItems.AddItem("S7:[S7 connection_1]db1,real96", 3);//送粉
151. myItemArray[3] = myItems.AddItem("S7:[S7 connection_1]db1,real72", 4);//温度
152. //**wfx
153. /*
154. myItemArray1[4] = myItems1.AddItem("S7:[S7 connection_1]MX3.1", 4);//Z 正
155. myItemArray1[5] = myItems1.AddItem("S7:[S7 connection_1]MX3.6", 5);//Z 负
156. */
157. myGroup.DataChange += new DIOPCGroupEvent_DataChangeEventHandler(myGroup_DataChange);
158. myGroup.AsyncWriteComplete += new DIOPCGroupEvent_AsyncWriteCompleteEventHandler(myGroup_AsyncWriteComplete);
159.
160. }
161. catch (Exception err)
162. {
163. MessageBox.Show("创建组出现错误:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
164. return false;
165. }
166. return true;
167. }
168.
169. /// <summary>
170. /// 设置缺省的组属性
171. /// </summary>
172. private void SetDefaultGroupProperty()
173. {
174. LocalServer.OPCGroups.DefaultGroupIsActive = true ;
175. LocalServer.OPCGroups.DefaultGroupDeadband = 0 ;
176. myGroup.UpdateRate = 250 ;
177. myGroup.IsActive = true ;
178. myGroup.IsSubscribed = true ;
179. myGroup1.UpdateRate = 250;
180. myGroup1.IsActive = true;
181. myGroup1.IsSubscribed = true;
182. }
183.
184.
185. #endregion
186.
187. #region 窗体事件
188.
189. //窗体载入时,查询本机安装的OPC Server 列表
190. private void OpcClient_Load(object sender, EventArgs e)
191. {
192.
193.
194. //获取本地计算机IP,计算机名称
195. IPHostEntry IPHost = Dns.GetHostEntry(Environment.MachineName);
196. IPHost.HostName.ToString();
197. if (IPHost.AddressList.Length > 0)
198. {
199. strHostIP = IPHost.AddressList[0].ToString();
200. }
201. else
202. {
203. return;
204. }
205. //通过IP来获取计算机名称,可用在局域网内
206. IPHostEntry ipHostEntry = Dns.GetHostEntry(strHostIP);
207. strHostName = ipHostEntry.HostName.ToString();
208.
209. //获取本地计算机上的OPCServerName
210. try
211. {
212. LocalServer = new OPCServer();
213. object serverList = LocalServer.GetOPCServers(strHostName);
214.
215. foreach (string turn in (Array)serverList)
216. {
217. cmbServer.Items.Add(turn);
218. }
219.
220. cmbServer.SelectedIndex = 0;
221. btnConnect.Enabled = true;
222. }
223. catch (Exception err)
224. {
225. MessageBox.Show("枚举本地OPC服务器出错:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
226. }
227.
228. }
229.
230.
231. //点击"连接"按钮, 动作
232. private void btnConnect_Click(object sender, EventArgs e)
233. {
234. //连接服务器
235. try
236. {
237. if (!ConnectRemoteServer("127.0.0.1", cmbServer.Text))
238. {
239. return;
240. }
241.
242. opc_connected = true; //已连接标记
243.
244. OPCBrowser oPCBrowser = LocalServer.CreateBrowser();
245. //展开分支
246. oPCBrowser.ShowBranches();
247. //展开叶子
248. oPCBrowser.ShowLeafs(true);
249. lstItems.Items.Clear(); //清空列表
250. foreach (object turn in oPCBrowser)
251. {
252. lstItems.Items.Add(turn.ToString());
253. }
254.
255. if (!CreateGroup())
256. {
257. return;
258. }
259. }
260. catch (Exception err)
261. {
262. MessageBox.Show("初始化出错:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
263. }
264.
265. }
266.
267.
268.
269.
270. /// 选择列表中的某个Item时处理的事情
271. private void lstItems_SelectedIndexChanged(object sender, EventArgs e)
272. {
273. try
274. {
275. this.txtName.Text = lstItems.SelectedItem.ToString();
276. if (itmHandleClient != 0)
277. {
278. this.txtValue.Text = "";
279. this.txtQuality.Text = "";
280. this.txtTime.Text = "";
281.
282. Array Errors;
283. OPCItem bItem = myItems.GetOPCItem(itmHandleServer);
284. //注:OPC中以1为数组的基数
285. int[] temp = new int[2] { 0, bItem.ServerHandle };
286. Array serverHandle = (Array)temp;
287. //移除上一次选择的项
288. myItems.Remove(myItems.Count, ref serverHandle, out Errors);
289. }
290. itmHandleClient = 1234;
291. myItem = myItems.AddItem(lstItems.SelectedItem.ToString(), itmHandleClient);
292. itmHandleServer = myItem.ServerHandle;
293. Console.WriteLine("*****" + itmHandleServer+"*****");//ServerHandle是随机分配,好比Socket的客户端端口号也是随机分配一样,ClientHandle是用户指定,
294. }
295. catch (Exception err)
296. {
297. //没有任何权限的项,都是OPC服务器保留的系统项,此处可不做处理。
298. itmHandleClient = 0;
299. txtValue.Text = "Error ox";
300. txtQuality.Text = "Error ox";
301. txtTime.Text = "Error ox";
302. MessageBox.Show("此项为系统保留项:" + err.Message, "提示信息");
303. }
304. }
305.
306.
307. //点击"写入"按钮
308. private void cmdWrite_Click(object sender, EventArgs e)
309. {
310. OPCItem bItem = myItems.GetOPCItem(itmHandleServer);
311. int[] temp = new int[2] { 0, bItem.ServerHandle };
312. Array serverHandles = (Array)temp;
313. object[] valueTemp = new object[2] { "", txtNewValue.Text };
314. Array values = (Array)valueTemp;
315. Array Errors;
316. int cancelID;
317. myGroup.AsyncWrite(1, ref serverHandles, ref values, out Errors, 2009, out cancelID);
318. //myItem.Write(txtNewValue.Text);//这句也可以写入,但并不触发写入事件
319. GC.Collect();
320. }
321.
322.
323. //点击"断开" 按钮
324. private void btnDisConn_Click(object sender, EventArgs e)
325. {
326. if (!opc_connected)
327. {
328. return;
329. }
330.
331. if (myGroup != null)
332. {
333. myGroup.DataChange -= new DIOPCGroupEvent_DataChangeEventHandler(myGroup_DataChange);
334. }
335.
336. if (LocalServer != null)
337. {
338. LocalServer.Disconnect();
339. //LocalServer = null;
340.
341. }
342.
343. opc_connected = false;
344. lstItems.Items.Clear();
345.
346. //显示信息
347. lblState.Text = "已经从OPC服务器断开." + "\r\n" ;
348. //显示服务器信息
349. lblState.Text = lblState.Text + "断开时间:" + "\r\n" + System.DateTime.Now.ToString() + "\r\n";
350.
351. }
352.
353.
354.
355.
356. //关闭窗体时候,清理工作
357. private void OpcClient_FormClosing(object sender, FormClosingEventArgs e)
358. {
359. btnDisConn_Click(new Object(),new EventArgs());
360. }
361.
362. #endregion
363.
364. private void button1_Click(object sender, EventArgs e)
365. {
366. myItemArray1[6] = myItems1.AddItem("S7:[S7 connection_1]db1,x0.3", 6);//刮刀正
367. myItemArray1[10] = myItems1.AddItem("S7:[S7 connection_1]db1,real41", 10);//刮刀速度
368. myItemArray1[11] = myItems1.AddItem("S7:[S7 connection_1]db1,real33", 11);//刮刀位置
369.
370. int[] temp = new int[4] { 0, myItemArray1[6].ServerHandle, myItemArray1[10].ServerHandle, myItemArray1[11].ServerHandle };
371. Array serverHandles = (Array)temp;
372. object[] valueTemp = new object[4] { "", true, textBox6.Text, textBox7.Text };
373. Array values = (Array)valueTemp;
374. Array Errors;
375. int cancelID;
376. myGroup1.AsyncWrite(3, ref serverHandles, ref values, out Errors, 2009, out cancelID);
377. //myItem.Write(txtNewValue.Text);//这句也可以写入,但并不触发写入事件
378.
379. }
380.
381. private void button2_Click(object sender, EventArgs e)
382. {
383. myItemArray1[7] = myItems1.AddItem("S7:[S7 connection_1]db1,x0.7", 7);//刮刀负
384. myItemArray1[10] = myItems1.AddItem("S7:[S7 connection_1]db1,real41", 10);//刮刀速度
385. myItemArray1[11] = myItems1.AddItem("S7:[S7 connection_1]db1,real33", 11);//刮刀位置
386.
387. int[] temp = new int[4] { 0, myItemArray1[7].ServerHandle, myItemArray1[10].ServerHandle, myItemArray1[11].ServerHandle };
388. Array serverHandles = (Array)temp;
389. object[] valueTemp = new object[4] { "", true, textBox6.Text, textBox7.Text };
390. Array values = (Array)valueTemp;
391. Array Errors;
392. int cancelID;
393. myGroup1.AsyncWrite(3, ref serverHandles, ref values, out Errors, 2009, out cancelID);
394. //myItem.Write(txtNewValue.Text);//这句也可以写入,但并不触发写入事件
395. }
396.
397. private void button3_Click(object sender, EventArgs e)
398. {
399. int[] temp = new int[4] { 0, myItemArray[8].ServerHandle, myItemArray[12].ServerHandle, myItemArray[13].ServerHandle };
400. Array serverHandles = (Array)temp;
401. object[] valueTemp = new object[4] { "", true, textBox8.Text, textBox9.Text };
402. Array values = (Array)valueTemp;
403. Array Errors;
404. int cancelID;
405. myGroup.AsyncWrite(3, ref serverHandles, ref values, out Errors, 2009, out cancelID);
406. //myItem.Write(txtNewValue.Text);//这句也可以写入,但并不触发写入事件
407. }
408.
409. private void button4_Click(object sender, EventArgs e)
410. {
411. int[] temp = new int[4] { 0, myItemArray[9].ServerHandle, myItemArray[12].ServerHandle, myItemArray[13].ServerHandle };
412. Array serverHandles = (Array)temp;
413. object[] valueTemp = new object[4] { "", true, textBox8.Text, textBox9.Text };
414. Array values = (Array)valueTemp;
415. Array Errors;
416. int cancelID;
417. myGroup.AsyncWrite(3, ref serverHandles, ref values, out Errors, 2009, out cancelID);
418. //myItem.Write(txtNewValue.Text);//这句也可以写入,但并不触发写入事件
419. }
420.
421. }
422. }
最后,从整体上说下OPC DA的协议规范,OPC DA是在WINDOWS的COM/DOM技术上定义的接口定义,在TCP IP七层模型的最高层应用层,决定了它必须运行在WINDOWS平台,不能够跨平台,灵活性和安全性不如OPC UA,因为OPC DA的会话层和表示层用户是有权利来使用的。对比OPC DA 和OPC UA的协议规范如下:更多不同点,后面会单独再说。
以上就介绍了C#.NET的相关知识,希望对C#.NET有兴趣的朋友有所帮助。了解更多内容,请关注职坐标编程语言C#.NET频道!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号