FRU object to be created once

- Create and recreate fru only when invnetory info change

Fixes jira https://jirasw.nvidia.com/browse/DGXOPENBMC-17802
diff --git a/nsmd/deviceManager.cpp b/nsmd/deviceManager.cpp
index 02b50b8..e5b1572 100644
--- a/nsmd/deviceManager.cpp
+++ b/nsmd/deviceManager.cpp
@@ -737,67 +737,63 @@
         }
     }
 
-    // expose inventory information to FruDevice PDI
-    std::string objPath = "/xyz/openbmc_project/FruDevice/" +
-                          std::to_string(eid);
-    nsmDevice->fruDeviceIntf = objServer.add_unique_interface(
-        objPath, "xyz.openbmc_project.FruDevice");
+    // Determine currently supported properties by checking specific known
+    // properties
+    std::set<std::string> currentProperties;
 
+    // Check for dynamic properties that the device might support
     if (properties.find(BOARD_PART_NUMBER) != properties.end())
     {
-        nsmDevice->fruDeviceIntf->register_property(
-            "BOARD_PART_NUMBER",
-            std::get<std::string>(properties[BOARD_PART_NUMBER]));
+        currentProperties.insert("BOARD_PART_NUMBER");
     }
-
     if (properties.find(FRU_PART_NUMBER) != properties.end())
     {
-        nsmDevice->fruDeviceIntf->register_property(
-            "FRU_PART_NUMBER",
-            std::get<std::string>(properties[FRU_PART_NUMBER]));
+        currentProperties.insert("FRU_PART_NUMBER");
     }
-
     if (properties.find(SERIAL_NUMBER) != properties.end())
     {
-        nsmDevice->fruDeviceIntf->register_property(
-            "SERIAL_NUMBER", std::get<std::string>(properties[SERIAL_NUMBER]));
+        currentProperties.insert("SERIAL_NUMBER");
     }
-
     if (properties.find(MARKETING_NAME) != properties.end())
     {
-        nsmDevice->fruDeviceIntf->register_property(
-            "MARKETING_NAME",
-            std::get<std::string>(properties[MARKETING_NAME]));
+        currentProperties.insert("MARKETING_NAME");
     }
-
     if (properties.find(BUILD_DATE) != properties.end())
     {
-        nsmDevice->fruDeviceIntf->register_property(
-            "BUILD_DATE", std::get<std::string>(properties[BUILD_DATE]));
+        currentProperties.insert("BUILD_DATE");
     }
-
-    auto mctpUuid = utils::getUUIDFromEID(eidTable, eid);
-    if (mctpUuid.has_value())
-    {
-        nsmDevice->uuid = *mctpUuid;
-    }
-
     if (properties.find(DEVICE_GUID) != properties.end())
     {
-        nsmDevice->fruDeviceIntf->register_property(
-            "DEVICE_UUID", std::get<uuid_t>(properties[DEVICE_GUID]));
-        nsmDevice->deviceUuid = std::get<uuid_t>(properties[DEVICE_GUID]);
+        currentProperties.insert("DEVICE_UUID");
     }
 
-    nsmDevice->fruDeviceIntf->register_property("DEVICE_TYPE",
-                                                nsmDevice->getDeviceType());
-    nsmDevice->fruDeviceIntf->register_property("INSTANCE_NUMBER",
-                                                nsmDevice->getInstanceNumber());
-    nsmDevice->fruDeviceIntf->register_property("UUID", nsmDevice->uuid);
+    // Always include fixed properties
+    currentProperties.insert("DEVICE_TYPE");
+    currentProperties.insert("INSTANCE_NUMBER");
+    currentProperties.insert("UUID");
 
-    nsmDevice->fruDeviceIntf->initialize();
+    // Check if interface needs recreation
+    if (nsmDevice->fruDeviceManager.needsRecreation(currentProperties))
+    {
+        lg2::info(
+            "Creating/Recreating FRU interface for eid={EID} with {COUNT} properties",
+            "EID", eid, "COUNT", currentProperties.size());
 
-    // coverity[missing_return]
+        // Create interface and register all properties
+        nsmDevice->fruDeviceManager.createAndRegisterInterface(
+            objServer, eid, nsmDevice, properties, eidTable);
+
+        nsmDevice->fruDeviceManager.markInitialized(currentProperties);
+    }
+    else
+    {
+        lg2::debug("Updating FRU property values for eid={EID}", "EID", eid);
+
+        // Just update property values
+        nsmDevice->fruDeviceManager.updateAllPropertyValues(
+            nsmDevice, properties, eidTable);
+    }
+
     co_return rc;
 }
 
