2023年11月30日发(作者:)
如何用FactorySoft OPC Server Toolkit实现
OPC数据访问服务器
How To Use FactorySoft OPC Server Toolkit
To Design An OPC Data Access Server
刘权
Liu,Quan
摘要:本文介绍了一种简单方便的OPC服务器开发工具,并借用一个基于
DeviceNet的OPC数据访问服务器的实例阐述了它的使用方法。
关键词:OPC,COM/DCOM,接口,客户/服务器,线程
Abstract:This article introduces an OPC server developing toolkit to the reader, and
uses a practical server example based on DeviceNet to illuminate the usage.
Keyword:OPC, COM/DCOM, Interface, Client/Server, Thread
一、OPC数据访问技术概要
在一个过程工业的环境中有着多种多样的信息,这些信息从现场收集到后,被
集成到系统中供用户使用,但信息种类之繁多使信息的收集十分困难。一个客户端
应用需要调用多种驱动程序去进行信息收集,每种驱动程序的调用方法都可能不
同。系统中一旦出现不支持的驱动程序,就需要更改并重新编译客户端应用程序,
维护和扩展非常困难,因此需要有一种开放的高效的面向数据而不是数据类型的通
信体系结构来改变这种情况。OPC(OLE for Process Control)就是这样一种基于
Windows操作系统COM/DCOM模型的通信体系结构标准。支持 OPC的客户端以
一种一致的方式去请求和获取 OPC服务器的数据,这种方式消除了繁杂的设备驱
动程序问题, 同时也加速了数据的传输。
OPC规范包含了数据访问规范、报警事件访问规范、历史数据访问规范等几个
部分,本文着重介绍OPC数据访问部分。一个OPC数据访问服务器向OPC客户
机提供多个OPC规范定义的COM接口,其体系结构如下图所示:
OPC定义的COM接口由定制接口(Custom Interfaces)和自动化接口
(Automation Interfaces)两部分组成,一个OPC服务器必须实现所有的定制接
口,自动化接口主要用于VB开发的应用程序,可以选择实现。
一个OPC服务器由数个Server对象、Group对象和Item对象构成。Server对
象包含了关于Server的信息并作为Group对象的容器;Group对象作为Item对象
的容器对Item对象进行逻辑上的组织,OPC支持对单个Group对象的禁止和使
能;每个Item代表对一个数据源的连接,而不是数据源本身。除Item外的每种对
象都实现了一套OPC定制接口,由OPC客户机调用来访问对象。Item对象不作为
OPC客户机可访问的对象,因此没有定义外部接口,所有对Item对象的访问都要
通过包含该Item对象的Group对象进行。
Server对象所包含的接口有:
IUnknown,每个COM接口所必须提供的标准接口,用于查询访问其它
COM接口。
IOPCCommon,提供了设置和查询指定客户机/服务器会话LocaleID的功
能,这个COM接口的实现使得每个客户机的动作彼此互不影响。
IOPCServer,是一个OPC服务器的主接口,提供了组对象管理和获取服务
器状态的功能,每个OPC服务器必须无条件的提供该COM接口。
IConnectionPointContainer,为IOPCShutdown接口提供对ConnectionPoint
的访问。
IOPCItemProperties,客户机使用该COM接口来浏览一个ITEMID所附带
的属性,并读取这些属性的当前值。该接口只适用于少量数据的浏览和读
取,大量数据可考虑实现可选接口IOPCBrowseServerAddressSpace。
Group对象所包含的接口有:
IUnknown,每个COM接口所必须提供的标准接口,用于查询访问其它
COM接口。
IOPCItemMgt,为客户机提供了添加、删除Group中的Item及控制它的行
为的功能。
IOPCGroupStateMgt,为客户机提供了Group状态管理的功能,主要是改
变更新速率和激活状态。
IOPCSyncIO,为客户机提供对服务器的同步读写操作。
IOPCASyncIO2,IOPCASyncIO的替代接口,为客户机提供了对服务器的
异步读写操作。IOPCASyncIO2引入了Transaction的概念,每次异步读写
都被看作是一个Transaction,被赋予一个TransactionID,以区分不同的异
步操作。
IConnectionPointContainer,为IOPCASyncIO2接口提供对ConnectionPoint
的访问。
二、FactorySoft OPC Server Toolkit
FactorySoft OPC Server Toolkit(下简称FS开发工具)是由美国FactorySoft公
司制作的OPC服务器开发工具,它使用面向对象的技术将OPC规范所定义的
COM接口及其实现都封装到一个文件名为的动态链接库中,然后定义了
COPCCallback基类、COPCBrowser基类和CTag基类,这些类中定义了构建数据
路径、访问数据和提交数据的虚函数接口,开发者通过类的派生以及重载这些虚函
数来对数据的访问和提交进行定制。FS开发工具极大的简化了OPC服务器的开发
过程,使得开发者不用去实现OPC规范的每个细节,而集中精力到数据的访问和
提交上。一个用FS开发工具开发的OPC服务器的结构如下图所示:
App
Call
back
tag
tag
tag
L
L
.
D
e
r
r
v
S
e
F
S
OPC
Client
共输出了7个API接口和3个类,描述如下:
1、StartFSServer()
功能:开始的操作。
定义:void StartFSServer(HINSTANCE hInstance, CLSID* pCLSID)
参数:hInstance,OPC服务器程序的实例句柄。
PCLSID,指向OPC服务器程序CLSID的指针。
2、StopFSServer()
功能:停止的操作。
定义:void StopFSServer()
3、RegisterServer()
功能:在注册表中注册OPC服务器信息。
定义:HRESULT RegisterServer()
返回值:从ATL注册脚本处理器返回的HRESULT。
4、UnregisterServer()
功能:从注册表中删除OPC服务器的注册信息。
定义:HRESULT UnregisterServer()
返回值:从ATL注册脚本处理器返回的HRESULT。
5、SetCallbackObject()
功能:传递OPC服务器的Callback对象指针给。
定义:void SetCallbackObject(COPCCallback* pNewCallback )
参数:pNewCallback,指向新的Callback对象的指针。
6、FSServerInUse()
功能:测试OPC服务器是否存在已经连接的客户端程序。
定义:void FSServerInUse()
返回值:如果存在已经连接的客户端则返回True,否则返回False。
7、MatchPattern()
功能:字符串匹配函数,用于浏览等功能。
定义:BOOL MatchPattern( LPCTSTR String, LPCTSTR Pattern, BOOL
bCaseSensitive )
参数:String,匹配的字符串,如Item名称等。
Pattern,用于匹配的表达式,语法遵照BASIC语言。
BcaseSensitive,True表示区分大小写,False表示不区分。
返回值:如果匹配成功返回True,否则返回False。
8、CTag类
CTag类保存了每个Item的数据、质量、时间标签、访问权限和引用计数,使
用CTag类访问Item的当前数据。引用计数用来判断CTag是否正在使用,当
Callback对象返回一个CTag指针时,引用计数加1,当使用完毕时,引用计数减
1,当引用计数为0时,可以对CTag进行删除或从扫描列表中移除等操作。CTag
的数据成员描述如下:
m_
value,保存Tag的数据值,VARIANT类型。
m_quality,,字,保存Tag数据的质量。
m_
timestamp,FILETIME类型,Tag数据的时间标签。
m_nativeType,VARTYPE类型,保存Tag数据的类型。
当OPC服务器程序访问现场数据时,应对值、质量和时间进行更新,然后通
过传递给客户端程序。OPC服务器程序可以从CTag类派生出自己的Tag类并加入
需要的其它信息,Callback对象的接口函数将使用CTag指针,开发者在定制
Callback对象时可将这些CTag指针强制转换为派生类指针。
9、COPCCallback类
COPCCallback类定义了FSServer DLL和OPC服务器程序之间的接口,这些接
口提供了一个OPC服务器所需的全部基本行为。OPC服务器程序如果需要定制某
些接口,可从COPCCallback类派生自己的Callback类,然后重载要定制的接口函
数。COPCCallback中的Scan()用于扫描客户机需要访问的所有Tag,但是对于非
常慢的设备或者只有状态改变才产生数据的设备,应由OPC服务器程序创建子线
程访问这些数据,然后保存到缓冲区中等待客户端的访问,不要在Scan()中包含这
些设备。ReadTag()和WriteTag()用于对数据的直接访问,当然,在Scan中也可以
调用ReadTag来读取数据。COPCCallback的主要接口函数(虚函数)描述如下:
(篇幅所限,恕不能一一详述)
QueryNumProperties(),查询指定名称对应Tag的属性个数。
QueryAvailableProperties(),查询指定名称对应Tag的属性ID、描述和类型。
GetItemProperties(),获取指定名称对应Tag的属性值。
LookupItemIDs(),根据属性查询对应的Tag名称。
CreateBrowser(),创建一个COPCBrowser对象并传递给。
SetUpdateRate(),设置Group更新间隔。
AddTag(),客户端添加Item后调用本接口函数,允许服务器端进行事后处理。
ValidateTag(),验证Tag的名称、访问路径和数据类型的合法性。
Remove(),通知服务器这些Tag已经被从Group中删除。
GetTagName(),获取指定Tag的名称。
Scan(),扫描添加到客户端的Tag,更新Tag的数据、质量和时间标签。
Read(),成批读取指定的Tag数据。
ReadTag(),读取指定Tag的数据。
Write(),修改指定Tag的数据。
WriteTag(),成批修改指定Tag数据。
GetServerState(),获取OPC服务器状态。
10、COPCBrowser类
COPCBrowser类用于对OPC服务器中的Item进行浏览。Callback对象的成员
函数CreateBrowser所创建的对象就是从COPCBrowser类派生出来的。当多个客户
端连接到OPC服务器时,Callback对象为每个客户端各创建一个Browser对象。
COPCBrowser类的接口函数(虚函数)描述如下:
~COPCBrowser(),Browser对象的析购函数,派生类通过重载本函数来定制析
购过程。
QueryOrganization(),查询服务器的数据组织方式,树状模式或平板模式。
MoveUp(),将浏览位置移动到数据树的上一级,如果是平板模式返回False。
MoveDown(),在数据树的当前浏览位置向下查询由参数指定的“树枝”,然
后将浏览位置移动这个“树枝”。如果数据树是平板模式返回False。
GetNames(),设置浏览时的字符串过滤器、数据类型过滤器和访问过滤器,并
查询收集所有符合过滤条件的Tag名称。
GetItemID(),根据给定的Tag名称,获取其对应的ItemID(全路径)。
Next(),从GetNames()收集的Tag名称中,依次得到符合条件的每个Tag名
称。
Reset(),重置Next()的开始位置。
GetAccessPaths(),获取给定Tag名称的所有访问路径。
NextAccessPath(),依次获取GetAccessPaths()所获得的每个访问路径。
三、一个OPC服务器的例子
为了充分说明FS开发工具的使用方法,现给出一个用FS开发工具开发的
DeviceNet OPC数据访问服务器的例子。
1、需求
用实现OPC数据访问接口。
DeviceNet OPC数据访问服务器应能自动查询总线上所挂接的设备,建立
设备扫描列表。
应可以自动与每个设备建立显式连接和I/O连接。
用子线程周期的对设备扫描列表中的每个设备的I/O数据进行扫描。
建立树状模式的OPC数据组织方式。
2、总体结构
初始化模块
D
e
设备扫描
Ov
线程
Pi
Cc
数e
I/O数据处理模块
据N
访e
问t
接通
I/O数据树
口讯
接
口
初始化模块完成的工作有:初始化OPC数据访问接口;初始化DeviceNet
通讯接口;查询DeviceNet总线上的设备建立设备扫描列表;与每个设备
建立I/O连接;创建设备扫描子线程;构建I/O数据树。
设备
扫描
列表
DeviceNet
总线
OPC
设备扫描线程负责周期性的对设备扫描列表中的每个设备进行扫描。
I/O数据处理模块一方面将设备扫描线程得到的I/O数据转换成OPC的数
据格式保存到I/O数据树的缓冲区中,另一方面将COPCCallback::Write()
或COPCCallback::WriteTag()的数据转换成DeviceNet通讯帧发送到目的设
备中去。
3、代码片断
在CApp::InitInstance()中初始化OPC数据访问接口:
BOOL CApp::InitInstance()
{
OleInitialize(NULL);
CDNetOpcCallback* pCallback = new CDNetOpcCallback;
if( !pCallback )
return FALSE;
if( !StartFSServer(m_hInstance, &CLSID_OPCServer) )
return FALSE;
//Register the Opc Server
……
SetCallbackObject( pHkopcCallback );
// Standard initialization
……
return TRUE;
}
在CApp::ExitInstance()中停止
int CApp::ExitInstance()
{
StopFSServer();
OleUninitialize();
return CWinApp::ExitInstance();
}
CDnOpcCallback::AddTag()伪代码:
CTag* CDnOpcCallback::AddTag(LPCTSTR name, LPCTSTR accessPath,
VARTYPE requestedType)
{
return S_OK;
}
CDnOpcCallback::WriteTag()伪代码:
HRESULT CDnOpcCallback::WriteTag(CTag * pTag, VARIANT &value)
{
BYTE *pData = ConvertRequestedTypeToNativeType(pTag, value);
DN_FRAME *pFrame = PackDeviceNetFrame(pTag, pData);
SendDeviceNetFrame(pFrame);
return S_OK;
}
CDnOpcBrowser::MoveUp()代码:
BOOL CDnOpcBrowser::MoveUp()
{
if( m_pBranch->m_parent )
{
m_pBranch = m_pBranch->m_parent;
Reset();
return TRUE;
}
// at the "root" level, can't go up
参考文献
[1] Rockwell,《DeviceNet Specification》Release 2.0,1994
[2] OPC Foundation,《OPC Specifications》Release 2.0,March,1999
[3] FactorySoft,《OPC Server Toolkit Guide》Version 2.03,1999
作者简介:
刘权,男,1973年生,北方交通大学计算机科学系,计算机应用技术专业在读研
究生,现任北京华控技术有限责任公司副总工程师,一直从事现场总线相关软件设
计开发工作。
电话:010--852
Email:
通信地址:群英科技园3号楼2层
北京华控技术有限责任公司
邮政编码: 100085


发布评论