diff --git a/.ci-local/install-open62541.py b/.ci-local/install-open62541.py index 58510f26..23bf8e2e 100644 --- a/.ci-local/install-open62541.py +++ b/.ci-local/install-open62541.py @@ -86,9 +86,6 @@ if cue.ci['static']: build_shared = 'OFF' - if ver[0] == '1' and ver[1] == '3': - sp.check_call(['patch', '-p1', '-i', os.path.join(curdir, '.ci-local', 'open62541-1.3.patch')], cwd=sdkdir) - sp.check_call(['cmake', '..', '-G', generator, '-DBUILD_SHARED_LIBS={0}'.format(build_shared), diff --git a/.ci-local/open62541-1.3.patch b/.ci-local/open62541-1.3.patch deleted file mode 100644 index 0c24cd7c..00000000 --- a/.ci-local/open62541-1.3.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff -uir open62541.orig/include/open62541/types.h open62541/include/open62541/types.h ---- open62541.orig/include/open62541/types.h 2024-08-09 11:27:52.000000000 +0200 -+++ open62541/include/open62541/types.h 2024-09-03 10:58:38.813382735 +0200 -@@ -1067,7 +1067,7 @@ - * If the member is an array, the offset points to the (size_t) length field. - * (The array pointer comes after the length field without any padding.) */ - #ifdef UA_ENABLE_TYPEDESCRIPTION --UA_Boolean -+UA_Boolean UA_EXPORT - UA_DataType_getStructMember(const UA_DataType *type, - const char *memberName, - size_t *outOffset, -diff -uir open62541.orig/src/ua_types.c open62541/src/ua_types.c ---- open62541.orig/src/ua_types.c 2024-09-03 11:00:06.897318665 +0200 -+++ open62541/src/ua_types.c 2024-09-02 13:27:49.688344016 +0200 -@@ -1882,7 +1882,7 @@ - } - - #ifdef UA_ENABLE_TYPEDESCRIPTION --UA_Boolean -+UA_Boolean UA_EXPORT - UA_DataType_getStructMember(const UA_DataType *type, const char *memberName, - size_t *outOffset, const UA_DataType **outMemberType, - UA_Boolean *outIsArray) { diff --git a/devOpcuaSup/UaSdk/DataElementUaSdk.cpp b/devOpcuaSup/UaSdk/DataElementUaSdk.cpp index a7dd6741..b02d4348 100644 --- a/devOpcuaSup/UaSdk/DataElementUaSdk.cpp +++ b/devOpcuaSup/UaSdk/DataElementUaSdk.cpp @@ -166,7 +166,6 @@ DataElementUaSdk::setIncomingData(const UaVariant &value, for (int i = 0; i < definition.childrenCount(); i++) { if (*timefrom == definition.child(i).name().toUtf8()) { timesrc = i; - pitem->tsData = epicsTimeFromUaVariant(genericValue.value(i)); } } OpcUa_BuiltInType t = genericValue.value(timesrc).type(); @@ -191,7 +190,6 @@ DataElementUaSdk::setIncomingData(const UaVariant &value, elementMap.insert({i, it}); delete pelem->enumChoices; pelem->enumChoices = pitem->session->getEnumChoices(definition.child(i).enumDefinition()); - pelem->setIncomingData(genericValue.value(i), reason); } } } @@ -200,16 +198,35 @@ DataElementUaSdk::setIncomingData(const UaVariant &value, << " child elements mapped to a " << "structure of " << definition.childrenCount() << " elements" << std::endl; mapped = true; - } else { - if (timefrom) { - if (timesrc >= 0) - pitem->tsData = epicsTimeFromUaVariant(genericValue.value(timesrc)); - else - pitem->tsData = pitem->tsSource; - } - for (auto &it : elementMap) { - auto pelem = it.second.lock(); - pelem->setIncomingData(genericValue.value(it.first), reason); + } + + 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); + } else { + pelem->setIncomingData(memberValue, reason); } } } diff --git a/devOpcuaSup/open62541/DataElementOpen62541.cpp b/devOpcuaSup/open62541/DataElementOpen62541.cpp index 4fa911b4..07d10778 100644 --- a/devOpcuaSup/open62541/DataElementOpen62541.cpp +++ b/devOpcuaSup/open62541/DataElementOpen62541.cpp @@ -107,28 +107,35 @@ DataElementOpen62541::show (const int level, const unsigned int indent) const #error Set UA_ENABLE_TYPEDESCRIPTION in open62541 #endif -#ifndef UA_DATATYPES_USE_POINTER // 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 -UA_DataType_getStructMember(const UA_DataType *type, const char *memberName, +UA_DataType_getStructMemberExt(const UA_DataType *type, const char *memberName, size_t *outOffset, const UA_DataType **outMemberType, - UA_Boolean *outIsArray) { + UA_Boolean *outIsArray, UA_Boolean *outIsOptional) { if(type->typeKind != UA_DATATYPEKIND_STRUCTURE && type->typeKind != UA_DATATYPEKIND_OPTSTRUCT) return false; 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) { 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 offset += m->padding; if(strcmp(memberName, m->memberName) == 0) { *outOffset = offset; *outMemberType = mt; *outIsArray = m->isArray; + *outIsOptional = m->isOptional; return true; } @@ -151,27 +158,28 @@ UA_DataType_getStructMember(const UA_DataType *type, const char *memberName, return false; } -#endif -bool +void DataElementOpen62541::createMap (const UA_DataType *type, const std::string *timefrom) { 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; if (timefrom) { const UA_DataType *timeMemberType; UA_Boolean timeIsArray; + UA_Boolean timeIsOptional; size_t timeOffset; - if (UA_DataType_getStructMember(type, timefrom->c_str(), + if (UA_DataType_getStructMemberExt(type, timefrom->c_str(), &timeOffset, &timeMemberType, - &timeIsArray)) { + &timeIsArray, + &timeIsOptional)) { if (typeKindOf(timeMemberType) != UA_TYPES_DATETIME || timeIsArray) { errlogPrintf("%s: timestamp element %s has invalid type %s%s - using " "source timestamp\n", @@ -191,10 +199,11 @@ DataElementOpen62541::createMap (const UA_DataType *type, for (auto &it : elements) { auto pelem = it.lock(); - if (UA_DataType_getStructMember(type, pelem->name.c_str(), + if (UA_DataType_getStructMemberExt(type, pelem->name.c_str(), &pelem->offset, &pelem->memberType, - &pelem->isArray)) { + &pelem->isArray, + &pelem->isOptional)) { if (typeKindOf(pelem->memberType) == UA_DATATYPEKIND_ENUM) { pelem->enumChoices = pitem->session->getEnumChoices(&pelem->memberType->typeId); } @@ -207,18 +216,17 @@ DataElementOpen62541::createMap (const UA_DataType *type, } if (debug() >= 5) std::cout << " ** " << elements.size() - << " child elements mapped to a " - << "structure of " << type->membersSize << " elements" << std::endl; + << " child elements mapped to " + << variantTypeString(type) + << " of " << type->membersSize << " elements" << std::endl; break; } default: - std::cerr << "Item " << pitem - << " has unimplemented type " << variantTypeString(type) + std::cerr << "Error: " << this + << " is not a structure or an optstruct but a " << typeKindName(typeKindOf(type)) << std::endl; - return false; } mapped = true; - return true; } // Getting the timestamp and status information from the Item assumes that only one thread @@ -271,7 +279,7 @@ DataElementOpen62541::setIncomingData (const UA_Variant &value, const UA_DataType *type = value.type; char* container = static_cast(value.data); - if (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 of decoded extension objects @@ -288,28 +296,32 @@ DataElementOpen62541::setIncomingData (const UA_Variant &value, if (!mapped) createMap(type, timefrom); - if (timefrom) { - if (timesrc >= 0) - pitem->tsData = ItemOpen62541::uaToEpicsTime(*reinterpret_cast(container + timesrc), 0); - else - pitem->tsData = pitem->tsSource; - } + if (timesrc >= 0) + pitem->tsData = ItemOpen62541::uaToEpicsTime(*reinterpret_cast(container + timesrc), 0); + else + pitem->tsData = pitem->tsSource; for (auto &it : elements) { auto pelem = it.lock(); - char* memberData = container + pelem->offset; const UA_DataType* memberType = pelem->memberType; + char* memberData = container + pelem->offset; UA_Variant memberValue; - if (pelem->isArray) - { - size_t arrayLength = *reinterpret_cast(memberData); + size_t arrayLength = 0; // default to scalar + if (pelem->isArray) { + arrayLength = *reinterpret_cast(memberData); memberData = *reinterpret_cast(memberData + sizeof(size_t)); - if (arrayLength == 0) memberData = reinterpret_cast(UA_EMPTY_ARRAY_SENTINEL); - UA_Variant_setArray(&memberValue, memberData, arrayLength, memberType); - } else { - UA_Variant_setScalar(&memberValue, memberData, memberType); + } else if (pelem->isOptional) { + /* optional scalar stored through pointer like an array */ + memberData = *reinterpret_cast(memberData); } - pelem->setIncomingData(memberValue, reason); + UA_Variant_setArray(&memberValue, memberData, arrayLength, memberType); + if (debug() && !memberData && pelem->isOptional) { + std::cerr << pelem << " absent optional " << variantTypeString(memberType) + << (pelem->isArray ? " array" : " scalar" ) + << std::endl; + } + pelem->setIncomingData(memberValue, + pelem->isOptional && !memberData ? ProcessReason::readFailure : reason); } } } @@ -358,35 +370,37 @@ DataElementOpen62541::setState(const ConnectionStatus state) // Helper to update one data structure element from pointer to child bool -DataElementOpen62541::updateDataInStruct (void* container, - std::shared_ptr pelem) +DataElementOpen62541::updateDataInStruct(void* container, + std::shared_ptr pelem) { bool updated = false; { // Scope of Guard G Guard G(pelem->outgoingLock); if (pelem->isDirty()) { - void* memberData = static_cast(container) + pelem->offset; - const UA_Variant& data = pelem->getOutgoingData(); + char* memberData = static_cast(container) + pelem->offset; + const UA_Variant& elementData = pelem->getOutgoingData(); const UA_DataType* memberType = pelem->memberType; - assert(memberType == data.type); - if (pelem->isArray) - { - size_t& arrayLength = *reinterpret_cast(memberData); - void*& arrayData = *reinterpret_cast(static_cast(memberData) + sizeof(size_t)); - UA_Array_delete(arrayData, arrayLength, memberType); - UA_StatusCode status = UA_Array_copy(data.data, data.arrayLength, &arrayData, memberType); - if (status == UA_STATUSCODE_GOOD) { - arrayLength = data.arrayLength; - } else { - arrayLength = 0; - std::cerr << "Item " << pitem - << ": inserting data from from child element" << pelem->name - << " failed (" << UA_StatusCode_name(status) << ')' - << std::endl; - return false; - } + assert(memberType == elementData.type); + if (!pelem->isArray && !pelem->isOptional) { + // mandatory scalar: shallow copy + UA_clear(memberData, memberType); + void* data = pelem->moveOutgoingData(); + memcpy(memberData, data, memberType->memSize); + UA_free(data); } else { - UA_copy(data.data, memberData, memberType); + // array or optional scalar: move content + void **memberDataPtr; + if (pelem->isArray) /* mandatory or optional array stored as length and pointer */ { + size_t& arrayLength = *reinterpret_cast(memberData); + memberDataPtr = reinterpret_cast(memberData + sizeof(size_t)); + UA_Array_delete(*memberDataPtr, arrayLength, memberType); + arrayLength = elementData.arrayLength; + } else /* optional scalar stored through pointer */ { + memberDataPtr = reinterpret_cast(memberData); + if (*memberDataPtr) /* absent optional has nullptr here */ + UA_Array_delete(*memberDataPtr, 1, memberType); + } + *memberDataPtr = pelem->moveOutgoingData(); } pelem->isdirty = false; updated = true; @@ -551,6 +565,7 @@ DataElementOpen62541::readScalar (char *value, const size_t num, ProcessReason nReason; std::shared_ptr upd = incomingQueue.popUpdate(&nReason); dbgReadScalar(upd.get(), "CString", num); + prec->udf = false; switch (upd->getType()) { case ProcessReason::readFailure: @@ -620,7 +635,6 @@ DataElementOpen62541::readScalar (char *value, const size_t num, strncpy(value, reinterpret_cast(datastring->data), n); value[n] = '\0'; UA_String_clear(&buffer); - prec->udf = false; UA_Variant_clear(&data); } if (statusCode) *statusCode = stat; @@ -693,6 +707,7 @@ DataElementOpen62541::readArray (char *value, const epicsUInt32 len, ProcessReason nReason; std::shared_ptr upd = incomingQueue.popUpdate(&nReason); dbgReadArray(upd.get(), num, epicsTypeString(value)); + prec->udf = false; switch (upd->getType()) { case ProcessReason::readFailure: @@ -733,7 +748,6 @@ DataElementOpen62541::readArray (char *value, const epicsUInt32 len, strncpy(value + i * len, reinterpret_cast(static_cast(data.data)[i].data), len); (value + i * len)[len - 1] = '\0'; } - prec->udf = false; } UA_Variant_clear(&data); } diff --git a/devOpcuaSup/open62541/DataElementOpen62541.h b/devOpcuaSup/open62541/DataElementOpen62541.h index a67cb78e..56c4f29e 100644 --- a/devOpcuaSup/open62541/DataElementOpen62541.h +++ b/devOpcuaSup/open62541/DataElementOpen62541.h @@ -722,6 +722,21 @@ class DataElementOpen62541 : public DataElement */ virtual void clearOutgoingData() { UA_Variant_clear(&outgoingData); } + /** + * @brief Move the contents of the current outgoing data. + * + * Avoids a deep copy by moving the contents out of the outgoing data + * and clearing it afterwards. + * + * Call holding outgoingLock! + */ + void* moveOutgoingData() { + void* data = outgoingData.data; + outgoingData.data = nullptr; + UA_Variant_clear(&outgoingData); + return data; + } + /** * @brief Create processing requests for record(s) attached to this element. * See DevOpcua::DataElement::requestRecordProcessing @@ -746,7 +761,7 @@ class DataElementOpen62541 : public DataElement void dbgWriteArray(const epicsUInt32 targetSize, const std::string &targetTypeName) const; bool updateDataInStruct(void* container, std::shared_ptr pelem); - bool createMap(const UA_DataType *type, const std::string* timefrom); + void createMap(const UA_DataType *type, const std::string* timefrom); // Structure always returns true to ensure full traversal bool isDirty() const { return isdirty || !isleaf; } @@ -757,7 +772,7 @@ class DataElementOpen62541 : public DataElement pitem->markAsDirty(); } - // Get the time stamp from the incoming object + // Get the time stamp from the incoming object const epicsTime &getIncomingTimeStamp() const { ProcessReason reason = pitem->getReason(); if ((reason == ProcessReason::incomingData || reason == ProcessReason::readComplete) @@ -800,6 +815,7 @@ class DataElementOpen62541 : public DataElement ProcessReason nReason; std::shared_ptr upd = incomingQueue.popUpdate(&nReason); dbgReadScalar(upd.get(), epicsTypeString(*value)); + prec->udf = false; switch (upd->getType()) { case ProcessReason::readFailure: @@ -925,7 +941,6 @@ class DataElementOpen62541 : public DataElement if (UA_STATUS_IS_UNCERTAIN(stat)) { (void) recGblSetSevr(prec, READ_ALARM, MINOR_ALARM); } - prec->udf = false; } UA_Variant_clear(&data); } @@ -1008,7 +1023,6 @@ class DataElementOpen62541 : public DataElement } elemsWritten = static_cast(num) < data.arrayLength ? num : static_cast(data.arrayLength); memcpy(value, data.data, sizeof(ET) * elemsWritten); - prec->udf = false; } UA_Variant_clear(&data); } @@ -1250,17 +1264,40 @@ class DataElementOpen62541 : public DataElement std::unordered_map> elementMap; ptrdiff_t timesrc; - const UA_DataType *memberType; /**< type of this element */ - UA_Boolean isArray; /**< is this element an array? */ - size_t offset; /**< data offset of this element in parent structure */ + const UA_DataType *memberType = NULL; /**< type of this element */ + 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 */ bool mapped; /**< child name to index mapping done */ UpdateQueue incomingQueue; /**< queue of incoming values */ UA_Variant incomingData; /**< cache of latest incoming value */ epicsMutex &outgoingLock; /**< data lock for outgoing value */ UA_Variant outgoingData; /**< cache of latest outgoing value */ bool isdirty; /**< outgoing value has been (or needs to be) updated */ + + friend std::ostream& operator << (std::ostream& os, const DataElementOpen62541& element); }; + +// print the full element name, e.g.: item elem.array[index].elem +inline std::ostream& operator << (std::ostream& os, const DataElementOpen62541& element) +{ + // Skip the [ROOT] element in printing and print the item name instead + if (!element.parent) + return os << element.pitem; + os << element.parent; + if (!element.parent->parent) + os << " element "; + else if (element.name[0] != '[') + os << '.'; + return os << element.name; +} + +inline std::ostream& operator << (std::ostream& os, const DataElementOpen62541* pelement) +{ + return os << *pelement; +} + } // namespace DevOpcua #endif // DEVOPCUA_DATAELEMENTOPEN62541_H diff --git a/devOpcuaSup/open62541/README.md b/devOpcuaSup/open62541/README.md index 4d57898c..ea4ef730 100644 --- a/devOpcuaSup/open62541/README.md +++ b/devOpcuaSup/open62541/README.md @@ -35,25 +35,6 @@ but it does not get the attention that the server parts get. Do *not* use the download link on the open62541 web site. Use their GitHub Release Page instead. -### Bugfix for Shared Build - -The 1.3 release series need the following fix to be applied -when building shared libraries: - -```Diff ---- src/ua_types.c.orig 2024-09-02 11:31:15.514006029 +0200 -+++ src/ua_types.c 2024-09-02 11:31:29.499010032 +0200 -@@ -1882,7 +1882,7 @@ - } - - #ifdef UA_ENABLE_TYPEDESCRIPTION --UA_Boolean -+UA_Boolean UA_EXPORT - UA_DataType_getStructMember(const UA_DataType *type, const char *memberName, - size_t *outOffset, const UA_DataType **outMemberType, - UA_Boolean *outIsArray) { -``` - ### On Linux * Unpack the open62541 distribution. Create a build directory on the top level and `cd` into it. We'll use the usual convention of calling it `build` .