button-handler: Add power button actions

Create match handlers for the following power button signals:

1) 'Released'
* If the system is off, then power it on.
* If the system is on, then soft power it off.

2) 'PressedLong' (Really means long press released)
* If the system is on, do an immediate power off.
* If the system is off, do nothing.

It only installs the match objects for these if the power button
object exists on D-Bus.  This is done so that systems that don't
implement a certain button won't still have watches on their
signals.

Tested:  Pushed buttons and watched the magic happen.

Change-Id: I6d17529d0dafc237f90a0e6a121e4b5da1204f81
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f5c3078..c4e8483 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,12 +12,17 @@
 set(ID_DBUS_OBJECT_NAME "xyz/openbmc_project/Chassis/Buttons/ID")
 set(GPIO_BASE_LABEL_NAME "1e780000.gpio")
 set(LONG_PRESS_TIME_MS 3000)
+set(CHASSIS_STATE_OBJECT_NAME "xyz/openbmc_project/state/chassis")
+set(HOST_STATE_OBJECT_NAME "xyz/openbmc_project/state/host")
 
 add_definitions(-DPOWER_DBUS_OBJECT_NAME="/${POWER_DBUS_OBJECT_NAME}0")
 add_definitions(-DRESET_DBUS_OBJECT_NAME="/${RESET_DBUS_OBJECT_NAME}0")
 add_definitions(-DID_DBUS_OBJECT_NAME="/${ID_DBUS_OBJECT_NAME}0")
 add_definitions(-DGPIO_BASE_LABEL_NAME="${GPIO_BASE_LABEL_NAME}")
 add_definitions(-DLONG_PRESS_TIME_MS=${LONG_PRESS_TIME_MS})
+add_definitions(-DHOST_STATE_OBJECT_NAME="/${HOST_STATE_OBJECT_NAME}0")
+add_definitions(-DCHASSIS_STATE_OBJECT_NAME="/${CHASSIS_STATE_OBJECT_NAME}0")
+
 set(SRC_FILES src/power_button.cpp
     src/reset_button.cpp
     src/id_button.cpp
diff --git a/inc/button_handler.hpp b/inc/button_handler.hpp
index 4bc6310..790bbe9 100644
--- a/inc/button_handler.hpp
+++ b/inc/button_handler.hpp
@@ -38,9 +38,58 @@
 
   private:
     /**
+     * @brief The handler for a power button press
+     *
+     * It will power on the system if it's currently off,
+     * else it will soft power it off.
+     *
+     * @param[in] msg - sdbusplus message from signal
+     */
+    void powerPressed(sdbusplus::message::message& msg);
+
+    /**
+     * @brief The handler for a long power button press
+     *
+     * If the system is currently powered on, it will
+     * perform an immediate power off.
+     *
+     * @param[in] msg - sdbusplus message from signal
+     */
+    void longPowerPressed(sdbusplus::message::message& msg);
+
+    /**
+     * @brief Checks if system is powered on
+     *
+     * @return true if powered on, false else
+     */
+    bool poweredOn() const;
+
+    /**
+     * @brief Returns the service name for an object
+     *
+     * @param[in] path - the object path
+     * @param[in] interface - the interface name
+     *
+     * @return std::string - the D-Bus service name if found, else
+     *                       an empty string
+     */
+    std::string getService(const std::string& path,
+                           const std::string& interface) const;
+
+    /**
      * @brief sdbusplus connection object
      */
     sdbusplus::bus::bus& bus;
+
+    /**
+     * @brief Matches on the power button released signal
+     */
+    std::unique_ptr<sdbusplus::bus::match_t> powerButtonReleased;
+
+    /**
+     * @brief Matches on the power button long press released signal
+     */
+    std::unique_ptr<sdbusplus::bus::match_t> powerButtonLongPressReleased;
 };
 
 } // namespace button
