基于OPC开发工具WtOPCSvr(服务器端)和WTclient(客户端)可以不去实现底层细节,通过调用API函数即可实现OPC通讯功能,但该开发工具的技术文档介绍简略,以及OPC技术本身的开发资料没有详细的介绍,故撰写此说明方便以后的OPC开发,本说明包含了OPC通讯的介绍,包括实现步骤以及细节的详细说明(通讯细节和一些关键变量),以及对开发工具的一些介绍。
警告:本文为作者对OPC的粗浅理解,加之主要为了向不了解OPC技术的人简单说明其原理与实现,难免会有较多介绍不严谨的地方,敬请谅解,如果可以,请指出错误的地方以便作者改正。
2020年3月23日修改:OPC服务器保存标签的结构有两种(叫做“命名空间”),树形结构和平面结构,而不是仅有树形结构一种。
注:在使用OPC相关软件时,无论是客户端还是服务器软件,尽量使用管理员模式启动,很多情况下只有管理员模式才能正常运行,否则会出现各种奇怪的问题。如果你的OPC配置按照步骤没有出现问题,相关软件还是没法正常工作,请核实是否是在管理员模式下启动的?
还请注意,由于COM本就是系统关键组件,所以安全软件一般都会将对COM的操作进行警告甚至阻止。如果系统防火墙没有关闭,也有可能会导致程序运行出现问题,如果使用的是360等安全软件,在进行OPC操作时会提示风险操作,甚至将软件报告为病毒直接删除,请点击允许操作和添加信任。
OPC是什么?网络释义:“OPC是OLE for Process Control的缩写,即应用于过程控制的OLE。OPC建立于OLE规范之上,它为工业控制领域提供了一种标准的数据访问机制。将硬件与应用软件有效地分离开来,是一套与厂商无关的软件数据交换标准接口和规程,主要解决过程控制系统与其数据源的数据交换问题,可以在各个应用之间提供透明的数据访问。”
可以看到,OPC是一种通讯规范。什么是通讯规范?以串口通讯为例,通讯双方的参数包括波特率,校验位等等设置正确,再通过标准的串口线相连,就可以实现通讯。OPC作为一种通讯规范,通讯双方通过搭建符合标准的连接,和经过符合标准的流程(设置好参数)建立通讯后,也就可以通讯了。不过串口通讯双方基本是对等关系,而OPC通讯是典型的C/S结构,分为客户端和服务器部分,且通讯的发出者都是客户端,服务器只能响应客户端的请求,这在下一部分部分详细介绍。
为什么在工业控制中要采用OPC通讯?在课程设计中经常会有这种情况:以一个嵌入式系统实现一些物理动作或采集某些信息,将嵌入式系统与计算机连接用来下装程序和在调试时读取其中的参数,为了更加方便还会开发计算机运行的上位监控软件,不用在调试状态就可以显示当前嵌入式系统某些参数。工业控制更需要类似的设计,传统工业控制和上述例子类似,现场的嵌入式系统和现场设备与计算机相连,计算机安装有上位软件用来读取监控到的实时数据和进行一些写入操作,例如温度,压力等,如下图所示:
但是这种传统的通讯方式存在的问题是,由于现场设备生产厂家的不同,上位软件以及上位和传感器交流的驱动程序需要厂家本身来提供,每次更换或升级设备,驱动程序要重新开发,上位软件也要更新,对于生产现场设备的厂家很麻烦,对于用户来说,由于生产厂家的独立性,读取某个厂家的设备数据必须使用相应的上位,也很繁琐。OPC技术的产生就是为了解决这样繁琐的问题,通过符合标准的OPC服务器,将现场设备的数据保存在其中,用户使用符合OPC标准的客户端(相当于上位监控软件),也不需要开发驱动程序,直接与服务器相连接,就可以读取现场设备的数据,极大地优化了通讯过程。
OPC通讯的粗略结构如下图:
如果现场设备符合OPC标准,它们提供数据给服务器,用户在计算机上操作客户端连接到服务器后,就可以访问所有连在服务器上的现场设备数据。也就是说,相比较传统的直接连接的通讯方式,OPC通讯在现场和用户角度来看,都经过了OPC服务器,这样的结构设计就解决了不同厂商现场设备的异构性。
对于客户端来说,具体的通信流程是这样的:首先要连接服务器,然后建立组,最后建立项,由项和服务器中某个数据点(规范说法是标签)对应,即可读取和写入标签的数据。
其中连接服务器,本地服务器只需要提供服务器的名字,远程服务器需要提供IP地址和服务器名。连接完毕后需要在服务器内建立组,所建立的组实际上并不是改变了服务器的数据结构,而是在客户端中建立了一个容纳项的“容器”,每个项都必须归属于一个组,一个组管理多个项——组相当于文件系统中的文件夹,本身不保存任何信息,而其中包含了多个用来保存数据的项,这种设计对于管理一些项很方便。最后用来与现场数据建立真正连接的是项,项不是一个数据结构,而是一个“连接”,一个指向真正现场数据的“指针”,客户端通过项就可以对应到一个现场数据点(准确来说,客户端的项指向的是服务器的标签,标签又和现场数据点对应)。
对于服务器来说,服务器都具有CLSID用来标识这个服务器,首先需要利用CLSID以及自定义的服务器名在本地注册服务器(注册服务器后注册表也会有相应变化),然后是初始化服务器功能,以及启动服务器(设置服务器状态为运行),服务器应当利用各种方式将现场数据读取到OPC服务器中并为每个数据点创建标签,创建标签时可以定义这个标签是否可读可写,为了不断地提供数据,需要设计设计一个定时器让服务器定时从现场采集数据进行更新,客户端如果要对可写的标签进行写入操作,需要在服务器程序中通过写标签回调函数实现,服务器运行时检测到客户端的写标签请求,会进入到回调函数中更新标签的数据。
OPC规范有多个协议,其中最基础的是DA(数据访问)规范,在头文件opcda.h中包含了规范定义的变量,这些变量作为OPC通讯的基础,必须很好地了解才能实现OPC通讯程序的编写。
这个枚举定义了服务器命名空间的类型,可以看到有两种类型,一种是树形结构,一种是平面结构。如果服务器的命名空间是树形结构,那么它的标签就是服务器这棵“树”的“叶子”,每个“叶子”都属于不同的“树枝”,想要获取标签名就比较繁琐,需要一个个遍历树枝,继续一层层查找才能找到真正的标签名,具体介绍参见下一个枚举。如果服务器的命名空间是平面结构,它的标签全部暴露在服务器下,获取标签名只需要浏览到这个服务器就可以全部获取。
内容承接上面,如果服务器采用的是树形结构保存标签,一个服务器作为最基本的根节点,展开后既有叶子,也有树枝,叶子对应了一个真正的标签,树枝则管理了多个叶子。将树枝展开,可能会有下一层的树枝,也可能有叶子存在,如果客户端读取到了一个有树枝的叶子,它的对应的标签全名一般是从服务器下属第一层树枝名字加一个“.”继续每一层树枝名字加一个.,直到最后加上叶子的名字,客户端访问服务器标签,首先就是要想办法获取这个标签全名。
注:客户端默认以“.”作为命名空间的划界符,即将每个“.”看作一层,但也有的服务器采用别的符号作为划界符,这样如果不修改客户端解析服务器采用的划界符,会无法将标签正确地分层解析。
这个枚举中OPC_BRANCH代表浏览树枝,当前浏览位置下的某一项如果有孩子(叶子)那么这一项就是一个树枝。OPC_LEAF代表浏览叶子,当前浏览位置下的某一项如果没有孩子(叶子)那么这一项就是一个叶子。OPC_FLAT代表当前位置这一层下所有的名字,包括树枝和叶子。
通过相应的函数访问到服务器标签后,会返回这个标签的句柄,根据句柄可以对标签进行各种操作,比如读取标签的值,标签的值不光有其对应设备的真正的数值,还有各种附带的属性,例如OPC读写标志宏,读取标签可以读取到标签的读写状态,如果这个标签只可读,那么读写标志位返回OPC_READABLE表明只可读,如果返回OPC_WRITEABLE表明只可写,如果是OPC_READABLE+OPC_WRITEABLE则表明即可读又可写。
这个枚举包含了服务器各种状态,通过相应函数获取服务器状态后,标志服务器状态的变量会有枚举中的各种情况,依次分别标志服务器状态为运行、失败、未设置、挂起、测试。
这个结构体中dwItemCount保存了属于这个组的项的数目,hClientGroup保存了客户端创建组后返回的组句柄,对组的操作都要通过组句柄完成。
这些结构体中hClient代表项的句柄,ftTimeStamp是项的时间戳,wQuality是项的品质,vDataValue是保存项对应标签的真正的值。szItemID是项对应标签的名字,bActive是项是否激活的标志。
这个结构体保存了服务器各种状态,ftStartTime是服务器启动的时间,ftCurrentTime是连接到的服务器当前时间,ftLastUpdateTime是服务器上次刷新的时间,dwServerState对应了之前的服务器状态枚举,dwGroupCount是服务器包含组的计数,dwBandWidth是服务器死区,wMajorVersion是服务器支持的最大OPC标准版本,下一个是最小支持版本。
Variant 是一种特殊的数据类型,除了定长String数据及用户定义类型外,可以包含任何种类的数据。Variant 也可以包含Empty、Error、Nothing及Null等特殊值。可以用VarType函数或TypeName函数来决定如何处理 Variant 中的数据。其部分定义如下图,最重要的是vt成员,该成员的取值表明这个数据属于的真正类型,确定类型后,在对应数据类型的指针赋值即可。部分vt取值如下:
vt所取的值 | 说明 |
---|---|
VT_EMPTY | 指示未指定值 |
VT_NULL | 指示空值(类似于 SQL 中的空值) |
VT_I2 | 指示 short 整数 |
VT_I4 | 指示 long 整数 |
VT_R4 | 指示 float 值 |
VT_R8 | 指示 double 值 |
VT_BSTR | 指示 BSTR 字符串 |
VT_BOOL | 指示一个布尔值 |
Variant还有很多包含的变量以及变量取值,详细内容请参阅百度百科相关词条。
使用开发工具开发服务器,首先要将WtOPCSvr.dll,WtOPCSvr.lib这两个运行库复制到工程下,然后复制WTOPCsvrAPI.h和WtOPCsvrEXTapi.h到工程下,这两个头文件包含了开发工具中API函数的声明,包含之后才能调用,还有需要将opc_ae.h和opcda.h包含到工程下,这两个是OPC官方的头文件,服务器和客户端开发都需要包含这两个头文件。
lib文件需要包含在工程中,我采用的是静态组件的方式,如下图所示添加工程目录下的文件:
要想使用OPC服务器功能,需要在cpp中include这四个头文件。
对于服务器开发的具体函数如何使用请参见WTOPCServer使用手册,这里只做大体上的描述。使用WTopcserver需要输入序列号注册解除30分钟时限,否则服务器30MIN后自动退出,然后首先需要注册服务器,注册时要提供服务器名字和其他参数,注册完成后是初始化服务器,再后是启动服务器,启动服务器后服务器就开始了运行,但仍是一个空服务器,需要通过创建标签来添加服务器的标签,添加时要确定好从哪里取数据放到标签中,启动服务器后还应当设置一个定时器,定时更新服务器数据。要实现服务器响应客户端写数据的请求,需要定义回调函数。
使用开发工具开发服务器,首先要将WTclient.dll,WTclient.lib这两个运行库复制到工程下,然后复制WTclientAPI.h和WtclientAPIex.h到工程下,这两个头文件包含了开发工具中API函数的声明,包含之后才能调用,还有需要将opc_ae.h和opcda.h包含到工程下,这两个是OPC官方的头文件,服务器和客户端开发都需要包含这两个头文件。
添加lib文件到工程中和之前的类似。使用客户端功能同样要记得在cpp文件中包含四个头文件。
对于客户端开发的具体函数如何使用请参见WTOPCclient使用手册,这里只做大体上的描述。OPC客户端需要初始化DCOM才能正常使用客户端功能,在程序结束时还要调用退出函数。客户端连接服务器前首先要获取本地/远程计算机的服务器数目,根据数目逐个获取服务器名字,在获取服务器名字后才能连接。连接服务器时需要提供计算机名字,服务器名字。计算机名字为空代表本地服务器,为IP地址代表连接指定IP地址的远程服务器,连接后会得到服务器句柄,对服务器进行操作时均需提供服务器句柄。
连接服务器后首先要创建组,组可以管理多个项,像一个“文件夹”,创建组时需要提供组的名字(自己起)和刷新速率,死区,然后就可以返回组句柄,通过组句柄对组进行操作。添加组后就要添加项,由于每一项和服务器一个标签对应,添加项前需要获取服务器标签。根据前面的介绍,服务器的标签是以一种树形的结构提供的,需要不断移动浏览服务器的位置,读取当前浏览位置下的标签名。不断移动浏览位置遍历完整个服务器后,获取到了所有的标签名,可以选择其中想要建立连接的标签,对其添加项,添加项时需要提供服务器句柄,组句柄和项(标签)的名字。添加完成,返回项的句柄,就可以进行读写操作了,在读写时提供对应的项句柄,读取时存放读取到值的变量,写入时写入值的变量,就可完成读写。
进行完所有操作,结束对服务器的访问,需要先将项全部删除,然后删除组,最后断开服务器。