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/leakdetector/LeakDetectionManager.cpp b/src/leakdetector/LeakDetectionManager.cpp
new file mode 100644
index 0000000..5348f5f
--- /dev/null
+++ b/src/leakdetector/LeakDetectionManager.cpp
@@ -0,0 +1,158 @@
+#include "LeakDetectionManager.hpp"
+
+#include "LeakGPIODetector.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/message/native_types.hpp>
+#include <sdbusplus/server/manager.hpp>
+
+#include <exception>
+#include <functional>
+#include <memory>
+#include <optional>
+#include <string>
+
+PHOSPHOR_LOG2_USING;
+
+namespace leak
+{
+
+DetectionManager::DetectionManager(sdbusplus::async::context& ctx) :
+    ctx(ctx), leakEvents(ctx),
+    entityManager(
+        ctx, {GPIODetectorConfigIntf::interface},
+        std::bind_front(&DetectionManager::processInventoryAdded, this),
+        std::bind_front(&DetectionManager::processInventoryRemoved, this))
+{
+    ctx.spawn(entityManager.handleInventoryGet());
+}
+
+auto DetectionManager::processInventoryAdded(
+    const sdbusplus::message::object_path& objectPath,
+    const std::string& /*unused*/) -> void
+{
+    ctx.spawn(processConfigAddedAsync(objectPath));
+}
+
+auto DetectionManager::processInventoryRemoved(
+    const sdbusplus::message::object_path& objectPath,
+    const std::string& /*unused*/) -> void
+{
+    if (!detectors.contains(objectPath.str))
+    {
+        return;
+    }
+    debug("Removed detector {DETECTOR}", "DETECTOR", objectPath);
+    detectors.erase(objectPath.str);
+}
+
+auto DetectionManager::processConfigAddedAsync(
+    sdbusplus::message::object_path objectPath) -> sdbusplus::async::task<>
+{
+    auto res = co_await getDetectorConfig(objectPath);
+    if (!res)
+    {
+        co_return;
+    }
+    auto config = res.value();
+
+    if (detectors.contains(objectPath.str))
+    {
+        warning("Detector {DETECTOR} already exist", "DETECTOR", config.name);
+        co_return;
+    }
+
+    try
+    {
+        detectors[objectPath.str] =
+            std::make_unique<GPIODetector>(ctx, leakEvents, config);
+    }
+    catch (std::exception& e)
+    {
+        error("Failed to create detector {DETECTOR}: {ERROR}", "DETECTOR",
+              config.name, "ERROR", e.what());
+    }
+
+    co_return;
+}
+
+auto DetectionManager::getDetectorConfig(
+    sdbusplus::message::object_path objectPath)
+    -> sdbusplus::async::task<std::optional<config::DetectorConfig>>
+{
+    config::DetectorConfig config = {};
+
+    auto properties =
+        co_await GPIODetectorConfigIntf(ctx)
+            .service(entity_manager::EntityManagerInterface::serviceName)
+            .path(objectPath.str)
+            .properties();
+
+    config.name = properties.name;
+
+    for (const auto& [key, value] : config::validDetectorTypes)
+    {
+        if (properties.type == key)
+        {
+            config.type = value;
+            break;
+        }
+    }
+
+    config.pinName = properties.pin_name;
+
+    for (const auto& [key, value] : config::validPinPolarity)
+    {
+        if (properties.polarity == key)
+        {
+            config.polarity = value;
+            break;
+        }
+    }
+    if (config.polarity == config::PinPolarity::unknown)
+    {
+        error("Invalid polarity {POLARITY} for {NAME}", "POLARITY",
+              properties.polarity, "NAME", config.name);
+        co_return std::nullopt;
+    }
+
+    for (const auto& [key, value] : config::validDetectorLevel)
+    {
+        if (properties.level == key)
+        {
+            config.level = value;
+            break;
+        }
+    }
+    if (config.level == config::DetectorLevel::unknown)
+    {
+        error("Invalid level {LEVEL} for {NAME}", "LEVEL", properties.level,
+              "NAME", config.name);
+        co_return std::nullopt;
+    }
+
+    debug("Detector config: {NAME} {PIN_NAME} {POLARITY} {LEVEL}", "NAME",
+          config.name, "PIN_NAME", config.pinName, "POLARITY", config.polarity,
+          "LEVEL", config.level);
+
+    co_return config;
+}
+
+} // namespace leak
+
+int main()
+{
+    constexpr auto path = leak::DetectorIntf::namespace_path::value;
+    constexpr auto serviceName = "xyz.openbmc_project.leakdetector";
+    sdbusplus::async::context ctx;
+    sdbusplus::server::manager_t manager{ctx, path};
+
+    info("Creating leak detection manager at {PATH}", "PATH", path);
+    leak::DetectionManager leakDetectionManager{ctx};
+
+    ctx.request_name(serviceName);
+
+    ctx.run();
+    return 0;
+}