PLC和OPC是怎么进行链接的

相关资料推荐
最完整的的AB-PLC培训教程
比较完整的IFIX资料合集
AB_plc的两种方法
西门子PID有关资料
比较完整的IFIX资料合集
比较完整的IFIX资料合集
三菱plc通过opc连接到wincc 利用OPC 接口实现SCADA 西门子PC Access_OPC Server(精).pptWinCC中文手册-OPC服务器.pdf
在此可输入您对该资料的评论~
资料阅读排行
(window.slotbydup=window.slotbydup || []).push({
id: '2371234',
container: s,
size: '300,250',
display: 'inlay-fix'
该用户的其它资料
请选择举报的类型
赌博犯罪类
资料评价:
所需积分:0如何通过OPC自定义接口来实现客户端数据的读取? - 往来往去 - 推酷
如何通过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进行的。
搭建程序运行环境
程序运行需要的软硬件环境:
.Net Framework 4.0
Simatic Net 2008(Or Other) HF1
西门子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引用名为:n.dll和OpcRcw.Da.dll这两个DLL。
我们定义一个名为OpcDaCustomAsync的类,让这个类继承自:IOPCDataCallback,IDisposable
2 using System.Collections.G
4 using OpcRcw.Da;
5 using System.Runtime.InteropS
7 namespace Opc.Net
/// &summary&
/// Opc自定义接口-异步管理类
/// &author name=&lm& date=&&/&
/// &/summary&
public class OpcDaCustomAsync : IOPCDataCallback,IDisposable
/// &summary&
/// OPC服务器对象
/// &/summary&
IOPCServer iOpcS
/// &summary&
/// 事务ID
/// &/summary&
int transactionID;
/// &summary&
/// OPC服务器名称
/// &/summary&
string opcServerN
/// &summary&
/// OPC服务器IP地址
/// &/summary&
IOPCAsyncIO2 _iopcAsyncIo2;
/// &summary&
/// OPC服务器IP地址
/// &/summary&
string opcServerIPA
/// &summary&
/// Opc组列表
/// &/summary&
List&OpcDaCustomGroup& opcDaCustomG
/// &summary&
/// 连接指针容器
/// &/summary&
IConnectionPointContainer IConnectionPointContainer = null;
/// &summary&
/// 连接指针
/// &/summary&
IConnectionPoint IConnectionPoint = null;
/// &summary&
/// Opc组管理器
/// &/summary&
IOPCGroupStateMgt IOPCGroupStateMgt = null;
//接收数据事件
public event EventHandler&OpcDaCustomAsyncEventArgs& OnDataC
/// &summary&
/// 异步写入数据完成事件
/// &/summary&
public event EventHandler&OpcDaCustomAsyncEventArgs& OnWriteC
/// &summary&
/// 异步读取数据完成事件
/// &/summary&
public event EventHandler&OpcDaCustomAsyncEventArgs& OnReadC
/// &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 = opcDaCustomG
this.opcServerName = opcServerN
this.opcServerIPAddress = opcServerIpA
/// &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 returnV
/// &summary&
/// 添加Opc组
/// &/summary&
private void AddOpcGroup()
foreach (OpcDaCustomGroup opcGroup in opcDaCustomGroups)
AddOpcGroup(opcGroup);
catch(COMException ex)
/// &summary&
/// 添加Opc项
/// &/summary&
/// &param name=&opcGroup&&&/param&
private void AddOpcGroup(OpcDaCustomGroup opcGroup)
//添加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 & 0)
//添加OPC项
AddOpcItem(opcGroup);
//激活订阅回调事件
ActiveDataChanged(IOPCGroupStateMgt);
catch (COMException ex)
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)
//组状态管理对象,改变组的刷新率和激活状态
IOPCGroupStateMgt = (IOPCGroupStateMgt)opcGroup.G
IConnectionPointContainer = (IConnectionPointContainer)opcGroup.G
Guid iid = typeof(IOPCDataCallback).GUID;
IConnectionPointContainer.FindConnectionPoint(ref iid, out IConnectionPoint);
//创建客户端与服务端之间的连接
IConnectionPoint.Advise(this, out
/// &summary&
/// 激活订阅回调事件
/// &/summary&
private void ActiveDataChanged(IOPCGroupStateMgt IOPCGroupStateMgt)
IntPtr pRequestedUpdateRate = IntPtr.Z
IntPtr hClientGroup = IntPtr.Z
IntPtr pTimeBias = IntPtr.Z
IntPtr pDeadband = IntPtr.Z
IntPtr pLCID = IntPtr.Z
int nActive = 0;
GCHandle hActive = GCHandle.Alloc(nActive, GCHandleType.Pinned);
hActive.Target = 1;
int nRevUpdateRate = 0;
IOPCGroupStateMgt.SetState(pRequestedUpdateRate, out nRevUpdateRate,
hActive.AddrOfPinnedObject(), pTimeBias, pDeadband, pLCID, hClientGroup);
catch (COMException ex)
hActive.Free();
/// &summary&
/// 添加Opc项
/// &/summary&
/// &param name=&opcGroup&&&/param&
private void AddOpcItem(OpcDaCustomGroup opcGroup)
OpcDaCustomItem[] opcDataCustomItemsService = opcGroup.OpcDataCustomI
IntPtr pResults = IntPtr.Z
IntPtr pErrors = IntPtr.Z
OPCITEMDEF[] itemDefyArray = new OPCITEMDEF[opcGroup.OpcDataCustomItems.Length];
int i = 0;
int[] errors = new int[opcGroup.OpcDataCustomItems.Length];
int[] itemServerHandle = new int[opcGroup.OpcDataCustomItems.Length];
foreach (OpcDaCustomItem itemService in opcDataCustomItemsService)
if (itemService != null)
itemDefyArray[i].szAccessPath = itemService.AccessP
itemDefyArray[i].szItemID = itemService.ItemID;
itemDefyArray[i].bActive = itemService.IsA
itemDefyArray[i].hClient = itemService.ClientH
itemDefyArray[i].dwBlobSize = itemService.BlobS
itemDefyArray[i].pBlob = itemService.B
itemDefyArray[i].vtRequestedDataType = itemService.RequestedDataT
//添加OPC项组
((IOPCItemMgt)opcGroup.Group).AddItems(opcGroup.OpcDataCustomItems.Length, itemDefyArray, out pResults, out pErrors);
IntPtr Pos = pR
Marshal.Copy(pErrors, errors, 0, opcGroup.OpcDataCustomItems.Length);
for (int j = 0; j & opcGroup.OpcDataCustomItems.L j++)
if (errors[j] == 0)
if (j != 0)
Pos = new IntPtr(Pos.ToInt32() + Marshal.SizeOf(typeof(OPCITEMRESULT)));
var result = (OPCITEMRESULT)Marshal.PtrToStructure(Pos, typeof(OPCITEMRESULT));
itemServerHandle[j] = opcDataCustomItemsService[j].ServerHandle = result.hS
Marshal.DestroyStructure(Pos, typeof(OPCITEMRESULT));
catch (COMException ex)
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.Z
_iopcAsyncIo2 = (IOPCAsyncIO2)opcGroup.G
if (_iopcAsyncIo2 != null)
int[] serverHandle = new int[opcGroup.OpcDataCustomItems.Length];
opcGroup.PErrors = new int[opcGroup.OpcDataCustomItems.Length];
for (int j = 0; j & opcGroup.OpcDataCustomItems.L j++)
serverHandle[j] = opcGroup.OpcDataCustomItems[j].ServerH
int cancelId=0;
_iopcAsyncIo2.Read(opcGroup.OpcDataCustomItems.Length, serverHandle, 2, out cancelId, out pErrors);
Marshal.Copy(pErrors, opcGroup.PErrors, 0, opcGroup.OpcDataCustomItems.Length);
catch (COMException ex)
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.G
IntPtr pErrors = IntPtr.Z
errors = new int[values.Length];
if (_iopcAsyncIo2 != null)
//异步写入数据
int cancelId;
_iopcAsyncIo2.Write(values.Length, serverHandle, values, transactionID + 1, out cancelId, out pErrors);
Marshal.Copy(pErrors, errors, 0, values.Length);
catch (COMException ex)
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的封装:
2 using System.Runtime.InteropS
3 using OpcRcw.Da;
5 namespace Opc.Net
/// &summary&
/// 自定义接口OPC组对象
/// &/summary&
public class OpcDaCustomGroup
private string groupN
private int isActive=1;
private int requestedUpdateR
private int clientGroupHandle=1;
private GCHandle timeBias = GCHandle.Alloc(0, GCHandleType.Pinned);
private GCHandle percendDeadBand = GCHandle.Alloc(0, GCHandleType.Pinned);
private int lcid = 0x409;
private int itemC
private bool onR
/// &summary&
/// 输出参数,服务器为新创建的组对象产生的句柄
/// &/summary&
public int ServerGroupH
/// &summary&
/// 输出参数,服务器返回给客户端的实际使用的数据更新率
/// &/summary&
public int RevisedUpdateR
/// &summary&
/// 引用参数,客户端想要的组对象的接口类型(如 IIDIOPCItemMgt)
/// &/summary&
public Guid Riid = typeof(IOPCItemMgt).GUID;
/// &summary&
/// 输出参数,用来存储返回的接口指针。如果函数操作出现任务失败,此参数将返回NULL。
/// &/summary&
public object G
private OpcDaCustomItem[] opcDataCustomI
public int[] PErrors { get; set; }
/// &summary&
/// 组对象是否激活
/// 1为激活,0为未激活,默认激活
/// &/summary&
public int IsActive
return isA
if (isActive == value)
isActive =
/// &summary&
/// 组是否采用异步读方式
/// &/summary&
public bool OnRead
return onR
if (onRead == value)
/// &summary&
/// 项的个数
/// &/summary&
public int ItemCount
get { return itemC }
if(itemCount == value)
itemCount=
/// &summary&
/// 客户端指定的数据变化率
/// &/summary&
public int RequestedUpdateRate
return requestedUpdateR
if (requestedUpdateRate == value)
requestedUpdateRate =
/// &summary&
/// OPC组名称
/// &/summary&
public string GroupName
return groupN
if (groupName == value)
groupName =
/// &summary&
/// 客户端程序为组对象提供的句柄
/// &/summary&
public int ClientGroupHandle
return clientGroupH
if (clientGroupHandle == value)
clientGroupHandle =
/// &summary&
/// 指向Long类型的指针
/// &/summary&
public GCHandle TimeBias
return timeB
if (timeBias == value)
timeBias =
/// &summary&
/// 一个项对象的值变化的百分比,可能引发客户端程序的订阅回调。
/// 此参数只应用于组对象中有模拟dwEUType(工程单位)类型的项对象。指针为NULL表示0.0
/// &/summary&
public GCHandle PercendDeadBand
return percendDeadB
if (percendDeadBand == value)
percendDeadBand =
/// &summary&
/// 当用于组对象上的操作的返回值为文本类型时,服务器使用的语言
/// &/summary&
public int LCID
if (lcid == value)
/// &summary&
/// OPC项数组
/// &/summary&
public OpcDaCustomItem[] OpcDataCustomItems
return opcDataCustomI
if (opcDataCustomItems != null && opcDataCustomItems == value)
opcDataCustomItems =
OpcItem的封装:
2 using System.Collections.G
3 using System.L
4 using System.T
5 using System.Runtime.InteropS
6 using OpcRcw.Da;
8 namespace Opc.Net
/// &summary&
/// 自定义接口Opc项
/// &/summary&
public class OpcDaCustomItem
private string
private string accessPath=&&;
private string itemID;
private int isActive = 1;
private int clientHandle = 0;
private int blobSize = 0;
private IntPtr blob = IntPtr.Z
private short requestedDataType = 0;
private object itemV
private int serverH
/// &summary&
/// 项名称
/// &/summary&
public string Name
if (name == value)
/// &summary&
/// 项对象的访问路径
/// &/summary&
public string AccessPath
return accessP
if (accessPath == value)
accessPath =
/// &summary&
/// 项对象的ItemIDea,唯一标识该数据项
/// &/summary&
public string ItemID
return itemID;
if (itemID == value)
/// &summary&
/// 项对象的激活状态
/// 1为激活,0为未激活,默认激活
/// &/summary&
public int IsActive
return isA
if (isActive == value)
isActive =
/// &summary&
/// 项对象的客户端句柄
/// &/summary&
public int ClientHandle
return clientH
if (clientHandle == value)
clientHandle =
public int BlobSize
return blobS
if (blobSize == value)
blobSize =
public IntPtr Blob
if (blob == value)
/// &summary&
/// OPC项的数据类型
/// VbBoolean:11,VbByte:17,VbDecimal:14,VbDouble:5,Vbinteger:2,VbLong:3,VbSingle:4,VbString:8
/// &/summary&
public short RequestedDataType
return requestedDataT
if (requestedDataType == value)
requestedDataType =
/// &summary&
/// OPC项的值
/// &/summary&
public object Value
return itemV
if (itemValue == value)
itemValue =
/// &summary&
/// OPC项的服务器句柄
/// &/summary&
public int ServerHandle
return serverH
if (serverHandle == value)
serverHandle =
项的客户端句柄和服务器句柄实际是一样的,项的数据类型用short表示,在下面的配置文件中体现出来了。
以下是我设计的配置文件:
1 &?xml version=&1.0& encoding=&utf-8&?&
2 &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&
40 &/System&
上述配置文件中,OpcServer节点对应的OpcServer对象,定义了ServerName和IPAddress属性,用来连接OPCServer。
ShearerInfo节点则对应一个OpcGroup,在OpcServer下定义多个OPCGrupo节点,OPCGroup节点需要指定组的客户端句柄和刷新频率。上文说到OPC的读写操作都是以组进行的,我们需要根据客户端句柄来判断是哪一个组,如果我们采用的事订阅模式读取数据,则还需要刷新频率,OpcServer对订阅模式的实现不太清楚,实际使用的过程发现,并没有按照刷新频率来,所以我就采用了直接读的方式来保证数据的实时性。
Item的ItemID是一个地址,由于我使用的是西门子的产品,所以格式是:S7:[S7连接名称]地址,我们只需要更改S7连接的名称和地址就好了。如果你使用的事其他类型的PLC,请参照他们的地址格式。
有了配置文件如何操作呢?下面我定义了一个实现类:
2 using System.Collections.G
3 using System.L
4 using System.IO;
5 using System.Runtime.InteropS
6 using System.Xml.L
8 namespace Opc.Net
public class OpcManager
/// &summary&
/// Opc异步接口类
/// &/summary&
OpcDaCustomAsync _opcDaCustomA
/// &summary&
/// 异步读取数据完成事件
/// &/summary&
public event EventHandler&OpcDaCustomAsyncEventArgs& OnReadC
/// &summary&
/// Opc组列表
/// &/summary&
List&OpcDaCustomGroup& _opcG
/// &summary&
/// OPC服务器名称
/// &/summary&
string _strRemoteServerN
/// &summary&
/// OPC服务器IP地址
/// &/summary&
string _strRemoteServerIpA
/// &summary&
/// 构造函数
/// &/summary&
/// &param name=&strConfigFilePath&&配置文件路径&/param&
public OpcManager(string strConfigFilePath)
LoadOpcGroupConfig(strConfigFilePath);
/// &summary&
/// 加载Opc组配置
/// &/summary&
/// &param name=&strConfigFilePath&&配置文件路径&/param&
public void LoadOpcGroupConfig(string strConfigFilePath)
if (!File.Exists(strConfigFilePath)) return;
XDocument xDoc = XDocument.Load(strConfigFilePath);
XElement xElement = xDoc.Element(&System&).Element(&OpcServer&);
_strRemoteServerName = xElement.Attribute(&ServerName&).V
_strRemoteServerIpAddress = xElement.Attribute(&IPAddress&).V
_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 += ReadC
catch(COMException 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 = 0;
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] = opcDaCustomItemS
return opcDaCustomI
public bool WriteForReturn(int itemClientHandle, int value, int clientHandle)
bool returnV
var itemDictionary = new Dictionary&int, object&
{itemClientHandle, value}
Write(itemDictionary, clientHandle, out pErrors);
returnValue = (pErrors[0] == 0);
catch (COMException ex)
return returnV
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 = 0;
foreach (KeyValuePair&int, object& itemId in itemDictionary)
foreach (var item in group.OpcDataCustomItems)
if (item.ClientHandle == itemId.Key)
values[index] = itemId.V
serverHandle[index] = item.ServerH
_opcDaCustomAsync.Write(values, serverHandle, out pErrors, group);
catch (COMException 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)
var serverHanlde = new int[1];
serverHanlde[0] = group.OpcDataCustomItems.First(c =& c.ClientHandle == clientHandle).ServerH
var values = new object[1];
values[0] =
_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);
这个类可以根据自己设计的配置文件进行相应的实现。
1 private OpcManager opcM
private System.Timers.Timer opcT
private int[] pE
private Dictionary&int, object&
/// &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(2, textBox2.Text);
opcManager.Write(items, 1, 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 = 100,
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, 3) != null)
textBox1.Text = OpcHelper.ShowValue(e, 3).ToString();
以上实现了数据的读取和写入。
源码戳这里:
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致

我要回帖

更多关于 opc与plc通讯 的文章

 

随机推荐