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

PubSub Publisher example, not setting the DataSetMetaData #2800

Open
4 of 7 tasks
cpipero opened this issue Jun 7, 2019 · 14 comments
Open
4 of 7 tasks

PubSub Publisher example, not setting the DataSetMetaData #2800

cpipero opened this issue Jun 7, 2019 · 14 comments

Comments

@cpipero
Copy link

cpipero commented Jun 7, 2019

Description

I'm running the tutorial_pubsub_publish.c example and everything works fine, as long as I sue the sample code for the subscriber.

But in truth, a true PubSub publisher should also inform potential susbcribers/clients/configurators of its configuration. That's why we have the PubSub information model and methods.

Take as an example the .NET reference implementation from the OPC Foundation, and look at the DataSet configuration in its address space.

Working solution

And look at the same property using the open62541 example:

non-orking solution

This prevents those 3rd parties to interact with my publisher.

Testing attempts

I have been poking around the issues and the test code and the best I could some up was to try code the DataSetMetaData using the available facilities, but so far I have no luck. I don't get any error, but the property does not get set.

Here the complete example with the added Metadata code in the addPublishedDataSet() function of the tutorial_pubsub_publish.c example (just cut&paste to replace the sample code):

/* 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. */

/**
 * .. _pubsub-tutorial:
 *
 * Working with Publish/Subscribe
 * ------------------------------
 *
 * Work in progress: This Tutorial will be continuously extended during the next
 * PubSub batches. More details about the PubSub extension and corresponding
 * open62541 API are located here: :ref:`pubsub`.
 *
 * Publishing Fields
 * ^^^^^^^^^^^^^^^^^
 * The PubSub publish example demonstrate the simplest way to publish
 * informations from the information model over UDP multicast using the UADP
 * encoding.
 *
 * **Connection handling**
 *
 * PubSubConnections can be created and deleted on runtime. More details about
 * the system preconfiguration and connection can be found in
 * ``tutorial_pubsub_connection.c``.
 */

#include <open62541/plugin/log_stdout.h>
#include <open62541/plugin/pubsub_ethernet.h>
#include <open62541/plugin/pubsub_udp.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <ua_pubsub.h>
#include <signal.h>
#include <open62541/server_pubsub.h>
#include <open62541/types.h>

UA_NodeId connectionIdent, publishedDataSetIdent, writerGroupIdent, dataSetWriterIdent;

UA_PublishedDataSetConfig publishedDataSetConfig;
UA_DataSetWriterConfig dataSetWriterConfig;

static void
addPubSubConnection(UA_Server *server, UA_String *transportProfile,
                    UA_NetworkAddressUrlDataType *networkAddressUrl){
    /* Details about the connection configuration and handling are located
     * in the pubsub connection tutorial */
    UA_PubSubConnectionConfig connectionConfig;
    memset(&connectionConfig, 0, sizeof(connectionConfig));
    connectionConfig.name = UA_STRING("Demo UADP Connection");
    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, &connectionIdent);
}

/**
 * **PublishedDataSet handling**
 *
 * The PublishedDataSet (PDS) and PubSubConnection are the toplevel entities and
 * can exist alone. The PDS contains the collection of the published fields. All
 * other PubSub elements are directly or indirectly linked with the PDS or
 * connection. */
static void
addPublishedDataSet(UA_Server *server) {
    /* The PublishedDataSetConfig contains all necessary public
    * informations for the creation of a new PublishedDataSet */

    memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
    publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
    publishedDataSetConfig.name = UA_STRING("Demo PDS");

    UA_DataSetMetaDataType *pMetaData = &publishedDataSetConfig.config.itemsTemplate.metaData;
    UA_DataSetMetaDataType_init (pMetaData);
    pMetaData->name = UA_STRING ("Server localtime");
    /* Static definition of number of fields size to 4 to create four different
     * targetVariables of distinct datatype */
    pMetaData->fieldsSize = 1;
    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].valueRank = -1; /* scalar */


    /* Create new PublishedDataSet based on the PublishedDataSetConfig. */
    UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);

}

