Implement Liquid Leak Detector

Implement liquid leak detector based on design
https://gerrit.openbmc.org/c/openbmc/docs/+/73152.

Related PRs:
https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/73151
https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/73707
https://gerrit.openbmc.org/c/openbmc/sdbusplus/+/75461
https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/75999

Tested:
```
>> busctl tree xyz.openbmc_project.LeakDetector
└─ /xyz
  └─ /xyz/openbmc_project
    └─ /xyz/openbmc_project/state
      └─ /xyz/openbmc_project/state/leak
        └─ /xyz/openbmc_project/state/leak/detector
          ├─ /xyz/openbmc_project/state/leak/detector/TrayDetector1
          └─ /xyz/openbmc_project/state/leak/detector/TrayDetector2

>> echo pull-up > /sys/devices/platform/gpio-sim.0/gpiochip2/sim_gpio0/pull

>> curl -k -H "X-Auth-Token: $token" -X GET https://${bmc}/redfish/v1/Systems/system/LogServices/EventLog/Entries
{
  "@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries",
  "@odata.type": "#LogEntryCollection.LogEntryCollection",
  "Description": "Collection of System Event Log Entries",
  "Members": [
  	...
    {
      "@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/2",
      "@odata.type": "#LogEntry.v1_9_0.LogEntry",
      "AdditionalDataURI": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/2/attachment",
      "Created": "2024-05-10T06:40:30.423+00:00",
      "EntryType": "Event",
      "Id": "2",
      "Message": "xyz.openbmc_project.State.Leak.Detector.LeakDetectedWarning",
      "Modified": "2024-05-10T06:40:30.423+00:00",
      "Name": "System Event Log Entry",
      "Resolved": false,
      "Severity": "Warning"
    }
  ],
  "Members@odata.count": 2,
  "Name": "System Event Log Entries"
}

>> echo pull-down > /sys/devices/platform/gpio-sim.0/gpiochip2/sim_gpio0/pull

>> curl -k -H "X-Auth-Token: $token" -X GET https://${bmc}/redfish/v1/Systems/system/LogServices/EventLog/Entries
{
  "@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries",
  "@odata.type": "#LogEntryCollection.LogEntryCollection",
  "Description": "Collection of System Event Log Entries",
  "Members": [
	...
    {
      "@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/2",
      "@odata.type": "#LogEntry.v1_9_0.LogEntry",
      "AdditionalDataURI": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/2/attachment",
      "Created": "2024-05-10T06:40:30.423+00:00",
      "EntryType": "Event",
      "Id": "2",
      "Message": "xyz.openbmc_project.State.Leak.Detector.LeakDetectedWarning",
      "Modified": "2024-05-10T06:42:23.989+00:00",
      "Name": "System Event Log Entry",
      "Resolved": true,
      "Severity": "Warning"
    },
    {
      "@odata.id": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/3",
      "@odata.type": "#LogEntry.v1_9_0.LogEntry",
      "AdditionalDataURI": "/redfish/v1/Systems/system/LogServices/EventLog/Entries/3/attachment",
      "Created": "2024-05-10T06:42:24.024+00:00",
      "EntryType": "Event",
      "Id": "3",
      "Message": "xyz.openbmc_project.State.Leak.Detector.LeakDetectedNormal",
      "Modified": "2024-05-10T06:42:24.024+00:00",
      "Name": "System Event Log Entry",
      "Resolved": false,
      "Severity": "OK"
    }
  ],
  "Members@odata.count": 3,
  "Name": "System Event Log Entries"
}
```

Change-Id: Id6981deb314ca6852c5e31b932b28e601c2f3976
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/src/EntityManagerInterface.cpp b/src/EntityManagerInterface.cpp
new file mode 100644
index 0000000..581cd1b
--- /dev/null
+++ b/src/EntityManagerInterface.cpp
@@ -0,0 +1,124 @@
+#include "EntityManagerInterface.hpp"
+
+#include "Utils.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/message/native_types.hpp>
+#include <xyz/openbmc_project/Inventory/Item/client.hpp>
+
+#include <algorithm>
+#include <utility>
+
+namespace entity_manager
+{
+
+PHOSPHOR_LOG2_USING;
+
+namespace rules_intf = sdbusplus::bus::match::rules;
+
+EntityManagerInterface::EntityManagerInterface(
+    sdbusplus::async::context& ctx, const interface_list_t& interfaceNames,
+    Callback_t addedCallback, Callback_t removedCallback) :
+    ctx(ctx), interfaceNames(interfaceNames),
+    addedCallback(std::move(addedCallback)),
+    removedCallback(std::move(removedCallback))
+{
+    ctx.spawn(handleInventoryAdded());
+    ctx.spawn(handleInventoryRemoved());
+}
+
+auto EntityManagerInterface::handleInventoryGet() -> sdbusplus::async::task<>
+{
+    if (!addedCallback)
+    {
+        error("addedCallback is not set");
+        co_return;
+    }
+
+    using InventoryIntf =
+        sdbusplus::client::xyz::openbmc_project::inventory::Item<>;
+
+    constexpr auto entityManager =
+        sdbusplus::async::proxy()
+            .service(serviceName)
+            .path(InventoryIntf::namespace_path)
+            .interface("org.freedesktop.DBus.ObjectManager");
+
+    for (const auto& [objectPath, detectorConfig] :
+         co_await entityManager.call<ManagedObjectType>(ctx,
+                                                        "GetManagedObjects"))
+    {
+        for (const auto& interfaceName : interfaceNames)
+        {
+            if (detectorConfig.contains(interfaceName))
+            {
+                addedCallback(objectPath, interfaceName);
+            }
+        }
+    }
+
+    co_return;
+}
+
+auto EntityManagerInterface::handleInventoryAdded() -> sdbusplus::async::task<>
+{
+    if (!addedCallback)
+    {
+        error("addedCallback is not set");
+        co_return;
+    }
+
+    auto addedMatch = sdbusplus::async::match(
+        ctx, rules_intf::interfacesAdded() + rules_intf::sender(serviceName));
+
+    while (!ctx.stop_requested())
+    {
+        auto [objectPath, inventoryData] =
+            co_await addedMatch
+                .next<sdbusplus::message::object_path, SensorData>();
+
+        for (const auto& interfaceName : interfaceNames)
+        {
+            if (inventoryData.contains(interfaceName))
+            {
+                addedCallback(objectPath, interfaceName);
+            }
+        }
+    }
+
+    co_return;
+}
+
+auto EntityManagerInterface::handleInventoryRemoved()
+    -> sdbusplus::async::task<>
+{
+    if (!removedCallback)
+    {
+        error("removedCallback is not set");
+        co_return;
+    }
+
+    auto removedMatch = sdbusplus::async::match(
+        ctx, rules_intf::interfacesRemoved() + rules_intf::sender(serviceName));
+
+    while (!ctx.stop_requested())
+    {
+        auto [objectPath, interfaces] =
+            co_await removedMatch
+                .next<sdbusplus::message::object_path, interface_list_t>();
+
+        for (const auto& interfaceName : interfaceNames)
+        {
+            if (std::ranges::find(interfaces, interfaceName) !=
+                interfaces.end())
+            {
+                removedCallback(objectPath, interfaceName);
+            }
+        }
+    }
+
+    co_return;
+}
+
+} // namespace entity_manager