上篇博文分享了我的知识库,被好多人关注,受宠若惊。今天我把我在项目中封装的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

    

 using System;
using System.Collections.Generic;
using OpcRcw.Comn;
using OpcRcw.Da;
using System.Runtime.InteropServices; namespace Opc.Net
{
/// <summary>
/// Opc自定义接口-异步管理类
/// <author name="lm" date="2012.3.14"/>
/// </summary>
public class OpcDaCustomAsync : IOPCDataCallback,IDisposable
{
/// <summary>
/// OPC服务器对象
/// </summary>
IOPCServer iOpcServer;
/// <summary>
/// 事务ID
/// </summary>
int transactionID;
/// <summary>
/// OPC服务器名称
/// </summary>
string opcServerName;
/// <summary>
/// OPC服务器IP地址
/// </summary>
IOPCAsyncIO2 _iopcAsyncIo2;
/// <summary>
/// OPC服务器IP地址
/// </summary>
string opcServerIPAddress;
/// <summary>
/// Opc组列表
/// </summary>
List<OpcDaCustomGroup> opcDaCustomGroups;
/// <summary>
/// 连接指针容器
/// </summary>
IConnectionPointContainer IConnectionPointContainer = null;
/// <summary>
/// 连接指针
/// </summary>
IConnectionPoint IConnectionPoint = null;
/// <summary>
/// Opc组管理器
/// </summary>
IOPCGroupStateMgt IOPCGroupStateMgt = null; //接收数据事件
public event EventHandler<OpcDaCustomAsyncEventArgs> OnDataChanged;
/// <summary>
/// 异步写入数据完成事件
/// </summary>
public event EventHandler<OpcDaCustomAsyncEventArgs> OnWriteCompleted;
/// <summary>
/// 异步读取数据完成事件
/// </summary>
public event EventHandler<OpcDaCustomAsyncEventArgs> OnReadCompleted; /// <summary>
/// 构造函数
/// </summary>
/// <param name="opcDaCustomGroups">Opc组列表</param>
/// <param name="opcServerName">OPC服务器名称</param>
/// <param name="opcServerIpAddress">OPC服务器IP地址</param>
public OpcDaCustomAsync(List<OpcDaCustomGroup> opcDaCustomGroups, string opcServerName, string opcServerIpAddress)
{
this.opcDaCustomGroups = opcDaCustomGroups;
this.opcServerName = opcServerName;
this.opcServerIPAddress = opcServerIpAddress;
Init();
}
/// <summary>
/// 初始化参数
/// </summary>
public void Init()
{
if (Connect())
{
AddOpcGroup();
}
} /// <summary>
/// 连接Opc服务器
/// </summary>
/// <returns></returns>
public bool Connect()
{
return Connect(opcServerName, opcServerIPAddress);
}
/// <summary>
/// 连接Opc服务器
/// </summary>
/// <returns></returns>
public bool Connect(string remoteOpcServerName, string remoteOpcServerIpAddress)
{
var returnValue = false;
if (!string.IsNullOrEmpty(remoteOpcServerIpAddress) && !string.IsNullOrEmpty(remoteOpcServerName))
{
var opcServerType = Type.GetTypeFromProgID(remoteOpcServerName, remoteOpcServerIpAddress);
if (opcServerType != null)
{
iOpcServer = (IOPCServer)Activator.CreateInstance(opcServerType);
returnValue = true;
}
}
return returnValue;
}
/// <summary>
/// 添加Opc组
/// </summary>
private void AddOpcGroup()
{
try
{
foreach (OpcDaCustomGroup opcGroup in opcDaCustomGroups)
{
AddOpcGroup(opcGroup);
}
}
catch(COMException ex)
{
throw ex;
}
}
/// <summary>
/// 添加Opc项
/// </summary>
/// <param name="opcGroup"></param>
private void AddOpcGroup(OpcDaCustomGroup opcGroup)
{
try
{ //添加OPC组
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);
InitIoInterfaces(opcGroup);
if (opcGroup.OpcDataCustomItems.Length > )
{
//添加OPC项
AddOpcItem(opcGroup);
//激活订阅回调事件
ActiveDataChanged(IOPCGroupStateMgt);
}
}
catch (COMException ex)
{
throw ex;
}
finally
{
if (opcGroup.TimeBias.IsAllocated)
{
opcGroup.TimeBias.Free();
}
if (opcGroup.PercendDeadBand.IsAllocated)
{
opcGroup.PercendDeadBand.Free();
}
}
}
/// <summary>
/// 初始化IO接口
/// </summary>
/// <param name="opcGroup"></param>
public void InitIoInterfaces(OpcDaCustomGroup opcGroup)
{
int cookie;
//组状态管理对象,改变组的刷新率和激活状态
IOPCGroupStateMgt = (IOPCGroupStateMgt)opcGroup.Group;
IConnectionPointContainer = (IConnectionPointContainer)opcGroup.Group;
Guid iid = typeof(IOPCDataCallback).GUID;
IConnectionPointContainer.FindConnectionPoint(ref iid, out IConnectionPoint);
//创建客户端与服务端之间的连接
IConnectionPoint.Advise(this, out
cookie);
}
/// <summary>
/// 激活订阅回调事件
/// </summary>
private void ActiveDataChanged(IOPCGroupStateMgt IOPCGroupStateMgt)
{
IntPtr pRequestedUpdateRate = IntPtr.Zero;
IntPtr hClientGroup = IntPtr.Zero;
IntPtr pTimeBias = IntPtr.Zero;
IntPtr pDeadband = IntPtr.Zero;
IntPtr pLCID = IntPtr.Zero;
int nActive = ;
GCHandle hActive = GCHandle.Alloc(nActive, GCHandleType.Pinned);
try
{
hActive.Target = ;
int nRevUpdateRate = ;
IOPCGroupStateMgt.SetState(pRequestedUpdateRate, out nRevUpdateRate,
hActive.AddrOfPinnedObject(), pTimeBias, pDeadband, pLCID, hClientGroup);
}
catch (COMException ex)
{
throw ex;
}
finally
{
hActive.Free();
}
} /// <summary>
/// 添加Opc项
/// </summary>
/// <param name="opcGroup"></param>
private void AddOpcItem(OpcDaCustomGroup opcGroup)
{
OpcDaCustomItem[] opcDataCustomItemsService = opcGroup.OpcDataCustomItems;
IntPtr pResults = IntPtr.Zero;
IntPtr pErrors = IntPtr.Zero;
OPCITEMDEF[] itemDefyArray = new OPCITEMDEF[opcGroup.OpcDataCustomItems.Length];
int i = ;
int[] errors = new int[opcGroup.OpcDataCustomItems.Length];
int[] itemServerHandle = new int[opcGroup.OpcDataCustomItems.Length];
try
{
foreach (OpcDaCustomItem itemService in opcDataCustomItemsService)
{
if (itemService != null)
{
itemDefyArray[i].szAccessPath = itemService.AccessPath;
itemDefyArray[i].szItemID = itemService.ItemID;
itemDefyArray[i].bActive = itemService.IsActive;
itemDefyArray[i].hClient = itemService.ClientHandle;
itemDefyArray[i].dwBlobSize = itemService.BlobSize;
itemDefyArray[i].pBlob = itemService.Blob;
itemDefyArray[i].vtRequestedDataType = itemService.RequestedDataType;
i++;
} }
//添加OPC项组
((IOPCItemMgt)opcGroup.Group).AddItems(opcGroup.OpcDataCustomItems.Length, itemDefyArray, out pResults, out pErrors);
IntPtr Pos = pResults;
Marshal.Copy(pErrors, errors, , opcGroup.OpcDataCustomItems.Length);
for (int j = ; j < opcGroup.OpcDataCustomItems.Length; j++)
{
if (errors[j] == )
{
if (j != )
{
Pos = new IntPtr(Pos.ToInt32() + Marshal.SizeOf(typeof(OPCITEMRESULT)));
}
var result = (OPCITEMRESULT)Marshal.PtrToStructure(Pos, typeof(OPCITEMRESULT));
itemServerHandle[j] = opcDataCustomItemsService[j].ServerHandle = result.hServer;
Marshal.DestroyStructure(Pos, typeof(OPCITEMRESULT));
}
}
}
catch (COMException ex)
{
throw ex;
}
finally
{
if (pResults != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pResults);
}
if (pErrors != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pErrors);
}
}
}
/// <summary>
/// 异步读取信息
/// </summary>
public void Read()
{
foreach (OpcDaCustomGroup opcGroup in opcDaCustomGroups)
{
IntPtr pErrors = IntPtr.Zero;
try
{
_iopcAsyncIo2 = (IOPCAsyncIO2)opcGroup.Group;
if (_iopcAsyncIo2 != null)
{
int[] serverHandle = new int[opcGroup.OpcDataCustomItems.Length];
opcGroup.PErrors = new int[opcGroup.OpcDataCustomItems.Length];
for (int j = ; j < opcGroup.OpcDataCustomItems.Length; j++)
{
serverHandle[j] = opcGroup.OpcDataCustomItems[j].ServerHandle;
}
int cancelId=;
_iopcAsyncIo2.Read(opcGroup.OpcDataCustomItems.Length, serverHandle, , out cancelId, out pErrors);
Marshal.Copy(pErrors, opcGroup.PErrors, , opcGroup.OpcDataCustomItems.Length);
}
}
catch (COMException ex)
{
throw ex;
}
finally
{
if (pErrors != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pErrors);
}
}
}
} /// <summary>
/// 异步写入数据
/// </summary>
/// <param name="values">要写入的值</param>
/// <param name="serverHandle">要写入的项的服务器句柄</param>
/// <param name="errors">错误信息,等于表示写入成功,否则写入失败</param>
/// <param name="opcGroup">要写入的Opc组</param>
public void Write(object[] values,int[] serverHandle,out int[] errors,OpcDaCustomGroup opcGroup)
{
_iopcAsyncIo2 = (IOPCAsyncIO2)opcGroup.Group;
IntPtr pErrors = IntPtr.Zero;
errors = new int[values.Length];
if (_iopcAsyncIo2 != null)
{
try
{
//异步写入数据
int cancelId;
_iopcAsyncIo2.Write(values.Length, serverHandle, values, transactionID + , out cancelId, out pErrors);
Marshal.Copy(pErrors, errors, , values.Length);
}
catch (COMException ex)
{
throw ex;
}
finally
{
if (pErrors != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pErrors);
}
}
}
}
/// <summary>
/// 数据订阅事件
/// </summary>
/// <param name="dwTransid"></param>
/// <param name="hGroup"></param>
/// <param name="hrMasterquality"></param>
/// <param name="hrMastererror"></param>
/// <param name="dwCount"></param>
/// <param name="phClientItems"></param>
/// <param name="pvValues"></param>
/// <param name="pwQualities"></param>
/// <param name="pftTimeStamps"></param>
/// <param name="pErrors"></param>
public virtual void OnDataChange(Int32 dwTransid,
Int32 hGroup,
Int32 hrMasterquality,
Int32 hrMastererror,
Int32 dwCount,
int[] phClientItems,
object[] pvValues,
short[] pwQualities,
OpcRcw.Da.FILETIME[] pftTimeStamps,
int[] pErrors) {
var e = new OpcDaCustomAsyncEventArgs
{
GroupHandle = hGroup,
Count = dwCount,
Errors = pErrors,
Values = pvValues,
ClientItemsHandle = phClientItems
};
if (OnDataChanged != null)
{
OnDataChanged(this, e);
}
} /// <summary>
/// 取消事件
/// </summary>
/// <param name="dwTransid"></param>
/// <param name="hGroup"></param>
public virtual void OnCancelComplete(Int32 dwTransid, Int32 hGroup)
{ } /// <summary>
/// 写入数据完成事件
/// </summary>
/// <param name="dwTransid"></param>
/// <param name="hGroup"></param>
/// <param name="hrMastererr"></param>
/// <param name="dwCount"></param>
/// <param name="pClienthandles"></param>
/// <param name="pErrors"></param>
public virtual void OnWriteComplete(Int32 dwTransid,
Int32 hGroup,
Int32 hrMastererr,
Int32 dwCount,
int[] pClienthandles,
int[] pErrors)
{
if (OnWriteCompleted != null)
{
var e = new OpcDaCustomAsyncEventArgs
{
Errors = pErrors
};
if (OnWriteCompleted != null)
{
OnWriteCompleted(this, e);
}
}
}
/// <summary>
/// 读取数据完成事件
/// </summary>
/// <param name="dwTransid"></param>
/// <param name="hGroup"></param>
/// <param name="hrMasterquality"></param>
/// <param name="hrMastererror"></param>
/// <param name="dwCount">要读取的组的项的个数</param>
/// <param name="phClientItems"></param>
/// <param name="pvValues">项值列表</param>
/// <param name="pwQualities"></param>
/// <param name="pftTimeStamps"></param>
/// <param name="pErrors">项错误列表</param>
public virtual void OnReadComplete(Int32 dwTransid,
Int32 hGroup,
Int32 hrMasterquality,
Int32 hrMastererror,
Int32 dwCount,
int[] phClientItems,
object[] pvValues,
short[] pwQualities,
OpcRcw.Da.FILETIME[] pftTimeStamps,
int[] pErrors)
{
if (OnReadCompleted != null)
{
var e = new OpcDaCustomAsyncEventArgs
{
GroupHandle = hGroup,
Count = dwCount,
Errors = pErrors,
Values = pvValues,
ClientItemsHandle = phClientItems
};
OnReadCompleted(this, e);
}
}
public void Dispose()
{ }
}
}

