open62541 client batch monitoring

Posted by slyte33 on Mon, 07 Mar 2022 07:31:19 +0100

Add monitoring variable

  • Interface: UA_Client_MonitoredItems_createDataChanges
  • use:
    Add two UAS to the server_ Uint32 type variable node, the Answer and myself
#include "open62541.h"

UA_Boolean running = true;

UA_NodeId addTheAnswerVariable(UA_Server *server)
{
	/* Define the attribute of the myInteger variable node */
	UA_VariableAttributes attr = UA_VariableAttributes_default;
	UA_Int32 myInteger = 1;
	UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
	attr.description = UA_LOCALIZEDTEXT("en-US", "the answer");
	attr.displayName = UA_LOCALIZEDTEXT("en-US", "the answer");
	attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
	attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;

	/* Add the variable node to the information model */
	UA_NodeId theAnswerNodeId = UA_NODEID_STRING(1, "the.answer");
	UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer");
	UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
	UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
	UA_Server_addVariableNode(server, theAnswerNodeId, parentNodeId,
		parentReferenceNodeId, myIntegerName,
		UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL);

	return theAnswerNodeId;
}

UA_NodeId addmyselfVariable(UA_Server *server)
{
	/* Define the attribute of the myInteger variable node */
	UA_VariableAttributes attr = UA_VariableAttributes_default;
	UA_Int32 myInteger = 1;
	UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
	attr.description = UA_LOCALIZEDTEXT("en-US", "myself");
	attr.displayName = UA_LOCALIZEDTEXT("en-US", "myself");
	attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
	attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;

	/* Add the variable node to the information model */
	UA_NodeId myselfNodeId = UA_NODEID_STRING(1, "myself");
	UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "myself");
	UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
	UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
	UA_Server_addVariableNode(server, myselfNodeId, parentNodeId,
		parentReferenceNodeId, myIntegerName,
		UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL);

	return myselfNodeId;
}

int main(void)
{
	UA_Server *server = UA_Server_new();
	//Set a user-defined port
	UA_ServerConfig_setMinimal(UA_Server_getConfig(server), 4999, NULL);

	UA_NodeId targetNodeId = addTheAnswerVariable(server);
	UA_NodeId myselfNodeId = addmyselfVariable(server);

	UA_StatusCode retval = UA_Server_run(server, &running);

	UA_Server_delete(server);

	return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

The client code is as follows:

#include "open62541.h"

static void handler_DataChanged(UA_Client *client, UA_UInt32 subId,
	void *subContext, UA_UInt32 monId,
	void *monContext, UA_DataValue *value)
{
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Received Notification");

	UA_NodeId *curNodeId = (UA_NodeId *)monContext;
	printf("nodeid: namespaceIndex = %d, identifierType = %d, identifier.string = %.*s\n",
		curNodeId->namespaceIndex, curNodeId->identifierType,
		curNodeId->identifier.string.length, curNodeId->identifier.string.data);

	UA_Int32 currentValue = *(UA_Int32*)(value->value.data);
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "SubId:%u, MonId:%u, Current Value: %d\n",
		subId, monId, currentValue);
}

static void handler_DeleteDataChanged(UA_Client *client, UA_UInt32 subId, void *subContext,
	UA_UInt32 monId, void *monContext) 
{
	UA_NodeId *curNodeId = (UA_NodeId *)monContext;
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT,
		"Delete item Nodeid message:namespaceIndex = %d, identifierType = %d, identifier.string = %.*s\n",
		curNodeId->namespaceIndex, curNodeId->identifierType,
		curNodeId->identifier.string.length, curNodeId->identifier.string.data);
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Delete item SubId:%u, MonId:%u\n", subId, monId);
}

