OPC服务器开发之WtOPCSvr(3)

发布于:2025-03-20 ⋅ 阅读:(22) ⋅ 点赞:(0)

本文接 本系列上一篇: 《OPC服务器开发之WtOPCSvr(2)》


在前面我们已经简单介绍了OPCSvr的开发环境的搭建,并简单聊了下关于WtOPcsvr的激活,以及各种回调事件的注册。本篇我们着重讲解Tag操作的几个高频函数。

一、标签读写和事件

1、OPC Item Functions 和 Callback Enabling Functions

/
//
//	OPC Item Functions
//
/
//
// SetWtOPCsvrQualifier(...)
//		Allows the application to supply the delimiting character used to
//		seperate tag names in a hiearchial namespace.
//		(The delimiting character by default is '.')
//
//	CreateTag()
//		Add an OPC Item to the WtOPCsvr.DLL local tag list.  The dll takes care of
//		all client references to the tag and provides a callback to the application
//		if the tag is updated by a client.  Once a tag gets created, it's name will
//		automatically be presented in the browse list to any interested OPC Client.
//
//	UpdateTag()
//	UpdateTagWithTimeStamp()
//	UpdateTagByName()
//		Allows the Server Application to change the value, quality and timestamp of
//		a tag.  All updates are automatically provided to subscribing clients as defined
//		by the particular connection.  
//
//	SetTagProperties()
//		Tag Properties are values associated with an OPC Item other than its Value,
//		Quality and TimeStamp.  Any property value may be assigned by the server
//		for a defined tag.
//
//	ReadTag()
//	ReadTagWithTimeStamp()
//		Allows the Application to read each tag value from the WtOPCsvr.dll local Tag List.
//
//	SuspendTagUpdates()
//		When a Tag is created by the Server Application, it is automatically enabled for
//		client subscription.  The tag name will automatically be included in the server
//		browse list and any client may connect and read it's current value.  In certain 
//		applications, it is desirable to only create the tag whenever a client requests it.
//		The UNKNOWNITEMPROC callback may be used for dynamic tag allocation, but until the
//		tag is created, the tag name will not show up in the browse list.  Creation of the
//		tag followed by a call to SuspendTagUpdates() will allow the tag name to be browsed
//		by a client, but not subscribed to.  In this scenario, the WtOPCsvr.dll will issue
//		the UNKNOWNITEMPROC callback to allow the Application to enable the tag and begin
//		updating it's value only when being actively subscribed by an OPC Client.
//
//	RemoveTag()
//		Deletes a tag from the WtOPCsvr Tag List.
//
/

_declspec(dllexport) char  WINAPI SetWtOPCsvrQualifier (char qualifier);

_declspec(dllexport) HANDLE WINAPI CreateTag (LPCSTR Name, VARIANT Value, WORD InitialQuality, BOOL IsWritable);

_declspec(dllexport) BOOL WINAPI UpdateTag (HANDLE TagHandle, VARIANT Value, WORD Quality);
_declspec(dllexport) BOOL WINAPI UpdateTagWithTimeStamp (HANDLE TagHandle, VARIANT Value, WORD Quality, FILETIME timestamp);

_declspec(dllexport) BOOL WINAPI UpdateTagByName (LPCSTR Name, VARIANT Value, WORD Quality);
_declspec(dllexport) BOOL WINAPI UpdateTagWithTimeStampByName (LPCSTR Name, VARIANT Value, FILETIME TimeStamp, WORD Quality);

_declspec(dllexport) BOOL WINAPI SetTagProperties (HANDLE TagHandle, DWORD PropertyID, LPCSTR Description, VARIANT Value);

_declspec(dllexport) BOOL WINAPI ReadTag (HANDLE TagHandle, VARIANT *pValue);

_declspec(dllexport) BOOL WINAPI ReadTagWithTimeStamp (HANDLE TagHandle, VARIANT *pValue, WORD *pQuality, FILETIME *pTimestamp);

