/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. * * Copyright (c) 2019 Kalycito Infotech Private Limited * Copyright (c) 2024 Fraunhofer IOSB (Author: Julius Pfrommer) */ /** * .. _pubsub-subscribe-tutorial: * * Subscribing Fields * ^^^^^^^^^^^^^^^^^^ * The PubSub subscribe example demonstrates the simplest way to receive * information over two transport layers such as UDP and Ethernet, that are * published by tutorial_pubsub_publish example and update values in the * TargetVariables of Subscriber Information Model. */ #include #include #include #include #include UA_NodeId connectionIdentifier; UA_NodeId readerGroupIdentifier; UA_NodeId readerIdentifier; UA_DataSetReaderConfig readerConfig; static void fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData); /* Add new connection to the server */ static void addPubSubConnection(UA_Server *server, UA_String *transportProfile, UA_NetworkAddressUrlDataType *networkAddressUrl) { /* Configuration creation for the connection */ UA_PubSubConnectionConfig connectionConfig; memset (&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); connectionConfig.name = UA_STRING("UDPMC Connection 1"); connectionConfig.transportProfileUri = *transportProfile; UA_Variant_setScalar(&connectionConfig.address, networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); connectionConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT32; connectionConfig.publisherId.id.uint32 = UA_UInt32_random(); UA_Server_addPubSubConnection (server, &connectionConfig, &connectionIdentifier); } /** * **ReaderGroup** * * ReaderGroup is used to group a list of DataSetReaders. All ReaderGroups are * created within a PubSubConnection and automatically deleted if the connection * is removed. All network message related filters are only available in the DataSetReader. */ static void addReaderGroup(UA_Server *server) { UA_ReaderGroupConfig readerGroupConfig; memset(&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); readerGroupConfig.name = UA_STRING("ReaderGroup1"); UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig, &readerGroupIdentifier); } /** * **DataSetReader** * * DataSetReader can receive NetworkMessages with the DataSetMessage * of interest sent by the Publisher. DataSetReader provides * the configuration necessary to receive and process DataSetMessages * on the Subscriber side. DataSetReader must be linked with a * SubscribedDataSet and be contained within a ReaderGroup. */ static void addDataSetReader(UA_Server *server) { memset (&readerConfig, 0, sizeof(UA_DataSetReaderConfig)); readerConfig.name = UA_STRING("DataSet Reader 1"); /* Parameters to filter which DataSetMessage has to be processed * by the DataSetReader */ /* The following parameters are used to show that the data published by * tutorial_pubsub_publish.c is being subscribed and is being updated in * the information model */ UA_UInt16 publisherIdentifier = 2234; readerConfig.publisherId.idType = UA_PUBLISHERIDTYPE_UINT16; readerConfig.publisherId.id.uint16 = publisherIdentifier; readerConfig.writerGroupId = 100; readerConfig.dataSetWriterId = 62541; /* Setting up Meta data configuration in DataSetReader */ fillTestDataSetMetaData(&readerConfig.dataSetMetaData); UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig, &readerIdentifier); } /** * **SubscribedDataSet** * * Set SubscribedDataSet type to TargetVariables data type. * Add subscribedvariables to the DataSetReader */ static void addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) { UA_NodeId folderId; UA_String folderName = readerConfig.dataSetMetaData.name; UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; UA_QualifiedName folderBrowseName; if(folderName.length > 0) { oAttr.displayName.locale = UA_STRING ("en-US"); oAttr.displayName.text = folderName; folderBrowseName.namespaceIndex = 1; folderBrowseName.name = folderName; } else { oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables"); folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables"); } UA_Server_addObjectNode(server, UA_NODEID_NULL, UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(ORGANIZES), folderBrowseName, UA_NS0ID(BASEOBJECTTYPE), oAttr, NULL, &folderId); /** * **TargetVariables** * * The SubscribedDataSet option TargetVariables defines a list of Variable * mappings between received DataSet fields and target Variables in the * Subscriber AddressSpace. The values subscribed from the Publisher are * updated in the value field of these variables */ /* Create the TargetVariables with respect to DataSetMetaData fields */ UA_FieldTargetDataType *targetVars = (UA_FieldTargetDataType *) UA_calloc(readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetDataType)); for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) { /* Variable to subscribe data */ UA_VariableAttributes vAttr = UA_VariableAttributes_default; UA_LocalizedText_copy(&readerConfig.dataSetMetaData.fields[i].description, &vAttr.description); vAttr.displayName.locale = UA_STRING("en-US"); vAttr.displayName.text = readerConfig.dataSetMetaData.fields[i].name; vAttr.dataType = readerConfig.dataSetMetaData.fields[i].dataType; UA_NodeId newNode; UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, (UA_UInt32)i + 50000), folderId, UA_NS0ID(HASCOMPONENT), UA_QUALIFIEDNAME(1, (char *)readerConfig.dataSetMetaData.fields[i].name.data), UA_NS0ID(BASEDATAVARIABLETYPE), vAttr, NULL, &newNode); /* For creating Targetvariables */ targetVars[i].attributeId = UA_ATTRIBUTEID_VALUE; targetVars[i].targetNodeId = newNode; } UA_Server_DataSetReader_createTargetVariables(server, dataSetReaderId, readerConfig.dataSetMetaData.fieldsSize, targetVars); UA_free(targetVars); UA_free(readerConfig.dataSetMetaData.fields); } /** * **DataSetMetaData** * * The DataSetMetaData describes the content of a DataSet. It provides the information necessary to decode * DataSetMessages on the Subscriber side. DataSetMessages received from the Publisher are decoded into * DataSet and each field is updated in the Subscriber based on datatype match of TargetVariable fields of Subscriber * and PublishedDataSetFields of Publisher */ static void fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) { UA_DataSetMetaDataType_init (pMetaData); pMetaData->name = UA_STRING ("DataSet 1"); /* Static definition of number of fields size to 4 to create four different * targetVariables of distinct datatype * Currently the publisher sends only DateTime data type */ pMetaData->fieldsSize = 4; pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, &UA_TYPES[UA_TYPES_FIELDMETADATA]); /* DateTime DataType */ UA_FieldMetaData_init (&pMetaData->fields[0]); UA_NodeId_copy (&UA_TYPES[UA_TYPES_DATETIME].typeId, &pMetaData->fields[0].dataType); pMetaData->fields[0].builtInType = UA_NS0ID_DATETIME; pMetaData->fields[0].name = UA_STRING ("DateTime"); pMetaData->fields[0].valueRank = -1; /* scalar */ /* Int32 DataType */ UA_FieldMetaData_init (&pMetaData->fields[1]); UA_NodeId_copy(&UA_TYPES[UA_TYPES_INT32].typeId, &pMetaData->fields[1].dataType); pMetaData->fields[1].builtInType = UA_NS0ID_INT32; pMetaData->fields[1].name = UA_STRING ("Int32"); pMetaData->fields[1].valueRank = -1; /* scalar */ /* Int64 DataType */ UA_FieldMetaData_init (&pMetaData->fields[2]); UA_NodeId_copy(&UA_TYPES[UA_TYPES_INT64].typeId, &pMetaData->fields[2].dataType); pMetaData->fields[2].builtInType = UA_NS0ID_INT64; pMetaData->fields[2].name = UA_STRING ("Int64"); pMetaData->fields[2].valueRank = -1; /* scalar */ /* Boolean DataType */ UA_FieldMetaData_init (&pMetaData->fields[3]); UA_NodeId_copy (&UA_TYPES[UA_TYPES_BOOLEAN].typeId, &pMetaData->fields[3].dataType); pMetaData->fields[3].builtInType = UA_NS0ID_BOOLEAN; pMetaData->fields[3].name = UA_STRING ("BoolToggle"); pMetaData->fields[3].valueRank = -1; /* scalar */ } /** * Followed by the main server code, making use of the above definitions */ static int run(UA_String *transportProfile, UA_NetworkAddressUrlDataType *networkAddressUrl) { UA_Server *server = UA_Server_new(); addPubSubConnection(server, transportProfile, networkAddressUrl); addReaderGroup(server); addDataSetReader(server); addSubscribedVariables(server, readerIdentifier); UA_Server_enableAllPubSubComponents(server); UA_Server_runUntilInterrupt(server); UA_Server_delete(server); return 0; } static void usage(char *progname) { printf("usage: %s [device]\n", progname); } int main(int argc, char **argv) { UA_String transportProfile = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")}; if(argc > 1) { if(strcmp(argv[1], "-h") == 0) { usage(argv[0]); return EXIT_SUCCESS; } else if(strncmp(argv[1], "opc.udp://", 10) == 0) { networkAddressUrl.url = UA_STRING(argv[1]); } else if(strncmp(argv[1], "opc.eth://", 10) == 0) { transportProfile = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"); if(argc < 3) { printf("Error: UADP/ETH needs an interface name\n"); return EXIT_FAILURE; } networkAddressUrl.url = UA_STRING(argv[1]); } else { printf ("Error: unknown URI\n"); return EXIT_FAILURE; } } if (argc > 2) { networkAddressUrl.networkInterface = UA_STRING(argv[2]); } return run(&transportProfile, &networkAddressUrl); }