我们看下IOPCDataCallback接口的定义:

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

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

OpcGroup的封装:

 using System;
using System.Runtime.InteropServices;
using OpcRcw.Da; namespace Opc.Net
{
/// <summary>
/// 自定义接口OPC组对象
/// </summary>
public class OpcDaCustomGroup
{
private string groupName;
private int isActive=;
private int requestedUpdateRate;
private int clientGroupHandle=;
private GCHandle timeBias = GCHandle.Alloc(, GCHandleType.Pinned);
private GCHandle percendDeadBand = GCHandle.Alloc(, GCHandleType.Pinned);
private int lcid = 0x409;
private int itemCount;
private bool onRead; /// <summary>
/// 输出参数,服务器为新创建的组对象产生的句柄
/// </summary>
public int ServerGroupHandle; /// <summary>
/// 输出参数,服务器返回给客户端的实际使用的数据更新率
/// </summary>
public int RevisedUpdateRate; /// <summary>
/// 引用参数,客户端想要的组对象的接口类型(如 IIDIOPCItemMgt)
/// </summary>
public Guid Riid = typeof(IOPCItemMgt).GUID; /// <summary>
/// 输出参数,用来存储返回的接口指针。如果函数操作出现任务失败,此参数将返回NULL。
/// </summary>
public object Group;
private OpcDaCustomItem[] opcDataCustomItems; public int[] PErrors { get; set; } /// <summary>
/// 组对象是否激活
/// 1为激活,0为未激活,默认激活
/// </summary>
public int IsActive
{
get
{
return isActive;
}
set
{
if (isActive == value)
return;
isActive = value;
}
}
/// <summary>
/// 组是否采用异步读方式
/// </summary>
public bool OnRead
{
get
{
return onRead;
}
set
{
if (onRead == value)
return;
onRead = value;
}
}
/// <summary>
/// 项的个数
/// </summary>
public int ItemCount
{
get { return itemCount; }
set
{
if(itemCount == value)
return;
itemCount=value;
}
}
/// <summary>
/// 客户端指定的数据变化率
/// </summary>
public int RequestedUpdateRate
{
get
{
return requestedUpdateRate;
}
set
{
if (requestedUpdateRate == value)
return;
requestedUpdateRate = value;
}
} /// <summary>
/// OPC组名称
/// </summary>
public string GroupName
{
get
{
return groupName;
}
set
{
if (groupName == value)
return;
groupName = value;
}
} /// <summary>
/// 客户端程序为组对象提供的句柄
/// </summary>
public int ClientGroupHandle
{
get
{
return clientGroupHandle;
}
set
{
if (clientGroupHandle == value)
return;
clientGroupHandle = value;
}
} /// <summary>
/// 指向Long类型的指针
/// </summary>
public GCHandle TimeBias
{
get
{
return timeBias;
}
set
{
if (timeBias == value)
return;
timeBias = value;
}
} /// <summary>
/// 一个项对象的值变化的百分比,可能引发客户端程序的订阅回调。
/// 此参数只应用于组对象中有模拟dwEUType(工程单位)类型的项对象。指针为NULL表示0.0
/// </summary>
public GCHandle PercendDeadBand
{
get
{
return percendDeadBand;
}
set
{
if (percendDeadBand == value)
return;
percendDeadBand = value;
}
} /// <summary>
/// 当用于组对象上的操作的返回值为文本类型时,服务器使用的语言
/// </summary>
public int LCID
{
get
{
return lcid;
}
set
{
if (lcid == value)
return;
lcid = value;
}
} /// <summary>
/// OPC项数组
/// </summary>
public OpcDaCustomItem[] OpcDataCustomItems
{
get
{
return opcDataCustomItems;
}
set
{
if (opcDataCustomItems != null && opcDataCustomItems == value)
return;
opcDataCustomItems = value;
}
}
}
}

