Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge 1.4 to master #6554

Merged
merged 52 commits into from
Jun 25, 2024
Merged
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
f083d7a
fix(client): added missing channel state "UA_SECURECHANNELSTATE_OPN_S…
peschuster May 28, 2024
3bf2471
feat(test): Add a test for MonitoredItems on a DataSource with a fixe…
jpfr May 30, 2024
3f40576
fix(build): Correct path for schema files of external build
keba-estr May 31, 2024
8eb85bb
fix(nc): Correct setting of the namespaceIndex. Improve condition for…
NoelGraf Jun 3, 2024
4c148ac
fix(build): fix OpenSSL link flags in build output files
rickboks Jun 4, 2024
dc0c061
refactor(build): Use the SONAME version x.y property to disambiguate …
jpfr Jun 4, 2024
c18a462
refactor(build): Remove UA_PACK_DEBIAN - use the tools/prepare_packag…
jpfr Jun 4, 2024
68ed72e
feat(tests): Extend tests for writing int32 into an enum type
jpfr Jun 4, 2024
11c1a55
refactor(plugins): Make TCP port reuse the default
jpfr Jun 1, 2024
5ddf7ff
feat(server): Avoid malloc when writing "pointerfree" values of the s…
jpfr Jun 4, 2024
3888af3
fix(ns): Add ModelChangeEventType and ConditionType to support subscr…
NoelGraf Jun 5, 2024
069fffd
fix(arch): Fix build error with undefined UA_IPV6
keba-estr Jun 14, 2024
88dae21
fix(client): channel state set to CLOSING without closing underlying …
peschuster Jun 12, 2024
cd44415
test(client) Added test for connection termination when server sent a…
peschuster Jun 14, 2024
5ff50e2
fix(server): Directly write into the external data source if no callb…
jpfr Jun 15, 2024
de087eb
refactor(examples): Move outdated PubSub RT examples to the attic
jpfr Jun 6, 2024
21a6ec8
refactor(tests): Move incomplete ETF tests to the attic
jpfr Jun 15, 2024
caa1e7e
refactor(examples): Clean up server_pubsub_publisher_rt_level.c
jpfr Jun 6, 2024
5cecbde
refactor(examples): Clean up server_pubsub_subscriber_rt_level.c
jpfr Jun 10, 2024
345ed46
refactor(examples): Clean up server_pubsub_rt_field_information_model.c
jpfr Jun 12, 2024
cff036a
refactor(pubsub): Improve the documentation of UA_PubSubRTLevel
jpfr Jun 6, 2024
913ea88
refactor(pubsub): Use UA_PUBSUB_RT_DETERMINISTIC as the ORed combinat…
jpfr Jun 15, 2024
e4b7f42
feat(pubsub): Don't require the server-lock for RT-publishing when th…
jpfr Jun 6, 2024
5702d83
refactor(pubsub): Disentangle cases for the DataSet preparation
jpfr Jun 6, 2024
d0dd4a3
refactor(pubsub): Memset-zero the offset table fields to initialize
jpfr Jun 15, 2024
9aaf137
refactor(pubsub): Store the external value source in the offset table
jpfr Jun 15, 2024
6ab6a32
refactor(pubsub): Update UA_DataSetWriter_prepareDataSet to check for…
jpfr Jun 15, 2024
bf03702
refactor(pubsub): Cache the direct value pointer for the offset table…
jpfr Jun 15, 2024
b30b07d
refactor(pubsub): Cosmetic improvement in ua_pubsub_writergroup.c
jpfr Jun 15, 2024
381e3ad
refactor(pubsub): Explicit sampling in the RT publish path if direct …
jpfr Jun 15, 2024
f98d982
refactor(pubsub): Use the RT configuration as a bit-field
jpfr Jun 15, 2024
5e6841a
fix(pubsub): Use the external value source in the Reader
jpfr Jun 15, 2024
ab2054b
refactor(build): RT examples depend on sigset_t. Do not build on WIN32
jpfr Jun 15, 2024
f0c4533
fix(pubsub): Don't abort Reader with monitoring enabled for messageRe…
jpfr Jun 15, 2024
152e315
fix(pubsub): Don't overwrite the ReaderGroup external value configura…
jpfr Jun 16, 2024
2b76ea7
refactor(tests): Use the RT_DETERMINISTIC setting in check_pubsub_sub…
jpfr Jun 16, 2024
d463871
Merge branch 'publish_pubsub_rt' into merge_14_master_16
jpfr Jun 18, 2024
4f567ac
refactor(pubsub): Replace nested switch with linear if/else for the P…
jpfr Jun 22, 2024
9832cec
refactor(pubsub): Simplify PubSub monitoring handling
jpfr Jun 22, 2024
554369f
refactor(pubsub): Move StandaloneSubscribedDataSet definitions to ua_…
jpfr Jun 22, 2024
c253cd6
refactor(pubsub): Cleanup in ua_pubsub_dataset.c
jpfr Jun 22, 2024
5ae3272
refactor(pubsub): Make the reader connected to the StandaloneSubscrib…
jpfr Jun 22, 2024
7b2094f
refactor(pubsub): Simplifications in ua_pubsub_writer.c
jpfr Jun 22, 2024
dd0dc62
refactor(pubsub): Make the PublishedDataSet attached a Writer a direc…
jpfr Jun 22, 2024
0f61aca
feat(pubsub): Additional size checks for payloads
jpfr Jun 25, 2024
80bcf11
refactor(pubsub): Simplify the handling of payloads with raw content
jpfr Jun 25, 2024
f29aefc
refactor(pubsub): Simplify state handling of the ReaderGroup
jpfr Jun 25, 2024
3aec47a
refactor(pubsub): Remove the dependency on the DataSetMetaData during…
jpfr Jun 25, 2024
7cfea1f
refactor(pubsub): Simplify DataSetReader_processRaw
jpfr Jun 25, 2024
849367e
fix(pubsub): Cleanup allocations after raw decoding
jpfr Jun 25, 2024
018114e
refactor(pubsub): Remove dead code for mirror variables
jpfr Jun 25, 2024
9406efb
refactor(pubsub): Remove unneeded arguments in the DataSetMessage dec…
jpfr Jun 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(examples): Clean up server_pubsub_publisher_rt_level.c
  • Loading branch information