_declspec(dllexport) BOOL WINAPI SuspendTagUpdates (HANDLE TagHandle, BOOL OnOff);

_declspec(dllexport) BOOL WINAPI RemoveTag (HANDLE TagHandle);
/
//
//	Callback Enabling Functions
//
/
_declspec(dllexport) BOOL WINAPI EnableWriteNotification (WRITENOTIFYPROC lpCallback, BOOL ConvertToNativeType);

_declspec(dllexport) BOOL WINAPI EnableUnknownItemNotification (UNKNOWNITEMPROC lpCallback);

_declspec(dllexport) BOOL WINAPI EnableItemRemovalNotification (ITEMREMOVEDPROC lpCallback);

_declspec(dllexport) BOOL WINAPI EnableDisconnectNotification (DISCONNECTPROC lpCallback);

_declspec(dllexport) BOOL WINAPI EnableEventMsgs (EVENTMSGPROC lpCallback);

_declspec(dllexport) BOOL WINAPI EnableRateNotification (RATECHANGEPROC lpCallback);

_declspec(dllexport) BOOL WINAPI EnableDeviceRead (DEVICEREADPROC lpCallback);

_declspec(dllexport) BOOL WINAPI EnableErrorCallback (ERRORPROCAPI lpCallback);

/

1.1 SetWtOPCsvrQualifier

设置OPC服务器解析标签名称的分隔符,通常默认使用".“,什么意思呢?我们在前面也提到过标签组的概念,“.” 分隔符可以帮助我们指定标签属于哪个组,用于分层管理,譬如"温度控制.温度1”、“温度控制.温度2” … ,这样可知标签温度1温度2 都属于温度控制的组,在创建OPCBrowser的时候,可以树形图分组展示和操作。
通常情况下,开发者都不必特别指定,使用默认即可。

1.2 创建标签CreateTag

_declspec(dllexport) HANDLE WINAPI CreateTag (LPCSTR Name, VARIANT Value, WORD InitialQuality, BOOL IsWritable);
  • 创建标签,需要特别注意的是注意创建标签时,指定读写属性。另外注意返回值 HANDLE 句柄,在后续操作对应标签时,均可通过此句柄进行操作。在接下来的函数中我们可以看到。

  • 所以,这里给出一点点小小的编程建议,名获取句柄的方法,二次开发的朋友可以通过自定义一个map,管理Tag名称和HANDLE句柄之间的映射关系(有需要可以这样做,没有项目需要可直接跳过),当然DLL也提供了一些ByName的操作方法,例如UpdateTagByName,通过标签名,修改对应的标签值

  • 对应的还有 _declspec(dllexport) BOOL WINAPI RemoveTag (HANDLE TagHandle); 方法用于移除指定标签

1.3 【写事件】 更新标签 UpdateTagUpdateTagByName

当服务端进入前面我们注册的写事件回调函数时,函数原型如下:

_declspec(dllexport) BOOL WINAPI EnableWriteNotification (WRITENOTIFYPROC lpCallback, BOOL ConvertToNativeType);

WRITENOTIFYPROC lpCallbac 事件回调函数中我们需要调用UpdateTagUpdateTagByName 等接口更新服务端标签的值(这里的值仅仅是写入到服务端维护的TagList 缓存中,如果服务端有接入设备,获取提供远程读写点位的接口,可以在此编写相应的业务逻辑!!)
在这里插入图片描述

1.4 【读事件】 ReadTagReadTagWithTimeStamp

当服务端进入前面我们注册的读回调函数时,函数原型如下:

_declspec(dllexport) BOOL WINAPI EnableDeviceRead (DEVICEREADPROC lpCallback);

在回调函数中,我们可以通过ReadTagReadTagWithTimeStamp 方法先获取标签的值,然后将数据返回给请求端。

1.5 客户端连接和断开事件

