Since many readers have asked me how to implement the Client to read and write multiple nodes at once, I took the time to do a simple research and found that I can read and write multiple nodes at a time, but I can only read or write at the same time, and does not support mixed reading and writing.
One used API
To read multiple nodes at once, UA_Client_Service_read() is used. The prototype is as follows,
static UA_INLINE UA_ReadResponse
UA_Client_Service_read(UA_Client *client, const UA_ReadRequest request)
To write multiple nodes at once, UA_Client_Service_write() is used. The prototype is as follows,
static UA_INLINE UA_WriteResponse
UA_Client_Service_write(UA_Client *client, const UA_WriteRequest request)
Two examples
This example only reads and writes the value attribute of the node
Server code
We create a server, and then add 6 nodes on the server side, 3 are LocalizedText type, 3 are UInt32 type,
#include <signal.h>
#include <stdlib.h>
#include "open62541.h"
UA_Boolean running = true;
static void stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = false;
}
static void addLocalizedTextVariable(UA_Server *server, char *name)
{
/* Define the attribute of the myInteger variable node */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_LocalizedText orig = UA_LOCALIZEDTEXT((char*)"en-US", "hello");
UA_Variant_setScalar(&attr.value, &orig, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
attr.description = UA_LOCALIZEDTEXT("en-US", name);
attr.displayName = UA_LOCALIZEDTEXT("en-US", name);
attr.dataType = UA_TYPES[UA_TYPES_LOCALIZEDTEXT].typeId;
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
/* Add the variable node to the information model */
UA_NodeId myNodeId = UA_NODEID_STRING(1, name);
UA_QualifiedName myName = UA_QUALIFIEDNAME(1, name);
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_Server_addVariableNode(server, myNodeId, parentNodeId, parentReferenceNodeId, myName,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL);
}
static void addUInt32Variable(UA_Server *server, char *name)
{
/* Define the attribute of the myInteger variable node */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_UInt32 orig = 100;
UA_Variant_setScalar(&attr.value, &orig, &UA_TYPES[UA_TYPES_UINT32]);
attr.description = UA_LOCALIZEDTEXT("en-US", name);
attr.displayName = UA_LOCALIZEDTEXT("en-US", name);
attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
/* Add the variable node to the information model */
UA_NodeId myNodeId = UA_NODEID_STRING(1, name);
UA_QualifiedName myName = UA_QUALIFIEDNAME(1, name);
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_Server_addVariableNode(server, myNodeId, parentNodeId, parentReferenceNodeId, myName,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL);
}
int main(void)
{
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
UA_Server *server = UA_Server_new();
UA_ServerConfig_setDefault(UA_Server_getConfig(server));
addLocalizedTextVariable(server, "info1");
addLocalizedTextVariable(server, "info2");
addLocalizedTextVariable(server, "info3");
addUInt32Variable(server, "uint1");
addUInt32Variable(server, "uint2");
addUInt32Variable(server, "uint3");
UA_StatusCode retval = UA_Server_run(server, &running);
UA_Server_delete(server);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
The code is relatively simple, that is, add 3 child nodes of LocalizedText type (the default value is "hello") and 3 child nodes of UInt32 type (the default value is 100) in the Objects directory
After compiling and running, use UaExpert to observe,
Client code
code show as below,
#include <stdlib.h>
#include <stdio.h>
#include "open62541.h"
static UA_StatusCode multiRead(UA_Client *client)
{
const int arraySize = 6;
UA_ReadValueId itemArray[arraySize];
for (int i = 0; i < arraySize; ++i)
{
UA_ReadValueId_init(&itemArray[i]);
itemArray[i].attributeId = UA_ATTRIBUTEID_VALUE;
}
itemArray[0].nodeId = UA_NODEID_STRING(1, "info1");
itemArray[1].nodeId = UA_NODEID_STRING(1, "info2");
itemArray[2].nodeId = UA_NODEID_STRING(1, "info3");
itemArray[3].nodeId = UA_NODEID_STRING(1, "uint1");
itemArray[4].nodeId = UA_NODEID_STRING(1, "uint2");
itemArray[5].nodeId = UA_NODEID_STRING(1, "uint3");
UA_ReadRequest request;
UA_ReadRequest_init(&request);
request.nodesToRead = &itemArray[0];
request.nodesToReadSize = arraySize;
UA_ReadResponse response = UA_Client_Service_read(client, request);
UA_StatusCode retStatusArray[arraySize];
UA_StatusCode retval = response.responseHeader.serviceResult;
if (retval == UA_STATUSCODE_GOOD)
{
if (response.resultsSize == arraySize)
{
for (int i = 0; i < arraySize; ++i)
{
retStatusArray[i] = response.results[i].status;
}
}
else
{
UA_ReadResponse_clear(&response);
return UA_STATUSCODE_BADUNEXPECTEDERROR;
}
}
for (int i = 0; i < arraySize; ++i)
{
if (retStatusArray[i] == UA_STATUSCODE_GOOD)
{
UA_DataValue res = response.results[i];
if (!res.hasValue) // no value
{
UA_ReadResponse_clear(&response);
return UA_STATUSCODE_BADUNEXPECTEDERROR;
}
UA_Variant out;
memcpy(&out, &res.value, sizeof(UA_Variant));
UA_Variant_init(&res.value);
if (out.type == &UA_TYPES[UA_TYPES_LOCALIZEDTEXT])
{
UA_LocalizedText * ptr = (UA_LocalizedText *)out.data;
printf("Text: %.*s\n", ptr->text.length, ptr->text.data);
}
else if (out.type == &UA_TYPES[UA_TYPES_UINT32])
{
UA_UInt32 * ptr = (UA_UInt32 *)out.data;
printf("UInt32 Value: %d\n", *ptr);
}
}
}
UA_ReadResponse_clear(&response);
return UA_STATUSCODE_GOOD;
}
UA_StatusCode multiWrite(UA_Client *client)
{
const int arraySize = 6;
UA_WriteValue wValueArray[arraySize];
for (int i = 0; i < arraySize; ++i)
{
UA_WriteValue_init(&wValueArray[i]);
wValueArray[i].attributeId = UA_ATTRIBUTEID_VALUE;
}
wValueArray[0].nodeId = UA_NODEID_STRING(1, "info1");
wValueArray[1].nodeId = UA_NODEID_STRING(1, "info2");
wValueArray[2].nodeId = UA_NODEID_STRING(1, "info3");
wValueArray[3].nodeId = UA_NODEID_STRING(1, "uint1");
wValueArray[4].nodeId = UA_NODEID_STRING(1, "uint2");
wValueArray[5].nodeId = UA_NODEID_STRING(1, "uint3");
UA_Variant infoVar;
UA_LocalizedText info1Value = UA_LOCALIZEDTEXT("en-US", "world1");
UA_Variant_init(&infoVar);
UA_Variant_setScalar(&infoVar, &info1Value, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
wValueArray[0].value.value = infoVar;
wValueArray[0].value.hasValue = true;
UA_LocalizedText info2Value = UA_LOCALIZEDTEXT("en-US", "world2");
UA_Variant_init(&infoVar);
UA_Variant_setScalar(&infoVar, &info2Value, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
wValueArray[1].value.value = infoVar;
wValueArray[1].value.hasValue = true;
UA_LocalizedText info3Value = UA_LOCALIZEDTEXT("en-US", "world3");
UA_Variant_init(&infoVar);
UA_Variant_setScalar(&infoVar, &info3Value, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
wValueArray[2].value.value = infoVar;
wValueArray[2].value.hasValue = true;
UA_UInt32 uint1Value = 101;
UA_Variant_init(&infoVar);
UA_Variant_setScalar(&infoVar, &uint1Value, &UA_TYPES[UA_TYPES_UINT32]);
wValueArray[3].value.value = infoVar;
wValueArray[3].value.hasValue = true;
UA_UInt32 uint2Value = 102;
UA_Variant_init(&infoVar);
UA_Variant_setScalar(&infoVar, &uint2Value, &UA_TYPES[UA_TYPES_UINT32]);
wValueArray[4].value.value = infoVar;
wValueArray[4].value.hasValue = true;
UA_UInt32 uint3Value = 103;
UA_Variant_init(&infoVar);
UA_Variant_setScalar(&infoVar, &uint3Value, &UA_TYPES[UA_TYPES_UINT32]);
wValueArray[5].value.value = infoVar;
wValueArray[5].value.hasValue = true;
UA_WriteRequest wReq;
UA_WriteRequest_init(&wReq);
wReq.nodesToWrite = &wValueArray[0];
wReq.nodesToWriteSize = arraySize;
UA_WriteResponse wResp = UA_Client_Service_write(client, wReq);
UA_StatusCode retval = wResp.responseHeader.serviceResult;
if (retval == UA_STATUSCODE_GOOD) {
if (wResp.resultsSize == 1)
retval = wResp.results[0];
else
retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
}
UA_WriteResponse_clear(&wResp);
return retval;
}
int main(void)
{
UA_Client *client = UA_Client_new();
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
if (retval != UA_STATUSCODE_GOOD) {
UA_Client_delete(client);
return (int)retval;
}
printf("---- Before write ---- \n");
multiRead(client);
printf("\n\n");
multiWrite(client);
printf("---- After write ---- \n");
multiRead(client);
UA_Client_delete(client); /* Disconnects the client internally */
return EXIT_SUCCESS;
}
The function multiRead() is used to read multiple nodes at once, here is to read 6 nodes, that is, the 6 nodes added on the server side; multiWrite() is used to write multiple nodes at once, here is to write 6 nodes, that is The 6 nodes added on the Server side.
These two functions are extensions of UA_Client_readValueAttribute() and UA_Client_writeValueAttribute(). Originally, these two functions can only read/write one node value at a time.
The results after running are as follows. It
can be seen that the value read before writing is the same as the default value set before. After writing, the value becomes the value we expect, so the operation is no problem.
key parameter
The key type is AttributeId, and its type is UA_AttributeId. Because the example in this article only reads and writes the node value, the value of AttributeId is UA_ATTRIBUTEID_VALUE
There are many other AttributeIds, such as UA_ATTRIBUTEID_DATATYPE, UA_ATTRIBUTEID_VALUERANK, etc., which can be selected as needed to support simultaneous reading or writing of different attributes.
Three summary
This article mainly describes how to read or write multiple node values at a time, but the same time can not be read or written, only read or write only.
If there is something wrong with the writing, I hope to leave a message to correct it, thank you for reading.