Skip to content
2 changes: 1 addition & 1 deletion opendaq_ref
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4757349a1db30721bead0cb695ec3a147913aef4
4757349a1db30721bead0cb695ec3a147913aef4
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class TmsClientPropertyObjectBaseImpl : public TmsClientObjectImpl, public Impl

ErrCode INTERFACE_FUNC setPropertyValue(IString* propertyName, IBaseObject* value) override;
ErrCode INTERFACE_FUNC setProtectedPropertyValue(IString* propertyName, IBaseObject* value) override;
ErrCode INTERFACE_FUNC setPropertySelectionValue(IString* propertyName, IBaseObject* value) override;
ErrCode INTERFACE_FUNC getPropertyValue(IString* propertyName, IBaseObject** value) override;
ErrCode INTERFACE_FUNC getPropertySelectionValue(IString* propertyName, IBaseObject** value) override;
ErrCode INTERFACE_FUNC clearPropertyValue(IString* propertyName) override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ void TmsClientPropertyImpl::configurePropertyFields()
switch (propertyField)
{
case details::PropertyField::DefaultValue:

this->defaultValue = EvalValue(evalStr);
break;

Expand Down Expand Up @@ -259,6 +258,7 @@ void TmsClientPropertyImpl::configurePropertyFields()
{
this->selectionValues =
SelectionVariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE));

if (this->selectionValues.supportsInterface<IFreezable>())
this->selectionValues.freeze();
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,79 @@ ErrCode INTERFACE_FUNC TmsClientPropertyObjectBaseImpl<Impl>::getPropertyValue(I
return OPENDAQ_SUCCESS;
}

template <typename Impl>
ErrCode INTERFACE_FUNC TmsClientPropertyObjectBaseImpl<Impl>::setPropertySelectionValue(IString* propertyName, IBaseObject* value)
{
if (propertyName == nullptr)
return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_ARGUMENT_NULL, "Property name must not be null");
if (value == nullptr)
return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_ARGUMENT_NULL, "Value must not be null");

return daqTry([&]
{
const auto propNamePtr = StringPtr::Borrow(propertyName);
const auto valuePtr = BaseObjectPtr::Borrow(value);

PropertyPtr prop;
const ErrCode err = getProperty(propertyName, &prop);
OPENDAQ_RETURN_IF_FAILED(err);

if (!prop.assigned())
return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Property "{}" not found)", propNamePtr));

const PropertyType propType = prop.getPropertyType();
BaseObjectPtr indexOrKey;

if (propType == PropertyType::IndexSelection)
{
const ListPtr<IBaseObject> selectionList = prop.getSelectionValues();
if (!selectionList.assigned())
return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY,
fmt::format(R"(Index selection property "{}" has no selection values assigned)", propNamePtr));

for (SizeT i = 0; i < selectionList.getCount(); ++i)
{
if (selectionList.getItemAt(i) == valuePtr)
{
indexOrKey = Int(i);
break;
}
}

if (!indexOrKey.assigned())
return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND,
fmt::format(R"(Value not found in selection values of property "{}")", propNamePtr));
}
else if (propType == PropertyType::SparseSelection)
{
const DictPtr<IBaseObject, IBaseObject> selectionDict = prop.getSelectionValues();
if (!selectionDict.assigned())
return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY,
fmt::format(R"(Sparse selection property "{}" has no selection values assigned)", propNamePtr));

for (const auto& [key, val] : selectionDict)
{
if (val == valuePtr)
{
indexOrKey = key;
break;
}
}

if (!indexOrKey.assigned())
return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND,
fmt::format(R"(Value not found in sparse selection values of property "{}")", propNamePtr));
}
else
{
return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY,
fmt::format(R"(Property "{}" is not a selection property)", propNamePtr));
}

return TmsClientPropertyObjectBaseImpl::setPropertyValue(propertyName, indexOrKey);
});
}

template <typename Impl>
ErrCode INTERFACE_FUNC TmsClientPropertyObjectBaseImpl<Impl>::getPropertySelectionValue(IString* propertyName, IBaseObject** value)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ void TmsServerProperty::bindCallbacks()

opcua::OpcUaNodeId TmsServerProperty::getTmsTypeId()
{
if (objectInternal.getSelectionValuesUnresolved().assigned())
if (objectInternal.getSelectionValuesUnresolved().assigned() && object.getItemType() != CoreType::ctUndefined)
return OpcUaNodeId(NAMESPACE_DAQBT, UA_DAQBTID_SELECTIONVARIABLETYPE);

if (objectInternal.getReferencedPropertyUnresolved().assigned())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ class TmsPropertyObjectTest : public TmsObjectIntegrationTest, public testing::T
return object;
}