这个比较简单,不过多说。但是结合
_declspec(dllexport) int WINAPI NumbrClientConnections (); 我们可以获取当前连接客户端的数量(在某写OPCSvr有界面展示需求的项目中,会有应用场景)

1.6 指定服务端数据发布频率的事件

_declspec(dllexport) BOOL WINAPI EnableRateNotification (RATECHANGEPROC lpCallback);

这个怎么理解?在客户端我们通常会指定数据的更新频率,当客户端改变Rate时,服务端则会进入此事件的回调。我们在服务端的回调函数中,需要通过
_declspec(dllexport) BOOL WINAPI ResetServerRate (UINT ServerRate); 来更新设置服务端发布数据的频率!

2 、 事件回调函数原型

typedef VOID (CALLBACK* WRITENOTIFYPROC)(HANDLE, VARIANT*, DWORD*);
typedef VOID (CALLBACK* UNKNOWNITEMPROC)(CHAR*,CHAR*);
typedef VOID (CALLBACK* ITEMREMOVEDPROC)(HANDLE,CHAR*,CHAR*);
typedef VOID (CALLBACK* DISCONNECTPROC)(DWORD);
typedef VOID (CALLBACK* EVENTMSGPROC)(CHAR*);
typedef VOID (CALLBACK* RATECHANGEPROC)(HANDLE, DWORD);
typedef VOID (CALLBACK* DEVICEREADPROC)(HANDLE, VARIANT*, WORD*, FILETIME*);
typedef VOID (CALLBACK* ERRORPROCAPI) (HANDLE, DWORD, CHAR*);

基本上,我们前面讲到的几个事件,回调函数都返回有 Tag对应的句柄,VARIANT 类型的数据值。具体实施方法可以查看帮助文档获取更多细节。

二、示例项目(代码节选)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

前面我们说到VARAINT类型,类型转换懒得写的朋友,下面的代码拿去改改,不谢哈~

void CWTSvrTestApp::FormatVariant(VARIANT *pValue, char *buf, int bufsize)
{

	switch(pValue->vt)
		{
		case VT_I1:
		case VT_UI1:		// BYTE
			sprintf (buf, "%d ", pValue->bVal );
			break;
		case VT_I2:			// SHORT
			sprintf	(buf, "%d ", pValue->iVal );
			break;
		case VT_UI2:		// UNSIGNED SHORT
			sprintf	(buf, "%d ", pValue->uiVal );
			break;
		case VT_I4:			// LONG
			sprintf	(buf, "%ld ", pValue->lVal );
			break;
		case VT_UI4:		// UNSIGNED LONG
			sprintf	(buf, "%ld ", pValue->ulVal );
			break;
		case VT_INT:		// INTEGER
			sprintf	(buf, "%ld ", pValue->intVal );
			break;
		case VT_UINT:		// UNSIGNED INTEGER
			sprintf	(buf, "%ld ", pValue->uintVal );
			break;
		case VT_R4:			// FLOAT
			sprintf	(buf, "%5.2f ", pValue->fltVal );
			break;
		case VT_R8:			// DOUBLE
			sprintf	(buf, "%9.4f ", pValue->dblVal );
			break;
		case VT_BSTR:		//BSTR
			sprintf	(buf, "%ls ", pValue->bstrVal );
			break;
		case VT_BOOL:
			if (pValue->boolVal)
				strcpy (buf, "TRUE");
			else
				strcpy (buf, "FALSE");
			break;
		case VT_ARRAY | VT_I2:
			FormatIntArray (pValue, buf, bufsize);
			return;
		case VT_ARRAY | VT_R4:
			FormatFltArray (pValue, buf, bufsize);
			return;
		case VT_ARRAY | VT_BOOL:
			FormatBoolArray (pValue, buf, bufsize);
			return;
		default:
			sprintf	(buf, " unknown type:%d ", pValue->vt );
			break;
		}
}

~ 未完,有时间再续! <<<<<<<<< 2025/3/19


路漫漫其修远兮,吾将上下而求索