/**
 * **DataSetField handling**
 *
 * The DataSetField (DSF) is part of the PDS and describes exactly one published
 * field. */
static void
addDataSetField(UA_Server *server) {


    /* Add a field to the previous created PublishedDataSet */
    UA_NodeId dataSetFieldIdent;
    UA_DataSetFieldConfig dataSetFieldConfig;
    memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig));
    dataSetFieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
    dataSetFieldConfig.field.variable.fieldNameAlias = UA_STRING("Server localtime");
    dataSetFieldConfig.field.variable.promotedField = UA_FALSE;
    dataSetFieldConfig.field.variable.publishParameters.publishedVariable =
    UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
    dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
    UA_Server_addDataSetField(server, publishedDataSetIdent,
                              &dataSetFieldConfig, &dataSetFieldIdent);


}

/**
 * **WriterGroup handling**
 *
 * The WriterGroup (WG) is part of the connection and contains the primary
 * configuration parameters for the message creation. */
static void
addWriterGroup(UA_Server *server) {
    /* Now we create a new WriterGroupConfig and add the group to the existing
     * PubSubConnection. */

    UA_WriterGroupConfig writerGroupConfig;
    memset(&writerGroupConfig, 0, sizeof(writerGroupConfig));
    writerGroupConfig.name = UA_STRING("Demo WriterGroup");
    writerGroupConfig.publishingInterval = 10;
    writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
    UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent);

}

/**
 * **DataSetWriter handling**
 *
 * A DataSetWriter (DSW) is the glue between the WG and the PDS. The DSW is
 * linked to exactly one PDS and contains additional informations for the
 * message generation. */
static void
addDataSetWriter(UA_Server *server) {
    /* We need now a DataSetWriter within the WriterGroup. This means we must
     * create a new DataSetWriterConfig and add call the addWriterGroup function. */
    //UA_NodeId dataSetWriterIdent;

    memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
    dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
    dataSetWriterConfig.dataSetWriterId = 62541;
    dataSetWriterConfig.keyFrameCount = 10;
    UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
                               &dataSetWriterConfig, &dataSetWriterIdent);
}

/**
 * That's it! You're now publishing the selected fields. Open a packet
 * inspection tool of trust e.g. wireshark and take a look on the outgoing
 * packages. The following graphic figures out the packages created by this
 * tutorial.
 *
 * .. figure:: ua-wireshark-pubsub.png
 *     :figwidth: 100 %
 *     :alt: OPC UA PubSub communication in wireshark
 *
 * The open62541 subscriber API will be released later. If you want to process
 * the the datagrams, take a look on the ua_network_pubsub_networkmessage.c
 * which already contains the decoding code for UADP messages.
 *
 * It follows the main server code, making use of the above definitions. */
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);

    UA_Server *server = UA_Server_new();
    UA_ServerConfig *config = UA_Server_getConfig(server);
    UA_ServerConfig_setDefault(config);

    /* Details about the connection configuration and handling are located in
     * the pubsub connection tutorial */
    config->pubsubTransportLayers =
        (UA_PubSubTransportLayer *) UA_calloc(2, sizeof(UA_PubSubTransportLayer));
    if(!config->pubsubTransportLayers) {
        UA_Server_delete(server);
        return EXIT_FAILURE;
    }
    config->pubsubTransportLayers[0] = UA_PubSubTransportLayerUDPMP();
    config->pubsubTransportLayersSize++;
#ifdef UA_ENABLE_PUBSUB_ETH_UADP
    config->pubsubTransportLayers[1] = UA_PubSubTransportLayerEthernet();
    config->pubsubTransportLayersSize++;
#endif

    addPubSubConnection(server, transportProfile, networkAddressUrl);
    addPublishedDataSet(server);
    addDataSetField(server);
    addWriterGroup(server);
    addDataSetWriter(server);

    UA_StatusCode retval = UA_Server_run(server, &running);

    UA_Server_delete(server);
    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:https://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
    UA_NetworkAddressUrlDataType networkAddressUrl =
        {UA_STRING_NULL , UA_STRING("opc.udp:https://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:https://", 10) == 0) {
            networkAddressUrl.url = UA_STRING(argv[1]);
        } else if (strncmp(argv[1], "opc.eth:https://", 10) == 0) {
            transportProfile =
                UA_STRING("http:https://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
            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");
            return EXIT_FAILURE;
        }
    }

    return run(&transportProfile, &networkAddressUrl);
}

