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

 

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

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

  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的协议规范如下:更多不同点,后面会单独再说。

 

转载于:https://my.oschina.net/u/576185/blog/800949

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值