OpcItem的封装:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using OpcRcw.Da; namespace Opc.Net
{
/// <summary>
/// 自定义接口Opc项
/// </summary>
public class OpcDaCustomItem
{
private string name;
private string accessPath="";
private string itemID;
private int isActive = ;
private int clientHandle = ;
private int blobSize = ;
private IntPtr blob = IntPtr.Zero;
private short requestedDataType = ;
private object itemValue;
private int serverHandle; /// <summary>
/// 项名称
/// </summary>
public string Name
{
get
{
return name;
}
set
{
if (name == value)
return;
name = value;
}
}
/// <summary>
/// 项对象的访问路径
/// </summary>
public string AccessPath
{
get
{
return accessPath;
}
set
{
if (accessPath == value)
return;
accessPath = value;
}
} /// <summary>
/// 项对象的ItemIDea,唯一标识该数据项
/// </summary>
public string ItemID
{
get
{
return itemID;
}
set
{
if (itemID == value)
return;
itemID = value;
}
} /// <summary>
/// 项对象的激活状态
/// 1为激活,0为未激活,默认激活
/// </summary>
public int IsActive
{
get
{
return isActive;
}
set
{
if (isActive == value)
return;
isActive = value;
}
} /// <summary>
/// 项对象的客户端句柄
/// </summary>
public int ClientHandle
{
get
{
return clientHandle;
}
set
{
if (clientHandle == value)
return;
clientHandle = value;
}
}
public int BlobSize
{
get
{
return blobSize;
}
set
{
if (blobSize == value)
return;
blobSize = value;
}
}
public IntPtr Blob
{
get
{
return blob;
}
set
{
if (blob == value)
return;
blob = value;
}
} /// <summary>
/// OPC项的数据类型
/// VbBoolean:11,VbByte:17,VbDecimal:14,VbDouble:5,Vbinteger:2,VbLong:3,VbSingle:4,VbString:8
/// </summary>
public short RequestedDataType
{
get
{
return requestedDataType;
}
set
{
if (requestedDataType == value)
return;
requestedDataType = value;
}
} /// <summary>
/// OPC项的值
/// </summary>
public object Value
{
get
{
return itemValue;
}
set
{
if (itemValue == value)
return;
itemValue = value;
}
} /// <summary>
/// OPC项的服务器句柄
/// </summary>
public int ServerHandle
{
get
{
return serverHandle;
}
set
{
if (serverHandle == value)
return;
serverHandle = value;
}
}
}
}

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

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

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

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

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

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

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

 using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Runtime.InteropServices;