Used CMake options:

UA_ENABLE_AMALGAMATION:BOOL=OFF
UA_ENABLE_COVERAGE:BOOL=OFF
UA_ENABLE_CUSTOM_NODESTORE:BOOL=OFF
UA_ENABLE_DA:BOOL=ON
UA_ENABLE_DETERMINISTIC_RNG:BOOL=OFF
UA_ENABLE_DISCOVERY:BOOL=ON
UA_ENABLE_DISCOVERY_MULTICAST:BOOL=OFF
UA_ENABLE_DISCOVERY_SEMAPHORE:BOOL=ON
UA_ENABLE_ENCRYPTION:BOOL=ON
UA_ENABLE_EXPERIMENTAL_HISTORIZING:BOOL=OFF
UA_ENABLE_HARDENING:BOOL=ON
UA_ENABLE_HISTORIZING:BOOL=OFF
UA_ENABLE_IMMUTABLE_NODES:BOOL=OFF
UA_ENABLE_JSON_ENCODING:BOOL=OFF
UA_ENABLE_MALLOC_SINGLETON:BOOL=OFF
UA_ENABLE_METHODCALLS:BOOL=ON
UA_ENABLE_MULTITHREADING:BOOL=OFF
UA_ENABLE_NODEMANAGEMENT:BOOL=ON
UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS:BOOL=ON
UA_ENABLE_PUBSUB:BOOL=ON
UA_ENABLE_PUBSUB_CUSTOM_PUBLISH_HANDLING:BOOL=OFF
UA_ENABLE_PUBSUB_DELTAFRAMES:BOOL=ON
UA_ENABLE_PUBSUB_ETH_UADP:BOOL=OFF
UA_ENABLE_PUBSUB_INFORMATIONMODEL:BOOL=ON
UA_ENABLE_PUBSUB_INFORMATIONMODEL_METHODS:BOOL=ON
UA_ENABLE_QUERY:BOOL=OFF
UA_ENABLE_STATIC_ANALYZER:STRING=OFF
UA_ENABLE_STATUSCODE_DESCRIPTIONS:BOOL=ON
UA_ENABLE_SUBSCRIPTIONS:BOOL=ON
UA_ENABLE_SUBSCRIPTIONS_EVENTS:BOOL=OFF
UA_ENABLE_TYPENAMES:BOOL=ON
UA_ENABLE_UNIT_TESTS_MEMCHECK:BOOL=OFF
UA_ENABLE_UNIT_TEST_FAILURE_HOOKS:BOOL=OFF
UA_ENABLE_VALGRIND_INTERACTIVE:BOOL=OFF
UA_FILE_NS0:BOOL=OFF
UA_LOGLEVEL:STRING=300

Checklist

Please provide the following information:

  • open62541 Version (release number or git tag): 0.4.0
  • Other OPC UA SDKs used (client or server):
  • Operating system: Debian 9.6 Stretch
  • Logs (with UA_LOGLEVEL set as low as necessary) attached
  • Wireshark network dump attached
  • Self-contained code example attached
  • Critical issue: trying to get it working for the OPAF Interop next week ;-)
@jpfr
Copy link
Member

jpfr commented Jun 8, 2019

@andreasebner Can you comment on this?

@mpostol
Copy link

mpostol commented Jun 8, 2019

@cpipero said:

