common: host power utility

Create this utility class to abstract host power operation with a single
function call.

This was refactored out of [1] (BIOS Code Updater)

Tested: Has been tested as part of [1]

References:

[1] https://gerrit.openbmc.org/c/openbmc/phosphor-bmc-code-mgmt/+/76101

Change-Id: I97dc8b1824f70f0aeede3b39683c2ee4ef9ca3c9
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/common/include/host_power.hpp b/common/include/host_power.hpp
new file mode 100644
index 0000000..90cfff1
--- /dev/null
+++ b/common/include/host_power.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <sdbusplus/async/context.hpp>
+#include <sdbusplus/async/match.hpp>
+#include <xyz/openbmc_project/State/Host/client.hpp>
+
+namespace phosphor::software::host_power
+{
+
+const auto stateOn =
+    sdbusplus::client::xyz::openbmc_project::state::Host<>::HostState::Running;
+const auto stateOff =
+    sdbusplus::client::xyz::openbmc_project::state::Host<>::HostState::Off;
+
+using HostState =
+    sdbusplus::client::xyz::openbmc_project::state::Host<>::HostState;
+
+class HostPower
+{
+  public:
+    HostPower(sdbusplus::async::context& ctx);
+
+    // @param state   desired powerstate
+    // @returns       true on success
+    static sdbusplus::async::task<bool> setState(sdbusplus::async::context& ctx,
+                                                 HostState state);
+
+    // @returns       host powerstate
+    static sdbusplus::async::task<HostState> getState(
+        sdbusplus::async::context& ctx);
+
+    sdbusplus::async::match stateChangedMatch;
+};
+
+}; // namespace phosphor::software::host_power
diff --git a/common/meson.build b/common/meson.build
index 73ef76f..1e29ae8 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -8,6 +8,7 @@
     'src/software_config.cpp',
     'src/software.cpp',
     'src/software_update.cpp',
+    'src/host_power.cpp',
     include_directories: ['.', 'include/', common_include],
     dependencies: [pdi_dep, phosphor_logging_dep, sdbusplus_dep, libpldm_dep],
 )
diff --git a/common/src/host_power.cpp b/common/src/host_power.cpp
new file mode 100644
index 0000000..d6a78a1
--- /dev/null
+++ b/common/src/host_power.cpp
@@ -0,0 +1,98 @@
+#include "host_power.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+#include <sdbusplus/async/context.hpp>
+#include <sdbusplus/async/match.hpp>
+#include <sdbusplus/async/proxy.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/message/native_types.hpp>
+#include <xyz/openbmc_project/ObjectMapper/client.hpp>
+#include <xyz/openbmc_project/State/Host/client.hpp>
+
+PHOSPHOR_LOG2_USING;
+
+using namespace std::literals;
+
+namespace RulesIntf = sdbusplus::bus::match::rules;
+
+using StateIntf =
+    sdbusplus::client::xyz::openbmc_project::state::Host<void, void>;
+
+const auto transitionOn =
+    sdbusplus::client::xyz::openbmc_project::state::Host<>::Transition::On;
+const auto transitionOff =
+    sdbusplus::client::xyz::openbmc_project::state::Host<>::Transition::Off;
+
+namespace phosphor::software::host_power
+{
+
+constexpr const char* host0ObjectPath = "/xyz/openbmc_project/state/host0";
+constexpr const char* service = "xyz.openbmc_project.State.Host";
+
+HostPower::HostPower(sdbusplus::async::context& ctx) :
+    stateChangedMatch(ctx, RulesIntf::propertiesChanged(host0ObjectPath,
+                                                        StateIntf::interface))
+{}
+
+// NOLINTBEGIN(readability-static-accessed-through-instance)
+sdbusplus::async::task<bool> HostPower::setState(sdbusplus::async::context& ctx,
+                                                 HostState state)
+// NOLINTEND(readability-static-accessed-through-instance)
+{
+    if (state != stateOn && state != stateOff)
+    {
+        error("Invalid power state {STATE}", "STATE", state);
+        co_return false;
+    }
+
+    auto client = sdbusplus::client::xyz::openbmc_project::state::Host(ctx)
+                      .service(service)
+                      .path(host0ObjectPath);
+
+    co_await client.requested_host_transition(
+        (state == stateOn) ? transitionOn : transitionOff);
+
+    debug("Requested host transition to {STATE}", "STATE", state);
+
+    constexpr size_t retries = 4;
+    constexpr size_t retryTimeout = 3;
+
+    for (size_t i = 0; i < retries; i++)
+    {
+        co_await sdbusplus::async::sleep_for(
+            ctx, std::chrono::seconds(retryTimeout));
+
+        if ((co_await client.current_host_state()) == state)
+        {
+            debug("Successfully achieved state {STATE}", "STATE", state);
+            co_return true;
+        }
+    }
+
+    error("Failed to achieve state {STATE} before the timeout of {TIMEOUT}s",
+          "STATE", state, "TIMEOUT", retries * retryTimeout);
+
+    co_return false;
+}
+
+// NOLINTBEGIN(readability-static-accessed-through-instance)
+sdbusplus::async::task<HostState> HostPower::getState(
+    sdbusplus::async::context& ctx)
+// NOLINTEND(readability-static-accessed-through-instance)
+{
+    auto client = sdbusplus::client::xyz::openbmc_project::state::Host(ctx)
+                      .service(service)
+                      .path(host0ObjectPath);
+
+    auto res = co_await client.current_host_state();
+
+    if (res != stateOn && res != stateOff)
+    {
+        error("Unexpected power state: {STATE}", "STATE", res);
+    }
+
+    co_return res;
+}
+
+} // namespace phosphor::software::host_power