OpcUA Open62541 PubSub : How to publish value change?

会有一股神秘感。 提交于 2020-01-06 05:14:26

问题


This is my cross post at https://github.com/open62541/open62541/issues/3275

Hello,

I'm trying to understand how PubSub work. My understanding is that publisher publishes the change to OPC UA middle ware, subscriber who subscribes to publisher will be notified by OPC UA middleware. I'm taking a look to this example : https://github.com/open62541/open62541/blob/v1.0/examples/pubsub/tutorial_pubsub_publish.c
We can see that in publisher side we need to :

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

The published data is made inside addDataSetField, in this case is sever time, then the server time is continuously publish across the multicast network. For my case I just want to update single variable, say uint32 variable when some condition is met. To doing so I change the the addDataSetField as following and then in another thread call Update() to update the variable.

UA_NodeId published_dataSet_id_, dataSet_field_id_;

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);
    UA_NODEID_NUMERIC(0, UA_NS0ID_UINT32);

    dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
    UA_Server_addDataSetField(server, published_dataSet_id_,
                              &dataSetFieldConfig, &dataSet_field_id_);
}

void Update()
{
        UA_Int32 myInteger = 42;
        UA_Variant value;
        UA_Variant_setScalar(&value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
        UA_Server_writeValue(server, dataSet_field_id_, value );
}

I got

[2019-11-14 14:28:51.467 (UTC+0700)] info/session Connection 0 | SecureChannel 0 | Session g=00000001-0000-0000-0000-000000000000 | WriteRequest returned status code BadNodeClassInvalid

What is the correct way to update variable ?


回答1:


I provided solution in open62541 issues
https://github.com/open62541/open62541/issues/3275#issuecomment-554454083

======================================================================
I was looking around in some other examples in the repo and found the solution as below: 1. I need to add a variable node

//node id
UA_NodeId gCounterNodePublisher = {1, UA_NODEIDTYPE_NUMERIC, {1234}};

addVariableNode( UA_Server * server )
{
    UA_UInt64 publishValue = 0xCAFEDEADCAFEBEAF; //0xCAFE
    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, gCounterNodePublisher,
                              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);
}  

in the addDataSetField we need to configure dataset with same node ID

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);
    //UA_NODEID_NUMERIC(0, UA_NS0ID_INT32);
    gCounterNodePublisher;
    dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
    UA_Server_addDataSetField(server, published_dataSet_id_,
                              &dataSetFieldConfig, &dataSet_field_id_);
}  
  1. Now the tricky thing is I need to call addVariableNode before addPubSubConnection
    //server init and configuration
    addVariableNode(server);
    addPubSubConnection(server, transportProfile, networkAddressUrl);  
    addPublishedDataSet(server);  
    addDataSetField(server);  
    addWriterGroup(server);  
    addDataSetWriter(server);   

Now I can made my variable as Published data.
3. To update the value my solution is we need to add callback function on write/read that variable. (Probably we have another way)

writeVariableNode( UA_Server * server )
{
    //Write a different integer value
    static UA_UInt64 myInteger = 0xCAFEDEADCAFEBEAF;
    UA_Variant myVar;
    UA_Variant_init(&myVar);
    myInteger++;
    UA_Variant_setScalar(&myVar, &myInteger, &UA_TYPES[UA_TYPES_UINT64]);
    UA_Server_writeValue(server, gCounterNodePublisher, myVar);

    // Set the status code of the value to an error code. The function
    //UA_Server_write provides access to the raw service. The above
    //UA_Server_writeValue is syntactic sugar for writing a specific node
    //attribute with the write service. 
    UA_WriteValue wv;
    UA_WriteValue_init(&wv);
    wv.nodeId = gCounterNodePublisher;
    wv.attributeId = UA_ATTRIBUTEID_VALUE;
    wv.value.status = UA_STATUSCODE_BADNOTCONNECTED;
    wv.value.hasStatus = true;
    UA_Server_write(server, &wv);

   //Reset the variable to a good statuscode with a value 
    wv.value.hasStatus = false;
    wv.value.value = myVar;
    wv.value.hasValue = true;
    UA_Server_write(server, &wv);
}

void onReadImpl(UA_Server *server,
               const UA_NodeId *sessionId, void *sessionContext,
               const UA_NodeId *nodeid, void *nodeContext,
               const UA_NumericRange *range, const UA_DataValue *data) {

    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
            "OPCUARuntime::onReadImpl");

    // Write a different integer value 
    static UA_UInt64 myInteger = 0xCAFEDEADCAFEBEAF;
    UA_Variant myVar;
    UA_Variant_init(&myVar);
    myInteger++;
    UA_Variant_setScalar(&myVar, &myInteger, &UA_TYPES[UA_TYPES_UINT64]);
    UA_Server_writeValue(server, gCounterNodePublisher, myVar);

    /// Set the status code of the value to an error code. The function
    // UA_Server_write provides access to the raw service. The above
    // UA_Server_writeValue is syntactic sugar for writing a specific node
    // attribute with the write service. 
    UA_WriteValue wv;
    UA_WriteValue_init(&wv);
    wv.nodeId = gCounterNodePublisher;
    wv.attributeId = UA_ATTRIBUTEID_VALUE;
    wv.value.status = UA_STATUSCODE_BADNOTCONNECTED;
    wv.value.hasStatus = true;
    UA_Server_write(server, &wv);

   //Reset the variable to a good statuscode with a value 
    wv.value.hasStatus = false;
    wv.value.value = myVar;
    wv.value.hasValue = true;
    UA_Server_write(server, &wv);
}

void onWriteImpl(UA_Server *server,
               const UA_NodeId *sessionId, void *sessionContext,
               const UA_NodeId *nodeid, void *nodeContext,
               const UA_NumericRange *range, const UA_DataValue *data) {

    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
            "OPCUARuntime::onWriteImpl");
}

addValueCallbackToVariableNode( UA_Server *server )
{
    UA_ValueCallback callback ;
    callback.onRead = onReadImpl;
    callback.onWrite = onWriteImpl;
    UA_Server_setVariableNode_valueCallback(server, gCounterNodePublisher, callback);
}  

The calling sequence in main function must be like this:

    //variable node need to be added before pubsub connection is establish  
    addVariableNode( server_ );

    //Register callback on read/write published data
    addValueCallbackToVariableNode( server_ );
    addPubSubConnection( server_, &transportProfile, &networkAddressUrl);
    addPublishedDataSet( server_ );
    addDataSetField( server_ );
    addWriterGroup( server_ );
    addDataSetWriter( server_ );
    //addVariableNode( server_ );

    //shutdown is taken place inside run once running is false
    UA_StatusCode retval = UA_Server_run(server_, &running_);


来源:https://stackoverflow.com/questions/58855146/opcua-open62541-pubsub-how-to-publish-value-change

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!