void addMonitoredItemsToVariable(UA_Client *client)
{
	/* Create a subscription */
	UA_CreateSubscriptionRequest subRequest = UA_CreateSubscriptionRequest_default();
	UA_CreateSubscriptionResponse subResponse = UA_Client_Subscriptions_create(client, subRequest, NULL, NULL, NULL);

	UA_UInt32 subId = subResponse.subscriptionId;
	if (subResponse.responseHeader.serviceResult == UA_STATUSCODE_GOOD)
	{
		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Create subscription succeeded, id %u\n", subId);
	}

	UA_MonitoredItemCreateRequest items[2];
	UA_UInt32 newMonitoredItemIds[2];
	UA_Client_DataChangeNotificationCallback callbacks[2];
	UA_Client_DeleteMonitoredItemCallback deleteCallbacks[2];

	UA_NodeId *contexts[2];

	items[0] = UA_MonitoredItemCreateRequest_default(UA_NODEID_STRING(1, "the.answer"));
	items[1] = UA_MonitoredItemCreateRequest_default(UA_NODEID_STRING(1, "myself"));

	callbacks[0] = handler_DataChanged;
	callbacks[1] = handler_DataChanged;
	deleteCallbacks[0] = handler_DeleteDataChanged;
	deleteCallbacks[1] = handler_DeleteDataChanged;

	contexts[0] = UA_NodeId_new();
	contexts[1] = UA_NodeId_new();
	*(contexts[0]) = UA_NODEID_STRING(1, "the.answer");
	*(contexts[1]) = UA_NODEID_STRING(1, "myself");

	/* Create monitored items */
	UA_CreateMonitoredItemsRequest monRequest;
	UA_CreateMonitoredItemsRequest_init(&monRequest);
	monRequest.subscriptionId = subId;
	monRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
	monRequest.itemsToCreate = (UA_MonitoredItemCreateRequest*)items;
	monRequest.itemsToCreateSize = 2;
	UA_CreateMonitoredItemsResponse monResponse =
		UA_Client_MonitoredItems_createDataChanges(client, monRequest, (void **)contexts, callbacks, deleteCallbacks);

	UA_MonitoredItemCreateResult result;
	UA_MonitoredItemCreateResult_init(&result);
	if (monResponse.responseHeader.serviceResult != UA_STATUSCODE_GOOD)
		result.statusCode = monResponse.responseHeader.serviceResult;

	if (result.statusCode == UA_STATUSCODE_GOOD && monResponse.resultsSize != 2)
		result.statusCode = UA_STATUSCODE_BADINTERNALERROR;

	if (result.statusCode == UA_STATUSCODE_GOOD)
		UA_MonitoredItemCreateResult_copy(&monResponse.results[0], &result);

	printf("add monitor item success , id1 = %d, id2 = %d\n", monResponse.results[0].monitoredItemId, monResponse.results[1].monitoredItemId);

	UA_CreateMonitoredItemsResponse_clear(&monResponse);
}

Running the client, you can see that you have successfully connected to the server, and there are two more monitoring item IDS 1 and 2

Here, we use UaExpert to connect to the server, then modify the corresponding value and look at the information output from the Client again

You can see the After the values of answer and myself nodes are changed, print the passed in NodeId and value through our callback interface.

The problems encountered here can be summarized as follows:
UA_ Client_ MonitoredItems_ UA in createdatachanges interface_ Client_ The deletemonitoreditemcallback * parameter cannot be passed in NULL directly when it is not needed, otherwise it will crash. Looking at the source code, you can find that there is no internal processing for this formal parameter, but directly copy it.

This leads to the occurrence of crash problems.

Remove monitoring variable

  • Interface: UA_Client_MonitoredItems_delete
    The code is as follows:
void deleteMonitoredItems(UA_Client *client)
{
	UA_DeleteMonitoredItemsRequest deleteRequest;
	UA_DeleteMonitoredItemsRequest_init(&deleteRequest);

	deleteRequest.subscriptionId = subId;
	deleteRequest.monitoredItemIds = monItemsId;
	deleteRequest.monitoredItemIdsSize = 2;

	UA_DeleteMonitoredItemsResponse deleteResponse =
		UA_Client_MonitoredItems_delete(client, deleteRequest);

	if (deleteResponse.responseHeader.serviceResult == UA_STATUSCODE_GOOD)
	{
		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "delete monitoredItems success!\n");
	}
	else
	{
		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "delete monitoredItems failed! ret = %d\n", deleteResponse.responseHeader.serviceResult);
	}
	//delete subscrioptionId
	UA_Client_Subscriptions_deleteSingle(client, subId);
}

After deleting the monitoring item, we will call the corresponding deleteCallback interface, which has the corresponding print information.

UA is used here_ Client_ Addrepeatedcallback timer, which will delete the monitoring item after 5000ms. At this time, modify the variable value through UaExpert again, and there will be no corresponding changes here.

Client complete code