diff --git a/src/button_handler.cpp b/src/button_handler.cpp
index e8edc41..f4b43fc 100644
--- a/src/button_handler.cpp
+++ b/src/button_handler.cpp
@@ -1,11 +1,148 @@
 #include "button_handler.hpp"
 
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/State/Chassis/server.hpp>
+#include <xyz/openbmc_project/State/Host/server.hpp>
+
 namespace phosphor
 {
 namespace button
 {
+
+namespace sdbusRule = sdbusplus::bus::match::rules;
+using namespace sdbusplus::xyz::openbmc_project::State::server;
+using namespace phosphor::logging;
+using sdbusplus::exception::SdBusError;
+
+constexpr auto propertyIface = "org.freedesktop.DBus.Properties";
+constexpr auto chassisIface = "xyz.openbmc_project.State.Chassis";
+constexpr auto hostIface = "xyz.openbmc_project.State.Host";
+constexpr auto powerButtonIface = "xyz.openbmc_project.Chassis.Buttons.Power";
+constexpr auto mapperIface = "xyz.openbmc_project.ObjectMapper";
+
+constexpr auto mapperObjPath = "/xyz/openbmc_project/object_mapper";
+constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
+
 Handler::Handler(sdbusplus::bus::bus& bus) : bus(bus)
 {
+    try
+    {
+        if (!getService(POWER_DBUS_OBJECT_NAME, powerButtonIface).empty())
+        {
+            log<level::INFO>("Starting power button handler");
+            powerButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
+                bus,
+                sdbusRule::type::signal() + sdbusRule::member("Released") +
+                    sdbusRule::path(POWER_DBUS_OBJECT_NAME) +
+                    sdbusRule::interface(powerButtonIface),
+                std::bind(std::mem_fn(&Handler::powerPressed), this,
+                          std::placeholders::_1));
+
+            powerButtonLongPressReleased =
+                std::make_unique<sdbusplus::bus::match_t>(
+                    bus,
+                    sdbusRule::type::signal() +
+                        sdbusRule::member("PressedLong") +
+                        sdbusRule::path(POWER_DBUS_OBJECT_NAME) +
+                        sdbusRule::interface(powerButtonIface),
+                    std::bind(std::mem_fn(&Handler::longPowerPressed), this,
+                              std::placeholders::_1));
+        }
+    }
+    catch (SdBusError& e)
+    {
+        // The button wasn't implemented
+    }
+}
+
+std::string Handler::getService(const std::string& path,
+                                const std::string& interface) const
+{
+    auto method = bus.new_method_call(mapperService, mapperObjPath, mapperIface,
+                                      "GetObject");
+    method.append(path, std::vector{interface});
+    auto result = bus.call(method);
+
+    std::map<std::string, std::vector<std::string>> objectData;
+    result.read(objectData);
+
+    return objectData.begin()->first;
+}
+
+bool Handler::poweredOn() const
+{
+    auto service = getService(CHASSIS_STATE_OBJECT_NAME, chassisIface);
+    auto method = bus.new_method_call(
+        service.c_str(), CHASSIS_STATE_OBJECT_NAME, propertyIface, "Get");
+    method.append(chassisIface, "CurrentPowerState");
+    auto result = bus.call(method);
+
+    sdbusplus::message::variant<std::string> state;
+    result.read(state);
+
+    return Chassis::PowerState::On ==
+           Chassis::convertPowerStateFromString(
+               sdbusplus::message::variant_ns::get<std::string>(state));
+}
+
+void Handler::powerPressed(sdbusplus::message::message& msg)
+{
+    auto transition = Host::Transition::On;
+
+    try
+    {
+        if (poweredOn())
+        {
+            transition = Host::Transition::Off;
+        }
+
+        log<level::INFO>("Handling power button press");
+
+        sdbusplus::message::variant<std::string> state =
+            convertForMessage(transition);
+
+        auto service = getService(HOST_STATE_OBJECT_NAME, hostIface);
+        auto method = bus.new_method_call(
+            service.c_str(), HOST_STATE_OBJECT_NAME, propertyIface, "Set");
+        method.append(hostIface, "RequestedHostTransition", state);
+
+        bus.call(method);
+    }
+    catch (SdBusError& e)
+    {
+        log<level::ERR>("Failed power state change on a power button press",
+                        entry("ERROR=%s", e.what()));
+    }
+}
+
+void Handler::longPowerPressed(sdbusplus::message::message& msg)
+{
+    try
+    {
+        if (!poweredOn())
+        {
+            log<level::INFO>(
+                "Power is off so ignoring long power button press");
+            return;
+        }
+
+        log<level::INFO>("Handling long power button press");
+
+        sdbusplus::message::variant<std::string> state =
+            convertForMessage(Chassis::Transition::Off);
+
+        auto service = getService(CHASSIS_STATE_OBJECT_NAME, chassisIface);
+        auto method = bus.new_method_call(
+            service.c_str(), CHASSIS_STATE_OBJECT_NAME, propertyIface, "Set");
+        method.append(chassisIface, "RequestedPowerTransition", state);
+
+        bus.call(method);
+    }
+    catch (SdBusError& e)
+    {
+        log<level::ERR>("Failed powering off on long power button press",
+                        entry("ERROR=%s", e.what()));
+    }
 }
 } // namespace button
 } // namespace phosphor