using System.Xml.Linq; namespace Opc.Net
{
public class OpcManager
{
/// <summary>
/// Opc异步接口类
/// </summary>
OpcDaCustomAsync _opcDaCustomAsync;
/// <summary>
/// 异步读取数据完成事件
/// </summary>
public event EventHandler<OpcDaCustomAsyncEventArgs> OnReadCompleted;
/// <summary>
/// Opc组列表
/// </summary>
List<OpcDaCustomGroup> _opcGroups;
/// <summary>
/// OPC服务器名称
/// </summary>
string _strRemoteServerName;
/// <summary>
/// OPC服务器IP地址
/// </summary>
string _strRemoteServerIpAddress; /// <summary>
/// 构造函数
/// </summary>
/// <param name="strConfigFilePath">配置文件路径</param>
public OpcManager(string strConfigFilePath)
{
LoadOpcGroupConfig(strConfigFilePath);
}
/// <summary>
/// 加载Opc组配置
/// </summary>
/// <param name="strConfigFilePath">配置文件路径</param>
public void LoadOpcGroupConfig(string strConfigFilePath)
{
try
{
if (!File.Exists(strConfigFilePath)) return;
XDocument xDoc = XDocument.Load(strConfigFilePath);
XElement xElement = xDoc.Element("System").Element("OpcServer");
_strRemoteServerName = xElement.Attribute("ServerName").Value;
_strRemoteServerIpAddress = xElement.Attribute("IPAddress").Value;
_opcGroups = new List<OpcDaCustomGroup>();
foreach (XElement xElementItem in xElement.Elements())
{
var opcDaCustomGroupService = new OpcDaCustomGroup
{
GroupName = xElementItem.Attribute("GroupName").Value,
ClientGroupHandle = Convert.ToInt32(xElementItem.Attribute("ClientHandle").Value),
RequestedUpdateRate = Convert.ToInt32(xElementItem.Attribute("UpdateRate").Value),
OpcDataCustomItems = LoadOpcItemConfig(xElementItem)
};
_opcGroups.Add(opcDaCustomGroupService);
}
_opcDaCustomAsync = new OpcDaCustomAsync(_opcGroups, _strRemoteServerName, _strRemoteServerIpAddress);
_opcDaCustomAsync.OnReadCompleted += ReadCompleted;
}
catch(COMException ex)
{
throw ex;
}
}
/// <summary>
/// 连接Opc服务器
/// </summary>
/// <returns></returns>
public bool Connect()
{
return _opcDaCustomAsync.Connect();
}
/// <summary>
/// 连接Opc服务器
/// </summary>
/// <returns></returns>
public bool Connect(string remoteOpcServerName,string remoteOpcServerIpAddress)
{
return _opcDaCustomAsync.Connect(remoteOpcServerName, remoteOpcServerIpAddress);
}
/// <summary>
/// 加载Opc项配置
/// </summary>
/// <param name="xElement">Opc组Xml节点</param>
/// <returns></returns>
public OpcDaCustomItem[] LoadOpcItemConfig(XElement xElement)
{
int itemCount = xElement.Elements().Count();
var opcDaCustomItems = new OpcDaCustomItem[itemCount];
int i = ;
foreach (var xElementItem in xElement.Elements())
{
var opcDaCustomItemService = new OpcDaCustomItem
{
ClientHandle = Convert.ToInt32(xElementItem.Attribute("ClientHandle").Value),
ItemID = xElementItem.Attribute("ItemID").Value,
RequestedDataType = short.Parse(xElementItem.Attribute("RequestedDataType").Value)
};
opcDaCustomItems[i] = opcDaCustomItemService;
i++;
}
return opcDaCustomItems;
}
public bool WriteForReturn(int itemClientHandle, int value, int clientHandle)
{
bool returnValue;
var itemDictionary = new Dictionary<int, object>
{
{itemClientHandle, value}
};
try
{
int[] pErrors;
Write(itemDictionary, clientHandle, out pErrors);
returnValue = (pErrors[] == );
}
catch (COMException ex)
{
throw ex;
}
return returnValue;
}
public void Write(Dictionary<int, object> itemDictionary, int groupHandle, out int[] pErrors)
{
var count = itemDictionary.Count();
var values = new object[count];
var serverHandle = new int[count];
pErrors = null;
OpcDaCustomGroup group = _opcGroups.First(p => p.ServerGroupHandle == groupHandle);
int index = ;
foreach (KeyValuePair<int, object> itemId in itemDictionary)
{
foreach (var item in group.OpcDataCustomItems)
{
if (item.ClientHandle == itemId.Key)
{
values[index] = itemId.Value;
serverHandle[index] = item.ServerHandle;
index++;
}
}
}
try
{
_opcDaCustomAsync.Write(values, serverHandle, out pErrors, group);
}
catch (COMException ex)
{
throw ex;
}
}
/// <summary>
/// 写单个数据
/// </summary>
/// <param name="value">值</param>
/// <param name="groupHandle">组ID</param>
/// <param name="clientHandle">项ID</param>
public void Write(int value, int groupHandle, int clientHandle)
{
OpcDaCustomGroup group = GetOpcGroup(groupHandle);
if (group != null)
{
int[] pErrors;
var serverHanlde = new int[];
serverHanlde[] = group.OpcDataCustomItems.First(c => c.ClientHandle == clientHandle).ServerHandle;
var values = new object[];
values[] = value; _opcDaCustomAsync.Write(values, serverHanlde, out pErrors, group); }
}
/// <summary>
/// 异步读取数据完成事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void ReadCompleted(object sender, OpcDaCustomAsyncEventArgs e)
{
if (OnReadCompleted != null)
{
OnReadCompleted(this, e);
}
}
/// <summary>
/// 异步读取控制模式数据
/// </summary>
public void Read()
{
if (_opcDaCustomAsync != null)
{
_opcDaCustomAsync.Read();
} }
/// <summary>
/// 根据OPC句柄获取OPC组对象
/// </summary>
/// <param name="groupHandle">OPC组对象</param>
/// <returns></returns>
public OpcDaCustomGroup GetOpcGroup(int groupHandle)
{
return _opcGroups.First(e => e.ClientGroupHandle == groupHandle);
}
}
}

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

 private OpcManager opcManager;
