|
@@ -1,14 +1,15 @@
|
|
|
/* 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
|
|
|
*/
|
|
|
|
|
|
/**
|
|
|
* IMPORTANT ANNOUNCEMENT
|
|
|
- * The PubSub subscriber API is currently not finished. This examples can be used to
|
|
|
- * receive and print the values, which are published by the tutorial_pubsub_publish
|
|
|
- * example. The following code uses internal API which will be later replaced by the
|
|
|
- * higher-level PubSub subscriber API. */
|
|
|
-
|
|
|
+ * The PubSub Subscriber API is currently not finished. This example can be used
|
|
|
+ * to receive and display values that are published by tutorial_pubsub_publish
|
|
|
+ * example in the TargetVariables of Subscriber Information Model .
|
|
|
+ */
|
|
|
#include <open62541/plugin/log_stdout.h>
|
|
|
#include <open62541/plugin/pubsub_udp.h>
|
|
|
#include <open62541/server.h>
|
|
@@ -16,7 +17,6 @@
|
|
|
#include <open62541/types_generated.h>
|
|
|
|
|
|
#include "ua_pubsub.h"
|
|
|
-#include "ua_pubsub_networkmessage.h"
|
|
|
|
|
|
#ifdef UA_ENABLE_PUBSUB_ETH_UADP
|
|
|
#include <open62541/plugin/pubsub_ethernet.h>
|
|
@@ -26,90 +26,172 @@
|
|
|
#include <signal.h>
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
-UA_Boolean running = true;
|
|
|
-static void stopHandler(int sign) {
|
|
|
- UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
|
|
|
- running = false;
|
|
|
+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) {
|
|
|
+ if((server == NULL) && (transportProfile == NULL) &&
|
|
|
+ (networkAddressUrl == NULL)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Configuration creation for the connection */
|
|
|
+ UA_PubSubConnectionConfig connectionConfig;
|
|
|
+ memset (&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
|
|
|
+ connectionConfig.name = UA_STRING("UDPMC Connection 1");
|
|
|
+ connectionConfig.transportProfileUri = *transportProfile;
|
|
|
+ connectionConfig.enabled = UA_TRUE;
|
|
|
+ UA_Variant_setScalar(&connectionConfig.address, networkAddressUrl,
|
|
|
+ &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
|
|
+ connectionConfig.publisherId.numeric = UA_UInt32_random ();
|
|
|
+ UA_Server_addPubSubConnection (server, &connectionConfig, &connectionIdentifier);
|
|
|
}
|
|
|
|
|
|
+/* Add ReaderGroup to the created connection */
|
|
|
static void
|
|
|
-subscriptionPollingCallback(UA_Server *server, UA_PubSubConnection *connection) {
|
|
|
- UA_ByteString buffer;
|
|
|
- if (UA_ByteString_allocBuffer(&buffer, 512) != UA_STATUSCODE_GOOD) {
|
|
|
- UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
|
|
|
- "Message buffer allocation failed!");
|
|
|
+addReaderGroup(UA_Server *server) {
|
|
|
+ if(server == NULL) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- /* Receive the message. Blocks for 5ms */
|
|
|
- UA_StatusCode retval =
|
|
|
- connection->channel->receive(connection->channel, &buffer, NULL, 5);
|
|
|
- if(retval != UA_STATUSCODE_GOOD || buffer.length == 0) {
|
|
|
- /* Workaround!! Reset buffer length. Receive can set the length to zero.
|
|
|
- * Then the buffer is not deleted because no memory allocation is
|
|
|
- * assumed.
|
|
|
- * TODO: Return an error code in 'receive' instead of setting the buf
|
|
|
- * length to zero. */
|
|
|
- buffer.length = 512;
|
|
|
- UA_ByteString_clear(&buffer);
|
|
|
+ UA_ReaderGroupConfig readerGroupConfig;
|
|
|
+ memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig));
|
|
|
+ readerGroupConfig.name = UA_STRING("ReaderGroup1");
|
|
|
+ UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig,
|
|
|
+ &readerGroupIdentifier);
|
|
|
+}
|
|
|
+
|
|
|
+/* Add DataSetReader to the ReaderGroup */
|
|
|
+static void
|
|
|
+addDataSetReader(UA_Server *server) {
|
|
|
+ if(server == NULL) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- /* Decode the message */
|
|
|
- UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
|
|
- "Message length: %lu", (unsigned long) buffer.length);
|
|
|
- UA_NetworkMessage networkMessage;
|
|
|
- memset(&networkMessage, 0, sizeof(UA_NetworkMessage));
|
|
|
- size_t currentPosition = 0;
|
|
|
- UA_NetworkMessage_decodeBinary(&buffer, ¤tPosition, &networkMessage);
|
|
|
- UA_ByteString_clear(&buffer);
|
|
|
-
|
|
|
- /* Is this the correct message type? */
|
|
|
- if(networkMessage.networkMessageType != UA_NETWORKMESSAGE_DATASET)
|
|
|
- goto cleanup;
|
|
|
-
|
|
|
- /* At least one DataSetMessage in the NetworkMessage? */
|
|
|
- if(networkMessage.payloadHeaderEnabled &&
|
|
|
- networkMessage.payloadHeader.dataSetPayloadHeader.count < 1)
|
|
|
- goto cleanup;
|
|
|
-
|
|
|
- /* Is this a KeyFrame-DataSetMessage? */
|
|
|
- UA_DataSetMessage *dsm = &networkMessage.payload.dataSetPayload.dataSetMessages[0];
|
|
|
- if(dsm->header.dataSetMessageType != UA_DATASETMESSAGE_DATAKEYFRAME)
|
|
|
- goto cleanup;
|
|
|
-
|
|
|
- /* Loop over the fields and print well-known content types */
|
|
|
- for(int i = 0; i < dsm->data.keyFrameData.fieldCount; i++) {
|
|
|
- const UA_DataType *currentType = dsm->data.keyFrameData.dataSetFields[i].value.type;
|
|
|
- if(currentType == &UA_TYPES[UA_TYPES_BYTE]) {
|
|
|
- UA_Byte value = *(UA_Byte *)dsm->data.keyFrameData.dataSetFields[i].value.data;
|
|
|
- UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
|
|
- "Message content: [Byte] \tReceived data: %i", value);
|
|
|
- } else if (currentType == &UA_TYPES[UA_TYPES_DATETIME]) {
|
|
|
- UA_DateTime value = *(UA_DateTime *)dsm->data.keyFrameData.dataSetFields[i].value.data;
|
|
|
- UA_DateTimeStruct receivedTime = UA_DateTime_toStruct(value);
|
|
|
- UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
|
|
- "Message content: [DateTime] \t"
|
|
|
- "Received date: %02i-%02i-%02i Received time: %02i:%02i:%02i",
|
|
|
- receivedTime.year, receivedTime.month, receivedTime.day,
|
|
|
- receivedTime.hour, receivedTime.min, receivedTime.sec);
|
|
|
- }
|
|
|
+ memset (&readerConfig, 0, sizeof(UA_DataSetReaderConfig));
|
|
|
+ readerConfig.name = UA_STRING("DataSet Reader 1");
|
|
|
+ readerConfig.dataSetWriterId = 1;
|
|
|
+
|
|
|
+ /* Setting up Meta data configuration in DataSetReader */
|
|
|
+ fillTestDataSetMetaData(&readerConfig.dataSetMetaData);
|
|
|
+ UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig,
|
|
|
+ &readerIdentifier);
|
|
|
+}
|
|
|
+
|
|
|
+/* Set SubscribedDataSet type to TargetVariables data type
|
|
|
+ * Add subscribedvariables to the DataSetReader */
|
|
|
+static void addSubscribedVariables (UA_Server *server, UA_NodeId dataSetReaderId) {
|
|
|
+ if(server == NULL) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER),
|
|
|
+ UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES),
|
|
|
+ folderBrowseName, UA_NODEID_NUMERIC (0,
|
|
|
+ UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId);
|
|
|
+
|
|
|
+ UA_Server_DataSetReader_addTargetVariables (server, &folderId,
|
|
|
+ dataSetReaderId,
|
|
|
+ UA_PUBSUB_SDS_TARGET);
|
|
|
+}
|
|
|
+
|
|
|
+/* Define MetaData for TargetVariables */
|
|
|
+static void fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) {
|
|
|
+ if(pMetaData == NULL) {
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- cleanup:
|
|
|
- UA_NetworkMessage_clear(&networkMessage);
|
|
|
+ UA_DataSetMetaDataType_init (pMetaData);
|
|
|
+ UA_String strTmp = UA_STRING ("DataSet 1");
|
|
|
+ UA_String_copy (&strTmp, &pMetaData->name);
|
|
|
+
|
|
|
+ /* 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;
|
|
|
+ strTmp = UA_STRING ("DateTime");
|
|
|
+ UA_String_copy (&strTmp, &pMetaData->fields[0].name);
|
|
|
+ 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;
|
|
|
+ strTmp = UA_STRING ("Int32");
|
|
|
+ UA_String_copy (&strTmp, &pMetaData->fields[1].name);
|
|
|
+ pMetaData->fields[1].valueRank = -1; /* scalar */
|
|
|
+
|
|
|
+ /* Int64 DataType */
|
|
|
+ UA_FieldMetaData_init (&pMetaData->fields[2]);
|
|
|
+ UA_NodeId_copy(&UA_TYPES[UA_TYPES_INT32].typeId,
|
|
|
+ &pMetaData->fields[2].dataType);
|
|
|
+ pMetaData->fields[2].builtInType = UA_NS0ID_INT32;
|
|
|
+ strTmp = UA_STRING ("Int32Fast");
|
|
|
+ UA_String_copy (&strTmp, &pMetaData->fields[2].name);
|
|
|
+ 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;
|
|
|
+ strTmp = UA_STRING ("BoolToggle");
|
|
|
+ UA_String_copy (&strTmp, &pMetaData->fields[3].name);
|
|
|
+ pMetaData->fields[3].valueRank = -1; /* scalar */
|
|
|
+}
|
|
|
+
|
|
|
+UA_Boolean running = true;
|
|
|
+static void stopHandler(int sign) {
|
|
|
+ UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
|
|
|
+ running = false;
|
|
|
}
|
|
|
|
|
|
static int
|
|
|
run(UA_String *transportProfile, UA_NetworkAddressUrlDataType *networkAddressUrl) {
|
|
|
signal(SIGINT, stopHandler);
|
|
|
signal(SIGTERM, stopHandler);
|
|
|
-
|
|
|
+ /* Return value initialized to Status Good */
|
|
|
+ UA_StatusCode retval;
|
|
|
UA_Server *server = UA_Server_new();
|
|
|
UA_ServerConfig *config = UA_Server_getConfig(server);
|
|
|
UA_ServerConfig_setMinimal(config, 4801, NULL);
|
|
|
|
|
|
- /* Details about the PubSubTransportLayer can be found inside the
|
|
|
+ /* Add the PubSub network layer implementation to the server config.
|
|
|
+ * The TransportLayer is acting as factory to create new connections
|
|
|
+ * on runtime. Details about the PubSubTransportLayer can be found inside the
|
|
|
* tutorial_pubsub_connection */
|
|
|
config->pubsubTransportLayers = (UA_PubSubTransportLayer *)
|
|
|
UA_calloc(2, sizeof(UA_PubSubTransportLayer));
|
|
@@ -117,6 +199,7 @@ run(UA_String *transportProfile, UA_NetworkAddressUrlDataType *networkAddressUrl
|
|
|
UA_Server_delete(server);
|
|
|
return EXIT_FAILURE;
|
|
|
}
|
|
|
+
|
|
|
config->pubsubTransportLayers[0] = UA_PubSubTransportLayerUDPMP();
|
|
|
config->pubsubTransportLayersSize++;
|
|
|
#ifdef UA_ENABLE_PUBSUB_ETH_UADP
|
|
@@ -124,75 +207,47 @@ run(UA_String *transportProfile, UA_NetworkAddressUrlDataType *networkAddressUrl
|
|
|
config->pubsubTransportLayersSize++;
|
|
|
#endif
|
|
|
|
|
|
- UA_PubSubConnectionConfig connectionConfig;
|
|
|
- memset(&connectionConfig, 0, sizeof(connectionConfig));
|
|
|
- connectionConfig.name = UA_STRING("UADP Connection 1");
|
|
|
- connectionConfig.transportProfileUri = *transportProfile;
|
|
|
- connectionConfig.enabled = UA_TRUE;
|
|
|
- UA_Variant_setScalar(&connectionConfig.address, networkAddressUrl,
|
|
|
- &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
|
|
- UA_NodeId connectionIdent;
|
|
|
- UA_StatusCode retval =
|
|
|
- UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
|
|
|
- if(retval == UA_STATUSCODE_GOOD)
|
|
|
- UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
|
|
|
- "The PubSub Connection was created successfully!");
|
|
|
-
|
|
|
- /* The following lines register the listening on the configured multicast
|
|
|
- * address and configure a repeated job, which is used to handle received
|
|
|
- * messages. */
|
|
|
- UA_PubSubConnection *connection =
|
|
|
- UA_PubSubConnection_findConnectionbyId(server, connectionIdent);
|
|
|
- if(connection != NULL) {
|
|
|
- UA_StatusCode rv = connection->channel->regist(connection->channel, NULL, NULL);
|
|
|
- if (rv == UA_STATUSCODE_GOOD) {
|
|
|
- UA_UInt64 subscriptionCallbackId;
|
|
|
- UA_Server_addRepeatedCallback(server, (UA_ServerCallback)subscriptionPollingCallback,
|
|
|
- connection, 100, &subscriptionCallbackId);
|
|
|
- } else {
|
|
|
- UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "register channel failed: %s!",
|
|
|
- UA_StatusCode_name(rv));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- retval |= UA_Server_run(server, &running);
|
|
|
+ /* API calls */
|
|
|
+ addPubSubConnection(server, transportProfile, networkAddressUrl);
|
|
|
+ addReaderGroup(server);
|
|
|
+ addDataSetReader(server);
|
|
|
+ addSubscribedVariables(server, readerIdentifier);
|
|
|
|
|
|
+ retval = UA_Server_run(server, &running);
|
|
|
UA_Server_delete(server);
|
|
|
- return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;;
|
|
|
+ return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
static void
|
|
|
usage(char *progname) {
|
|
|
printf("usage: %s <uri> [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) {
|
|
|
+ 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) {
|
|
|
+ } else if(strncmp(argv[1], "opc.udp://", 10) == 0) {
|
|
|
networkAddressUrl.url = UA_STRING(argv[1]);
|
|
|
- } else if (strncmp(argv[1], "opc.eth://", 10) == 0) {
|
|
|
+ } else if(strncmp(argv[1], "opc.eth://", 10) == 0) {
|
|
|
transportProfile =
|
|
|
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
|
|
|
- if (argc < 3) {
|
|
|
+ if(argc < 3) {
|
|
|
printf("Error: UADP/ETH needs an interface name\n");
|
|
|
return EXIT_FAILURE;
|
|
|
}
|
|
|
+
|
|
|
networkAddressUrl.networkInterface = UA_STRING(argv[2]);
|
|
|
networkAddressUrl.url = UA_STRING(argv[1]);
|
|
|
} else {
|
|
|
- printf("Error: unknown URI\n");
|
|
|
+ printf ("Error: unknown URI\n");
|
|
|
return EXIT_FAILURE;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return run(&transportProfile, &networkAddressUrl);
|
|
|
}
|
|
|
+
|