diff --git a/nsmd/nsmDevice.cpp b/nsmd/nsmDevice.cpp
index 60e2136..5852739 100644
--- a/nsmd/nsmDevice.cpp
+++ b/nsmd/nsmDevice.cpp
@@ -318,4 +318,163 @@
     addSensor(msgTypesSensor, false);
 }
 
+// FruInterfaceManager method implementations
+bool FruInterfaceManager::needsRecreation(
+    const std::set<std::string>& newProperties) const
+{
+    return !initialized || (supportedProperties != newProperties);
+}
+
+void FruInterfaceManager::markInitialized(
+    const std::set<std::string>& properties)
+{
+    supportedProperties = properties;
+    initialized = true;
+}
+
+bool FruInterfaceManager::isPropertySupported(
+    const std::string& propertyName) const
+{
+    return supportedProperties.find(propertyName) != supportedProperties.end();
+}
+
+void FruInterfaceManager::reset()
+{
+    interface.reset();
+    supportedProperties.clear();
+    initialized = false;
+}
+
+void FruInterfaceManager::createAndRegisterInterface(
+    sdbusplus::asio::object_server& objServer, uint8_t eid,
+    std::shared_ptr<NsmDevice> nsmDevice, const InventoryProperties& properties,
+    const std::multimap<uuid_t, std::tuple<eid_t, MctpMedium, MctpBinding>>&
+        eidTable)
+{
+    std::string objPath = "/xyz/openbmc_project/FruDevice/" +
+                          std::to_string(eid);
+    interface = objServer.add_unique_interface(objPath,
+                                               "xyz.openbmc_project.FruDevice");
+    registerAllProperties(nsmDevice, properties, eidTable);
+    interface->initialize();
+}
+
+void FruInterfaceManager::updateAllPropertyValues(
+    std::shared_ptr<NsmDevice> nsmDevice, const InventoryProperties& properties,
+    const std::multimap<uuid_t, std::tuple<eid_t, MctpMedium, MctpBinding>>&
+        eidTable)
+{
+    if (!interface)
+        return;
+
+    if (properties.find(BOARD_PART_NUMBER) != properties.end())
+    {
+        interface->set_property(
+            "BOARD_PART_NUMBER",
+            std::get<std::string>(properties.at(BOARD_PART_NUMBER)));
+    }
+
+    if (properties.find(FRU_PART_NUMBER) != properties.end())
+    {
+        interface->set_property(
+            "FRU_PART_NUMBER",
+            std::get<std::string>(properties.at(FRU_PART_NUMBER)));
+    }
+
+    if (properties.find(SERIAL_NUMBER) != properties.end())
+    {
+        interface->set_property(
+            "SERIAL_NUMBER",
+            std::get<std::string>(properties.at(SERIAL_NUMBER)));
+    }
+
+    if (properties.find(MARKETING_NAME) != properties.end())
+    {
+        interface->set_property(
+            "MARKETING_NAME",
+            std::get<std::string>(properties.at(MARKETING_NAME)));
+    }
+
+    if (properties.find(BUILD_DATE) != properties.end())
+    {
+        interface->set_property(
+            "BUILD_DATE", std::get<std::string>(properties.at(BUILD_DATE)));
+    }
+
+    auto mctpUuid = utils::getUUIDFromEID(eidTable, nsmDevice->eid);
+    if (mctpUuid.has_value())
+    {
+        nsmDevice->uuid = *mctpUuid;
+    }
+
+    if (properties.find(DEVICE_GUID) != properties.end())
+    {
+        interface->set_property("DEVICE_UUID",
+                                std::get<uuid_t>(properties.at(DEVICE_GUID)));
+        nsmDevice->deviceUuid = std::get<uuid_t>(properties.at(DEVICE_GUID));
+    }
+
+    interface->set_property("DEVICE_TYPE", nsmDevice->getDeviceType());
+    interface->set_property("INSTANCE_NUMBER", nsmDevice->getInstanceNumber());
+    interface->set_property("UUID", nsmDevice->uuid);
+}
+
+void FruInterfaceManager::registerAllProperties(
+    std::shared_ptr<NsmDevice> nsmDevice, const InventoryProperties& properties,
+    const std::multimap<uuid_t, std::tuple<eid_t, MctpMedium, MctpBinding>>&
+        eidTable)
+{
+    if (properties.find(BOARD_PART_NUMBER) != properties.end())
+    {
+        interface->register_property(
+            "BOARD_PART_NUMBER",
+            std::get<std::string>(properties.at(BOARD_PART_NUMBER)));
+    }
+
+    if (properties.find(FRU_PART_NUMBER) != properties.end())
+    {
+        interface->register_property(
+            "FRU_PART_NUMBER",
+            std::get<std::string>(properties.at(FRU_PART_NUMBER)));
+    }
+
+    if (properties.find(SERIAL_NUMBER) != properties.end())
+    {
+        interface->register_property(
+            "SERIAL_NUMBER",
+            std::get<std::string>(properties.at(SERIAL_NUMBER)));
+    }
+
+    if (properties.find(MARKETING_NAME) != properties.end())
+    {
+        interface->register_property(
+            "MARKETING_NAME",
+            std::get<std::string>(properties.at(MARKETING_NAME)));
+    }
+
+    if (properties.find(BUILD_DATE) != properties.end())
+    {
+        interface->register_property(
+            "BUILD_DATE", std::get<std::string>(properties.at(BUILD_DATE)));
+    }
+
+    auto mctpUuid = utils::getUUIDFromEID(eidTable, nsmDevice->eid);
+    if (mctpUuid.has_value())
+    {
+        nsmDevice->uuid = *mctpUuid;
+    }
+
+    if (properties.find(DEVICE_GUID) != properties.end())
+    {
+        interface->register_property(
+            "DEVICE_UUID", std::get<uuid_t>(properties.at(DEVICE_GUID)));
+        nsmDevice->deviceUuid = std::get<uuid_t>(properties.at(DEVICE_GUID));
+    }
+
+    interface->register_property("DEVICE_TYPE", nsmDevice->getDeviceType());
+    interface->register_property("INSTANCE_NUMBER",
+                                 nsmDevice->getInstanceNumber());
+    interface->register_property("UUID", nsmDevice->uuid);
+}
+
 } // namespace nsm
