diff --git a/modules/opcua_client_module/src/opcua_client_module_impl.cpp b/modules/opcua_client_module/src/opcua_client_module_impl.cpp index 0ceb21a3..b50856af 100644 --- a/modules/opcua_client_module/src/opcua_client_module_impl.cpp +++ b/modules/opcua_client_module/src/opcua_client_module_impl.cpp @@ -1,18 +1,17 @@ #include #include -#include -#include #include -#include +#include #include +#include +#include #include #include #include -#include #include -#include - #include +#include +#include BEGIN_NAMESPACE_OPENDAQ_OPCUA_CLIENT_MODULE static const char* DaqOpcUaDeviceTypeId = "OpenDAQOPCUAConfiguration"; @@ -115,6 +114,7 @@ DevicePtr OpcUaClientModule::onCreateDevice(const StringPtr& connectionString, .addAddressInfo(addressInfo) .freeze(); + device.asPtr(true).setAsRoot(); return device; } diff --git a/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_component_impl.h b/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_component_impl.h index 18511284..e6b93e0c 100644 --- a/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_component_impl.h +++ b/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_component_impl.h @@ -73,6 +73,8 @@ class TmsClientComponentBaseImpl : public TmsClientPropertyObjectBaseImpl // Component overrides ErrCode INTERFACE_FUNC getActive(Bool* active) override; + ErrCode INTERFACE_FUNC getLocalActive(Bool* active) override; + ErrCode INTERFACE_FUNC getParentActive(Bool* active) override; ErrCode INTERFACE_FUNC setActive(Bool active) override; ErrCode INTERFACE_FUNC getName(IString** name) override; ErrCode INTERFACE_FUNC setName(IString* name) override; diff --git a/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_device_impl.h b/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_device_impl.h index 229a9e92..ba8264e2 100644 --- a/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_device_impl.h +++ b/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_device_impl.h @@ -38,6 +38,7 @@ class TmsClientDeviceImpl : public TmsClientComponentBaseImpl::getActive(Bool* active) return OPENDAQ_SUCCESS; } +template +ErrCode TmsClientComponentBaseImpl::getLocalActive(Bool* active) +{ + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOT_SUPPORTED); +} + +template +ErrCode TmsClientComponentBaseImpl::getParentActive(Bool* active) +{ + ComponentPtr parent; + OPENDAQ_RETURN_IF_FAILED(this->getParent(&parent)); + + if (parent.assigned()) + return parent->getActive(active); + else + *active = True; + return OPENDAQ_SUCCESS; +} + template ErrCode TmsClientComponentBaseImpl::setActive(Bool active) { diff --git a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_device_impl.cpp b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_device_impl.cpp index 0d23f8e8..fc2c6525 100644 --- a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_device_impl.cpp +++ b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_device_impl.cpp @@ -489,6 +489,19 @@ PropertyObjectPtr TmsClientDeviceImpl::onCreateDefaultAddDeviceConfig() return PropertyObject(); } +ErrCode TmsClientDeviceImpl::getParentActive(Bool* active) +{ + OPENDAQ_PARAM_NOT_NULL(active); + + if (this->isRootDevice) + { + *active = True; + return OPENDAQ_SUCCESS; + } + + return Super::getParentActive(active); +} + void TmsClientDeviceImpl::findAndCreateFunctionBlocks() { std::map orderedFunctionBlocks; diff --git a/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_folder.cpp b/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_folder.cpp index b3ed8a4f..66286075 100644 --- a/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_folder.cpp +++ b/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_folder.cpp @@ -116,6 +116,95 @@ TEST_F(TmsFolderTest, Active) folder.clientFolder.getItems()[0].asPtr().getItems()[0].getActive()); } +TEST_F(TmsFolderTest, GetParentActiveParentIsActive) +{ + auto folder = registerTestFolder(createTestFolder()); + + auto child = folder.clientFolder.getItems()[0]; + Bool active = false; + ASSERT_EQ(child->getParentActive(&active), OPENDAQ_SUCCESS); + ASSERT_EQ(active, folder.serverFolder.getActive()); +} + +TEST_F(TmsFolderTest, GetParentActiveParentIsInactive) +{ + auto folder = registerTestFolder(createTestFolder()); + + folder.clientFolder.setActive(false); + auto child = folder.clientFolder.getItems()[0]; + Bool active = true; + ASSERT_EQ(child->getParentActive(&active), OPENDAQ_SUCCESS); + ASSERT_EQ(active, folder.serverFolder.getActive()); +} + +// When the folder is set inactive, nested components inherit that state: +// server's getActive() returns localActive && parentActive, so OPC UA +// reflects the combined value. All assertions below verify client == server. + +TEST_F(TmsFolderTest, NestedActiveInheritsParentInactive) +{ + auto folder = registerTestFolder(createTestFolder()); + auto child = folder.clientFolder.getItems()[0]; + + // baseline + ASSERT_TRUE(folder.clientFolder.getActive()); + ASSERT_TRUE(child.getActive()); + + folder.clientFolder.setActive(false); + + ASSERT_FALSE(folder.clientFolder.getActive()); + ASSERT_EQ(folder.serverFolder.getActive(), folder.clientFolder.getActive()); + + // child's getActive reads OPC UA which reflects parentActive && localActive + ASSERT_FALSE(child.getActive()); + ASSERT_EQ(folder.serverFolder.getItems()[0].getActive(), child.getActive()); +} + +TEST_F(TmsFolderTest, NestedGetParentActiveMatchesFolderActive) +{ + auto folder = registerTestFolder(createTestFolder()); + auto child = folder.clientFolder.getItems()[0]; + + folder.clientFolder.setActive(false); + + Bool parentActive; + ASSERT_EQ(child->getParentActive(&parentActive), OPENDAQ_SUCCESS); + ASSERT_FALSE(parentActive); + ASSERT_EQ(parentActive, folder.serverFolder.getActive()); +} + +TEST_F(TmsFolderTest, DeepNestedActiveInheritsParentInactive) +{ + auto folder = registerTestFolder(createTestFolder()); + auto child = folder.clientFolder.getItems()[0]; + auto leaf = child.asPtr().getItems()[0]; + + folder.clientFolder.setActive(false); + + // leaf.getActive() reads OPC UA: serverLeaf.getActive() = localActive && serverChild.getActive() + // = true && (localActive && serverFolder.getActive()) = true && (true && false) = false + ASSERT_FALSE(leaf.getActive()); + ASSERT_EQ(folder.serverFolder.getItems()[0].asPtr().getItems()[0].getActive(), leaf.getActive()); + + // leaf.getParentActive() calls child.getActive() which is also false + Bool parentActive; + ASSERT_EQ(leaf->getParentActive(&parentActive), OPENDAQ_SUCCESS); + ASSERT_FALSE(parentActive); +} + +TEST_F(TmsFolderTest, NestedActiveRestoresAfterFolderReactivated) +{ + auto folder = registerTestFolder(createTestFolder()); + auto child = folder.clientFolder.getItems()[0]; + + folder.clientFolder.setActive(false); + ASSERT_FALSE(child.getActive()); + + folder.clientFolder.setActive(true); + ASSERT_TRUE(child.getActive()); + ASSERT_EQ(folder.serverFolder.getItems()[0].getActive(), child.getActive()); +} + TEST_F(TmsFolderTest, Tags) { auto folder = registerTestFolder(createTestFolder());