open62541 / open62541

Open source implementation of OPC UA (OPC Unified Architecture) aka IEC 62541 licensed under Mozilla Public License v2.0
http://open62541.org
Mozilla Public License 2.0
2.61k stars 1.25k forks source link

Server: custom OptionSet type mismatch error #3934

Open juangburgos opened 4 years ago

juangburgos commented 4 years ago

Description

I am trying to add custom OptionSet data types (not variables) to QUaServer. But I keep getting type mismatch errors when setting the type or the value to a variable that uses those custom OptionSet data types.

I have made small adjustments to the open62541 source to ignore the errors, and now UaExpert can read the OptionSet value. But when I try to write the value from UaExpert, the server always return type mismatch error.

Background Information / Reproduction Steps

To create the custom OptionSet type, I did the following:

// register enum as new ua data type
UA_DataTypeAttributes ddaatt = UA_DataTypeAttributes_default;
ddaatt.description = UA_LOCALIZEDTEXT((char*)(""), charOptionSetName);
ddaatt.displayName = UA_LOCALIZEDTEXT((char*)(""), charOptionSetName);

// ...

UA_Server_addDataTypeNode(server,
        reqNodeId,
        UA_NODEID_NUMERIC(0, UA_NS0ID_OPTIONSET),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
        UA_QUALIFIEDNAME(1, charOptionSetName),
        ddaatt,
        NULL,
        NULL);
// setup variable attrs
UA_StatusCode retVal         = UA_STATUSCODE_GOOD;
UA_VariableAttributes attr   = UA_VariableAttributes_default;
attr.minimumSamplingInterval = 0.000000;
attr.userAccessLevel         = 1;
attr.accessLevel             = 1;
attr.valueRank               = 1;
attr.arrayDimensionsSize     = 1;
UA_UInt32 arrayDimensions[1];
arrayDimensions[0]           = numOptionSetValues;
attr.arrayDimensions         = &arrayDimensions[0];
attr.dataType                = UA_NODEID_NUMERIC(0, UA_NS0ID_LOCALIZEDTEXT);
// create array of option set values
UA_LocalizedText * valueOptionSet = numOptionSetValues > 0 ? (UA_LocalizedText*)UA_malloc(sizeof(UA_LocalizedText) * numOptionSetValues) : nullptr;
for (size_t i = 0; i < numOptionSetValues; i++)
{
    UA_init(&valueOptionSet[i], &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
    valueOptionSet[i] = ... // set names here
}
// create variant with array of option set values
UA_Variant_setArray(&attr.value, valueOptionSet, (UA_Int32)numOptionSetValues, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
attr.displayName   = UA_LOCALIZEDTEXT((char*)"", (char*)"OptionSetValues");
attr.description   = UA_LOCALIZEDTEXT((char*)"", (char*)"");
attr.writeMask     = 0;
attr.userWriteMask = 0;
// add variable with array of option set values
UA_NodeId optionSetValuesNodeId;
retVal |= UA_Server_addVariableNode(server,
                                    UA_NODEID_NULL, 
                                    *parent, 
                                    UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
                                    UA_QUALIFIEDNAME (0, (char*)"OptionSetValues"),
                                    UA_NODEID_NUMERIC(0, UA_NS0ID_PROPERTYTYPE), 
                                    attr, 
                                    NULL, 
                                    &optionSetValuesNodeId);
// make mandatory
retVal |= UA_Server_addReference(server,
                                 optionSetValuesNodeId,
                                 UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
                                 UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), 
                                 true);
UA_Server_writeDataType(server,
        variableNodeId,
        optionSetTypeNodeId);
UA_Variant open62541value;

UA_OptionSet uaOptionSet;
uaOptionSet.value = ... // set bytearray
uaOptionSet.validBits = ... // set bytearray

UA_Variant_setScalar(&open62541value, &uaOptionSet, &UA_TYPES[UA_TYPES_OPTIONSET]);

UA_Server_writeValue(server, variableNodeId, open62541value);

This is where I get a type mismatch error, specifically in compatibleDataType:

// ... other code
/* More checks for the data type of real values (variants) */
if(isValue) {
    /* If value is a built-in type: The target data type may be a sub type of
     * the built-in type. (e.g. UtcTime is sub-type of DateTime and has a
     * DateTime value). A type is builtin if its NodeId is in Namespace 0 and
     * has a numeric identifier <= 25 (DiagnosticInfo) */
    if(dataType->namespaceIndex == 0 &&
       dataType->identifierType == UA_NODEIDTYPE_NUMERIC &&
       dataType->identifier.numeric <= 25 &&
       isNodeInTree_singleRef(server, constraintDataType, dataType,
                              UA_REFERENCETYPEINDEX_HASSUBTYPE))
        return true;
}

Here I commented out the /*dataType->identifier.numeric <= 25 &&*/ part, because in my case, constraintDataType is a subtype of dataType and taking out the numeric <= 25, then I succeed in setting the value.

Now UaExpert can see it:

image

image

But when I write thevalue from UaExpert, then the server fails to receive it with a type mismatch error, as it can be seen in the attached Wireshark capture:

option_set.zip

The open62541 code involved is again the compatibleDataType function.

I don't know what else to try. Any help is appreciated.

Used CMake options:

cmake -DUA_NAMESPACE_ZERO_FULL=ON ..

I try to describe the steps below in detail, but I cannot easily provide self-contained C code because it is mostly C++, although it is possible to reproduce it with the latest master branch of QUaServer building with:

qmake "CONFIG+=ua_alarms_conditions" -r -tp vc examples.pro

And running a server project with the code:

#include <QCoreApplication>
#include <QDebug>

#include <QUaServer>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QUaServer server;

    QUaFolderObject * objsFolder = server.objectsFolder();

    QUaOptionSet optionSet(56);

    QUaOptionSetMap optionSetMap;
    optionSetMap.insert(0, "BIT00");
    optionSetMap.insert(1, "BIT01");
    optionSetMap.insert(2, "BIT02");
    optionSetMap.insert(3, "BIT03");
    optionSetMap.insert(4, "BIT04");
    optionSetMap.insert(5, "BIT05");
    optionSetMap.insert(6, "BIT06");

    server.registerOptionSet("MyOptionSet", optionSetMap);

    QUaBaseDataVariable * varOptionSet = objsFolder->addBaseDataVariable("my_optionset");
    varOptionSet->setDataTypeOptionSet("MyOptionSet");
    varOptionSet->setWriteAccess(true);

    varOptionSet->setValue(optionSet);

    server.start();

    return a.exec(); 
}

Checklist

Please provide the following information:

jpfr commented 4 years ago

As a general rule, it might make sense to always allow subtypes of the required DataType. And not only subtypes for the builtin types.

Is the value decoded correctly in the server? Or do you get an ExtensionObject?

We might change the rules to also allow un-decoded ExtensionObjects if the type is correct.

juangburgos commented 4 years ago

Actually I already hacked it externally, by creating a custom UA_DataType for every new OptionSet type I create, that custom UA_DataType has the new OptionSet's nodeId, this way I can bypass the type mismatch error given by open62541.

So now I can read custom OptionSets on UaExpert, but I cannot write them. I still get a type mismatch error on write. I haven't looked deeper into why this error happens as for now I only need OptionSet read access, but is in my TODO list for QUaServer.

jpfr commented 4 years ago

Did you add your custom UA_Datatype to the server?

Also, each UA_Datatype has two identifiers. One for the information model. And the other just for the binary encoding. Make sure that the binary encoding id is set correctly.

NoelGraf commented 3 years ago

What is the status of the issue?

juangburgos commented 3 years ago

Haven't been able to make it work yet.