|
@@ -0,0 +1,322 @@
|
|
|
+
|
|
|
+ * See http:
|
|
|
+ *
|
|
|
+ * Copyright 2018-2019 (c) Kalycito Infotech
|
|
|
+ * Copyright 2019 (c) Fraunhofer IOSB (Author: Andreas Ebner)
|
|
|
+ * Copyright 2019 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
|
|
|
+ */
|
|
|
+
|
|
|
+#include <signal.h>
|
|
|
+#include <stdio.h>
|
|
|
+#include <time.h>
|
|
|
+#include <open62541/server.h>
|
|
|
+#include <open62541/server_config_default.h>
|
|
|
+#include <open62541/plugin/log_stdout.h>
|
|
|
+#include <open62541/plugin/pubsub_ethernet.h>
|
|
|
+#include "bufmalloc.h"
|
|
|
+
|
|
|
+#define ETH_PUBLISH_ADDRESS "opc.eth://0a-00-27-00-00-08"
|
|
|
+#define ETH_INTERFACE "enp0s8"
|
|
|
+#define MAX_MEASUREMENTS 10000
|
|
|
+#define MILLI_AS_NANO_SECONDS (1000 * 1000)
|
|
|
+#define SECONDS_AS_NANO_SECONDS (1000 * 1000 * 1000)
|
|
|
+#define CLOCKID CLOCK_REALTIME
|
|
|
+#define SIG SIGUSR1
|
|
|
+#define PUB_INTERVAL 0.25
|
|
|
+#define DATA_SET_WRITER_ID 62541
|
|
|
+#define MEASUREMENT_OUTPUT "publisher_measurement.csv"
|
|
|
+
|
|
|
+UA_NodeId counterNodePublisher = {1, UA_NODEIDTYPE_NUMERIC, {1234}};
|
|
|
+UA_Int64 pubIntervalNs;
|
|
|
+UA_ServerCallback pubCallback = NULL;
|
|
|
+UA_Server *pubServer;
|
|
|
+UA_Boolean running = true;
|
|
|
+void *pubData;
|
|
|
+timer_t pubEventTimer;
|
|
|
+struct sigevent pubEvent;
|
|
|
+struct sigaction signalAction;
|
|
|
+
|
|
|
+
|
|
|
+UA_Int32 currentPublishCycleTime[MAX_MEASUREMENTS+1];
|
|
|
+struct timespec calculatedCycleStartTime[MAX_MEASUREMENTS+1];
|
|
|
+struct timespec cycleStartDelay[MAX_MEASUREMENTS+1];
|
|
|
+struct timespec cycleDuration[MAX_MEASUREMENTS+1];
|
|
|
+size_t publisherMeasurementsCounter = 0;
|
|
|
+
|
|
|
+static void
|
|
|
+timespec_diff(struct timespec *start, struct timespec *stop,
|
|
|
+ struct timespec *result) {
|
|
|
+ if((stop->tv_nsec - start->tv_nsec) < 0) {
|
|
|
+ result->tv_sec = stop->tv_sec - start->tv_sec - 1;
|
|
|
+ result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
|
|
|
+ } else {
|
|
|
+ result->tv_sec = stop->tv_sec - start->tv_sec;
|
|
|
+ result->tv_nsec = stop->tv_nsec - start->tv_nsec;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static void
|
|
|
+nanoSecondFieldConversion(struct timespec *timeSpecValue) {
|
|
|
+ while(timeSpecValue->tv_nsec > (SECONDS_AS_NANO_SECONDS - 1)) {
|
|
|
+ timeSpecValue->tv_sec += 1;
|
|
|
+ timeSpecValue->tv_nsec -= SECONDS_AS_NANO_SECONDS;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static void
|
|
|
+publishInterrupt(int sig, siginfo_t* si, void* uc) {
|
|
|
+ if(si->si_value.sival_ptr != &pubEventTimer) {
|
|
|
+ UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "stray signal");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ struct timespec begin, end;
|
|
|
+ clock_gettime(CLOCKID, &begin);
|
|
|
+ useMembufAlloc();
|
|
|
+ pubCallback(pubServer, pubData);
|
|
|
+ useNormalAlloc();
|
|
|
+ clock_gettime(CLOCKID, &end);
|
|
|
+
|
|
|
+ if(publisherMeasurementsCounter >= MAX_MEASUREMENTS)
|
|
|
+ return;
|
|
|
+
|
|
|
+
|
|
|
+ currentPublishCycleTime[publisherMeasurementsCounter] = pubIntervalNs;
|
|
|
+
|
|
|
+
|
|
|
+ timespec_diff(&calculatedCycleStartTime[publisherMeasurementsCounter],
|
|
|
+ &begin, &cycleStartDelay[publisherMeasurementsCounter]);
|
|
|
+
|
|
|
+
|
|
|
+ timespec_diff(&begin, &end, &cycleDuration[publisherMeasurementsCounter]);
|
|
|
+
|
|
|
+
|
|
|
+ calculatedCycleStartTime[publisherMeasurementsCounter + 1].tv_nsec +=
|
|
|
+ calculatedCycleStartTime[publisherMeasurementsCounter].tv_nsec + pubIntervalNs;
|
|
|
+ calculatedCycleStartTime[publisherMeasurementsCounter + 1].tv_sec =
|
|
|
+ calculatedCycleStartTime[publisherMeasurementsCounter].tv_sec;
|
|
|
+ nanoSecondFieldConversion(&calculatedCycleStartTime[publisherMeasurementsCounter + 1]);
|
|
|
+
|
|
|
+ publisherMeasurementsCounter++;
|
|
|
+
|
|
|
+ if(publisherMeasurementsCounter == MAX_MEASUREMENTS) {
|
|
|
+
|
|
|
+ FILE *fpPublisher = fopen(MEASUREMENT_OUTPUT, "w");
|
|
|
+ for(UA_UInt32 i = 0; i < publisherMeasurementsCounter; i++) {
|
|
|
+ fprintf(fpPublisher, "%u, %u, %ld.%09ld, %ld.%09ld, %ld.%09ld\n",
|
|
|
+ i,
|
|
|
+ currentPublishCycleTime[i],
|
|
|
+ calculatedCycleStartTime[i].tv_sec,
|
|
|
+ calculatedCycleStartTime[i].tv_nsec,
|
|
|
+ cycleStartDelay[i].tv_sec,
|
|
|
+ cycleStartDelay[i].tv_nsec,
|
|
|
+ cycleDuration[i].tv_sec,
|
|
|
+ cycleDuration[i].tv_nsec);
|
|
|
+ }
|
|
|
+ fclose(fpPublisher);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * /src/pubsub/ua_pubsub_manager.c. We provide a custom implementation here to
|
|
|
+ * use system interrupts instead if time-triggered callbacks in the OPC UA
|
|
|
+ * server control flow. */
|
|
|
+
|
|
|
+UA_StatusCode
|
|
|
+UA_PubSubManager_addRepeatedCallback(UA_Server *server,
|
|
|
+ UA_ServerCallback callback,
|
|
|
+ void *data, UA_Double interval_ms,
|
|
|
+ UA_UInt64 *callbackId) {
|
|
|
+ if(pubCallback) {
|
|
|
+ UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
|
|
+ "At most one publisher can be registered for interrupt callbacks");
|
|
|
+ return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ int resultTimerCreate = 0;
|
|
|
+ pubServer = server;
|
|
|
+ pubCallback = callback;
|
|
|
+ pubData = data;
|
|
|
+ pubIntervalNs = (UA_Int64) (interval_ms * MILLI_AS_NANO_SECONDS);
|
|
|
+
|
|
|
+
|
|
|
+ memset(&signalAction, 0, sizeof(signalAction));
|
|
|
+ signalAction.sa_flags = SA_SIGINFO;
|
|
|
+ signalAction.sa_sigaction = publishInterrupt;
|
|
|
+ sigemptyset(&signalAction.sa_mask);
|
|
|
+ sigaction(SIG, &signalAction, NULL);
|
|
|
+
|
|
|
+
|
|
|
+ memset(&pubEventTimer, 0, sizeof(pubEventTimer));
|
|
|
+ pubEvent.sigev_notify = SIGEV_SIGNAL;
|
|
|
+ pubEvent.sigev_signo = SIG;
|
|
|
+ pubEvent.sigev_value.sival_ptr = &pubEventTimer;
|
|
|
+ resultTimerCreate = timer_create(CLOCKID, &pubEvent, &pubEventTimer);
|
|
|
+ if(resultTimerCreate != 0) {
|
|
|
+ UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
|
|
+ "Failed to create a system event");
|
|
|
+ return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ struct itimerspec timerspec;
|
|
|
+ timerspec.it_interval.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
|
|
|
+ timerspec.it_interval.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
|
|
|
+ timerspec.it_value.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
|
|
|
+ timerspec.it_value.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
|
|
|
+ resultTimerCreate = timer_settime(pubEventTimer, 0, &timerspec, NULL);
|
|
|
+ if(resultTimerCreate != 0) {
|
|
|
+ UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
|
|
+ "Failed to arm the system timer");
|
|
|
+ timer_delete(pubEventTimer);
|
|
|
+ return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ publisherMeasurementsCounter = 0;
|
|
|
+ clock_gettime(CLOCKID, &calculatedCycleStartTime[0]);
|
|
|
+ calculatedCycleStartTime[0].tv_nsec += pubIntervalNs;
|
|
|
+ nanoSecondFieldConversion(&calculatedCycleStartTime[0]);
|
|
|
+
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+UA_StatusCode
|
|
|
+UA_PubSubManager_changeRepeatedCallbackInterval(UA_Server *server,
|
|
|
+ UA_UInt64 callbackId,
|
|
|
+ UA_Double interval_ms) {
|
|
|
+ struct itimerspec timerspec;
|
|
|
+ int resultTimerCreate = 0;
|
|
|
+ pubIntervalNs = (UA_Int64) (interval_ms * MILLI_AS_NANO_SECONDS);
|
|
|
+ timerspec.it_interval.tv_sec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
|
|
|
+ timerspec.it_interval.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
|
|
|
+ timerspec.it_value.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
|
|
|
+ timerspec.it_value.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
|
|
|
+ resultTimerCreate = timer_settime(pubEventTimer, 0, &timerspec, NULL);
|
|
|
+ if(resultTimerCreate != 0) {
|
|
|
+ UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
|
|
|
+ "Failed to arm the system timer");
|
|
|
+ timer_delete(pubEventTimer);
|
|
|
+ return UA_STATUSCODE_BADINTERNALERROR;
|
|
|
+ }
|
|
|
+
|
|
|
+ clock_gettime(CLOCKID, &calculatedCycleStartTime[publisherMeasurementsCounter]);
|
|
|
+ calculatedCycleStartTime[publisherMeasurementsCounter].tv_nsec += pubIntervalNs;
|
|
|
+ nanoSecondFieldConversion(&calculatedCycleStartTime[publisherMeasurementsCounter]);
|
|
|
+
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+UA_PubSubManager_removeRepeatedPubSubCallback(UA_Server *server, UA_UInt64 callbackId) {
|
|
|
+ timer_delete(pubEventTimer);
|
|
|
+ pubCallback = NULL;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+addPubSubConfiguration(UA_Server* server) {
|
|
|
+ UA_NodeId connectionIdent;
|
|
|
+ UA_NodeId publishedDataSetIdent;
|
|
|
+ UA_NodeId writerGroupIdent;
|
|
|
+
|
|
|
+ UA_PubSubConnectionConfig connectionConfig;
|
|
|
+ memset(&connectionConfig, 0, sizeof(connectionConfig));
|
|
|
+ connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
|
|
|
+ connectionConfig.transportProfileUri =
|
|
|
+ UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
|
|
|
+ connectionConfig.enabled = true;
|
|
|
+ UA_NetworkAddressUrlDataType networkAddressUrl =
|
|
|
+ {UA_STRING(ETH_INTERFACE), UA_STRING(ETH_PUBLISH_ADDRESS)};
|
|
|
+ UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
|
|
|
+ &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
|
|
+ connectionConfig.publisherId.numeric = UA_UInt32_random();
|
|
|
+ UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
|
|
|
+
|
|
|
+ UA_PublishedDataSetConfig publishedDataSetConfig;
|
|
|
+ memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
|
|
|
+ publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
|
|
|
+ publishedDataSetConfig.name = UA_STRING("Demo PDS");
|
|
|
+
|
|
|
+ UA_Server_addPublishedDataSet(server, &publishedDataSetConfig,
|
|
|
+ &publishedDataSetIdent);
|
|
|
+
|
|
|
+ UA_NodeId dataSetFieldIdentCounter;
|
|
|
+ UA_DataSetFieldConfig counterValue;
|
|
|
+ memset(&counterValue, 0, sizeof(UA_DataSetFieldConfig));
|
|
|
+ counterValue.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
|
|
|
+ counterValue.field.variable.fieldNameAlias = UA_STRING ("Counter Variable 1");
|
|
|
+ counterValue.field.variable.promotedField = UA_FALSE;
|
|
|
+ counterValue.field.variable.publishParameters.publishedVariable = counterNodePublisher;
|
|
|
+ counterValue.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
|
|
|
+ UA_Server_addDataSetField(server, publishedDataSetIdent, &counterValue,
|
|
|
+ &dataSetFieldIdentCounter);
|
|
|
+
|
|
|
+ UA_WriterGroupConfig writerGroupConfig;
|
|
|
+ memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
|
|
|
+ writerGroupConfig.name = UA_STRING("Demo WriterGroup");
|
|
|
+ writerGroupConfig.publishingInterval = PUB_INTERVAL;
|
|
|
+ writerGroupConfig.enabled = UA_FALSE;
|
|
|
+ writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
|
|
|
+ UA_Server_addWriterGroup(server, connectionIdent,
|
|
|
+ &writerGroupConfig, &writerGroupIdent);
|
|
|
+
|
|
|
+ UA_NodeId dataSetWriterIdent;
|
|
|
+ UA_DataSetWriterConfig dataSetWriterConfig;
|
|
|
+ memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
|
|
|
+ dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
|
|
|
+ dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID;
|
|
|
+ dataSetWriterConfig.keyFrameCount = 10;
|
|
|
+ UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
|
|
|
+ &dataSetWriterConfig, &dataSetWriterIdent);
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+addServerNodes(UA_Server* server) {
|
|
|
+ UA_UInt64 publishValue = 0;
|
|
|
+ UA_VariableAttributes publisherAttr = UA_VariableAttributes_default;
|
|
|
+ UA_Variant_setScalar(&publisherAttr.value, &publishValue, &UA_TYPES[UA_TYPES_UINT64]);
|
|
|
+ publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher Counter");
|
|
|
+ publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
|
|
|
+ UA_Server_addVariableNode(server, counterNodePublisher,
|
|
|
+ UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
|
|
+ UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
|
|
+ UA_QUALIFIEDNAME(1, "Publisher Counter"),
|
|
|
+ UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
|
|
|
+ publisherAttr, NULL, NULL);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static void stopHandler(int sign) {
|
|
|
+ UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
|
|
|
+ running = UA_FALSE;
|
|
|
+}
|
|
|
+
|
|
|
+int main(void) {
|
|
|
+ signal(SIGINT, stopHandler);
|
|
|
+ signal(SIGTERM, stopHandler);
|
|
|
+
|
|
|
+ UA_ServerConfig* config = UA_ServerConfig_new_minimal(4840, NULL);
|
|
|
+ config->pubsubTransportLayers = (UA_PubSubTransportLayer *)
|
|
|
+ UA_malloc(sizeof(UA_PubSubTransportLayer));
|
|
|
+ if(!config->pubsubTransportLayers) {
|
|
|
+ UA_ServerConfig_delete(config);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ config->pubsubTransportLayers[0] = UA_PubSubTransportLayerEthernet();
|
|
|
+ config->pubsubTransportLayersSize++;
|
|
|
+
|
|
|
+ UA_Server *server = UA_Server_new(config);
|
|
|
+ addServerNodes(server);
|
|
|
+ addPubSubConfiguration(server);
|
|
|
+
|
|
|
+
|
|
|
+ UA_StatusCode retval = UA_Server_run(server, &running);
|
|
|
+ UA_Server_delete(server);
|
|
|
+ UA_ServerConfig_delete(config);
|
|
|
+
|
|
|
+ return (int)retval;
|
|
|
+}
|