`But in truth, a true PubSub publisher should also inform potential susbcribers/clients/configurators of its configuration. That's why we have the PubSub information model and methods.'

Let me recall a well-known definition:

Publisher-subscriber communication pattern

Publish-subscribe is a messages distribution scenario where senders of messages, called publishers, do not send them directly to specific receivers, called subscribers, but instead categorize published messages into classes without knowledge of which subscribers if any, there may be. Similarly, subscribers express interest in one or more classes and only receive messages that are of interest, without knowledge of which publishers, if any, there are. In the publish-subscribe model, subscribers typically receive only a subset of the total messages published. The process of selecting messages for reception and processing is called filtering. There are two common forms of filtering: topic-based and content-based.

Read the full story OPC UA PubSub Main Technology Features

I hope you may be also interested to read: Underlying Transport over Ethernet

Hope it helps.
Mariusz

@jpfr
Copy link
Member

jpfr commented Jun 9, 2019

Fixed in #2802. Can you confirm @cpipero ?

@cpipero
Copy link
Author

cpipero commented Jun 9, 2019

Thank you @jpfr . We are getting closer.
I added the changes and yes, this improves things.

I now have to understand if there is anything I should add in my code.

Let me explain details (sorry for the lengthy description, but I hope it helps).

Adding the Fix

I added the changes in #2802 . That basically exposes the PublishedData information correctly and without me needing to change the code in the tutorial_pubsub_publish.c file (it basically just works, as I'd expect).

Here's a screenshot:

Working example

However, the DataSetMetaData property of the DataSet is not announced correctly (but at least is not null anymore).

In this screenshot you see the comparison between the .NET server and the tutorial_pubsub_publish, where the Fields array is empty where it should instead list the one DataField for the current time:

Side by side

Additional Improvements Needed

This part of the PubSub information model is important because it gives the potential subscribers the needed info to map the DataSetWriter's fields.

Look at the .NET Sample Config UI that now can successfully navigate to the remote writer dataset (now it doesn' t crash). But if I want to configure the DataReader, I am missing the necessary metadata:

No Cigar

Possible Next Steps

I looked at the code in the src/pubsub/ua_pubsub_ns0.c (the file changed by #2802) and I can see that a lot happens in the onRead handler:

When the client browses to the PublishedData property (around line 136) the following code maps the data from the published variable to the PublishedData fields. That's why we can see that info correctly in the client:

[...]
case UA_NS0ID_PUBLISHEDDATAITEMSTYPE: {
            UA_PublishedDataSet *publishedDataSet = UA_PublishedDataSet_findPDSbyId(server, *myNodeId);
            if(!publishedDataSet)
                return;
            switch(nodeContext->elementClassiefier) {
                case UA_NS0ID_PUBLISHEDDATAITEMSTYPE_PUBLISHEDDATA: {
                    UA_PublishedVariableDataType *pvd = (UA_PublishedVariableDataType *)
                            UA_calloc(publishedDataSet->fieldSize, sizeof(UA_PublishedVariableDataType));
                    size_t counter = 0;
                    UA_DataSetField *field;
                    LIST_FOREACH(field, &publishedDataSet->fields, listEntry) {
                        pvd[counter].attributeId = UA_ATTRIBUTEID_VALUE;
                        pvd[counter].publishedVariable = field->config.field.variable.publishParameters.publishedVariable;
                        //UA_NodeId_copy(&field->config.field.variable.publishParameters.publishedVariable, &pvd[counter].publishedVariable);
                        counter++;
                    }
                    UA_Variant_setArray(&value, pvd, publishedDataSet->fieldSize,
                                        &UA_TYPES[UA_TYPES_PUBLISHEDVARIABLEDATATYPE]);
                    break;
                }
[...]

However, when the client requests the DataSetMeta data (hence switching the nodeContext->elementClassiefier (spelling ;-) to UA_NS0ID_PUBLISHEDDATAITEMSTYPE_DATASETMETADATA) the only thing that happens (I think) it's copying the PublishedData.DataSetMetaData attributes to the DataSetMetaData property value. But at that point nobody has set anything in the publishedDataSet->dataSetMetaData fields.

//publishedDataSet->dataSetMetaData is EMPTY at this point so nothing will happen
 case UA_NS0ID_PUBLISHEDDATAITEMSTYPE_DATASETMETADATA: {
                    UA_Variant_setScalarCopy(&value, &publishedDataSet->dataSetMetaData, &UA_TYPES[UA_TYPES_DATASETMETADATATYPE]);
                    break;
                }

I don't have time now to attempt to fix this, but I figured I write this and later I keep looking at it while someone might be seeing this and get some ideas.

Thank you again for the tremendous help.

@cpipero
Copy link
Author

cpipero commented Jun 10, 2019

Small Update

Upon more investigation of the following code (copied from above, repeated for convenience)

[...]
case UA_NS0ID_PUBLISHEDDATAITEMSTYPE: {
            UA_PublishedDataSet *publishedDataSet = UA_PublishedDataSet_findPDSbyId(server, *myNodeId);
            if(!publishedDataSet)
                return;
            switch(nodeContext->elementClassiefier) {
                case UA_NS0ID_PUBLISHEDDATAITEMSTYPE_PUBLISHEDDATA: {
                    UA_PublishedVariableDataType *pvd = (UA_PublishedVariableDataType *)
                            UA_calloc(publishedDataSet->fieldSize, sizeof(UA_PublishedVariableDataType));
                    size_t counter = 0;
                    UA_DataSetField *field;
                    LIST_FOREACH(field, &publishedDataSet->fields, listEntry) {
                        pvd[counter].attributeId = UA_ATTRIBUTEID_VALUE;
                        pvd[counter].publishedVariable = field->config.field.variable.publishParameters.publishedVariable;
                        //UA_NodeId_copy(&field->config.field.variable.publishParameters.publishedVariable, &pvd[counter].publishedVariable);
                        counter++;
                    }
                    UA_Variant_setArray(&value, pvd, publishedDataSet->fieldSize,
                                        &UA_TYPES[UA_TYPES_PUBLISHEDVARIABLEDATATYPE]);
                    break;
                }
[...]

the publishedDataSet->fields do not contain any metadata also.

This is something that I think the UA_PubSubManager should do...

I'm now looking at the ua_pubsub_manager.c trying to make sense of it

@cpipero
Copy link
Author

cpipero commented Jun 12, 2019

@jpfr would you have any idea on this? It might make it for a great Interop week if I can fix this.

If you look at my last comment, I'm either very close or way off track.

Thanks in advance,

Cos

@jpfr
Copy link
Member

jpfr commented Jun 12, 2019

Hey Cos,

you are looking at the right code to set the content during reading.
Two things are required:

  1. Set and store the UA_FieldMetaData in the internal DataSet representation
  2. Return the fieldmetadata in the onRead callback.

The UA_DataSetField structure (publishedDataSet->fields) contains a UA_FieldMetaData entry.
So you can copy that over during onRead.

But the UA_FieldMetaData is not automatically filled atm.
Most of the information can be read from the variable definition when the field is created.

We might have to extend the public API so that the user can fill in more MetaData information.
At this time you need to expose some internals to add the data for your demo.

@andreasebner
Copy link
Contributor

andreasebner commented Jun 12, 2019

@cpipero thanks for your feedback and detailed reports. As you already recognized, the pubsub metadata field is currently not supported. The DataSetMetaData should be created and updated by the pubsub core without any configuration.

One short comment to your example code: The OPC UA PubSub Standard defines four methods to add published informations (AddPublishedDataItems 9.1.4.5.2., AddPublishedDataItemsTemplate 9.1.4.5.4., AddPublishedEvents 9.1.4.5.3. and AddPublishedEventsTemplate 9.1.4.5.5). The so called 'Template' methods allow to inject a complete pubsub configuration into the server including the metadata. The non template methods create the metadata (if supported) on the fly. The union 'itemsTemplate' within the UA_PublishedDataSetConfig is used to provide the template functionalty by the open62541 PubSub API. If the 'standard' PublishedDataItems are used you will not need to configure the 'itemTemplate'.

We are currently working on a PubSub feature list, which will contain supported/partly supportet and currently not supported aspects of the pubsub standard. I assume the finialization and release of this list next week. I agree with your statement about the importance of the PubSub-metadata for the subscribers. We will work on this feature and try to provide a first implementation as soon as possible.

@cpipero
Copy link
Author

cpipero commented Jun 14, 2019

Thank you guys,
I have been looking at a way to "cheat" just to do some final testing at the interop this week.

In reality, as you are probably saying, things should start happening when the server adds datafields.

But in the meanwhile, I tried to modify the OnRead call in the ua_pubsub_ns0.c.

I'm not successful yet, because I don't have much time left and I'm just shooting in the dark.

The following code (very ugly and should NEVER be user) does succeed on the server side, but the client doesn't like it and eventually leads to bad outcomes.

But I put it here for you to look at and see if it can be easily "steered" in the right direction.

I'm making up the field name (using the tutorial sample I will only have one field all the times).

Aagin, this is just for you to see if modifying the OnRead this way makes sense. Clearly it needs to be improved.

static void
onRead(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext,
       const UA_NodeId *nodeid, void *context,
       const UA_NumericRange *range, const UA_DataValue *data) {
    UA_Variant value;
    UA_Variant_init(&value);
    const UA_NodePropertyContext *nodeContext = (const UA_NodePropertyContext*)context;
    const UA_NodeId *myNodeId = &nodeContext->parentNodeId;

    switch(nodeContext->parentClassifier){
    case UA_NS0ID_PUBSUBCONNECTIONTYPE: {
        UA_PubSubConnection *pubSubConnection =
            UA_PubSubConnection_findConnectionbyId(server, *myNodeId);
        switch(nodeContext->elementClassiefier) {
        case UA_NS0ID_PUBSUBCONNECTIONTYPE_PUBLISHERID:
            if(pubSubConnection->config->publisherIdType == UA_PUBSUB_PUBLISHERID_STRING) {
                UA_Variant_setScalar(&value, &pubSubConnection->config->publisherId.numeric,
                                     &UA_TYPES[UA_TYPES_STRING]);
            } else {
                UA_Variant_setScalar(&value, &pubSubConnection->config->publisherId.numeric,
                                     &UA_TYPES[UA_TYPES_UINT32]);
            }
            break;
        default:
            UA_LOG_WARNING(&server->config.logger, UA_LOGCATEGORY_SERVER,
                           "Read error! Unknown property.");
        }
        break;
    }
    case UA_NS0ID_WRITERGROUPTYPE: {
        UA_WriterGroup *writerGroup = UA_WriterGroup_findWGbyId(server, *myNodeId);
        if(!writerGroup)
            return;
        switch(nodeContext->elementClassiefier){
        case UA_NS0ID_WRITERGROUPTYPE_PUBLISHINGINTERVAL:
            UA_Variant_setScalar(&value, &writerGroup->config.publishingInterval,
                                 &UA_TYPES[UA_TYPES_DURATION]);
            break;
        default:
            UA_LOG_WARNING(&server->config.logger, UA_LOGCATEGORY_SERVER,
                           "Read error! Unknown property.");
        }
        break;
    }
    case UA_NS0ID_PUBLISHEDDATAITEMSTYPE: {
        UA_PublishedDataSet *publishedDataSet = UA_PublishedDataSet_findPDSbyId(server, *myNodeId);
        if(!publishedDataSet)
            return;
        switch(nodeContext->elementClassiefier) {
        case UA_NS0ID_PUBLISHEDDATAITEMSTYPE_PUBLISHEDDATA: {
            UA_PublishedVariableDataType *pvd = (UA_PublishedVariableDataType *)
                UA_calloc(publishedDataSet->fieldSize, sizeof(UA_PublishedVariableDataType));
            size_t counter = 0;
            UA_DataSetField *field;
            LIST_FOREACH(field, &publishedDataSet->fields, listEntry) {
                pvd[counter].attributeId = UA_ATTRIBUTEID_VALUE;
                pvd[counter].publishedVariable = field->config.field.variable.publishParameters.publishedVariable;
                //UA_NodeId_copy(&field->config.field.variable.publishParameters.publishedVariable, &pvd[counter].publishedVariable);
                counter++;
            }
            UA_Variant_setArray(&value, pvd, publishedDataSet->fieldSize,
                                &UA_TYPES[UA_TYPES_PUBLISHEDVARIABLEDATATYPE]);
            break;
        }
        //HERE's WHAT I ADDED
        case UA_NS0ID_PUBLISHEDDATAITEMSTYPE_DATASETMETADATA: {

            UA_DataSetMetaDataType *dsmd = UA_DataSetMetaDataType_new();
            UA_DataSetMetaDataType_init(dsmd);

            dsmd->configurationVersion = publishedDataSet->dataSetMetaData.configurationVersion;
            dsmd->dataSetClassId = publishedDataSet->dataSetMetaData.dataSetClassId;
            dsmd->name = publishedDataSet->config.name;


            dsmd->fieldsSize++;
            dsmd->fields =
                    (UA_FieldMetaData *) UA_realloc(dsmd->fields, dsmd->fieldsSize * sizeof(UA_FieldMetaData));
            for (size_t i = 0; i < dsmd->fieldsSize; ++i) { //TODO:of course, here I should use the List access methods
                                                            //against the publishedDataSet.fields LIST
                                                            // so I can get all the attributes from those
                                                            //but for now I'm just testing something
                UA_FieldMetaData_init(&dsmd->fields[i]);

                UA_NodeId_copy (&UA_TYPES[UA_TYPES_DATETIME].typeId,
                                &dsmd->fields[i].dataType);
                dsmd->fields[i].builtInType = UA_NS0ID_DATETIME;
                dsmd->fields[i].dataSetFieldId = UA_Guid_random(); //TODO: gotta figure this one out
                UA_String strTmp = UA_STRING ("DateTime");
                UA_String_copy (&strTmp, &dsmd->fields[0].name);
                dsmd->fields[i].valueRank = -1; /* scalar */

            }

            UA_Variant_setScalarCopy(&value, dsmd, &UA_TYPES[UA_TYPES_DATASETMETADATATYPE]);

            break;
        }
        case UA_NS0ID_PUBLISHEDDATAITEMSTYPE_CONFIGURATIONVERSION: {
            UA_Variant_setScalarCopy(&value, &publishedDataSet->dataSetMetaData.configurationVersion,
                                     &UA_TYPES[UA_TYPES_CONFIGURATIONVERSIONDATATYPE]);
            break;
        }
        default:
            UA_LOG_WARNING(&server->config.logger, UA_LOGCATEGORY_SERVER,
                           "Read error! Unknown property.");
        }
        break;
    }
    default:
        UA_LOG_WARNING(&server->config.logger, UA_LOGCATEGORY_SERVER,
                       "Read error! Unknown parent element.");
    }
    UA_StatusCode retval = UA_Server_writeValue(server, *nodeid, value);
    if (retval != UA_STATUSCODE_GOOD) UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, "Read error! %s", UA_StatusCode_name(retval));
}

Sorry I am doing this in a rush. I will look at it more this weekend with more time.

EDITED: improved version after @jpfr hints (still not decoding correctly).

@jpfr
Copy link
Member

jpfr commented Jun 14, 2019

UA_realloc(dsmd->fields, dsmd->fieldsSize);

This is not big enough.
It should be UA_realloc(dsmd->fields, dsmd->fieldsSize * sizeof(UA_FieldMetaData));

@jpfr
Copy link
Member

jpfr commented Jun 14, 2019

And then you can just say UA_FieldMetaData_init(&dsmd->fields[i]); to init.

@cpipero
Copy link
Author

cpipero commented Jun 14, 2019

Thanks @jpfr , that was a silly one...
And I forgot that I had an example in the subscriber tutorial. I edited my code above.
Still no decoding but I'm getting closer.

I know I'm implementing all this one level above where it should be but it helps understanding. We probably need to add some good documentation on this whole pub-sub. Let me know if I can help

@cpipero
Copy link
Author

cpipero commented Sep 3, 2019

Just capturing a note from our release meeting. Cross reference Pull Request #3052 that starts fixing this.

@Stasik0
Copy link
Contributor

Stasik0 commented May 18, 2021

Bump in 2021. Looks like the DataSetMetaData of ReaderGroup1 is still empty...
image
@cpipero @jpfr

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants