上篇博文分享了我的知识库,被好多人关注,受宠若惊。今天我把我在项目中封装的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. SAP SLT (Landscape Transformation) 企业定制培训

    No. Item Remark 1 SAP SLT概述 SAP Landscape Transformation Overview 2 SAP SLT 安装与配置<1> for abap ...

  2. weui 问题

    1.阻止checkbox 被 checked 方法1: $('.weui-check').on('click', function(e){ $(this).attr('disabled', true) ...

  3. Java-ios推送

    之前做的消息推送,向Android和ios客户端推送.这里只说ios的推送,ios的推送最后都会推送到苹果的APNS服务器上,再有APNS服务器推送到ios设备上.因为考虑到这一点,第一版的消息推送苹 ...

  4. JavaScript对异常的处理

    JavaScript提供了一套异常处理机制.当查出事故时,你的程序应该抛出一个异常: var add=function(a,b){ if(typeof a !== 'number' || typeof ...

  5. GNU Makefile编写

    [Introduction] make 是程序员很好用的工具,如果存在makefile存在,每次更新代码,执行shell命令 shell 就可以执行所有需要编译的文件,make是根据你编写的Makef ...

  6. 遭遇AutoMapper性能问题:映射200条数据比100条慢了近千倍

    今天遇到了AutoMapper的一个性能问题,使用的是AutoMapper的Project特性,AutoMapper版本是3.3.0,代码如下: return await _repository .G ...

  7. 来科普下游标(MSSQL)这东西。。。

    刚刚接到一个面试电话,对头的先生问我懂不懂触发器和存储过程,当时是觉得有些好笑,毕竟“视图.触发和存储”是咱数据库工程师的吉祥三宝,怎么可能不认识?只是稍后他还问了下游标这东西,仔细想想我是不常使用C ...

  8. 软件测试基本理论-IBM模式

    软件测试基本理论(1) IBM生产模式 1   参考书目 <IBM-从菜鸟到测试架构师-一个测试工程师的成长日记> 出版社:电子工业出版社 印次:2013年6月 作者:IBM主要工程师 2 ...

  9. Python3实现TCP端口扫描器

    本文来自 高海峰对 玄魂工作室 的投稿 作者:高海峰 QQ:543589796 在渗透测试的初步阶段通常我们都需要对攻击目标进行信息搜集,而端口扫描就是信息搜集中至关重要的一个步骤.通过端口扫描我们可 ...

  10. [.net 面向对象编程基础] (4) 基础中的基础——数据类型转换

    [.net面向对象编程基础] (4)基础中的基础——数据类型转换 1.为什么要进行数据转换? 首先,为什么要进行数据转换,拿值类型例子说明一下, 比如:我们要把23角零钱,换成2.30元,就需要把整形 ...