C#实现与西门子SIMATIC NET OPC DA通讯

2016-12-07 10:16:11来源:oschina作者:i讯蜂人点击

第七城市

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又开发了新的OPCUnified 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 plaincopy



在CODE上查看代码片派生到我的代码片

usingSystem;%20
%20usingSystem.Collections.Generic;%20
%20usingSystem.ComponentModel;%20
%20usingSystem.Data;%20
%20usingSystem.Drawing;%20
%20usingSystem.Text;%20
%20usingSystem.Windows.Forms;usingSystem.Net;%20
%20usingOPCAutomation;namespaceOpcClient%20
%20{%20
%20publicpartialclassOpcClient:Form%20
%20{%20
%20publicOpcClient()%20
%20{%20
%20InitializeComponent();%20
%20}#region私有变量%20
%20privateStringstrHostIP;%20
%20privateStringstrHostName;%20
%20privateBooleanopc_connected;privateOPCServerLocalServer;%20
%20privateOPCGroupsmyGroups;%20
%20privateOPCGroupmyGroup;%20
%20privateOPCGroupmyGroup1;%20
%20privateOPCItemsmyItems;%20
%20privateOPCItemsmyItems1;%20
%20privateOPCItemmyItem;%20
%20intitmHandleClient=0;///客户端句柄%20
%20intitmHandleServer=0;///服务端句柄%20
%20//**wfx%20
%20privateOPCItem[]myItemArray;%20
%20privateOPCItem[]myItemArray1;#endregion#region私有方法%20
%20///%20
%20///连接OPC服务器%20
%20///
%20
%20///OPCServerIP%20
%20///OPCServer名称%20
%20privateboolConnectRemoteServer(stringremoteServerIP,stringremoteServerName)%20
%20{%20
%20try%20
%20{%20
%20LocalServer.Connect(remoteServerName,remoteServerIP);if(LocalServer.ServerState==(int)OPCServerState.OPCRunning)%20
%20{%20
%20lblState.Text="已连接到:"+"/r/n"+LocalServer.ServerName+"/r/n";%20
%20//显示服务器信息%20
%20lblState.Text=lblState.Text+"开始时间:"+"/r/n"+LocalServer.StartTime.ToString()+"/r/n";%20
%20lblState.Text=lblState.Text+"版本:"+LocalServer.MajorVersion.ToString()+"."+LocalServer.MinorVersion.ToString()+"."+LocalServer.BuildNumber.ToString();%20
%20}%20
%20else%20
%20{%20
%20//这里你可以根据返回的状态来自定义显示信息,请查看自动化接口API文档%20
%20lblState.Text="状态:"+LocalServer.ServerState.ToString()+"/r/n";}%20
%20}%20
%20catch(Exceptionerr)%20
%20{%20
%20MessageBox.Show("连接远程服务器出现错误:"+err.Message,"提示信息",MessageBoxButtons.OK,MessageBoxIcon.Warning);%20
%20returnfalse;%20
%20}%20
%20returntrue;%20
%20}///%20
%20///每当项数据有变化时执行的事件%20
%20///
%20
%20///处理ID%20
%20///项个数%20
%20///项客户端句柄%20
%20///TAG值%20
%20///品质%20
%20///时间戳%20
%20voidmyGroup_DataChange(intTransactionID,intNumItems,refArrayClientHandles,refArrayItemValues,refArrayQualities,refArrayTimeStamps)%20
%20{%20
%20//为了测试,所以加了控制台的输出,来查看事物ID号%20
%20Console.WriteLine("********"+TransactionID.ToString()+NumItems.ToString()+"*********");//第二次进来后为啥变成1了,之前都是4个%20
%20//**wfx%20
%20/*%20
%20for(inti=1;i<=NumItems;i++)%20
%20{%20
%20this.txtValue.Text=ItemValues.GetValue(i).ToString();%20
%20this.txtQuality.Text=Qualities.GetValue(i).ToString();%20
%20this.txtTime.Text=TimeStamps.GetValue(i).ToString();%20
%20}%20
%20*/%20
%20//**wfx%20
%20TextBox[]tb=newTextBox[4];%20
%20tb[0]=textBox1;%20
%20tb[1]=textBox2;%20
%20tb[2]=textBox3;%20
%20tb[3]=textBox4;%20
%20//ClientHandles.GetValue(i);%20
%20for(inti=1;i<=NumItems;i++)%20
%20{%20
%20tb[(int)ClientHandles.GetValue(i)-1].Text=((float)ItemValues.GetValue(i)).ToString("0.00");%20
%20}%20
%20}///%20
%20///写入TAG值时执行的事件%20
%20///
%20
%20///%20
%20///%20
%20///%20
%20///%20
%20voidmyGroup_AsyncWriteComplete(intTransactionID,intNumItems,refArrayClientHandles,refArrayErrors)%20
%20{%20
%20lblState.Text="";%20
%20for(inti=1;i<=NumItems;i++)%20
%20{%20
%20lblState.Text+="TransactionID:"+TransactionID.ToString()+"/r/n"+"ClientHandle:"+ClientHandles.GetValue(i).ToString()+"/r/n"+"ErrorValue:"+Errors.GetValue(i).ToString()+"/r/n";%20
%20}%20
%20}///%20
%20///创建组%20
%20///
%20
%20privateboolCreateGroup()%20
%20{%20
%20try%20
%20{%20
%20myGroups=LocalServer.OPCGroups;%20
%20myGroup=myGroups.Add("OpcDotNetGroup");%20
%20myGroup1=myGroups.Add("OpcDotNetGroup1");%20
%20SetDefaultGroupProperty();%20
%20myItems=myGroup.OPCItems;%20
%20myItems1=myGroup1.OPCItems;%20
%20//**wfx%20
%20myItemArray=newOPCItem[16];%20
%20myItemArray1=newOPCItem[16];%20
%20//**additems%20
%20//DA格式S7:[S7connection_1]MX3.1%20
%20//S7:[S7connection_1]DB1,X3.0%20
%20//OPCUA格式S7:S7connection_1.db5.68,r%20
%20//S7:S7connection_1.db1.20,x7%20
%20myItemArray[0]=myItems.AddItem("S7:[S7connection_1]db5,real68",1);//Z轴%20
%20myItemArray[1]=myItems.AddItem("S7:[S7connection_1]db1,real50",2);//刮刀%20
%20myItemArray[2]=myItems.AddItem("S7:[S7connection_1]db1,real96",3);//送粉%20
%20myItemArray[3]=myItems.AddItem("S7:[S7connection_1]db1,real72",4);//温度%20
%20//**wfx%20
%20/*%20
%20myItemArray1[4]=myItems1.AddItem("S7:[S7connection_1]MX3.1",4);//Z正%20
%20myItemArray1[5]=myItems1.AddItem("S7:[S7connection_1]MX3.6",5);//Z负%20
%20*/%20
%20myGroup.DataChange+=newDIOPCGroupEvent_DataChangeEventHandler(myGroup_DataChange);%20
%20myGroup.AsyncWriteComplete+=newDIOPCGroupEvent_AsyncWriteCompleteEventHandler(myGroup_AsyncWriteComplete);}%20
%20catch(Exceptionerr)%20
%20{%20
%20MessageBox.Show("创建组出现错误:"+err.Message,"提示信息",MessageBoxButtons.OK,MessageBoxIcon.Warning);%20
%20returnfalse;%20
%20}%20
%20returntrue;%20
%20}///%20
%20///设置缺省的组属性%20
%20///
%20
%20privatevoidSetDefaultGroupProperty()%20
%20{%20
%20LocalServer.OPCGroups.DefaultGroupIsActive=true;%20
%20LocalServer.OPCGroups.DefaultGroupDeadband=0;%20
%20myGroup.UpdateRate=250;%20
%20myGroup.IsActive=true;%20
%20myGroup.IsSubscribed=true;%20
%20myGroup1.UpdateRate=250;%20
%20myGroup1.IsActive=true;%20
%20myGroup1.IsSubscribed=true;%20
%20}#endregion#region窗体事件//窗体载入时,查询本机安装的OPCServer列表%20
%20privatevoidOpcClient_Load(objectsender,EventArgse)%20
%20{//获取本地计算机IP,计算机名称%20
%20IPHostEntryIPHost=Dns.GetHostEntry(Environment.MachineName);%20
%20IPHost.HostName.ToString();%20
%20if(IPHost.AddressList.Length>0)%20
%20{%20
%20strHostIP=IPHost.AddressList[0].ToString();%20
%20}%20
%20else%20
%20{%20
%20return;%20
%20}%20
%20//通过IP来获取计算机名称,可用在局域网内%20
%20IPHostEntryipHostEntry=Dns.GetHostEntry(strHostIP);%20
%20strHostName=ipHostEntry.HostName.ToString();//获取本地计算机上的OPCServerName%20
%20try%20
%20{%20
%20LocalServer=newOPCServer();%20
%20objectserverList=LocalServer.GetOPCServers(strHostName);foreach(stringturnin(Array)serverList)%20
%20{%20
%20cmbServer.Items.Add(turn);%20
%20}cmbServer.SelectedIndex=0;%20
%20btnConnect.Enabled=true;%20
%20}%20
%20catch(Exceptionerr)%20
%20{%20
%20MessageBox.Show("枚举本地OPC服务器出错:"+err.Message,"提示信息",MessageBoxButtons.OK,MessageBoxIcon.Warning);%20
%20}}//点击"连接"按钮,动作%20
%20privatevoidbtnConnect_Click(objectsender,EventArgse)%20
%20{%20
%20//连接服务器%20
%20try%20
%20{%20
%20if(!ConnectRemoteServer("127.0.0.1",cmbServer.Text))%20
%20{%20
%20return;%20
%20}opc_connected=true;//已连接标记OPCBrowseroPCBrowser=LocalServer.CreateBrowser();%20
%20//展开分支%20
%20oPCBrowser.ShowBranches();%20
%20//展开叶子%20
%20oPCBrowser.ShowLeafs(true);%20
%20lstItems.Items.Clear();//清空列表%20
%20foreach(objectturninoPCBrowser)%20
%20{%20
%20lstItems.Items.Add(turn.ToString());%20
%20}if(!CreateGroup())%20
%20{%20
%20return;%20
%20}%20
%20}%20
%20catch(Exceptionerr)%20
%20{%20
%20MessageBox.Show("初始化出错:"+err.Message,"提示信息",MessageBoxButtons.OK,MessageBoxIcon.Warning);%20
%20}}///选择列表中的某个Item时处理的事情%20
%20privatevoidlstItems_SelectedIndexChanged(objectsender,EventArgse)%20
%20{%20
%20try%20
%20{%20
%20this.txtName.Text=lstItems.SelectedItem.ToString();%20
%20if(itmHandleClient!=0)%20
%20{%20
%20this.txtValue.Text="";%20
%20this.txtQuality.Text="";%20
%20this.txtTime.Text="";ArrayErrors;%20
%20OPCItembItem=myItems.GetOPCItem(itmHandleServer);%20
%20//注:OPC中以1为数组的基数%20
%20int[]temp=newint[2]{0,bItem.ServerHandle};%20
%20ArrayserverHandle=(Array)temp;%20
%20//移除上一次选择的项%20
%20myItems.Remove(myItems.Count,refserverHandle,outErrors);%20
%20}%20
%20itmHandleClient=1234;%20
%20myItem=myItems.AddItem(lstItems.SelectedItem.ToString(),itmHandleClient);%20
%20itmHandleServer=myItem.ServerHandle;%20
%20Console.WriteLine("*****"+itmHandleServer+"*****");//ServerHandle是随机分配,好比Socket的客户端端口号也是随机分配一样,ClientHandle是用户指定,%20
%20}%20
%20catch(Exceptionerr)%20
%20{%20
%20//没有任何权限的项,都是OPC服务器保留的系统项,此处可不做处理。%20
%20itmHandleClient=0;%20
%20txtValue.Text="Errorox";%20
%20txtQuality.Text="Errorox";%20
%20txtTime.Text="Errorox";%20
%20MessageBox.Show("此项为系统保留项:"+err.Message,"提示信息");%20
%20}%20
%20}//点击"写入"按钮%20
%20privatevoidcmdWrite_Click(objectsender,EventArgse)%20
%20{%20
%20OPCItembItem=myItems.GetOPCItem(itmHandleServer);%20
%20int[]temp=newint[2]{0,bItem.ServerHandle};%20
%20ArrayserverHandles=(Array)temp;%20
%20object[]valueTemp=newobject[2]{"",txtNewValue.Text};%20
%20Arrayvalues=(Array)valueTemp;%20
%20ArrayErrors;%20
%20intcancelID;%20
%20myGroup.AsyncWrite(1,refserverHandles,refvalues,outErrors,2009,outcancelID);%20
%20//myItem.Write(txtNewValue.Text);//这句也可以写入,但并不触发写入事件%20
%20GC.Collect();%20
%20}//点击"断开"按钮%20
%20privatevoidbtnDisConn_Click(objectsender,EventArgse)%20
%20{%20
%20if(!opc_connected)%20
%20{%20
%20return;%20
%20}if(myGroup!=null)%20
%20{%20
%20myGroup.DataChange-=newDIOPCGroupEvent_DataChangeEventHandler(myGroup_DataChange);%20
%20}if(LocalServer!=null)%20
%20{%20
%20LocalServer.Disconnect();%20
%20//LocalServer=null;}opc_connected=false;%20
%20lstItems.Items.Clear();//显示信息%20
%20lblState.Text="已经从OPC服务器断开."+"/r/n";%20
%20//显示服务器信息%20
%20lblState.Text=lblState.Text+"断开时间:"+"/r/n"+System.DateTime.Now.ToString()+"/r/n";}//关闭窗体时候,清理工作%20
%20privatevoidOpcClient_FormClosing(objectsender,FormClosingEventArgse)%20
%20{%20
%20btnDisConn_Click(newObject(),newEventArgs());%20
%20}#endregionprivatevoidbutton1_Click(objectsender,EventArgse)%20
%20{%20
%20myItemArray1[6]=myItems1.AddItem("S7:[S7connection_1]db1,x0.3",6);//刮刀正%20
%20myItemArray1[10]=myItems1.AddItem("S7:[S7connection_1]db1,real41",10);//刮刀速度%20
%20myItemArray1[11]=myItems1.AddItem("S7:[S7connection_1]db1,real33",11);//刮刀位置int[]temp=newint[4]{0,myItemArray1[6].ServerHandle,myItemArray1[10].ServerHandle,myItemArray1[11].ServerHandle};%20
%20ArrayserverHandles=(Array)temp;%20
%20object[]valueTemp=newobject[4]{"",true,textBox6.Text,textBox7.Text};%20
%20Arrayvalues=(Array)valueTemp;%20
%20ArrayErrors;%20
%20intcancelID;%20
%20myGroup1.AsyncWrite(3,refserverHandles,refvalues,outErrors,2009,outcancelID);%20
%20//myItem.Write(txtNewValue.Text);//这句也可以写入,但并不触发写入事件}privatevoidbutton2_Click(objectsender,EventArgse)%20
%20{%20
%20myItemArray1[7]=myItems1.AddItem("S7:[S7connection_1]db1,x0.7",7);//刮刀负%20
%20myItemArray1[10]=myItems1.AddItem("S7:[S7connection_1]db1,real41",10);//刮刀速度%20
%20myItemArray1[11]=myItems1.AddItem("S7:[S7connection_1]db1,real33",11);//刮刀位置int[]temp=newint[4]{0,myItemArray1[7].ServerHandle,myItemArray1[10].ServerHandle,myItemArray1[11].ServerHandle};%20
%20ArrayserverHandles=(Array)temp;%20
%20object[]valueTemp=newobject[4]{"",true,textBox6.Text,textBox7.Text};%20
%20Arrayvalues=(Array)valueTemp;%20
%20ArrayErrors;%20
%20intcancelID;%20
%20myGroup1.AsyncWrite(3,refserverHandles,refvalues,outErrors,2009,outcancelID);%20
%20//myItem.Write(txtNewValue.Text);//这句也可以写入,但并不触发写入事件%20
%20}privatevoidbutton3_Click(objectsender,EventArgse)%20
%20{%20
%20int[]temp=newint[4]{0,myItemArray[8].ServerHandle,myItemArray[12].ServerHandle,myItemArray[13].ServerHandle};%20
%20ArrayserverHandles=(Array)temp;%20
%20object[]valueTemp=newobject[4]{"",true,textBox8.Text,textBox9.Text};%20
%20Arrayvalues=(Array)valueTemp;%20
%20ArrayErrors;%20
%20intcancelID;%20
%20myGroup.AsyncWrite(3,refserverHandles,refvalues,outErrors,2009,outcancelID);%20
%20//myItem.Write(txtNewValue.Text);//这句也可以写入,但并不触发写入事件%20
%20}privatevoidbutton4_Click(objectsender,EventArgse)%20
%20{%20
%20int[]temp=newint[4]{0,myItemArray[9].ServerHandle,myItemArray[12].ServerHandle,myItemArray[13].ServerHandle};%20
%20ArrayserverHandles=(Array)temp;%20
%20object[]valueTemp=newobject[4]{"",true,textBox8.Text,textBox9.Text};%20
%20Arrayvalues=(Array)valueTemp;%20
%20ArrayErrors;%20
%20intcancelID;%20
%20myGroup.AsyncWrite(3,refserverHandles,refvalues,outErrors,2009,outcancelID);%20
%20//myItem.Write(txtNewValue.Text);//这句也可以写入,但并不触发写入事件%20
%20}}%20
%20}%20

%20


最后,从整体上说下OPC DA的协议规范,OPC DA是在WINDOWS的COM/DOM技术上定义的接口定义,在TCP IP七层模型的最高层应用层,决定了它必须运行在WINDOWS平台,不能够跨平台,灵活性和安全性不如OPC UA,因为OPC DA的会话层和表示层用户是有权利来使用的。对比OPC DA 和OPC UA的协议规范如下:更多不同点,后面会单独再说。



第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台