diff --git a/nsmd/nsmDevice.hpp b/nsmd/nsmDevice.hpp
index aa496d9..119ce25 100644
--- a/nsmd/nsmDevice.hpp
+++ b/nsmd/nsmDevice.hpp
@@ -19,6 +19,7 @@
 
 #include "base.h"
 #include "device-capability-discovery.h"
+#include "platform-environmental.h"
 
 #include "common/coroutineSemaphore.hpp"
 #include "common/types.hpp"
@@ -38,6 +39,7 @@
 #include <deque>
 #include <map>
 #include <ranges> // For ranges::find_if
+#include <set>
 
 #define MAX_SENSOR_UPDATE_BATCH_SIZE 10
 
@@ -68,6 +70,37 @@
     LongRunning,              // Long running polling for long running sensors
 };
 
+struct FruInterfaceManager
+{
+    std::unique_ptr<sdbusplus::asio::dbus_interface> interface;
+    std::set<std::string> supportedProperties;
+    bool initialized = false;
+
+    // Public method declarations only
+    bool needsRecreation(const std::set<std::string>& newProperties) const;
+    void markInitialized(const std::set<std::string>& properties);
+    bool isPropertySupported(const std::string& propertyName) const;
+    void reset();
+    void createAndRegisterInterface(
+        sdbusplus::asio::object_server& objServer, uint8_t eid,
+        std::shared_ptr<NsmDevice> nsmDevice,
+        const InventoryProperties& properties,
+        const std::multimap<uuid_t, std::tuple<eid_t, MctpMedium, MctpBinding>>&
+            eidTable);
+    void updateAllPropertyValues(
+        std::shared_ptr<NsmDevice> nsmDevice,
+        const InventoryProperties& properties,
+        const std::multimap<uuid_t, std::tuple<eid_t, MctpMedium, MctpBinding>>&
+            eidTable);
+
+  private:
+    void registerAllProperties(
+        std::shared_ptr<NsmDevice> nsmDevice,
+        const InventoryProperties& properties,
+        const std::multimap<uuid_t, std::tuple<eid_t, MctpMedium, MctpBinding>>&
+            eidTable);
+};
+
 class NsmDevice : public StateChangeLogger
 {
   public:
@@ -97,7 +130,7 @@
 #endif
     }
 
-    std::unique_ptr<sdbusplus::asio::dbus_interface> fruDeviceIntf;
+    FruInterfaceManager fruDeviceManager;
     std::unique_ptr<void, std::function<void(void*)>> nsmRawCmdIntf;
 
     eid_t eid = 0;