diff --git a/opendaq_ref b/opendaq_ref index 881016eb..58efd2cd 100644 --- a/opendaq_ref +++ b/opendaq_ref @@ -1 +1 @@ -e6f13d80fa7c64697e5549fc1d897b03ad813415 +move_propertyImpl_to_cpp diff --git a/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_object_impl.h b/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_object_impl.h index 1f5d8f41..8cd8e118 100644 --- a/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_object_impl.h +++ b/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_object_impl.h @@ -24,6 +24,8 @@ BEGIN_NAMESPACE_OPENDAQ_OPCUA_TMS +bool GetExecutePermission(const TmsClientContextPtr& clientContext, const ContextPtr& daqContext, const opcua::OpcUaNodeId& nodeId); + class TmsClientObjectImpl { public: diff --git a/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_property_factory.h b/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_property_factory.h index 5c3d64a7..6d13912a 100644 --- a/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_property_factory.h +++ b/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_property_factory.h @@ -14,29 +14,13 @@ * limitations under the License. */ #pragma once +#include +#include #include -#include BEGIN_NAMESPACE_OPENDAQ_OPCUA_TMS -OPENDAQ_DECLARE_CLASS_FACTORY_WITH_INTERFACE(INLINE_FACTORY, - TmsClientProperty, IProperty, - const daq::ContextPtr&, daqContext, - const daq::opcua::tms::TmsClientContextPtr&, ctx, - const opcua::OpcUaNodeId&, nodeId, - const daq::StringPtr&, propertyName) - -OPENDAQ_DEFINE_CLASS_FACTORY_WITH_INTERFACE(INLINE_FACTORY, - TmsClientProperty, IProperty, - const daq::ContextPtr&, daqContext, - const daq::opcua::tms::TmsClientContextPtr&, client, - const opcua::OpcUaNodeId&, nodeId, - const daq::StringPtr&, propertyName) - -inline PropertyPtr TmsClientProperty(const ContextPtr& daqContext, const TmsClientContextPtr& ctx, const OpcUaNodeId& nodeId, const StringPtr& propertyName = nullptr) -{ - PropertyPtr obj(TmsClientProperty_Create(daqContext, ctx, nodeId, propertyName)); - return obj; -} +// Reads a Property node from the OpcUa server and builds the corresponding openDAQ Property. +PropertyPtr TmsClientProperty(const ContextPtr& daqContext, const TmsClientContextPtr& ctx, const opcua::OpcUaNodeId& nodeId, const StringPtr& propertyName = nullptr); END_NAMESPACE_OPENDAQ_OPCUA_TMS diff --git a/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_property_impl.h b/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_property_impl.h deleted file mode 100644 index 9d1bd9b1..00000000 --- a/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_property_impl.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2022-2025 openDAQ d.o.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include -#include - -BEGIN_NAMESPACE_OPENDAQ_OPCUA_TMS - -class TmsClientPropertyImpl : public TmsClientObjectImpl, public PropertyImpl -{ -public: - explicit TmsClientPropertyImpl(const ContextPtr& daqContext, const TmsClientContextPtr& ctx, const opcua::OpcUaNodeId& nodeId, const StringPtr& propertyName = nullptr); - -protected: - LoggerComponentPtr loggerComponent; - - void readBasicInfo(); - void configurePropertyFields(); -}; - -END_NAMESPACE_OPENDAQ_OPCUA_TMS diff --git a/shared/libraries/opcuatms/opcuatms_client/src/CMakeLists.txt b/shared/libraries/opcuatms/opcuatms_client/src/CMakeLists.txt index da1527d5..eae27102 100644 --- a/shared/libraries/opcuatms/opcuatms_client/src/CMakeLists.txt +++ b/shared/libraries/opcuatms/opcuatms_client/src/CMakeLists.txt @@ -30,7 +30,6 @@ set(SRC_Objects_Headers ${OBJECT_SRC_DIR}/tms_client_object_impl.h ${OBJECT_SRC_DIR}/tms_client_channel_impl.h ${OBJECT_SRC_DIR}/tms_client_channel_factory.h - ${OBJECT_SRC_DIR}/tms_client_property_impl.h ${OBJECT_SRC_DIR}/tms_client_property_factory.h ${OBJECT_SRC_DIR}/tms_client_input_port_impl.h @@ -77,7 +76,7 @@ set(SRC_Objects ${OBJECT_SRC_DIR}/tms_client_object_impl.cpp ${OBJECT_SRC_DIR}/tms_client_function_block_impl.cpp ${OBJECT_SRC_DIR}/tms_client_channel_impl.cpp ${OBJECT_SRC_DIR}/tms_client_input_port_impl.cpp - ${OBJECT_SRC_DIR}/tms_client_property_impl.cpp + ${OBJECT_SRC_DIR}/tms_client_property_factory.cpp ${OBJECT_SRC_DIR}/tms_client_property_object_impl.cpp ${OBJECT_SRC_DIR}/tms_client_device_impl.cpp ${OBJECT_SRC_DIR}/tms_client_folder_impl.cpp diff --git a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_object_impl.cpp b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_object_impl.cpp index 86f4dbf9..daa9423a 100644 --- a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_object_impl.cpp +++ b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_object_impl.cpp @@ -132,8 +132,13 @@ bool TmsClientObjectImpl::getAttributeWritePermission(const opcua::OpcUaNodeId& bool TmsClientObjectImpl::getExecutePermission(const opcua::OpcUaNodeId& nodeId) { - // Note: This method is used to determine if a method node is executable. - // Other nodes don't have executable attributes and this method will return true for them. + return GetExecutePermission(clientContext, daqContext, nodeId); +} + +bool GetExecutePermission(const TmsClientContextPtr& clientContext, const ContextPtr& daqContext, const opcua::OpcUaNodeId& nodeId) +{ + // Note: This function is used to determine if a method node is executable. + // Other nodes don't have executable attributes and this function will return true for them. bool commonExecutable = true; try { @@ -144,9 +149,9 @@ bool TmsClientObjectImpl::getExecutePermission(const opcua::OpcUaNodeId& nodeId) } catch (...) { - if (this->daqContext.getLogger().assigned()) + if (daqContext.getLogger().assigned()) { - auto loggerComponent = this->daqContext.getLogger().getOrAddComponent("OpcUaClientObject"); + auto loggerComponent = daqContext.getLogger().getOrAddComponent("OpcUaClientObject"); LOG_W("Cannot read executable mask attributes for OpcUA node"); } } diff --git a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_factory.cpp b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_factory.cpp new file mode 100644 index 00000000..33048cce --- /dev/null +++ b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_factory.cpp @@ -0,0 +1,300 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_OPCUA_TMS + +using namespace daq::opcua; + +namespace details +{ + enum class PropertyField + { + CoercionExpression = 0, + ValidationExpression, + DefaultValue, + IsReadOnly, + IsVisible, + Unit, + MaxValue, + MinValue, + SuggestedValues, + SelectionValues + }; + + static std::unordered_map stringToPropertyFieldEnum{ + {"CoercionExpression", PropertyField::CoercionExpression}, + {"ValidationExpression", PropertyField::ValidationExpression}, + {"DefaultValue", PropertyField::DefaultValue}, + {"IsReadOnly", PropertyField::IsReadOnly}, + {"IsVisible", PropertyField::IsVisible}, + {"Unit", PropertyField::Unit}, + {"MaxValue", PropertyField::MaxValue}, + {"MinValue", PropertyField::MinValue}, + {"SuggestedValues", PropertyField::SuggestedValues}, + {"SelectionValues", PropertyField::SelectionValues}, + }; + + static CoreType readValueType(const TmsClientContextPtr& clientContext, const ContextPtr& daqContext, const opcua::OpcUaNodeId& nodeId) + { + const auto reader = clientContext->getAttributeReader(); + const auto dataType = reader->getValue(nodeId, UA_ATTRIBUTEID_DATATYPE).toNodeId(); + const auto enumerationTypeId = OpcUaNodeId(0, UA_NS0ID_ENUMERATION); + + if (clientContext->getReferenceBrowser()->isSubtypeOf(dataType, enumerationTypeId)) + return ctEnumeration; + + const auto variant = reader->getValue(nodeId, UA_ATTRIBUTEID_VALUE); + const auto object = VariantConverter::ToDaqObject(variant, daqContext); + return object.getCoreType(); + } + + static void applyEvalExpressionField(const PropertyBuilderPtr& propBuilder, + PropertyField propertyField, + const StringPtr& evalStr, + int64_t commonAccessLevel, + bool isExecutableProperty, + bool commonExecutable) + { + switch (propertyField) + { + case PropertyField::DefaultValue: + propBuilder.setDefaultValue(EvalValue(evalStr)); + break; + + case PropertyField::IsReadOnly: + if ((commonAccessLevel & UA_ACCESSLEVELMASK_WRITE) != 0) + propBuilder.setReadOnly(EvalValue(evalStr).asPtr()); + else + propBuilder.setReadOnly(true); + break; + + case PropertyField::IsVisible: + if (!isExecutableProperty || commonExecutable) + propBuilder.setVisible(EvalValue(evalStr).asPtr()); + else + propBuilder.setVisible(false); + break; + + case PropertyField::Unit: + propBuilder.setUnit(EvalValue(evalStr).asPtr()); + break; + + case PropertyField::MaxValue: + propBuilder.setMaxValue(EvalValue(evalStr).asPtr()); + break; + + case PropertyField::MinValue: + propBuilder.setMinValue(EvalValue(evalStr).asPtr()); + break; + + case PropertyField::SuggestedValues: + propBuilder.setSuggestedValues(EvalValue(evalStr).asPtr()); + break; + + case PropertyField::SelectionValues: + propBuilder.setSelectionValues(EvalValue(evalStr)); + break; + case PropertyField::CoercionExpression: + case PropertyField::ValidationExpression: + break; + } + } + + static void applyDirectValueField(const PropertyBuilderPtr& propBuilder, + PropertyField propertyField, + const ContextPtr& daqContext, + const TmsClientContextPtr& clientContext, + const opcua::OpcUaNodeId& nodeId, + const opcua::OpcUaNodeId& childNodeId, + const StringPtr& name, + const LoggerComponentPtr& loggerComponent, + int64_t commonAccessLevel, + bool isExecutableProperty, + bool commonExecutable) + { + const auto reader = clientContext->getAttributeReader(); + + switch (propertyField) + { + case PropertyField::DefaultValue: + { + // ToDo: This is a workarround for devices which are delivering not a default value, + // even if this is a mandatory property in the openDAQ Standard. + // However, the SDK creates too strong a requirement, which cannot be + // met by all the standards or devices to be embraced. + // In this case the actual value from the first connect is set to it. + // But, this creates a weak point: + // SDK stores only values of variables which are != to the device default value. + // The choosen default value could be not the true default value from the device. + // So, all in all we aligned on that in future the SDK will also support properties + // which have not a default value as the device for which the workaround is needed. + // But this is feature request and is covered with + // https://blueberrydaq.atlassian.net/browse/TBBAS-1216. + // But as long as the feature is not implemented this is a valid workarround to get + // devices working which are deliviering not a default value via the opc-ua interface. + // Afterwards, the workaround needs to be rolled back. + + auto value = reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE); + BaseObjectPtr defaultValue; + if (value.isNull()) + { + value = reader->getValue(nodeId, UA_ATTRIBUTEID_VALUE); + defaultValue = VariantConverter::ToDaqObject(value, daqContext); + LOG_W( + "Failed to read default value of property {} on OpcUa client. Default value is set to the value at connection time.", + name); + } + + //Special handling for enumerations as this data type is encoded as Int32 in OPCUA + const auto dataType = reader->getValue(nodeId, UA_ATTRIBUTEID_DATATYPE).toNodeId(); + const auto enumerationTypeId = OpcUaNodeId(0, UA_NS0ID_ENUMERATION); + + if (clientContext->getReferenceBrowser()->isSubtypeOf(dataType, enumerationTypeId)) + { + if (value->type != &UA_TYPES[UA_TYPES_INT32]) + DAQ_THROW_EXCEPTION(ConversionFailedException, "Enumeration node data type is not uint32_t"); + + const auto enumBrowseName = clientContext->getClient()->readBrowseName(dataType); + const auto enumType = GetUAEnumerationDataTypeByName(enumBrowseName); + OpcUaVariant variant{}; + UA_Variant_setScalarCopy(&variant.getValue(), value->data, enumType); + defaultValue = VariantConverter::ToDaqObject(variant, daqContext); + } + else + defaultValue = VariantConverter::ToDaqObject(value, daqContext); + + propBuilder.setDefaultValue(defaultValue); + break; + } + case PropertyField::IsReadOnly: + if ((commonAccessLevel & UA_ACCESSLEVELMASK_WRITE) != 0) + propBuilder.setReadOnly(VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE))); + else + propBuilder.setReadOnly(true); + break; + case PropertyField::IsVisible: + if (!isExecutableProperty || commonExecutable) + propBuilder.setVisible(VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE))); + else + propBuilder.setVisible(false); + break; + case PropertyField::Unit: + propBuilder.setUnit(VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE))); + break; + case PropertyField::MaxValue: + propBuilder.setMaxValue(VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE))); + break; + case PropertyField::MinValue: + propBuilder.setMinValue(VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE))); + break; + case PropertyField::SuggestedValues: + propBuilder.setSuggestedValues( + VariantConverter::ToDaqList(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE), daqContext)); + break; + case PropertyField::SelectionValues: + propBuilder.setSelectionValues( + SelectionVariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE))); + break; + case PropertyField::CoercionExpression: + case PropertyField::ValidationExpression: + break; + } + } + + static void configurePropertyFields(const PropertyBuilderPtr& propBuilder, + const ContextPtr& daqContext, + const TmsClientContextPtr& clientContext, + const opcua::OpcUaNodeId& nodeId, + const StringPtr& name, + const LoggerComponentPtr& loggerComponent, + CoreType valueType) + { + const auto evaluationVariableTypeId = OpcUaNodeId(NAMESPACE_DAQBT, UA_DAQBTID_EVALUATIONVARIABLETYPE); + const auto& references = clientContext->getReferenceBrowser()->browse(nodeId); + const auto reader = clientContext->getAttributeReader(); + + int64_t userAccessLevel = reader->getValue(nodeId, UA_ATTRIBUTEID_USERACCESSLEVEL).toInteger(); + int64_t accessLevel = reader->getValue(nodeId, UA_ATTRIBUTEID_ACCESSLEVEL).toInteger(); + int64_t commonAccessLevel = userAccessLevel & accessLevel; + + propBuilder.setReadOnly((commonAccessLevel & UA_ACCESSLEVELMASK_WRITE) == 0); + + bool isExecutableProperty = (valueType == CoreType::ctFunc || valueType == CoreType::ctProc); + bool commonExecutable = true; + if (isExecutableProperty) + { + commonExecutable = GetExecutePermission(clientContext, daqContext, nodeId); + propBuilder.setVisible(commonExecutable); + } + + for (const auto& [browseName, ref] : references.byBrowseName) + { + const auto childNodeId = OpcUaNodeId(ref->nodeId.nodeId); + + if (browseName == "CoercionExpression") + { + const auto eval = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); + if (eval.assigned() && eval.getLength() > 0) + propBuilder.setCoercer(Coercer(eval)); + } + else if (browseName == "ValidationExpression") + { + const auto eval = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); + if (eval.assigned() && eval.getLength() > 0) + propBuilder.setValidator(Validator(eval)); + } + else if (clientContext->getReferenceBrowser()->isSubtypeOf(ref->typeDefinition.nodeId, evaluationVariableTypeId)) + { + auto evalId = clientContext->getReferenceBrowser()->getChildNodeId(childNodeId, "EvaluationExpression"); + + StringPtr evalStr = VariantConverter::ToDaqObject(reader->getValue(evalId, UA_ATTRIBUTEID_VALUE)); + if (details::stringToPropertyFieldEnum.count(browseName)) + { + const auto propertyField = details::stringToPropertyFieldEnum[browseName]; + bool strHasValue = evalStr.assigned() && evalStr.getLength() > 0; + if (strHasValue) + applyEvalExpressionField(propBuilder, propertyField, evalStr, commonAccessLevel, isExecutableProperty, commonExecutable); + else + applyDirectValueField(propBuilder, propertyField, daqContext, clientContext, nodeId, childNodeId, name, loggerComponent, + commonAccessLevel, isExecutableProperty, commonExecutable); + } + } + } + } +} + +PropertyPtr TmsClientProperty(const ContextPtr& daqContext, const TmsClientContextPtr& ctx, const opcua::OpcUaNodeId& nodeId, const StringPtr& propertyName) +{ + if (!daqContext.getLogger().assigned()) + DAQ_THROW_EXCEPTION(ArgumentNullException, "Logger must not be null"); + + const auto loggerComponent = daqContext.getLogger().getOrAddComponent("TmsClientProperty"); + + ctx->readObjectAttributes(nodeId); + + const auto reader = ctx->getAttributeReader(); + + StringPtr name = propertyName; + if (!name.assigned()) + name = String(reader->getValue(nodeId, UA_ATTRIBUTEID_DISPLAYNAME).toString()); + + const auto description = String(reader->getValue(nodeId, UA_ATTRIBUTEID_DESCRIPTION).toString()); + const auto valueType = details::readValueType(ctx, daqContext, nodeId); + + auto propBuilder = PropertyBuilder(name).setValueType(valueType).setDescription(description); + details::configurePropertyFields(propBuilder, daqContext, ctx, nodeId, name, loggerComponent, valueType); + + return propBuilder.build(); +} + +END_NAMESPACE_OPENDAQ_OPCUA_TMS diff --git a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_impl.cpp b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_impl.cpp deleted file mode 100644 index 96065fac..00000000 --- a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_impl.cpp +++ /dev/null @@ -1,276 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -BEGIN_NAMESPACE_OPENDAQ_OPCUA_TMS - -using namespace daq::opcua; - -namespace details -{ - enum class PropertyField - { - CoercionExpression = 0, - ValidationExpression, - DefaultValue, - IsReadOnly, - IsVisible, - Unit, - MaxValue, - MinValue, - SuggestedValues, - SelectionValues - }; - - static std::unordered_map stringToPropertyFieldEnum{ - {"CoercionExpression", PropertyField::CoercionExpression}, - {"ValidationExpression", PropertyField::ValidationExpression}, - {"DefaultValue", PropertyField::DefaultValue}, - {"IsReadOnly", PropertyField::IsReadOnly}, - {"IsVisible", PropertyField::IsVisible}, - {"Unit", PropertyField::Unit}, - {"MaxValue", PropertyField::MaxValue}, - {"MinValue", PropertyField::MinValue}, - {"SuggestedValues", PropertyField::SuggestedValues}, - {"SelectionValues", PropertyField::SelectionValues}, - }; -} - -TmsClientPropertyImpl::TmsClientPropertyImpl(const ContextPtr& daqContext, const TmsClientContextPtr& ctx, const opcua::OpcUaNodeId& nodeId, const StringPtr& propertyName) - : TmsClientObjectImpl(daqContext, ctx, nodeId) -{ - if (!this->daqContext.getLogger().assigned()) - DAQ_THROW_EXCEPTION(ArgumentNullException, "Logger must not be null"); - - this->loggerComponent = this->daqContext.getLogger().getOrAddComponent("TmsClientPropertyImpl"); - this->name = propertyName; - - clientContext->readObjectAttributes(nodeId); - - readBasicInfo(); - configurePropertyFields(); -} - -void TmsClientPropertyImpl::readBasicInfo() -{ - auto reader = clientContext->getAttributeReader(); - if (!this->name.assigned()) - this->name = String(reader->getValue(nodeId, UA_ATTRIBUTEID_DISPLAYNAME).toString()); - this->description = String(reader->getValue(nodeId, UA_ATTRIBUTEID_DESCRIPTION).toString()); - - const auto dataType = reader->getValue(nodeId, UA_ATTRIBUTEID_DATATYPE).toNodeId(); - const auto enumerationTypeId = OpcUaNodeId(0, UA_NS0ID_ENUMERATION); - - if (clientContext->getReferenceBrowser()->isSubtypeOf(dataType, enumerationTypeId)) - { - this->valueType = ctEnumeration; - } - else - { - const auto variant = reader->getValue(nodeId, UA_ATTRIBUTEID_VALUE); - const auto object = VariantConverter::ToDaqObject(variant, daqContext); - this->valueType = object.getCoreType(); - } -} - -void TmsClientPropertyImpl::configurePropertyFields() -{ - const auto evaluationVariableTypeId = OpcUaNodeId(NAMESPACE_DAQBT, UA_DAQBTID_EVALUATIONVARIABLETYPE); - const auto& references = clientContext->getReferenceBrowser()->browse(nodeId); - const auto reader = clientContext->getAttributeReader(); - - int64_t userAccessLevel = reader->getValue(nodeId, UA_ATTRIBUTEID_USERACCESSLEVEL).toInteger(); - int64_t accessLevel = reader->getValue(nodeId, UA_ATTRIBUTEID_ACCESSLEVEL).toInteger(); - int64_t commonAccessLevel = userAccessLevel & accessLevel; - - this->readOnly = ((commonAccessLevel & UA_ACCESSLEVELMASK_WRITE) == 0); - - bool isExecutableProperty = (valueType == CoreType::ctFunc || valueType == CoreType::ctProc); - bool commonExecutable = true; - if (isExecutableProperty) - { - commonExecutable = getExecutePermission(nodeId); - this->visible = commonExecutable; - } - - for (const auto& [browseName, ref] : references.byBrowseName) - { - const auto childNodeId = OpcUaNodeId(ref->nodeId.nodeId); - - if (browseName == "CoercionExpression") - { - const auto eval = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); - if (eval.assigned() && eval.getLength() > 0) - this->coercer = Coercer(eval); - } - else if (browseName == "ValidationExpression") - { - const auto eval = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); - if (eval.assigned() && eval.getLength() > 0) - this->validator = Validator(eval); - } - else if (clientContext->getReferenceBrowser()->isSubtypeOf(ref->typeDefinition.nodeId, evaluationVariableTypeId)) - { - auto evalId = clientContext->getReferenceBrowser()->getChildNodeId(childNodeId, "EvaluationExpression"); - - StringPtr evalStr = VariantConverter::ToDaqObject(reader->getValue(evalId, UA_ATTRIBUTEID_VALUE)); - if (details::stringToPropertyFieldEnum.count(browseName)) - { - const auto propertyField = details::stringToPropertyFieldEnum[browseName]; - bool strHasValue = evalStr.assigned() && evalStr.getLength() > 0; - if (strHasValue) - { - switch (propertyField) - { - case details::PropertyField::DefaultValue: - this->defaultValue = EvalValue(evalStr); - break; - - case details::PropertyField::IsReadOnly: - if ((commonAccessLevel & UA_ACCESSLEVELMASK_WRITE) != 0) - this->readOnly = EvalValue(evalStr).asPtr(); - else - this->readOnly = true; - break; - - case details::PropertyField::IsVisible: - if (!isExecutableProperty || commonExecutable) - this->visible = EvalValue(evalStr).asPtr(); - else - this->visible = false; - break; - - case details::PropertyField::Unit: - this->unit = EvalValue(evalStr).asPtr(); - break; - - case details::PropertyField::MaxValue: - this->maxValue = EvalValue(evalStr).asPtr(); - break; - - case details::PropertyField::MinValue: - this->minValue = EvalValue(evalStr).asPtr(); - break; - - case details::PropertyField::SuggestedValues: - this->suggestedValues = EvalValue(evalStr).asPtr(); - break; - - case details::PropertyField::SelectionValues: - this->selectionValues = EvalValue(evalStr); - break; - case details::PropertyField::CoercionExpression: - case details::PropertyField::ValidationExpression: - break; - } - } - else - { - switch (propertyField) - { - case details::PropertyField::DefaultValue: - { - // ToDo: This is a workarround for devices which are delivering not a default value, - // even if this is a mandatory property in the openDAQ Standard. - // However, the SDK creates too strong a requirement, which cannot be - // met by all the standards or devices to be embraced. - // In this case the actual value from the first connect is set to it. - // But, this creates a weak point: - // SDK stores only values of variables which are != to the device default value. - // The choosen default value could be not the true default value from the device. - // So, all in all we aligned on that in future the SDK will also support properties - // which have not a default value as the device for which the workaround is needed. - // But this is feature request and is covered with - // https://blueberrydaq.atlassian.net/browse/TBBAS-1216. - // But as long as the feature is not implemented this is a valid workarround to get - // devices working which are deliviering not a default value via the opc-ua interface. - // Afterwards, the workaround needs to be rolled back. - - auto value = reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE); - if(value.isNull()) - { - value = reader->getValue(nodeId, UA_ATTRIBUTEID_VALUE); - this->defaultValue = VariantConverter::ToDaqObject(value, daqContext); - LOG_W( - "Failed to read default value of property {} on OpcUa client. Default value is set to the value at connection time.", - this->name); - } - - //Special handling for enumerations as this data type is encoded as Int32 in OPCUA - const auto dataType = reader->getValue(nodeId, UA_ATTRIBUTEID_DATATYPE).toNodeId(); - const auto enumerationTypeId = OpcUaNodeId(0, UA_NS0ID_ENUMERATION); - - if (clientContext->getReferenceBrowser()->isSubtypeOf(dataType, enumerationTypeId)) - { - if (value->type != &UA_TYPES[UA_TYPES_INT32]) - DAQ_THROW_EXCEPTION(ConversionFailedException, "Enumeration node data type is not uint32_t"); - - const auto enumBrowseName = client->readBrowseName(dataType); - const auto enumType = GetUAEnumerationDataTypeByName(enumBrowseName); - OpcUaVariant variant{}; - UA_Variant_setScalarCopy(&variant.getValue(), value->data, enumType); - this->defaultValue = VariantConverter::ToDaqObject(variant, daqContext); - } - else - this->defaultValue = VariantConverter::ToDaqObject(value, daqContext); - - if (this->defaultValue.supportsInterface()) - this->defaultValue.freeze(); - - break; - } - case details::PropertyField::IsReadOnly: - if ((commonAccessLevel & UA_ACCESSLEVELMASK_WRITE) != 0) - this->readOnly = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); - else - this->readOnly = true; - break; - case details::PropertyField::IsVisible: - if (!isExecutableProperty || commonExecutable) - this->visible = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); - else - this->visible = false; - break; - case details::PropertyField::Unit: - this->unit = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); - break; - case details::PropertyField::MaxValue: - this->maxValue = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); - break; - case details::PropertyField::MinValue: - this->minValue = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); - break; - case details::PropertyField::SuggestedValues: - { - this->suggestedValues = - VariantConverter::ToDaqList(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE), daqContext); - if (this->suggestedValues.supportsInterface()) - this->suggestedValues.freeze(); - break; - } - case details::PropertyField::SelectionValues: - { - this->selectionValues = - SelectionVariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); - - if (this->selectionValues.supportsInterface()) - this->selectionValues.freeze(); - break; - } - case details::PropertyField::CoercionExpression: - case details::PropertyField::ValidationExpression: - break; - } - } - } - } - } -} - -END_NAMESPACE_OPENDAQ_OPCUA_TMS diff --git a/shared/libraries/opcuatms/opcuatms_server/src/objects/tms_server_property.cpp b/shared/libraries/opcuatms/opcuatms_server/src/objects/tms_server_property.cpp index 001ee0b1..68ba4632 100644 --- a/shared/libraries/opcuatms/opcuatms_server/src/objects/tms_server_property.cpp +++ b/shared/libraries/opcuatms/opcuatms_server/src/objects/tms_server_property.cpp @@ -221,10 +221,9 @@ opcua::OpcUaNodeId TmsServerProperty::getDataTypeId() bool TmsServerProperty::checkPermission(const Permission permission, const UA_NodeId* const nodeId, const OpcUaSession* const sessionContext) { bool allow = true; - if (auto tmsNodeAsProp = getObject().asPtrOrNull(); tmsNodeAsProp.assigned() && sessionContext) + if (auto tmsNodeAsProp = getObject().asPtrOrNull(); tmsNodeAsProp.assigned() && sessionContext) { - const auto propObj = static_cast(tmsNodeAsProp.getObject())->getOwner(); - if (propObj.assigned()) + if (const auto propObj = tmsNodeAsProp.getOwner(); propObj.assigned()) allow = propObj.getPermissionManager().isAuthorized(sessionContext->getUser(), permission); } return allow;