jpfr committed Jun 17, 2024
commit caa1e7e3952794099eaf11d3a353f50e41f518f5
247 changes: 117 additions & 130 deletions examples/pubsub_realtime/server_pubsub_publisher_rt_level.c
Original file line number Diff line number Diff line change
@@ -1,196 +1,183 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http:https://creativecommons.org/publicdomain/zero/1.0/ for more information. */

#include <open62541/plugin/eventloop.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>
#include <open62541/server_pubsub.h>
#include <open62541/server_config_default.h>
#include <open62541/types.h>

#include <stdlib.h>
#include <signal.h>

/* possible options: PUBSUB_CONFIG_FASTPATH_NONE, PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS, PUBSUB_CONFIG_FASTPATH_STATIC_VALUES */
#define PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS
#define PUBSUB_CONFIG_PUBLISH_CYCLE_MS 100
#define PUBSUB_CONFIG_FIELD_COUNT 10

UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier;

/**
* The PubSub RT level example points out the configuration of different PubSub RT-levels. These levels will be
* used together with an RT capable OS for deterministic message generation. The main target is to reduce the time
* spread and effort during the publish cycle. Most of the RT levels are based on pre-generated and buffered
* DataSetMesseges and NetworkMessages. Since changes in the PubSub-configuration will invalidate the buffered
* frames, the PubSub configuration must be frozen after the configuration phase.
* For realtime publishing the following is configured:
*
* - Direct-access data source with double-pointers to a DataValue.
* That allows atomic updates by switching the pointer to a new DataValue.
* - The WriterGroup has UA_PUBSUB_RT_FIXED_SIZE configured. So
* the message is pre-computed and updated only at fixed offsets.
* - A dedicated EventLoop is instantiated for PubSub.
* So the client/server operations do not interfere with the publisher.
*
* Note that for true realtime the following additions are needed (at least):
*
* This example can be configured to compare and measure the different PubSub options by the following define options:
* PUBSUB_CONFIG_FASTPATH_NONE -> The published fields are added to the information model and published in the default non-RT mode
* PUBSUB_CONFIG_FASTPATH_STATIC_VALUES -> The published fields are not visible in the information model. The publish cycle is improved
* by prevent the value lookup within the information model.
* PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS -> The published fields are not visible in the information model. After the PubSub-configuration
* freeze, the NetworkMessages and DataSetMessages will be calculated and buffered. During the publish cycle these buffers will only be updated.
* - An EventLoop + PubSubConnection that supports TSN.
* - The EventLoop uses a fixed TX buffer instead of a malloc each.
* - Preparing the message with a time-offset before sending.
*/

UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier;
/* Values in static locations. We cycle the dvPointers double-pointer to the
* next with atomic operations. */
UA_UInt32 valueStore[PUBSUB_CONFIG_FIELD_COUNT];
UA_DataValue dvStore[PUBSUB_CONFIG_FIELD_COUNT];
UA_DataValue *dvPointers[PUBSUB_CONFIG_FIELD_COUNT];