#include "open62541.h"

static UA_UInt32 subId;
static UA_UInt32 monItemsId[2];
static UA_UInt64 timeId;

static void handler_DataChanged(UA_Client *client, UA_UInt32 subId,
	void *subContext, UA_UInt32 monId,
	void *monContext, UA_DataValue *value)
{
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Received Notification");

	UA_NodeId *curNodeId = (UA_NodeId *)monContext;
	printf("nodeid: namespaceIndex = %d, identifierType = %d, identifier.string = %.*s\n",
		curNodeId->namespaceIndex, curNodeId->identifierType,
		curNodeId->identifier.string.length, curNodeId->identifier.string.data);

	UA_Int32 currentValue = *(UA_Int32*)(value->value.data);
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "SubId:%u, MonId:%u, Current Value: %d\n",
		subId, monId, currentValue);
}


/*DeleteDataChanged Callback function*/
static void handler_DeleteDataChanged(UA_Client *client, UA_UInt32 subId, void *subContext,
	UA_UInt32 monId, void *monContext) 
{
	UA_NodeId *curNodeId = (UA_NodeId *)monContext;
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT,
		"Delete item Nodeid message:namespaceIndex = %d, identifierType = %d, identifier.string = %.*s\n",
		curNodeId->namespaceIndex, curNodeId->identifierType,
		curNodeId->identifier.string.length, curNodeId->identifier.string.data);
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Delete item SubId:%u, MonId:%u\n", subId, monId);
}

void addMonitoredItemToVariable(UA_Client *client, UA_NodeId *target)
{
	/* Create a subscription */
	UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
	UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
		NULL, NULL, NULL);

	subId = response.subscriptionId;
	if (response.responseHeader.serviceResult == UA_STATUSCODE_GOOD)
	{
		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Create subscription succeeded, id %u\n", subId);
	}

	UA_MonitoredItemCreateRequest monRequest =
		UA_MonitoredItemCreateRequest_default(*target);

	UA_MonitoredItemCreateResult monResponse =
		UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId,
		UA_TIMESTAMPSTORETURN_BOTH,
		monRequest, NULL,
		handler_DataChanged, NULL);
	if (monResponse.statusCode == UA_STATUSCODE_GOOD)
	{
		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Monitoring 'the.answer', id %u\n",
			monResponse.monitoredItemId);
	}
}

void addMonitoredItemsToVariable(UA_Client *client)
{
	/* Create a subscription */
	UA_CreateSubscriptionRequest subRequest = UA_CreateSubscriptionRequest_default();
	UA_CreateSubscriptionResponse subResponse = UA_Client_Subscriptions_create(client, subRequest, NULL, NULL, NULL);

	subId = subResponse.subscriptionId;
	if (subResponse.responseHeader.serviceResult == UA_STATUSCODE_GOOD)
	{
		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Create subscription succeeded, id %u\n", subId);
	}

	UA_MonitoredItemCreateRequest items[2];
	UA_Client_DataChangeNotificationCallback callbacks[2];
	UA_Client_DeleteMonitoredItemCallback deleteCallbacks[2];
	UA_NodeId *contexts[2];

	items[0] = UA_MonitoredItemCreateRequest_default(UA_NODEID_STRING(1, "the.answer"));
	items[1] = UA_MonitoredItemCreateRequest_default(UA_NODEID_STRING(1, "myself"));

	callbacks[0] = handler_DataChanged;
	callbacks[1] = handler_DataChanged;
	deleteCallbacks[0] = handler_DeleteDataChanged;
	deleteCallbacks[1] = handler_DeleteDataChanged;

	contexts[0] = UA_NodeId_new();
	contexts[1] = UA_NodeId_new();
	*(contexts[0]) = UA_NODEID_STRING(1, "the.answer");
	*(contexts[1]) = UA_NODEID_STRING(1, "myself");

	/* Create monitored items */
	UA_CreateMonitoredItemsRequest monRequest;
	UA_CreateMonitoredItemsRequest_init(&monRequest);
	monRequest.subscriptionId = subId;
	monRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
	monRequest.itemsToCreate = (UA_MonitoredItemCreateRequest*)items;
	monRequest.itemsToCreateSize = 2;
	UA_CreateMonitoredItemsResponse monResponse =
		UA_Client_MonitoredItems_createDataChanges(client, monRequest, (void **)contexts, callbacks, deleteCallbacks);

	UA_MonitoredItemCreateResult result;
	UA_MonitoredItemCreateResult_init(&result);
	if (monResponse.responseHeader.serviceResult != UA_STATUSCODE_GOOD)
		result.statusCode = monResponse.responseHeader.serviceResult;

	if (result.statusCode == UA_STATUSCODE_GOOD && monResponse.resultsSize != 2)
		result.statusCode = UA_STATUSCODE_BADINTERNALERROR;

	if (result.statusCode == UA_STATUSCODE_GOOD)
		UA_MonitoredItemCreateResult_copy(&monResponse.results[0], &result);

	monItemsId[0] = monResponse.results[0].monitoredItemId;
	monItemsId[1] = monResponse.results[1].monitoredItemId;
	printf("add monitor item success , id1 = %d, id2 = %d\n", monResponse.results[0].monitoredItemId, monResponse.results[1].monitoredItemId);

	UA_CreateMonitoredItemsResponse_clear(&monResponse);
}

