diff --git a/devOpcuaSup/UaSdk/DataElementUaSdk.cpp b/devOpcuaSup/UaSdk/DataElementUaSdk.cpp index b02d4348..976e70b2 100644 --- a/devOpcuaSup/UaSdk/DataElementUaSdk.cpp +++ b/devOpcuaSup/UaSdk/DataElementUaSdk.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -151,84 +152,91 @@ DataElementUaSdk::setIncomingData(const UaVariant &value, // Try to get the structure definition from the dictionary UaStructureDefinition definition = pitem->structureDefinition(extensionObject.encodingTypeId()); if (!definition.isNull()) { - if (!definition.isUnion()) { - // ExtensionObject is a structure - // Decode the ExtensionObject to a UaGenericValue to provide access to the structure fields - if (extensionObject.encoding() == UaExtensionObject::EncodeableObject) - extensionObject.changeEncoding(UaExtensionObject::Binary); - UaGenericStructureValue genericValue; - genericValue.setGenericValue(extensionObject, definition); - - if (!mapped) { - if (debug() >= 5) - std::cout << " ** creating index-to-element map for child elements" << std::endl; - if (timefrom) { - for (int i = 0; i < definition.childrenCount(); i++) { - if (*timefrom == definition.child(i).name().toUtf8()) { - timesrc = i; - } - } - OpcUa_BuiltInType t = genericValue.value(timesrc).type(); - if (timesrc == -1) { - errlogPrintf( - "%s: timestamp element %s not found - using source timestamp\n", - pitem->recConnector->getRecordName(), - timefrom->c_str()); - } else if (t != OpcUaType_DateTime) { - errlogPrintf("%s: timestamp element %s has invalid type %s - using " - "source timestamp\n", - pitem->recConnector->getRecordName(), - timefrom->c_str(), - variantTypeString(t)); - timesrc = -1; + // Decode the ExtensionObject to a UaGenericValue to provide access to the structure fields + if (extensionObject.encoding() == UaExtensionObject::EncodeableObject) + extensionObject.changeEncoding(UaExtensionObject::Binary); + if (!mapped) { + if (debug() >= 5) + std::cout << " ** creating index-to-element map for child elements" << std::endl; + if (timefrom) { + for (int i = 0; i < definition.childrenCount(); i++) { + if (*timefrom == definition.child(i).name().toUtf8()) { + timesrc = i; } } - for (auto &it : elements) { - auto pelem = it.lock(); - for (int i = 0; i < definition.childrenCount(); i++) { - if (pelem->name == definition.child(i).name().toUtf8()) { - elementMap.insert({i, it}); - delete pelem->enumChoices; - pelem->enumChoices = pitem->session->getEnumChoices(definition.child(i).enumDefinition()); - } + OpcUa_BuiltInType t = definition.child(timesrc).valueType(); + if (timesrc == -1) { + errlogPrintf( + "%s: timestamp element %s not found - using source timestamp\n", + pitem->recConnector->getRecordName(), + timefrom->c_str()); + } else if (t != OpcUaType_DateTime) { + errlogPrintf("%s: timestamp element %s has invalid type %s - using " + "source timestamp\n", + pitem->recConnector->getRecordName(), + timefrom->c_str(), + variantTypeString(t)); + timesrc = -1; + } + } + for (auto &it : elements) { + auto pelem = it.lock(); + for (int i = 0; i < definition.childrenCount(); i++) { + if (pelem->name == definition.child(i).name().toUtf8()) { + elementMap.insert({i, it}); + delete pelem->enumChoices; + pelem->enumChoices = pitem->session->getEnumChoices(definition.child(i).enumDefinition()); } } - if (debug() >= 5) - std::cout << " ** " << elementMap.size() << "/" << elements.size() - << " child elements mapped to a " - << "structure of " << definition.childrenCount() << " elements" << std::endl; - mapped = true; } + if (debug() >= 5) + std::cout << " ** " << elementMap.size() << "/" << elements.size() + << " child elements mapped to a " + << "structure of " << definition.childrenCount() << " elements" << std::endl; + mapped = true; + } - if (timesrc >= 0) - pitem->tsData = epicsTimeFromUaVariant(genericValue.value(timesrc)); - else - pitem->tsData = pitem->tsSource; - - for (auto &it : elementMap) { - auto pelem = it.second.lock(); - OpcUa_StatusCode stat; - const UaVariant &memberValue = genericValue.value(it.first, &stat); - if (stat != OpcUa_Good) { - // Absent optional field: The incoming type is Null. - // Pass a fake value with correct data type so that - // later writing will not fail with type mismatch. - const UaStructureField &field = definition.child(it.first); - OpcUa_Variant fakeValue; - OpcUa_Variant_Clear(&fakeValue); - fakeValue.Datatype = field.valueType(); - fakeValue.ArrayType = field.valueRank() != 0; - if (debug()) - std::cerr << pitem->recConnector->getRecordName() - << " element " << pelem->name - << " absent optional " << variantTypeString((OpcUa_BuiltInType)fakeValue.Datatype) - << (fakeValue.ArrayType ? " array" : " scalar") - << std::endl; - pelem->setIncomingData(fakeValue, ProcessReason::readFailure); + if (timesrc >= 0) + pitem->tsData = epicsTimeFromUaVariant(UaGenericStructureValue(extensionObject, definition).value(timesrc)); + else + pitem->tsData = pitem->tsSource; + + for (auto &it : elementMap) { + auto pelem = it.second.lock(); + OpcUa_StatusCode stat; + if (definition.isUnion()) { + UaGenericUnionValue genericValue(extensionObject, definition); + int index = genericValue.switchValue() - 1; + if (it.first == index) { + pelem->setIncomingData(genericValue.value(), reason); + stat = OpcUa_Good; } else { + stat = OpcUa_BadNoData; + } + } else { + const UaVariant &memberValue = UaGenericStructureValue(extensionObject, definition).value(it.first, &stat); + if (stat == OpcUa_Good) { pelem->setIncomingData(memberValue, reason); } } + if (stat == OpcUa_BadNoData) { + // Absent optional field or union choice not taken: + // Pass a fake value with correct data type so that + // later writing will not fail with type mismatch. + const UaStructureField &field = definition.child(it.first); + OpcUa_Variant fakeValue; + OpcUa_Variant_Clear(&fakeValue); + fakeValue.Datatype = field.valueType(); + fakeValue.ArrayType = field.valueRank() != 0; + if (debug()) + std::cerr << pitem->recConnector->getRecordName() + << " element " << pelem->name + << (definition.isUnion() ? " not taken choice " : " absent optional " ) + << variantTypeString((OpcUa_BuiltInType)fakeValue.Datatype) + << (fakeValue.ArrayType ? " array" : " scalar") + << std::endl; + pelem->setIncomingData(fakeValue, ProcessReason::readFailure); + } } } else @@ -287,33 +295,6 @@ DataElementUaSdk::setState(const ConnectionStatus state) } } -// Helper to update one data structure element from pointer to child -bool -DataElementUaSdk::updateDataInGenericValue (UaGenericStructureValue &value, - const int index, - std::shared_ptr pelem) -{ - bool updated = false; - { // Scope of Guard G - Guard G(pelem->outgoingLock); - if (pelem->isDirty()) { - value.setField(index, pelem->getOutgoingData()); - pelem->isdirty = false; - updated = true; - } - } - if (debug() >= 4) { - if (updated) { - std::cout << "Data from child element " << pelem->name - << " inserted into data structure" << std::endl; - } else { - std::cout << "Data from child element " << pelem->name - << " ignored (not dirty)" << std::endl; - } - } - return updated; -} - const UaVariant & DataElementUaSdk::getOutgoingData () { @@ -332,57 +313,87 @@ DataElementUaSdk::getOutgoingData () // Try to get the structure definition from the dictionary UaStructureDefinition definition = pitem->structureDefinition(extensionObject.encodingTypeId()); - if (!definition.isNull()) { - if (!definition.isUnion()) { - // ExtensionObject is a structure - // Decode the ExtensionObject to a UaGenericValue to provide access to the structure fields - UaGenericStructureValue genericValue; - genericValue.setGenericValue(extensionObject, definition); - - if (!mapped) { - if (debug() >= 5) - std::cout << " ** creating index-to-element map for child elements" << std::endl; - for (auto &it : elements) { - auto pelem = it.lock(); - for (int i = 0; i < definition.childrenCount(); i++) { - if (pelem->name == definition.child(i).name().toUtf8()) { - elementMap.insert({i, it}); - if (updateDataInGenericValue(genericValue, i, pelem)) - isdirty = true; - } - } - } - if (debug() >= 5) - std::cout << " ** " << elementMap.size() << "/" << elements.size() - << " child elements mapped to a " - << "structure of " << definition.childrenCount() << " elements" << std::endl; - mapped = true; - } else { - for (auto &it : elementMap) { - auto pelem = it.second.lock(); - if (updateDataInGenericValue(genericValue, it.first, pelem)) - isdirty = true; - } - } - if (isdirty) { - if (debug() >= 4) - std::cout << "Encoding changed data structure to outgoingData of element " << name - << std::endl; - genericValue.toExtensionObject(extensionObject); - outgoingData.setExtensionObject(extensionObject, OpcUa_True); - } else { - if (debug() >= 4) - std::cout << "Returning unchanged outgoingData of element " << name - << std::endl; - } - } - - } else + if (definition.isNull()) { errlogPrintf( "Cannot get a structure definition for extensionObject with dataTypeID %s " "/ encodingTypeID %s - check access to type dictionary\n", extensionObject.dataTypeId().toString().toUtf8(), extensionObject.encodingTypeId().toString().toUtf8()); + return outgoingData; + } + + // Decode the ExtensionObject to a UaGenericValue to provide access to the structure fields + if (!mapped) { + if (debug() >= 5) + std::cout << " ** creating index-to-element map for child elements" << std::endl; + for (auto &it : elements) { + auto pelem = it.lock(); + for (int i = 0; i < definition.childrenCount(); i++) { + if (pelem->name == definition.child(i).name().toUtf8()) { + elementMap.insert({i, it}); + } + } + } + if (debug() >= 5) + std::cout << " ** " << elementMap.size() << "/" << elements.size() + << " child elements mapped to a " + << "structure of " << definition.childrenCount() << " elements" << std::endl; + mapped = true; + } + if (definition.isUnion()) { + UaGenericUnionValue genericUnion(extensionObject, definition); + for (auto &it : elementMap) { + auto pelem = it.second.lock(); + bool updated = false; + { // Scope of Guard G + Guard G(pelem->outgoingLock); + if (pelem->isDirty()) { + genericUnion.setValue(it.first + 1, pelem->getOutgoingData()); + pelem->isdirty = false; + isdirty = true; + updated = true; + } + } + if (debug() >= 4) + std::cout << "Data from child element " << pelem->name + << (updated ? " inserted into union" : " ignored (not dirty)") + << std::endl; + } + if (isdirty) + genericUnion.toExtensionObject(extensionObject); + + } else { + UaGenericStructureValue genericStruct(extensionObject, definition); + for (auto &it : elementMap) { + auto pelem = it.second.lock(); + bool updated = false; + { // Scope of Guard G + Guard G(pelem->outgoingLock); + if (pelem->isDirty()) { + genericStruct.setField(it.first, pelem->getOutgoingData()); + pelem->isdirty = false; + isdirty = true; + updated = true; + } + } + if (debug() >= 4) + std::cout << "Data from child element " << pelem->name + << (updated ? " inserted into structure" : " ignored (not dirty)") + << std::endl; + } + if (isdirty) + genericStruct.toExtensionObject(extensionObject); + } + if (isdirty) { + if (debug() >= 4) + std::cout << "Encoding changed data structure to outgoingData of element " << name + << std::endl; + outgoingData.setExtensionObject(extensionObject, OpcUa_True); + } else { + if (debug() >= 4) + std::cout << "Returning unchanged outgoingData of element " << name + << std::endl; + } } } return outgoingData; @@ -513,7 +524,24 @@ DataElementUaSdk::readScalar (char *value, const size_t num, (void) recGblSetSevr(prec, READ_ALARM, MINOR_ALARM); } UaVariant& data = upd->getData(); - if (enumChoices) { + if (data.type() == OpcUaType_ExtensionObject) { + UaExtensionObject extensionObject; + data.toExtensionObject(extensionObject); + UaStructureDefinition definition = pitem->structureDefinition(extensionObject.encodingTypeId()); + if (definition.isUnion()) { + memset(value, 0, num); + UaGenericUnionValue genericValue(extensionObject, definition); + int switchValue = genericValue.switchValue(); + if (switchValue > 0) { + snprintf(value, num, "%s:%s", + definition.child(switchValue-1).name().toUtf8(), + genericValue.value().toString().toUtf8()); + } else { + (void) recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + ret = 1; + } + } + } else if (enumChoices) { OpcUa_Int32 enumIndex; data.toInt32(enumIndex); auto it = enumChoices->find(enumIndex); @@ -929,8 +957,48 @@ DataElementUaSdk::writeScalar (const char *value, const epicsUInt32 len, dbCommo unsigned long ul; double d; char* end = nullptr; + OpcUa_BuiltInType type = incomingData.type(); - switch (incomingData.type()) { + if (type == OpcUaType_ExtensionObject) + { + UaExtensionObject extensionObject; + incomingData.toExtensionObject(extensionObject); + UaStructureDefinition definition = pitem->structureDefinition(extensionObject.encodingTypeId()); + if (definition.isUnion()) { + if (value[0] == '\0') { + } else for (int i = 0; i < definition.childrenCount(); i++) { + UaGenericUnionValue genericValue(extensionObject, definition); + const UaString& memberName = definition.child(i).name(); + epicsUInt32 namelen = static_cast(memberName.length()); + if ((strncmp(value, memberName.toUtf8(), namelen) == 0) + && value[namelen] == ':') { + // temporarily set incomingData to selected union member type + UaVariant saveValue = incomingData; + OpcUa_Variant fakeValue; + OpcUa_Variant_Clear(&fakeValue); + fakeValue.Datatype = definition.child(i).valueType(); + incomingData = fakeValue; + const UaNodeId& typeId = definition.child(i).typeId(); + enumChoices = pitem->session->getEnumChoices(&typeId); + // recurse to set union member + ret = writeScalar(value+namelen+1, len-(namelen+1), prec); + // restore incomingData type to union + delete enumChoices; + enumChoices = nullptr; + incomingData = saveValue; + if (ret == 0) { + Guard G(outgoingLock); + genericValue.setValue(i+1, outgoingData); + genericValue.toExtensionObject(extensionObject); + outgoingData.setExtensionObject(extensionObject,true); + } + return ret; + } + } + } + } + + switch (type) { case OpcUaType_String: { // Scope of Guard G Guard G(outgoingLock); @@ -1074,10 +1142,11 @@ DataElementUaSdk::writeScalar (const char *value, const epicsUInt32 len, dbCommo } default: errlogPrintf("%s : unsupported conversion from string to %s for outgoing data\n", - prec->name, variantTypeString(incomingData.type())); + prec->name, variantTypeString(type)); (void) recGblSetSevr(prec, WRITE_ALARM, INVALID_ALARM); return -1; } + if (ret != 0) { errlogPrintf("%s : value \"%s\" out of range\n", prec->name, value); diff --git a/devOpcuaSup/UaSdk/DataElementUaSdk.h b/devOpcuaSup/UaSdk/DataElementUaSdk.h index 48ce166e..ebef3bc8 100644 --- a/devOpcuaSup/UaSdk/DataElementUaSdk.h +++ b/devOpcuaSup/UaSdk/DataElementUaSdk.h @@ -683,9 +683,6 @@ class DataElementUaSdk : public DataElement const std::string &targetTypeName) const; void checkWriteArray(OpcUa_BuiltInType expectedType, const std::string &targetTypeName) const; void dbgWriteArray(const epicsUInt32 targetSize, const std::string &targetTypeName) const; - bool updateDataInGenericValue(UaGenericStructureValue &value, - const int index, - std::shared_ptr pelem); // Structure always returns true to ensure full traversal bool isDirty() const { return isdirty || !isleaf; } void diff --git a/devOpcuaSup/open62541/DataElementOpen62541.cpp b/devOpcuaSup/open62541/DataElementOpen62541.cpp index 07d10778..a943c9e1 100644 --- a/devOpcuaSup/open62541/DataElementOpen62541.cpp +++ b/devOpcuaSup/open62541/DataElementOpen62541.cpp @@ -109,26 +109,31 @@ DataElementOpen62541::show (const int level, const unsigned int indent) const // Older open62541 version 1.2 has no UA_DataType_getStructMember // Code from open62541 version 1.3.7 modified for compatibility with version 1.2 -// and extended to return isOptional flag -UA_Boolean +// and extended to return isOptional flag and index for union + +inline const UA_DataType* +memberTypeOf(const UA_DataType *type, const UA_DataTypeMember *m) { +#ifdef UA_DATATYPES_USE_POINTER + return m->memberType; +#else + const UA_DataType *typelists[2] = { UA_TYPES, &type[-type->typeIndex] }; + return &typelists[!m->namespaceZero][m->memberTypeIndex]; +#endif +} + +UA_UInt32 UA_DataType_getStructMemberExt(const UA_DataType *type, const char *memberName, size_t *outOffset, const UA_DataType **outMemberType, UA_Boolean *outIsArray, UA_Boolean *outIsOptional) { if(type->typeKind != UA_DATATYPEKIND_STRUCTURE && - type->typeKind != UA_DATATYPEKIND_OPTSTRUCT) - return false; + type->typeKind != UA_DATATYPEKIND_OPTSTRUCT && + type->typeKind != UA_DATATYPEKIND_UNION) + return 0; size_t offset = 0; -#ifndef UA_DATATYPES_USE_POINTER - const UA_DataType *typelists[2] = { UA_TYPES, &type[-type->typeIndex] }; -#endif - for(size_t i = 0; i < type->membersSize; ++i) { + for(UA_UInt32 i = 0; i < type->membersSize; ++i) { const UA_DataTypeMember *m = &type->members[i]; -#ifndef UA_DATATYPES_USE_POINTER - const UA_DataType *mt = &typelists[!m->namespaceZero][m->memberTypeIndex]; -#else - const UA_DataType *mt = m->memberType; -#endif + const UA_DataType *mt = memberTypeOf(type, m); offset += m->padding; if(strcmp(memberName, m->memberName) == 0) { @@ -136,10 +141,12 @@ UA_DataType_getStructMemberExt(const UA_DataType *type, const char *memberName, *outMemberType = mt; *outIsArray = m->isArray; *outIsOptional = m->isOptional; - return true; + return i+1; } - if(!m->isOptional) { + if (type->typeKind == UA_DATATYPEKIND_UNION) { + offset = 0; + } else if(!m->isOptional) { if(!m->isArray) { offset += mt->memSize; } else { @@ -156,19 +163,20 @@ UA_DataType_getStructMemberExt(const UA_DataType *type, const char *memberName, } } - return false; + return 0; } void DataElementOpen62541::createMap (const UA_DataType *type, const std::string *timefrom) { + if (debug() >= 5) + std::cout << " ** creating index-to-element map for child elements" << std::endl; + switch (typeKindOf(type)) { case UA_DATATYPEKIND_STRUCTURE: - case UA_DATATYPEKIND_OPTSTRUCT: { - if (debug() >= 5) - std::cout << " ** creating index-to-element map for child elements" << std::endl; - + case UA_DATATYPEKIND_OPTSTRUCT: + case UA_DATATYPEKIND_UNION: if (timefrom) { const UA_DataType *timeMemberType; UA_Boolean timeIsArray; @@ -199,11 +207,20 @@ DataElementOpen62541::createMap (const UA_DataType *type, for (auto &it : elements) { auto pelem = it.lock(); - if (UA_DataType_getStructMemberExt(type, pelem->name.c_str(), + if ((pelem->index = UA_DataType_getStructMemberExt(type, pelem->name.c_str(), &pelem->offset, &pelem->memberType, &pelem->isArray, - &pelem->isOptional)) { + &pelem->isOptional))) { + if (debug() >= 5) + std::cout << typeKindName(typeKindOf(type)) + << " " << pelem + << " index=" << pelem->index + << " offset=" << pelem->offset + << " type=" << variantTypeString(pelem->memberType) + << (pelem->isArray ? "[]" : "") + << (pelem->isOptional ? " optional" : "") + << std::endl; if (typeKindOf(pelem->memberType) == UA_DATATYPEKIND_ENUM) { pelem->enumChoices = pitem->session->getEnumChoices(&pelem->memberType->typeId); } @@ -220,7 +237,6 @@ DataElementOpen62541::createMap (const UA_DataType *type, << variantTypeString(type) << " of " << type->membersSize << " elements" << std::endl; break; - } default: std::cerr << "Error: " << this << " is not a structure or an optstruct but a " << typeKindName(typeKindOf(type)) @@ -314,14 +330,21 @@ DataElementOpen62541::setIncomingData (const UA_Variant &value, /* optional scalar stored through pointer like an array */ memberData = *reinterpret_cast(memberData); } + if (type->typeKind == UA_DATATYPEKIND_UNION && + pelem->index != *reinterpret_cast(container)) { + // union option not taken + memberData = nullptr; + } UA_Variant_setArray(&memberValue, memberData, arrayLength, memberType); - if (debug() && !memberData && pelem->isOptional) { - std::cerr << pelem << " absent optional " << variantTypeString(memberType) + if (debug() && !memberData) { + std::cerr << pitem->recConnector->getRecordName() + << " " << pelem + << (type->typeKind == UA_DATATYPEKIND_UNION ? " not taken choice " : " absent optional ") + << variantTypeString(memberType) << (pelem->isArray ? " array" : " scalar" ) << std::endl; } - pelem->setIncomingData(memberValue, - pelem->isOptional && !memberData ? ProcessReason::readFailure : reason); + pelem->setIncomingData(memberValue, memberData ? reason : ProcessReason::readFailure); } } } @@ -385,6 +408,9 @@ DataElementOpen62541::updateDataInStruct(void* container, // mandatory scalar: shallow copy UA_clear(memberData, memberType); void* data = pelem->moveOutgoingData(); + if (typeKindOf(outgoingData) == UA_DATATYPEKIND_UNION) { + *reinterpret_cast(container) = pelem->index; + } memcpy(memberData, data, memberType->memSize); UA_free(data); } else { @@ -435,7 +461,8 @@ DataElementOpen62541::getOutgoingData () isdirty = false; const UA_DataType *type = outgoingData.type; void* container = outgoingData.data; - if (type && type->typeKind == UA_DATATYPEKIND_EXTENSIONOBJECT) { + + if (typeKindOf(type) == UA_DATATYPEKIND_EXTENSIONOBJECT) { UA_ExtensionObject &extensionObject = *reinterpret_cast(container); if (extensionObject.encoding >= UA_EXTENSIONOBJECT_DECODED) { // Access content decoded extension objects @@ -590,35 +617,57 @@ DataElementOpen62541::readScalar (char *value, const size_t num, if (UA_STATUS_IS_UNCERTAIN(stat)) { (void) recGblSetSevr(prec, READ_ALARM, MINOR_ALARM); } - UA_Variant &data = upd->getData(); - size_t n = num-1; + UA_String buffer = UA_STRING_NULL; UA_String *datastring = &buffer; - switch (typeKindOf(data)) { + size_t n = num-1; + + UA_Variant &data = upd->getData(); + void* payload = data.data; + const UA_DataType* type = data.type; + + if (type->typeKind == UA_DATATYPEKIND_UNION) + { + UA_UInt32 switchfield = *static_cast(payload)-1; + if (switchfield >= type->membersSize) { + (void) recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + break; + } + const UA_DataTypeMember *member = &type->members[switchfield]; + payload = static_cast(payload) + member->padding; + type = memberTypeOf(type, member); + + // prefix value string with switch choice name + size_t len = snprintf(value, n, "%s:", member->memberName); + value += len; + n -= len; + } + + switch (type->typeKind) { case UA_DATATYPEKIND_STRING: { - datastring = static_cast(data.data); + datastring = static_cast(payload); break; } case UA_DATATYPEKIND_LOCALIZEDTEXT: { - datastring = &static_cast(data.data)->text; + datastring = &static_cast(payload)->text; break; } case UA_DATATYPEKIND_DATETIME: { // UA_print does not correct printed time for time zone UA_Int64 tOffset = UA_DateTime_localTimeUtcOffset(); - UA_DateTime dt = *static_cast(data.data); + UA_DateTime dt = *static_cast(payload); dt += tOffset; - UA_print(&dt, data.type, &buffer); + UA_print(&dt, type, &buffer); break; } case UA_DATATYPEKIND_ENUM: case UA_DATATYPEKIND_INT32: { if (enumChoices) { - auto it = enumChoices->find(*static_cast(data.data)); + auto it = enumChoices->find(*static_cast(payload)); if (it != enumChoices->end()) { buffer = UA_String_fromChars(it->second.c_str()); break; @@ -627,9 +676,9 @@ DataElementOpen62541::readScalar (char *value, const size_t num, // no enum or index not found: fall through } default: - if (data.type) - UA_print(data.data, data.type, &buffer); - } + if (type) + UA_print(payload, type, &buffer); + }; if (n > datastring->length) n = datastring->length; strncpy(value, reinterpret_cast(datastring->data), n); @@ -939,7 +988,7 @@ DataElementOpen62541::writeScalar (const epicsFloat64 &value, dbCommon *prec) } long -DataElementOpen62541::writeScalar (const char *value, const epicsUInt32 len, dbCommon *prec) +DataElementOpen62541::writeScalar (const char *value, epicsUInt32 len, dbCommon *prec) { long ret = 1; UA_StatusCode status = UA_STATUSCODE_BADUNEXPECTEDERROR; @@ -951,7 +1000,25 @@ DataElementOpen62541::writeScalar (const char *value, const epicsUInt32 len, dbC { // Scope of Guard G Guard G(outgoingLock); UA_Variant_clear(&outgoingData); // unlikely but we may still have unsent old data to discard - switch (typeKindOf(incomingData)) { + const UA_DataType* type = incomingData.type; + + int switchfield = -1; + if (typeKindOf(type) == UA_DATATYPEKIND_UNION) { + if (value[0] == '\0') { + switchfield = 0; + } else for (UA_UInt32 i = 0; i < type->membersSize; i++) { + size_t namelen = strlen(type->members[i].memberName); + if (strncmp(value, type->members[i].memberName, namelen) == 0 + && value[namelen] == ':') { + value += namelen+1; + len -= namelen+1; + switchfield = i+1; + type = memberTypeOf(type, &type->members[i]); + } + } + } + + switch (typeKindOf(type)) { case UA_DATATYPEKIND_STRING: { UA_String val; @@ -1088,8 +1155,6 @@ DataElementOpen62541::writeScalar (const char *value, const epicsUInt32 len, dbC if (end != value) { UA_Double val = static_cast(d); status = UA_Variant_setScalarCopy(&outgoingData, &val, &UA_TYPES[UA_TYPES_DOUBLE]); - markAsDirty(); - ret = 0; } break; } @@ -1098,7 +1163,23 @@ DataElementOpen62541::writeScalar (const char *value, const epicsUInt32 len, dbC prec->name, variantTypeString(incomingData)); (void) recGblSetSevr(prec, WRITE_ALARM, INVALID_ALARM); } - } + if (switchfield >= 0) { + // manually wrap value from outgoingData into union + void *p = UA_new(incomingData.type); + if (p) { + *static_cast(p) = switchfield; + if (switchfield > 0) { + memcpy(static_cast(p) + incomingData.type->members[switchfield-1].padding, + outgoingData.data, outgoingData.type->memSize); + UA_free(outgoingData.data); + } + UA_Variant_setScalar(&outgoingData, p, incomingData.type); + status = UA_STATUSCODE_GOOD; + markAsDirty(); + ret = 0; + } + } + } // Scope of Guard G if (ret != 0) { errlogPrintf("%s : value \"%s\" out of range\n", prec->name, value); diff --git a/devOpcuaSup/open62541/DataElementOpen62541.h b/devOpcuaSup/open62541/DataElementOpen62541.h index 56c4f29e..a129e1a2 100644 --- a/devOpcuaSup/open62541/DataElementOpen62541.h +++ b/devOpcuaSup/open62541/DataElementOpen62541.h @@ -597,7 +597,7 @@ class DataElementOpen62541 : public DataElement * DevOpcua::DataElement::writeScalar(const char*,const epicsUInt32,dbCommon*) */ virtual long int writeScalar(const char *value, - const epicsUInt32 num, + epicsUInt32 num, dbCommon *prec) override; /** @@ -1268,6 +1268,7 @@ class DataElementOpen62541 : public DataElement UA_Boolean isArray = false; /**< is this element an array? */ UA_Boolean isOptional = false; /**< is this element optional? */ size_t offset = 0; /**< data offset of this element in parent structure */ + UA_UInt32 index = 0; /**< element index (for unions) */ bool mapped; /**< child name to index mapping done */ UpdateQueue incomingQueue; /**< queue of incoming values */ UA_Variant incomingData; /**< cache of latest incoming value */