private System.Timers.Timer opcTimer;
private int[] pErrors;
private Dictionary<int, object> items;
/// <summary>
/// 写入采煤机位置数据
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
items = new Dictionary<int, object>();
items.Add(, textBox2.Text);
opcManager.Write(items, , pErrors);
} private void FrmMain_Load(object sender, EventArgs e)
{
opcManager = new OpcManager(AppDomain.CurrentDomain.BaseDirectory+"\\Opc.config.xml");
opcManager.OnReadCompleted += new EventHandler<OpcDaCustomAsyncEventArgs>(opcManager_OnReadCompleted); opcTimer = new System.Timers.Timer()
{
Interval = ,
AutoReset = true,
Enabled = true
};
opcTimer.Elapsed += new ElapsedEventHandler(opcTimer_Elapsed);
} void opcTimer_Elapsed(object sender, ElapsedEventArgs e)
{
opcManager.Read();
} void opcManager_OnReadCompleted(object sender, OpcDaCustomAsyncEventArgs e)
{
Invoke((ThreadStart)(() =>
{ if (OpcHelper.ShowValue(e, ) != null)
{
textBox1.Text = OpcHelper.ShowValue(e, ).ToString();
}
}));
}

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

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

v\:* {behavior:url(#default#VML);}
o\:* {behavior:url(#default#VML);}
w\:* {behavior:url(#default#VML);}
.shape {behavior:url(#default#VML);}

Normal
0
false

7.8 磅
0
2

false
false
false

EN-US
ZH-CN
X-NONE

/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.5pt;
mso-bidi-font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-font-kerning:1.0pt;}

【干货】如何通过OPC自定义接口来实现客户端数据的读取?的更多相关文章

  1. unittest管理接口用例(数据分离-读取excel)

    1.简单读取 #coding=utf-8 #调用封装好的excel读取公共方法 from python_API.common.ReadExcel import ReadExcel import req ...

  2. unittest 管理接口用例(数据分离-读取excel)

    1.公共模块 ---> login.xls """ common (package) ---> ReadFile.py """ ...

  3. 关于OPC自动化接口编程(OPCDAAuto.dll)几点注意问题

    为了能够在工作中方便的应用OPC和充分的理解OPC的开发流程.内部机制,这两天正在研究开发OPC客户端程序,一般我们开发OPC客户端程序有以下几种方式: (1)       使用OPCNetAPI,需 ...

  4. PHP玩转微信公众平台自定义接口

    从微信公众平台开通自定义回复后,就一直在关注微信接口这一块,很想用自定义回复这块做个站长工具的查询,例如PR查询,备案查询等,输入网址信息,就能自动获取PR,获取备案信息,应该是一个不错的想法.不过以 ...

  5. C#的委托与Java的自定义接口的异曲同工的同步操作

    C#的委托(以WinForm为例) 在子窗体(ChildFrm)中定义一个委托 this.CaptureListener(callback);//子窗体触发委托事件,以告诉调用的窗体 /// < ...

  6. spring data 自定义接口

    1 spring data jpa 虽然说spring data 提供了很多DAO 接口,但是依然可能不能满足我们日常的使用,所以,有时我们需要自定义接口方法.自定义接口方法步骤如下: 1.  创建自 ...

  7. .NET Core开发的iNeuOS工业互联平台,升级四大特性:配置数据接口、图元绑定数据、预警配置和自定义菜单

    目       录 1.      概述... 2 2.      演示信息... 2 3.      iNeuView(Web组态)配置数据接口... 2 4.      iNeuView(Web组 ...

  8. GPRS RTU设备OPC Server接口C# 实现

    通过本OPC Server程序接口可为用户提供以OPC标准接口访问远程GPRS/3G/以太网 RTU设备实时数据的方式.从而方便实现GPRS/3G/以太网 RTU设备与组态软件或DCS系统的对接.本程 ...

  9. WCF服务接口多,客户端在引用时出错!报WCF The maximum nametable character count quota (16384) has been exceeded while reading XML data错误

    WCF服务接口多,客户端在引用时出错!报WCF The maximum nametable character count quota (16384) has been exceeded while ...

随机推荐

  1. 解决Win7下VC6.0插入ActiveX控件对话框为空的问题

    在Win7环境下,编写MFC应用程序,Project菜单下Add To Project子菜单中的 Components and Controls…选项,在弹出的对话框中Gallery文件为空,也就无法 ...

  2. Android中自定义checkbox样式

    1.首先在drawable文件夹中添加drawable文件checkbox_style.xml.

  3. java 类反射记录

    Class的getDeclaredMethod方法是获取当前类下的所有方法,包括private修饰的,该方法不获取父类的方法. getMethod获取父类及本类下的所有public方法.

  4. Java学习笔记:控制反转

    控制反转(Ioc)模式(又称DI:Dependency Injection)就是Inversion of Control,控制反转.在Java开发中,IoC意味着将你设计好的类交给系统去控制,而不是在 ...

  5. JAVA缓存技术

    介绍 JNotify:http://jnotify.sourceforge.net/,通过JNI技术,让Java代码可以实时的监控制定文件夹内文件的变动信息,支持Linux/Windows/MacOS ...

  6. Unsafe与CAS

    Unsafe 简单讲一下这个类.Java无法直接访问底层操作系统,而是通过本地(native)方法来访问.不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作 ...

  7. 详解SQL集合运算

    以前总是追求新东西,发现基础才是最重要的,今年主要的目标是精通SQL查询和SQL性能优化. 本系列[T-SQL基础]主要是针对T-SQL基础的总结. [T-SQL基础]01.单表查询-几道sql查询题 ...

  8. 使用NodeList

    理解NodeList.NamedNodeMap和HTMLCollection是整体透彻理解DOM的关键. 这三个集合都是“动态”的,也就是说:每当文档结构发生变化时,他们都会得到更新,他们始终保存的都 ...

  9. python 实现web框架simfish

    python 实现web框架simfish 本文主要记录本人利用python实现web框架simfish的过程.源码github地址:simfish WSGI HTTP Server wsgi模块提供 ...

  10. Windows上帝模式,上帝应该就是这样使用Windows的

    Windows上帝模式(Windows Master Control Panel)由来已久,最早是从Win7优化大湿里看到的一个选项,开启后在桌面生成一个图标,点进去后里面包含了几乎全部Windows ...