void deleteMonitoredItems(UA_Client *client)
{
	UA_DeleteMonitoredItemsRequest deleteRequest;
	UA_DeleteMonitoredItemsRequest_init(&deleteRequest);

	deleteRequest.subscriptionId = subId;
	deleteRequest.monitoredItemIds = monItemsId;
	deleteRequest.monitoredItemIdsSize = 2;

	UA_DeleteMonitoredItemsResponse deleteResponse =
		UA_Client_MonitoredItems_delete(client, deleteRequest);

	if (deleteResponse.responseHeader.serviceResult == UA_STATUSCODE_GOOD)
	{
		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "delete monitoredItems success!\n");
	}
	else
	{
		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "delete monitoredItems failed! ret = %d\n", deleteResponse.responseHeader.serviceResult);
	}
	//delete subscrioptionId
	UA_Client_Subscriptions_deleteSingle(client, subId);
}

void timeCallback(UA_Client *client, void *data)
{
	deleteMonitoredItems(client);
	//off timer 
	UA_Client_removeCallback(client, timeId);
}

int main(void)
{
	UA_StatusCode statuscode;
	UA_Client *client = UA_Client_new();
	UA_ClientConfig_setDefault(UA_Client_getConfig(client));
	statuscode = UA_Client_connect(client, "opc.tcp://127.0.0.1:4999");
	if (statuscode != UA_STATUSCODE_GOOD)
	{
		printf("connect server failed!\n");
		return -1;
	}
	//Add items
	addMonitoredItemsToVariable(client);
	//delete items after 5000ms
	UA_Client_addRepeatedCallback(client, timeCallback, NULL, 5000, &timeId);
	while (true)
	{
		UA_Client_run_iterate(client, 100);
	}

	UA_Client_delete(client);
	return 0;
}

In addition to the above, UA is used_ Client_ MonitoredItems_ Delete deletes the monitoring node and finds that UA is used_ Client_ Subscriptions_ When deletesingle deletes the corresponding subscription id(subId), all monitoring items corresponding to the subId will be deleted at the same time. The relevant codes in open62541 are as follows:

You can check the source code for other more detailed operations. We will modify the client interface as follows:

void deleteMonitoredItems(UA_Client *client)
{
	/*UA_DeleteMonitoredItemsRequest deleteRequest;
	UA_DeleteMonitoredItemsRequest_init(&deleteRequest);

	deleteRequest.subscriptionId = subId;
	deleteRequest.monitoredItemIds = monItemsId;
	deleteRequest.monitoredItemIdsSize = 2;

	UA_DeleteMonitoredItemsResponse deleteResponse =
		UA_Client_MonitoredItems_delete(client, deleteRequest);

	if (deleteResponse.responseHeader.serviceResult == UA_STATUSCODE_GOOD)
	{
		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "delete monitoredItems success!\n");
	}
	else
	{
		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "delete monitoredItems failed! ret = %d\n", deleteResponse.responseHeader.serviceResult);
	}*/
	//delete subscrioptionId
	UA_Client_Subscriptions_deleteSingle(client, subId);
}

Here we will UA_ Client_ MonitoredItems_ The delete section is annotated to directly see the effect of deleting subId.

You can see that both the server and the client have information output corresponding to the removal of monitoring items.

Topics: C++