/* The following PubSub configuration does not differ from the 'normal' configuration */
static void
addMinimalPubSubConfiguration(UA_Server * server){
/* Add one PubSubConnection */
valueUpdateCallback(UA_Server *server, void *data) {
for(int i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; ++i) {
if(dvPointers[i] < &dvStore[PUBSUB_CONFIG_FIELD_COUNT - 1])
UA_atomic_xchg((void**)&dvPointers[i], dvPointers[i]+1);
else
UA_atomic_xchg((void**)&dvPointers[i], &dvStore[0]);
}
}

/* Dedicated EventLoop for PubSub */
volatile UA_Boolean pubSubELRunning = true;
UA_EventLoop *pubSubEL;

static void *
runPubSubEL(void *_) {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
pthread_sigmask(SIG_BLOCK, &set, NULL);
while(pubSubELRunning)
pubSubEL->run(pubSubEL, 100);
return NULL;
}

int main(void) {
UA_Server *server = UA_Server_new();

/* Instantiate the custom EventLoop.
* Will be attached to the PubSubConnection and gets used for everything "above".
* This should be bound to a dedicated core for RT. */
pubSubEL = UA_EventLoop_new_POSIX(UA_Log_Stdout);
UA_ConnectionManager *udpCM =
UA_ConnectionManager_new_POSIX_UDP(UA_STRING("udp connection manager"));
pubSubEL->registerEventSource(pubSubEL, (UA_EventSource *)udpCM);
pubSubEL->start(pubSubEL);

pthread_t pubSubELThread;
pthread_create(&pubSubELThread, NULL, runPubSubEL, NULL);

/* Prepare the values */
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
valueStore[i] = (UA_UInt32) i + 1;
UA_Variant_setScalar(&dvStore[i].value, &valueStore[i], &UA_TYPES[UA_TYPES_UINT32]);
dvStore[i].hasValue = true;
dvPointers[i] = &dvStore[i];
}

/* Add a PubSubConnection */
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(connectionConfig));
connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
connectionConfig.transportProfileUri = UA_STRING("http:https://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
connectionConfig.enabled = UA_TRUE;
connectionConfig.enabled = true;
connectionConfig.eventLoop = pubSubEL;
UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp:https://224.0.0.22:4840/")};
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16;
connectionConfig.publisherId.uint16 = 2234;
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
/* Add one PublishedDataSet */

/* Add a PublishedDataSet */
UA_PublishedDataSetConfig publishedDataSetConfig;
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
publishedDataSetConfig.name = UA_STRING("Demo PDS");
/* Add one DataSetField to the PDS */
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);
}

static void
valueUpdateCallback(UA_Server *server, void *data) {
#if defined PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS || defined PUBSUB_CONFIG_FASTPATH_STATIC_VALUES
for(int i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; ++i)
valueStore[i] = valueStore[i] + 1;
#endif
#if defined PUBSUB_CONFIG_FASTPATH_NONE
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++){
UA_Variant value;
UA_Variant_init(&value);
if(UA_Server_readValue(server, UA_NODEID_NUMERIC(1, 1000 + (UA_UInt32) i), &value) != UA_STATUSCODE_GOOD) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Failed to read publish value. Node number: %zu", i);
continue;
}
UA_UInt32 *intValue = (UA_UInt32 *) value.data;
*intValue = *intValue + 1;
if(UA_Server_writeValue(server, UA_NODEID_NUMERIC(1, 1000 + (UA_UInt32) i), value) != UA_STATUSCODE_GOOD){
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Failed to update publish value. Node number: %zu", i);
continue;
}
UA_Variant_clear(&value);
/* Add DataSetFields with static value source to PDS */
UA_DataSetFieldConfig dsfConfig;
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
/* TODO: Point to a variable in the information model */
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = true;
dsfConfig.field.variable.rtValueSource.staticValueSource = &dvPointers[i];
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent);
}
#endif
}

int main(void) {
UA_Server *server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config);

/*Add standard PubSub configuration (no difference to the std. configuration)*/
addMinimalPubSubConfiguration(server);

/* Add a WriterGroup */
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS;
writerGroupConfig.enabled = UA_FALSE;
writerGroupConfig.enabled = true;
writerGroupConfig.writerGroupId = 100;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE];
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;

/* Change message settings of writerGroup to send PublisherId, WriterGroupId
* in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */
UA_UadpWriterGroupMessageDataType writerGroupMessage;
UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage);
/* Change message settings of writerGroup to send PublisherId,
* WriterGroupId in GroupHeader and DataSetWriterId in PayloadHeader
* of NetworkMessage */
writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask) ((UA_UadpNetworkMessageContentMask) UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
(UA_UadpNetworkMessageContentMask) UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
(UA_UadpNetworkMessageContentMask) UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
(UA_UadpNetworkMessageContentMask) UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER |
(UA_UadpNetworkMessageContentMask) UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
writerGroupConfig.messageSettings.content.decoded.data = &writerGroupMessage;
#ifdef PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
#elif defined PUBSUB_CONFIG_FASTPATH_STATIC_VALUES
writerGroupConfig.rtLevel = UA_PUBSUB_RT_DIRECT_VALUE_ACCESS;
#endif
writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask)
(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER |
UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage,
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]);

UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent);
/* Add one DataSetWriter */

/* Add a DataSetWriter to the WriterGroup */
UA_NodeId dataSetWriterIdent;
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
dataSetWriterConfig.dataSetWriterId = 62541;
dataSetWriterConfig.keyFrameCount = 10;
dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA;

UA_UadpDataSetWriterMessageDataType uadpDataSetWriterMessageDataType;
UA_UadpDataSetWriterMessageDataType_init(&uadpDataSetWriterMessageDataType);
uadpDataSetWriterMessageDataType.dataSetMessageContentMask = (UA_UadpDataSetMessageContentMask) UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER;
dataSetWriterConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
dataSetWriterConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETWRITERMESSAGEDATATYPE];
dataSetWriterConfig.messageSettings.content.decoded.data = &uadpDataSetWriterMessageDataType;

/* Encode fields as RAW-Encoded */
dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA;
uadpDataSetWriterMessageDataType.dataSetMessageContentMask =
UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER;
UA_ExtensionObject_setValue(&dataSetWriterConfig.messageSettings,
&uadpDataSetWriterMessageDataType,
&UA_TYPES[UA_TYPES_UADPDATASETWRITERMESSAGEDATATYPE]);

UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent, &dataSetWriterConfig, &dataSetWriterIdent);
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
&dataSetWriterConfig, &dataSetWriterIdent);

#if defined PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS || defined PUBSUB_CONFIG_FASTPATH_STATIC_VALUES
/* Add one DataSetField with static value source to PDS */
UA_DataValue *staticValueSource = UA_DataValue_new();
UA_DataSetFieldConfig dsfConfig;
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
/* Create Variant and configure as DataSetField source */
valueStore[i] = (UA_UInt32) i * 1000;
UA_Variant_setScalar(&staticValueSource->value, &valueStore[i],
&UA_TYPES[UA_TYPES_UINT32]);
dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
dsfConfig.field.variable.rtValueSource.staticValueSource = &staticValueSource;
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent);
}
#endif
#if defined PUBSUB_CONFIG_FASTPATH_NONE
UA_DataSetFieldConfig dsfConfig;
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++){
UA_VariableAttributes attributes = UA_VariableAttributes_default;
UA_Variant value;
UA_Variant_init(&value);
UA_UInt32 intValue = (UA_UInt32) i * 1000;
UA_Variant_setScalar(&value, &intValue, &UA_TYPES[UA_TYPES_UINT32]);
attributes.value = value;
if(UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 1000 + (UA_UInt32) i),
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "variable"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attributes, NULL, NULL) != UA_STATUSCODE_GOOD){
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Failed to add Publish-Node to the server. Node number: %zu", i);
continue;
}
dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(1, 1000 + (UA_UInt32) i);
dsfConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent);
}
#endif

/* The PubSub configuration is currently editable and the publish callback is not running */
writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS;
UA_Server_updateWriterGroupConfig(server, writerGroupIdent, &writerGroupConfig);

/* Freeze the PubSub configuration (and start implicitly the publish callback) */
/* Freeze the PubSub configuration and enable */
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
UA_Server_setWriterGroupOperational(server, writerGroupIdent);

/* Add a callback that updates the value */
UA_UInt64 callbackId;
UA_Server_addRepeatedCallback(server, valueUpdateCallback, NULL, PUBSUB_CONFIG_PUBLISH_CYCLE_MS, &callbackId);
UA_Server_addRepeatedCallback(server, valueUpdateCallback, NULL,
PUBSUB_CONFIG_PUBLISH_CYCLE_MS, &callbackId);

UA_StatusCode retval = UA_Server_runUntilInterrupt(server);

pubSubELRunning = false;
pthread_join(pubSubELThread, NULL);

pubSubEL->stop(pubSubEL);
while(pubSubEL->state != UA_EVENTLOOPSTATE_STOPPED)
pubSubEL->run(pubSubEL, 0);
pubSubEL->free(pubSubEL);

UA_Server_delete(server);

/* Remove the source after deleting the server.
* It might be accessed during shutdown. */
#if defined PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS || defined PUBSUB_CONFIG_FASTPATH_STATIC_VALUES
UA_DataValue_init(staticValueSource);
UA_DataValue_delete(staticValueSource);
#endif
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}