PropertyObjectPtr createSelectionPropertyObject()
{
auto obj = PropertyObject();

// Non-index/sparse selections
obj.addProperty(StringPropertyBuilder("StringSelection", "foo").setSelectionValues(List<IString>("foo", "bar")).build());
obj.addProperty(IntPropertyBuilder("IntSelection", 10).setSelectionValues(List<IInteger>(0, 6, 15, 10)).setIsIntegerValueSelection(true).build());
obj.addProperty(FloatPropertyBuilder("FloatSelection", 5.12).setSelectionValues(List<IFloat>(0.12, -5.2, 5.12, 10.2)).build());
obj.addProperty(IntPropertyBuilder("IndexSelection", 3).setSelectionValues(List<IInteger>(0, 6, 15, 10)).build());

return obj;
}

StringPtr getLastMessage()
{
logger.flush();
Expand Down Expand Up @@ -425,6 +438,53 @@ TEST_F(TmsPropertyObjectTest, StringSuggestedValues)
ASSERT_NO_THROW(clientObj.setPropertyValue("StringSuggestedValues", "Tomato"));
}

// NOTE: Value-based selection properties are not yet supported over OPC UA. They still function, but the list
// of selection values is not populated on the client.
TEST_F(TmsPropertyObjectTest, SelectionPropertyValues)
{
auto obj = createSelectionPropertyObject();
auto [serverObj, clientObj] = registerPropertyObject(obj);

ASSERT_FALSE(clientObj.getProperty("StringSelection").getSelectionValues().assigned());
ASSERT_FALSE(clientObj.getProperty("IntSelection").getSelectionValues().assigned());
ASSERT_FALSE(clientObj.getProperty("FloatSelection").getSelectionValues().assigned());
ASSERT_TRUE(clientObj.getProperty("IndexSelection").getSelectionValues().assigned());

// check default values
ASSERT_EQ(clientObj.getPropertyValue("StringSelection"), "foo");
ASSERT_EQ(clientObj.getPropertyValue("IntSelection"), 10);
ASSERT_EQ(clientObj.getPropertyValue("FloatSelection"), 5.12);
ASSERT_EQ(clientObj.getPropertyValue("IndexSelection"), 3);

ASSERT_NO_THROW(clientObj.setPropertyValue("StringSelection", "Invalid")); // will print a warning but should not throw
ASSERT_EQ(clientObj.getPropertyValue("StringSelection"), "foo");
ASSERT_NO_THROW(clientObj.setPropertyValue("IntSelection", 42)); // will print a warning but should not throw
ASSERT_EQ(clientObj.getPropertyValue("IntSelection"), 10);
ASSERT_NO_THROW(clientObj.setPropertyValue("FloatSelection", 3.14)); // will print a warning but should not throw
ASSERT_EQ(clientObj.getPropertyValue("FloatSelection"), 5.12);
ASSERT_NO_THROW(clientObj.setPropertyValue("IndexSelection", 5)); // will print a warning but should not throw
ASSERT_EQ(clientObj.getPropertyValue("IndexSelection"), 3);

ASSERT_NO_THROW(clientObj.setPropertyValue("StringSelection", "bar"));
ASSERT_NO_THROW(clientObj.setPropertyValue("IntSelection", 0));
ASSERT_NO_THROW(clientObj.setPropertyValue("FloatSelection", 0.12));
ASSERT_NO_THROW(clientObj.setPropertyValue("IndexSelection", 0));

ASSERT_EQ(obj.getPropertyValue("StringSelection"), "bar");
ASSERT_EQ(obj.getPropertyValue("IntSelection"), 0);
ASSERT_DOUBLE_EQ(obj.getPropertyValue("FloatSelection"), 0.12);
ASSERT_EQ(obj.getPropertyValue("IndexSelection"), 0);

ASSERT_EQ(clientObj.getPropertyValue("StringSelection"), "bar");
ASSERT_EQ(clientObj.getPropertyValue("IntSelection"), 0);
ASSERT_DOUBLE_EQ(clientObj.getPropertyValue("FloatSelection"), 0.12);
ASSERT_EQ(clientObj.getPropertyValue("IndexSelection"), 0);

ASSERT_NO_THROW(clientObj.setPropertySelectionValue("IndexSelection", 10));
ASSERT_EQ(clientObj.getPropertyValue("IndexSelection"), 3);
ASSERT_EQ(clientObj.getPropertySelectionValue("IndexSelection"), 10);
}

class TmsNestedPropertyObjectTest : public TmsObjectIntegrationTest, public testing::Test
{
public:
Expand Down
Loading