zoukankan      html  css  js  c++  java
  • 【干货】如何通过OPC自定义接口来实现客户端数据的读取?

      上篇博文分享了我的知识库,被好多人关注,受宠若惊。今天我把我在项目中封装的OPC自定义接口的程序分享一下。下面将会简单简单介绍下OPC DA客户端数据访问,以及搭配整个系统的运行环境。

    • OPC(OLE for Process Control)其实就是一套标准,我对这套标准理解不多,使用过程中就把它理解一套协议或者规范,主要用于工控领域。OPC中有很多规范,我主要使用OPC DA规范来进行数据的读写操作。还有其他规范,比如OPC UA、OPC HDA等。如果你做的是OPC Server开发查下这方面的资料了解下,这篇博文主要介绍OPC Client开发的知识。

      使用OPC DA进行Client的读写操作时,我们使用Custom接口,出此之外还有Automation接口。以下是Custome接口开发时涉及到的三个关键对象:OpcServer、OpcGroup、OpcItem,下图是他们之间的逻辑关系:

      

      在客户端开发时,要使用OpcServer对象来实现客户端与Opc服务器之间的连接。一个OpcServer对象下有多个OpcGroup,一个OpcGroup下有多个OpcItem,在自定义接口下的Client开发,是以Group为单位的操作,数据读写都是通过OpcGroup进行的。

    •   搭建程序运行环境

        程序运行需要的软硬件环境:

      1. .Net Framework 4.0
      2. Simatic Net 2008(Or Other) HF1
      3. 西门子300(Or Other) PLC

        我们可以通过本机的配置来实现OPC的远程连接,我没有采用这种方式,一是这种配置比较麻烦,而是这种方式不稳定。所以我采用本机安装一个OPCServer来实现与PLC的交互。

        对于OPCServer软件,我选择的是SimaticNet 2008 HF1(安装WinCC的时候会有选择安装SimaticNet的选项),没有特别的原因,就是比较熟悉了而已,而且PLC选用的是西门子的。

        我们可以不写OPC Client程序来测试,如何通过OPCServer与PLC之间的交互。首先当我们安装完毕SimaticNet之后,需要对Station Configuration Editor进行配置,如下图:

        

        首先我们要指定Station的名称,上图叫PCStation,点击下方的StationName可以进行更改。下一步在1号栈上选择一个OPCServer,3号栈上选择一个通信网卡。

        接下来我们需要在Step 7中建立Station Configuration Editor与PLC之间的连接,我们暂且叫组态。组态的过程中要建立与Station Configuration Editor中对应的Opc Server和IE General(所在栈号相同),Station Configuration Edition起到桥接的作用    用,主要让PLC与Opc Server之间建立一条S7连接。暂时没有拿到组态图,以后补上。

        当我们组态完毕时,如何判断组态是否正确呢?在SimaticNet的目录上有个叫Opc Scout(Opc Scout V10)的软件,打开如下图:

        

        上图列出来了本机所有的Server,我们能使用名为OPC.SimaticNET的Server。双击这个Server添加一个组,多次双击这个Server可以添加多个组,验证了上图的Server与Group的关系了。

        我们双击新建的Group,进入如下图的界面:

        

        上图列出了所有的连接。上文说到的组态中建立的S7连接可以在S7节点中看到,展开这个节点可以看到我们建立的S7连接,如下图:

        

        上图列出了名为S7 connection_1的S7连接,展开Object对象,列出PLC的结构。我们选择一种来新建我们的Item,由于我这里没有PLC模块,所以无法截图给大家看。

        至此我们的OPC Client的运行环境搭建完毕。

    •  编写OPC Client端程序。

        我们需要使用OPC Foundation提供的自定义接口来进行开发,在Visual Studio引用名为:OpcRcw.Comn.dll和OpcRcw.Da.dll这两个DLL。

        我们定义一个名为OpcDaCustomAsync的类,让这个类继承自:IOPCDataCallback,IDisposable

        

      1 using System;
      2 using System.Collections.Generic;
      3 using OpcRcw.Comn;
      4 using OpcRcw.Da;
      5 using System.Runtime.InteropServices;
      6 
      7 namespace Opc.Net
      8 {
      9     /// <summary>
     10     /// Opc自定义接口-异步管理类
     11     /// <author name="lm" date="2012.3.14"/>
     12     /// </summary>
     13     public class OpcDaCustomAsync : IOPCDataCallback,IDisposable
     14     {
     15         /// <summary>
     16         /// OPC服务器对象
     17         /// </summary>
     18         IOPCServer iOpcServer;
     19         /// <summary>
     20         /// 事务ID
     21         /// </summary>
     22         int transactionID;
     23         /// <summary>
     24         /// OPC服务器名称
     25         /// </summary>
     26         string opcServerName;
     27         /// <summary>
     28         /// OPC服务器IP地址
     29         /// </summary>
     30         IOPCAsyncIO2 _iopcAsyncIo2;
     31         /// <summary>
     32         /// OPC服务器IP地址
     33         /// </summary>
     34         string opcServerIPAddress;
     35         /// <summary>
     36         /// Opc组列表
     37         /// </summary>
     38         List<OpcDaCustomGroup> opcDaCustomGroups;
     39         /// <summary>
     40         /// 连接指针容器
     41         /// </summary>
     42         IConnectionPointContainer IConnectionPointContainer = null;
     43         /// <summary>
     44         /// 连接指针
     45         /// </summary>
     46         IConnectionPoint IConnectionPoint = null;
     47         /// <summary>
     48         /// Opc组管理器
     49         /// </summary>
     50         IOPCGroupStateMgt IOPCGroupStateMgt = null;
     51 
     52 
     53         //接收数据事件
     54         public event EventHandler<OpcDaCustomAsyncEventArgs> OnDataChanged;
     55         /// <summary>
     56         /// 异步写入数据完成事件
     57         /// </summary>
     58         public event EventHandler<OpcDaCustomAsyncEventArgs> OnWriteCompleted;
     59         /// <summary>
     60         /// 异步读取数据完成事件
     61         /// </summary>
     62         public event EventHandler<OpcDaCustomAsyncEventArgs> OnReadCompleted;
     63 
     64         /// <summary>
     65         /// 构造函数
     66         /// </summary>
     67         /// <param name="opcDaCustomGroups">Opc组列表</param>
     68         /// <param name="opcServerName">OPC服务器名称</param>
     69         /// <param name="opcServerIpAddress">OPC服务器IP地址</param>
     70         public OpcDaCustomAsync(List<OpcDaCustomGroup> opcDaCustomGroups, string opcServerName, string opcServerIpAddress)
     71         {
     72             this.opcDaCustomGroups = opcDaCustomGroups;
     73             this.opcServerName = opcServerName;
     74             this.opcServerIPAddress = opcServerIpAddress;
     75             Init();
     76         }
     77         /// <summary>
     78         /// 初始化参数
     79         /// </summary>
     80         public void Init()
     81         {
     82             if (Connect())
     83             {
     84                 AddOpcGroup();
     85             }
     86         }
     87 
     88         /// <summary>
     89         /// 连接Opc服务器
     90         /// </summary>
     91         /// <returns></returns>
     92         public bool Connect()
     93         {
     94             return Connect(opcServerName, opcServerIPAddress);
     95         }
     96         /// <summary>
     97         /// 连接Opc服务器
     98         /// </summary>
     99         /// <returns></returns>
    100         public bool Connect(string remoteOpcServerName, string remoteOpcServerIpAddress)
    101         {
    102             var returnValue = false;
    103             if (!string.IsNullOrEmpty(remoteOpcServerIpAddress) && !string.IsNullOrEmpty(remoteOpcServerName))
    104             {
    105                 var opcServerType = Type.GetTypeFromProgID(remoteOpcServerName, remoteOpcServerIpAddress);
    106                 if (opcServerType != null)
    107                 {
    108                     iOpcServer = (IOPCServer)Activator.CreateInstance(opcServerType);
    109                     returnValue = true;
    110                 }
    111             }  
    112             return returnValue;
    113         }
    114         /// <summary>
    115         /// 添加Opc组
    116         /// </summary>
    117         private void AddOpcGroup()
    118         {
    119             try
    120             {
    121                 foreach (OpcDaCustomGroup opcGroup in opcDaCustomGroups)
    122                 {
    123                     AddOpcGroup(opcGroup);
    124                 }
    125             }
    126             catch(COMException ex)
    127             {
    128                 throw ex;
    129             }
    130         }
    131         /// <summary>
    132         /// 添加Opc项
    133         /// </summary>
    134         /// <param name="opcGroup"></param>
    135         private void AddOpcGroup(OpcDaCustomGroup opcGroup)
    136         {
    137             try
    138             {
    139 
    140                 //添加OPC组
    141                 iOpcServer.AddGroup(opcGroup.GroupName, opcGroup.IsActive, opcGroup.RequestedUpdateRate, opcGroup.ClientGroupHandle, opcGroup.TimeBias.AddrOfPinnedObject(), opcGroup.PercendDeadBand.AddrOfPinnedObject(), opcGroup.LCID, out opcGroup.ServerGroupHandle, out opcGroup.RevisedUpdateRate, ref opcGroup.Riid, out opcGroup.Group);
    142                 InitIoInterfaces(opcGroup);
    143                 if (opcGroup.OpcDataCustomItems.Length > 0)
    144                 {
    145                     //添加OPC项
    146                     AddOpcItem(opcGroup);
    147                     //激活订阅回调事件
    148                     ActiveDataChanged(IOPCGroupStateMgt);
    149                 }
    150             }
    151             catch (COMException ex)
    152             {
    153                 throw ex;
    154             }
    155             finally
    156             {
    157                 if (opcGroup.TimeBias.IsAllocated)
    158                 {
    159                     opcGroup.TimeBias.Free();
    160                 }
    161                 if (opcGroup.PercendDeadBand.IsAllocated)
    162                 {
    163                     opcGroup.PercendDeadBand.Free();
    164                 }
    165             }
    166         }
    167         /// <summary>
    168         /// 初始化IO接口
    169         /// </summary>
    170         /// <param name="opcGroup"></param>
    171         public void InitIoInterfaces(OpcDaCustomGroup opcGroup)
    172         {
    173             int cookie;
    174             //组状态管理对象,改变组的刷新率和激活状态
    175             IOPCGroupStateMgt = (IOPCGroupStateMgt)opcGroup.Group;
    176             IConnectionPointContainer = (IConnectionPointContainer)opcGroup.Group;
    177             Guid iid = typeof(IOPCDataCallback).GUID;
    178             IConnectionPointContainer.FindConnectionPoint(ref iid, out IConnectionPoint);
    179             //创建客户端与服务端之间的连接
    180             IConnectionPoint.Advise(this, out 
    181                     cookie);
    182         }
    183         /// <summary>
    184         /// 激活订阅回调事件
    185         /// </summary>
    186         private void ActiveDataChanged(IOPCGroupStateMgt IOPCGroupStateMgt)
    187         {
    188             IntPtr pRequestedUpdateRate = IntPtr.Zero;
    189             IntPtr hClientGroup = IntPtr.Zero;
    190             IntPtr pTimeBias = IntPtr.Zero;
    191             IntPtr pDeadband = IntPtr.Zero;
    192             IntPtr pLCID = IntPtr.Zero;
    193             int nActive = 0;
    194             GCHandle hActive = GCHandle.Alloc(nActive, GCHandleType.Pinned);
    195             try
    196             {
    197                 hActive.Target = 1;
    198                 int nRevUpdateRate = 0;
    199                 IOPCGroupStateMgt.SetState(pRequestedUpdateRate, out nRevUpdateRate,
    200                         hActive.AddrOfPinnedObject(), pTimeBias, pDeadband, pLCID, hClientGroup);
    201             }
    202             catch (COMException ex)
    203             {
    204                 throw ex;
    205             }
    206             finally
    207             {
    208                 hActive.Free();
    209             }
    210         }
    211 
    212         /// <summary>
    213         /// 添加Opc项
    214         /// </summary>
    215         /// <param name="opcGroup"></param>
    216         private void AddOpcItem(OpcDaCustomGroup opcGroup)
    217         {
    218             OpcDaCustomItem[] opcDataCustomItemsService = opcGroup.OpcDataCustomItems;
    219             IntPtr pResults = IntPtr.Zero;
    220             IntPtr pErrors = IntPtr.Zero;
    221             OPCITEMDEF[] itemDefyArray = new OPCITEMDEF[opcGroup.OpcDataCustomItems.Length];
    222             int i = 0;
    223             int[] errors = new int[opcGroup.OpcDataCustomItems.Length];
    224             int[] itemServerHandle = new int[opcGroup.OpcDataCustomItems.Length];
    225             try
    226             {
    227                 foreach (OpcDaCustomItem itemService in opcDataCustomItemsService)
    228                 {
    229                     if (itemService != null)
    230                     {
    231                         itemDefyArray[i].szAccessPath = itemService.AccessPath;
    232                         itemDefyArray[i].szItemID = itemService.ItemID;
    233                         itemDefyArray[i].bActive = itemService.IsActive;
    234                         itemDefyArray[i].hClient = itemService.ClientHandle;
    235                         itemDefyArray[i].dwBlobSize = itemService.BlobSize;
    236                         itemDefyArray[i].pBlob = itemService.Blob;
    237                         itemDefyArray[i].vtRequestedDataType = itemService.RequestedDataType;
    238                         i++;
    239                     }
    240 
    241                 }
    242                 //添加OPC项组
    243                 ((IOPCItemMgt)opcGroup.Group).AddItems(opcGroup.OpcDataCustomItems.Length, itemDefyArray, out pResults, out pErrors);
    244                 IntPtr Pos = pResults;
    245                 Marshal.Copy(pErrors, errors, 0, opcGroup.OpcDataCustomItems.Length);
    246                 for (int j = 0; j < opcGroup.OpcDataCustomItems.Length; j++)
    247                 {
    248                     if (errors[j] == 0)
    249                     {
    250                         if (j != 0)
    251                         {
    252                             Pos = new IntPtr(Pos.ToInt32() + Marshal.SizeOf(typeof(OPCITEMRESULT)));
    253                         }
    254                         var result = (OPCITEMRESULT)Marshal.PtrToStructure(Pos, typeof(OPCITEMRESULT));
    255                         itemServerHandle[j] = opcDataCustomItemsService[j].ServerHandle = result.hServer;
    256                         Marshal.DestroyStructure(Pos, typeof(OPCITEMRESULT));
    257                     }
    258                 }
    259             }
    260             catch (COMException ex)
    261             {
    262                 throw ex;
    263             }
    264             finally
    265             {
    266                 if (pResults != IntPtr.Zero)
    267                 {
    268                     Marshal.FreeCoTaskMem(pResults);
    269                 }
    270                 if (pErrors != IntPtr.Zero)
    271                 {
    272                     Marshal.FreeCoTaskMem(pErrors);
    273                 }
    274             }
    275         }
    276         /// <summary>
    277         /// 异步读取信息
    278         /// </summary>
    279         public void Read()
    280         {
    281             foreach (OpcDaCustomGroup opcGroup in opcDaCustomGroups)
    282             {
    283                 IntPtr pErrors = IntPtr.Zero;
    284                 try
    285                 {
    286                     _iopcAsyncIo2 = (IOPCAsyncIO2)opcGroup.Group;
    287                     if (_iopcAsyncIo2 != null)
    288                     {
    289                         int[] serverHandle = new int[opcGroup.OpcDataCustomItems.Length];
    290                         opcGroup.PErrors = new int[opcGroup.OpcDataCustomItems.Length];
    291                         for (int j = 0; j < opcGroup.OpcDataCustomItems.Length; j++)
    292                         {
    293                             serverHandle[j] = opcGroup.OpcDataCustomItems[j].ServerHandle;
    294                         }
    295                         int cancelId=0;
    296                         _iopcAsyncIo2.Read(opcGroup.OpcDataCustomItems.Length, serverHandle, 2, out cancelId, out pErrors);
    297                         Marshal.Copy(pErrors, opcGroup.PErrors, 0, opcGroup.OpcDataCustomItems.Length);
    298                     }
    299                 }
    300                 catch (COMException ex)
    301                 {
    302                     throw ex;
    303                 }
    304                 finally
    305                 {
    306                     if (pErrors != IntPtr.Zero)
    307                     {
    308                         Marshal.FreeCoTaskMem(pErrors);
    309                     }
    310                 }
    311             }
    312         }
    313 
    314         /// <summary>
    315         /// 异步写入数据
    316         /// </summary>
    317         /// <param name="values">要写入的值</param>
    318         /// <param name="serverHandle">要写入的项的服务器句柄</param>
    319         /// <param name="errors">错误信息,等于表示写入成功,否则写入失败</param>
    320         /// <param name="opcGroup">要写入的Opc组</param>
    321         public void Write(object[] values,int[] serverHandle,out int[] errors,OpcDaCustomGroup opcGroup)
    322         {
    323             _iopcAsyncIo2 = (IOPCAsyncIO2)opcGroup.Group;
    324             IntPtr pErrors = IntPtr.Zero;
    325             errors = new int[values.Length];
    326             if (_iopcAsyncIo2 != null)
    327             {
    328                 try
    329                 {
    330                     //异步写入数据
    331                     int cancelId;
    332                     _iopcAsyncIo2.Write(values.Length, serverHandle, values, transactionID + 1, out cancelId, out pErrors);
    333                     Marshal.Copy(pErrors, errors, 0, values.Length);
    334                 }
    335                 catch (COMException ex)
    336                 {
    337                     throw ex;
    338                 }
    339                 finally
    340                 {
    341                     if (pErrors != IntPtr.Zero)
    342                     {
    343                         Marshal.FreeCoTaskMem(pErrors);
    344                     }
    345                 }
    346             }
    347         }
    348         /// <summary>
    349         /// 数据订阅事件
    350         /// </summary>
    351         /// <param name="dwTransid"></param>
    352         /// <param name="hGroup"></param>
    353         /// <param name="hrMasterquality"></param>
    354         /// <param name="hrMastererror"></param>
    355         /// <param name="dwCount"></param>
    356         /// <param name="phClientItems"></param>
    357         /// <param name="pvValues"></param>
    358         /// <param name="pwQualities"></param>
    359         /// <param name="pftTimeStamps"></param>
    360         /// <param name="pErrors"></param>
    361         public virtual void OnDataChange(Int32 dwTransid,
    362             Int32 hGroup,
    363             Int32 hrMasterquality,
    364             Int32 hrMastererror,
    365             Int32 dwCount,
    366             int[] phClientItems,
    367             object[] pvValues,
    368             short[] pwQualities,
    369             OpcRcw.Da.FILETIME[] pftTimeStamps,
    370             int[] pErrors)
    371         
    372         {
    373             var e = new OpcDaCustomAsyncEventArgs
    374             {
    375                 GroupHandle = hGroup,
    376                 Count = dwCount,
    377                 Errors = pErrors,
    378                 Values = pvValues,
    379                 ClientItemsHandle = phClientItems
    380             };
    381             if (OnDataChanged != null)
    382             {
    383                 OnDataChanged(this, e);
    384             }
    385         }
    386 
    387         /// <summary>
    388         /// 取消事件
    389         /// </summary>
    390         /// <param name="dwTransid"></param>
    391         /// <param name="hGroup"></param>
    392         public virtual void OnCancelComplete(Int32 dwTransid, Int32 hGroup)
    393         {
    394 
    395         }
    396 
    397         /// <summary>
    398         /// 写入数据完成事件
    399         /// </summary>
    400         /// <param name="dwTransid"></param>
    401         /// <param name="hGroup"></param>
    402         /// <param name="hrMastererr"></param>
    403         /// <param name="dwCount"></param>
    404         /// <param name="pClienthandles"></param>
    405         /// <param name="pErrors"></param>
    406         public virtual void OnWriteComplete(Int32 dwTransid,
    407             Int32 hGroup,
    408             Int32 hrMastererr,
    409             Int32 dwCount,
    410             int[] pClienthandles,
    411             int[] pErrors)
    412         {
    413             if (OnWriteCompleted != null)
    414             {
    415                 var e = new OpcDaCustomAsyncEventArgs
    416                 {
    417                     Errors = pErrors
    418                 };
    419                 if (OnWriteCompleted != null)
    420                 {
    421                     OnWriteCompleted(this, e);
    422                 }
    423             }
    424         }
    425         /// <summary>
    426         /// 读取数据完成事件
    427         /// </summary>
    428         /// <param name="dwTransid"></param>
    429         /// <param name="hGroup"></param>
    430         /// <param name="hrMasterquality"></param>
    431         /// <param name="hrMastererror"></param>
    432         /// <param name="dwCount">要读取的组的项的个数</param>
    433         /// <param name="phClientItems"></param>
    434         /// <param name="pvValues">项值列表</param>
    435         /// <param name="pwQualities"></param>
    436         /// <param name="pftTimeStamps"></param>
    437         /// <param name="pErrors">项错误列表</param>
    438         public virtual void OnReadComplete(Int32 dwTransid,
    439             Int32 hGroup,
    440             Int32 hrMasterquality,
    441             Int32 hrMastererror,
    442             Int32 dwCount,
    443             int[] phClientItems,
    444             object[] pvValues,
    445             short[] pwQualities,
    446             OpcRcw.Da.FILETIME[] pftTimeStamps,
    447             int[] pErrors)
    448         {
    449             if (OnReadCompleted != null)
    450             {
    451                 var e = new OpcDaCustomAsyncEventArgs
    452                 {
    453                     GroupHandle = hGroup,
    454                     Count = dwCount,
    455                     Errors = pErrors,
    456                     Values = pvValues,
    457                     ClientItemsHandle = phClientItems
    458                 };
    459                 OnReadCompleted(this, e);
    460             }
    461         }
    462         public void Dispose()
    463         {
    464 
    465         }
    466     }
    467 }

    我们看下IOPCDataCallback接口的定义:

    这个接口提供了4个函数。如果我们采用订阅模式(默认的模式),会执行OnDataChange函数,主动读数据则执行OnReadComplete函数,写数据则执行OnWriteComplete函数。在OpcDaCustomAsync类中,我已经对这四个函数进行了实现,每个实现对应一个事件。

    OpcDaCustomAsync类的实现,我主要是扒了SimaticNet下的一个Sample,自己封装了下。使用这个类的时候需要提供Group列表和OPCServer的名称,以及OPCServer所在的主机的IP地址。

    OpcGroup的封装:

      1 using System;
      2 using System.Runtime.InteropServices;
      3 using OpcRcw.Da;
      4 
      5 namespace Opc.Net
      6 {
      7     /// <summary>
      8     /// 自定义接口OPC组对象
      9     /// </summary>
     10     public class OpcDaCustomGroup
     11     {
     12         private string groupName;
     13         private int isActive=1;
     14         private int requestedUpdateRate;
     15         private int clientGroupHandle=1;
     16         private GCHandle timeBias = GCHandle.Alloc(0, GCHandleType.Pinned);
     17         private GCHandle percendDeadBand = GCHandle.Alloc(0, GCHandleType.Pinned);
     18         private int lcid = 0x409;
     19         private int itemCount;
     20         private bool onRead;
     21 
     22         /// <summary>
     23         /// 输出参数,服务器为新创建的组对象产生的句柄
     24         /// </summary>
     25         public int ServerGroupHandle;
     26 
     27         /// <summary>
     28         /// 输出参数,服务器返回给客户端的实际使用的数据更新率
     29         /// </summary>
     30         public int RevisedUpdateRate;
     31 
     32         /// <summary>
     33         /// 引用参数,客户端想要的组对象的接口类型(如 IIDIOPCItemMgt)
     34         /// </summary>
     35         public Guid Riid = typeof(IOPCItemMgt).GUID;
     36 
     37         /// <summary>
     38         /// 输出参数,用来存储返回的接口指针。如果函数操作出现任务失败,此参数将返回NULL。
     39         /// </summary>
     40         public object Group;
     41         private OpcDaCustomItem[] opcDataCustomItems;
     42 
     43         public int[] PErrors { get; set; }
     44 
     45         /// <summary>
     46         /// 组对象是否激活
     47         /// 1为激活,0为未激活,默认激活
     48         /// </summary>
     49         public int IsActive
     50         {
     51             get
     52             {
     53                 return isActive;
     54             }
     55             set
     56             {
     57                 if (isActive == value)
     58                     return;
     59                 isActive = value;
     60             }
     61         }
     62         /// <summary>
     63         /// 组是否采用异步读方式
     64         /// </summary>
     65         public bool OnRead
     66         {
     67             get
     68             {
     69                 return onRead;
     70             }
     71             set
     72             {
     73                 if (onRead == value)
     74                     return;
     75                 onRead = value;
     76             }
     77         }
     78         /// <summary>
     79         /// 项的个数
     80         /// </summary>
     81         public int ItemCount
     82         {
     83             get { return itemCount; }
     84             set 
     85             {
     86                 if(itemCount == value)
     87                     return;
     88                 itemCount=value;
     89             }
     90         }
     91         /// <summary>
     92         /// 客户端指定的数据变化率
     93         /// </summary>
     94         public int RequestedUpdateRate
     95         {
     96             get
     97             {
     98                 return requestedUpdateRate;
     99             }
    100             set
    101             {
    102                 if (requestedUpdateRate == value)
    103                     return;
    104                 requestedUpdateRate = value;
    105             }
    106         }
    107 
    108         /// <summary>
    109         /// OPC组名称
    110         /// </summary>
    111         public string GroupName
    112         {
    113             get
    114             {
    115                 return groupName;
    116             }
    117             set
    118             {
    119                 if (groupName == value)
    120                     return;
    121                 groupName = value;
    122             }
    123         }
    124 
    125         /// <summary>
    126         /// 客户端程序为组对象提供的句柄
    127         /// </summary>
    128         public int ClientGroupHandle
    129         {
    130             get
    131             {
    132                 return clientGroupHandle;
    133             }
    134             set
    135             {
    136                 if (clientGroupHandle == value)
    137                     return;
    138                 clientGroupHandle = value;
    139             }
    140         }
    141 
    142         /// <summary>
    143         /// 指向Long类型的指针
    144         /// </summary>
    145         public GCHandle TimeBias
    146         {
    147             get
    148             {
    149                 return timeBias;
    150             }
    151             set
    152             {
    153                 if (timeBias == value)
    154                     return;
    155                 timeBias = value;
    156             }
    157         }
    158 
    159         /// <summary>
    160         /// 一个项对象的值变化的百分比,可能引发客户端程序的订阅回调。
    161         /// 此参数只应用于组对象中有模拟dwEUType(工程单位)类型的项对象。指针为NULL表示0.0
    162         /// </summary>
    163         public GCHandle PercendDeadBand
    164         {
    165             get
    166             {
    167                 return percendDeadBand;
    168             }
    169             set
    170             {
    171                 if (percendDeadBand == value)
    172                     return;
    173                 percendDeadBand = value;
    174             }
    175         }
    176 
    177         /// <summary>
    178         /// 当用于组对象上的操作的返回值为文本类型时,服务器使用的语言
    179         /// </summary>
    180         public int LCID
    181         {
    182             get
    183             {
    184                 return lcid;
    185             }
    186             set
    187             {
    188                 if (lcid == value)
    189                     return;
    190                 lcid = value;
    191             }
    192         }
    193 
    194         /// <summary>
    195         /// OPC项数组
    196         /// </summary>
    197         public OpcDaCustomItem[] OpcDataCustomItems
    198         {
    199             get
    200             {
    201                 return opcDataCustomItems;
    202             }
    203             set
    204             {
    205                 if (opcDataCustomItems != null && opcDataCustomItems == value)
    206                     return;
    207                 opcDataCustomItems = value;
    208             }
    209         }
    210     }
    211 }

    OpcItem的封装:

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Runtime.InteropServices;
      6 using OpcRcw.Da;
      7 
      8 namespace Opc.Net
      9 {
     10     /// <summary>
     11     /// 自定义接口Opc项
     12     /// </summary>
     13     public class OpcDaCustomItem
     14     {
     15         private string name;
     16         private string accessPath="";
     17         private string itemID;
     18         private int isActive = 1;
     19         private int clientHandle = 0;
     20         private int blobSize = 0;
     21         private IntPtr blob = IntPtr.Zero;
     22         private short requestedDataType = 0;
     23         private object itemValue;
     24         private int serverHandle;
     25 
     26         /// <summary>
     27         /// 项名称
     28         /// </summary>
     29         public string Name
     30         {
     31             get
     32             {
     33                 return name;
     34             }
     35             set
     36             {
     37                 if (name == value)
     38                     return;
     39                 name = value;
     40             }
     41         }
     42         /// <summary>
     43         /// 项对象的访问路径
     44         /// </summary>
     45         public string AccessPath
     46         {
     47             get
     48             {
     49                 return accessPath;
     50             }
     51             set
     52             {
     53                 if (accessPath == value)
     54                     return;
     55                 accessPath = value;
     56             }
     57         }
     58 
     59         /// <summary>
     60         /// 项对象的ItemIDea,唯一标识该数据项
     61         /// </summary>
     62         public string ItemID
     63         {
     64             get
     65             {
     66                 return itemID;
     67             }
     68             set
     69             {
     70                 if (itemID == value)
     71                     return;
     72                 itemID = value;
     73             }
     74         }
     75 
     76         /// <summary>
     77         /// 项对象的激活状态
     78         /// 1为激活,0为未激活,默认激活
     79         /// </summary>
     80         public int IsActive
     81         {
     82             get
     83             {
     84                 return isActive;
     85             }
     86             set
     87             {
     88                 if (isActive == value)
     89                     return;
     90                 isActive = value;
     91             }
     92         }
     93 
     94         /// <summary>
     95         /// 项对象的客户端句柄
     96         /// </summary>
     97         public int ClientHandle
     98         {
     99             get
    100             {
    101                 return clientHandle;
    102             }
    103             set
    104             {
    105                 if (clientHandle == value)
    106                     return;
    107                 clientHandle = value;
    108             }
    109         }
    110         public int BlobSize
    111         {
    112             get
    113             {
    114                 return blobSize;
    115             }
    116             set
    117             {
    118                 if (blobSize == value)
    119                     return;
    120                 blobSize = value;
    121             }
    122         }
    123         public IntPtr Blob
    124         {
    125             get
    126             {
    127                 return blob;
    128             }
    129             set
    130             {
    131                 if (blob == value)
    132                     return;
    133                 blob = value;
    134             }
    135         }
    136 
    137         /// <summary>
    138         /// OPC项的数据类型
    139         /// VbBoolean:11,VbByte:17,VbDecimal:14,VbDouble:5,Vbinteger:2,VbLong:3,VbSingle:4,VbString:8
    140         /// </summary>
    141         public short RequestedDataType
    142         {
    143             get
    144             {
    145                 return requestedDataType;
    146             }
    147             set
    148             {
    149                 if (requestedDataType == value)
    150                     return;
    151                 requestedDataType = value;
    152             }
    153         }
    154 
    155        /// <summary>
    156        /// OPC项的值
    157        /// </summary>
    158         public object Value
    159         {
    160             get
    161             {
    162                 return itemValue;
    163             }
    164             set
    165             {
    166                 if (itemValue == value)
    167                     return;
    168                 itemValue = value;
    169             }
    170         }
    171 
    172         /// <summary>
    173         /// OPC项的服务器句柄
    174         /// </summary>
    175         public int ServerHandle
    176         {
    177             get
    178             {
    179                 return serverHandle;
    180             }
    181             set
    182             {
    183                 if (serverHandle == value)
    184                     return;
    185                 serverHandle = value;
    186             }
    187         }
    188     }
    189 }

    项的客户端句柄和服务器句柄实际是一样的,项的数据类型用short表示,在下面的配置文件中体现出来了。

    以下是我设计的配置文件:

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <System>
     3   <OpcServer ServerName="OPC.SimaticNET" IPAddress="10.102.102.118">
     4     <!--采煤机参数-->
     5     <ShearerInfo GroupName="ShearerInfoGroup" ClientHandle="1" UpdateRate="100">
     6       <!--左牵,1表示左牵,0表示未运动-->
     7       <Item ItemID="S7:[S7 connection_2]DB201,X20.2" ClientHandle="1" RequestedDataType="11"></Item>
     8       <!--右牵,1表示右牵,0表示未运动-->
     9       <Item ItemID="S7:[S7 connection_2]DB201,X20.1" ClientHandle="2" RequestedDataType="11"></Item>
    10       <!--牵引速度-->
    11       <Item ItemID="S7:[S7 connection_2]DB201,REAL40" ClientHandle="3" RequestedDataType="5"></Item>
    12       <!--采煤机位置-->
    13       <Item ItemID="S7:[S7 connection_2]DB201,REAL44" ClientHandle="4" RequestedDataType="5"></Item>
    14       <!--左滚筒高度-->
    15       <Item ItemID="S7:[S7 connection_2]DB201,REAL48" ClientHandle="5" RequestedDataType="5"></Item>
    16       <!--右滚筒高度-->
    17       <Item ItemID="S7:[S7 connection_2]DB201,REAL52" ClientHandle="6" RequestedDataType="5"></Item>
    18       <!--左截电流-->
    19       <Item ItemID="S7:[S7 connection_2]DB201,INT6" ClientHandle="7" RequestedDataType="2"></Item>
    20       <!--右截电流-->
    21       <Item ItemID="S7:[S7 connection_2]DB201,INT8" ClientHandle="8" RequestedDataType="2"></Item>
    22       <!--左牵电流-->
    23       <Item ItemID="S7:[S7 connection_2]DB201,INT2" ClientHandle="9" RequestedDataType="2"></Item>
    24       <!--右牵电流-->
    25       <Item ItemID="S7:[S7 connection_2]DB201,INT4" ClientHandle="10" RequestedDataType="2"></Item>
    26       <!--左截启-->
    27       <Item ItemID="S7:[S7 connection_2]DB201,X20.6" ClientHandle="11" RequestedDataType="11"></Item>
    28       <!--右截启-->
    29       <Item ItemID="S7:[S7 connection_2]DB201,X20.5" ClientHandle="12" RequestedDataType="11"></Item>
    30       <!--左截温度-->
    31       <Item ItemID="S7:[S7 connection_2]DB201,INT10" ClientHandle="13" RequestedDataType="2"></Item>
    32       <!--右截温度-->
    33       <Item ItemID="S7:[S7 connection_2]DB201,INT12" ClientHandle="14" RequestedDataType="2"></Item>
    34       <!--油泵电机电流-->
    35       <Item ItemID="S7:[S7 connection_2]DB201,INT14" ClientHandle="15" RequestedDataType="2"></Item>
    36       <!--工作模式 2人工 4学习 8自动割煤 16 传感器配置-->
    37       <Item ItemID="S7:[S7 connection_2]DB201,INT34" ClientHandle="16" RequestedDataType="2"></Item>
    38     </ShearerInfo>
    39   </OpcServer>
    40 </System>

    上述配置文件中,OpcServer节点对应的OpcServer对象,定义了ServerName和IPAddress属性,用来连接OPCServer。

    ShearerInfo节点则对应一个OpcGroup,在OpcServer下定义多个OPCGrupo节点,OPCGroup节点需要指定组的客户端句柄和刷新频率。上文说到OPC的读写操作都是以组进行的,我们需要根据客户端句柄来判断是哪一个组,如果我们采用的事订阅模式读取数据,则还需要刷新频率,OpcServer对订阅模式的实现不太清楚,实际使用的过程发现,并没有按照刷新频率来,所以我就采用了直接读的方式来保证数据的实时性。

    Item的ItemID是一个地址,由于我使用的是西门子的产品,所以格式是:S7:[S7连接名称]地址,我们只需要更改S7连接的名称和地址就好了。如果你使用的事其他类型的PLC,请参照他们的地址格式。

    有了配置文件如何操作呢?下面我定义了一个实现类:

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.IO;
      5 using System.Runtime.InteropServices;
      6 using System.Xml.Linq;
      7 
      8 namespace Opc.Net
      9 {
     10     public class OpcManager
     11     {
     12         /// <summary>
     13         /// Opc异步接口类
     14         /// </summary>
     15         OpcDaCustomAsync _opcDaCustomAsync;
     16         /// <summary>
     17         /// 异步读取数据完成事件
     18         /// </summary>
     19         public event EventHandler<OpcDaCustomAsyncEventArgs> OnReadCompleted;
     20         /// <summary>
     21         /// Opc组列表
     22         /// </summary>
     23         List<OpcDaCustomGroup> _opcGroups;
     24         /// <summary>
     25         /// OPC服务器名称
     26         /// </summary>
     27         string _strRemoteServerName;
     28         /// <summary>
     29         /// OPC服务器IP地址
     30         /// </summary>
     31         string _strRemoteServerIpAddress;
     32 
     33         /// <summary>
     34         /// 构造函数
     35         /// </summary>
     36         /// <param name="strConfigFilePath">配置文件路径</param>
     37         public OpcManager(string strConfigFilePath)
     38         {
     39             LoadOpcGroupConfig(strConfigFilePath);
     40         }
     41         /// <summary>
     42         /// 加载Opc组配置
     43         /// </summary>
     44         /// <param name="strConfigFilePath">配置文件路径</param>
     45         public void LoadOpcGroupConfig(string strConfigFilePath)
     46         {
     47             try
     48             {
     49                 if (!File.Exists(strConfigFilePath)) return;
     50                 XDocument xDoc = XDocument.Load(strConfigFilePath);
     51                 XElement xElement = xDoc.Element("System").Element("OpcServer");
     52                 _strRemoteServerName = xElement.Attribute("ServerName").Value;
     53                 _strRemoteServerIpAddress = xElement.Attribute("IPAddress").Value;
     54                 _opcGroups = new List<OpcDaCustomGroup>();
     55                 foreach (XElement xElementItem in xElement.Elements())
     56                 {
     57                     var opcDaCustomGroupService = new OpcDaCustomGroup
     58                     {
     59                         GroupName = xElementItem.Attribute("GroupName").Value,
     60                         ClientGroupHandle = Convert.ToInt32(xElementItem.Attribute("ClientHandle").Value),
     61                         RequestedUpdateRate = Convert.ToInt32(xElementItem.Attribute("UpdateRate").Value),
     62                         OpcDataCustomItems = LoadOpcItemConfig(xElementItem)
     63                     };
     64                     _opcGroups.Add(opcDaCustomGroupService);
     65                 }
     66                 _opcDaCustomAsync = new OpcDaCustomAsync(_opcGroups, _strRemoteServerName, _strRemoteServerIpAddress);
     67                 _opcDaCustomAsync.OnReadCompleted += ReadCompleted;
     68             }
     69             catch(COMException ex)
     70             {
     71                 throw ex;
     72             }
     73         }
     74         /// <summary>
     75         /// 连接Opc服务器
     76         /// </summary>
     77         /// <returns></returns>
     78         public bool Connect()
     79         {
     80             return _opcDaCustomAsync.Connect();
     81         }
     82         /// <summary>
     83         /// 连接Opc服务器
     84         /// </summary>
     85         /// <returns></returns>
     86         public bool Connect(string remoteOpcServerName,string remoteOpcServerIpAddress)
     87         {
     88             return _opcDaCustomAsync.Connect(remoteOpcServerName, remoteOpcServerIpAddress);
     89         }
     90         /// <summary>
     91         /// 加载Opc项配置
     92         /// </summary>
     93         /// <param name="xElement">Opc组Xml节点</param>
     94         /// <returns></returns>
     95         public OpcDaCustomItem[] LoadOpcItemConfig(XElement xElement)
     96         {
     97             int itemCount = xElement.Elements().Count();
     98             var opcDaCustomItems = new OpcDaCustomItem[itemCount];
     99             int i = 0;
    100             foreach (var xElementItem in xElement.Elements())
    101             {
    102                 var opcDaCustomItemService = new OpcDaCustomItem
    103                 {
    104                     ClientHandle = Convert.ToInt32(xElementItem.Attribute("ClientHandle").Value),
    105                     ItemID = xElementItem.Attribute("ItemID").Value,
    106                     RequestedDataType = short.Parse(xElementItem.Attribute("RequestedDataType").Value)
    107                 };
    108                 opcDaCustomItems[i] = opcDaCustomItemService;
    109                 i++;
    110             }
    111             return opcDaCustomItems;
    112         }
    113         public bool WriteForReturn(int itemClientHandle, int value, int clientHandle)
    114         {
    115             bool returnValue;
    116             var itemDictionary = new Dictionary<int, object>
    117             {
    118                 {itemClientHandle, value}
    119             };
    120             try
    121             {
    122                 int[] pErrors;
    123                 Write(itemDictionary, clientHandle, out pErrors);
    124                 returnValue = (pErrors[0] == 0);
    125             }
    126             catch (COMException ex)
    127             {
    128                 throw ex;
    129             }
    130             return returnValue;
    131         }
    132         public void Write(Dictionary<int, object> itemDictionary, int groupHandle, out int[] pErrors)
    133         {
    134             var count = itemDictionary.Count();
    135             var values = new object[count];
    136             var serverHandle = new int[count];
    137             pErrors = null;
    138             OpcDaCustomGroup group = _opcGroups.First(p => p.ServerGroupHandle == groupHandle);
    139             int index = 0;
    140             foreach (KeyValuePair<int, object> itemId in itemDictionary)
    141             {
    142                 foreach (var item in group.OpcDataCustomItems)
    143                 {
    144                     if (item.ClientHandle == itemId.Key)
    145                     {
    146                         values[index] = itemId.Value;
    147                         serverHandle[index] = item.ServerHandle;
    148                         index++;
    149                     }
    150                 }
    151             }
    152             try
    153             {
    154                 _opcDaCustomAsync.Write(values, serverHandle, out pErrors, group);
    155             }
    156             catch (COMException ex)
    157             {
    158                 throw ex;
    159             }
    160         }
    161         /// <summary>
    162         /// 写单个数据
    163         /// </summary>
    164         /// <param name="value"></param>
    165         /// <param name="groupHandle">组ID</param>
    166         /// <param name="clientHandle">项ID</param>
    167         public void Write(int value, int groupHandle, int clientHandle)
    168         {
    169             OpcDaCustomGroup group = GetOpcGroup(groupHandle);
    170             if (group != null)
    171             {
    172                 int[] pErrors;
    173                 var serverHanlde = new int[1];
    174                 serverHanlde[0] = group.OpcDataCustomItems.First(c => c.ClientHandle == clientHandle).ServerHandle;
    175                 var values = new object[1];
    176                 values[0] = value;
    177 
    178                 _opcDaCustomAsync.Write(values, serverHanlde, out pErrors, group);
    179 
    180             }
    181         }
    182         /// <summary>
    183         /// 异步读取数据完成事件
    184         /// </summary>
    185         /// <param name="sender"></param>
    186         /// <param name="e"></param>
    187         public void ReadCompleted(object sender, OpcDaCustomAsyncEventArgs e)
    188         {
    189             if (OnReadCompleted != null)
    190             {
    191                 OnReadCompleted(this, e);
    192             }
    193         }
    194         /// <summary>
    195         /// 异步读取控制模式数据
    196         /// </summary>
    197         public void Read()
    198         {
    199             if (_opcDaCustomAsync != null)
    200             {
    201                 _opcDaCustomAsync.Read();
    202             }
    203 
    204         }
    205         /// <summary>
    206         /// 根据OPC句柄获取OPC组对象
    207         /// </summary>
    208         /// <param name="groupHandle">OPC组对象</param>
    209         /// <returns></returns>
    210         public OpcDaCustomGroup GetOpcGroup(int groupHandle)
    211         {
    212             return _opcGroups.First(e => e.ClientGroupHandle == groupHandle);
    213         }
    214     }
    215 }
    View Code

    这个类可以根据自己设计的配置文件进行相应的实现。

     1 private OpcManager opcManager;
     2         private System.Timers.Timer opcTimer;
     3         private int[] pErrors;
     4         private Dictionary<int, object> items;
     5         /// <summary>
     6         /// 写入采煤机位置数据
     7         /// </summary>
     8         /// <param name="sender"></param>
     9         /// <param name="e"></param>
    10         private void button1_Click(object sender, EventArgs e)
    11         {
    12             items = new Dictionary<int, object>();
    13             items.Add(2, textBox2.Text);
    14             opcManager.Write(items, 1, pErrors);
    15         }
    16 
    17         private void FrmMain_Load(object sender, EventArgs e)
    18         {
    19             opcManager = new OpcManager(AppDomain.CurrentDomain.BaseDirectory+"\Opc.config.xml");
    20             opcManager.OnReadCompleted += new EventHandler<OpcDaCustomAsyncEventArgs>(opcManager_OnReadCompleted);
    21 
    22             opcTimer = new System.Timers.Timer()
    23             {
    24                 Interval = 100,
    25                 AutoReset = true,
    26                 Enabled = true
    27             };
    28             opcTimer.Elapsed += new ElapsedEventHandler(opcTimer_Elapsed);
    29         }
    30 
    31         void opcTimer_Elapsed(object sender, ElapsedEventArgs e)
    32         {
    33             opcManager.Read();
    34         }
    35 
    36         void opcManager_OnReadCompleted(object sender, OpcDaCustomAsyncEventArgs e)
    37         {
    38             Invoke((ThreadStart)(() =>
    39             {
    40 
    41                 if (OpcHelper.ShowValue(e, 3) != null)
    42                 {
    43                     textBox1.Text = OpcHelper.ShowValue(e, 3).ToString();
    44                 }
    45             }));
    46         }

    以上实现了数据的读取和写入。

    源码戳这里:http://pan.baidu.com/s/1ntp1JAx

  • 相关阅读:
    C#中的WebBrowser控件的使用
    xshell5 可用注册码
    一次多数据源 配置问题记录
    org.springframework.data.mongodb.core.MongoTemplate]: Constructor threw exception; nested exception is java.lang.NoSuchMethodError: org.springframework.core.convert.support.ConversionServiceFactory.cr
    关于 <mvc:argument-resolvers> 的一次使用记录
    补码、反码、原码 ~ ^ 运算
    mysql中int、bigint、smallint 和 tinyint的区别与长度的含义【转】
    tomcat优化记录
    判读40亿数字中是否有某个数字
    LinkedList源码疑问记录
  • 原文地址:https://www.cnblogs.com/code-ming/p/3848818.html
Copyright © 2011-2022 走看看