sensor-mon: Create sensor-monitor application

This is a new application that will be used to take actions based on
sensor values or sensor threshold alarm values.  It's not built unless
the --enable-sensor-monitor option is used.

The first behavior will be to do power offs and create event logs based
on the xyz.openbmc_project.Sensor.Threshold.HardShutdown and
SoftShutdown threshold interfaces that may be present on sensor object
paths.  It will watch the high and low alarm properties on those
interfaces.  This could then be used to power off the system after some
delay if some sensor value gets too high or low, such as a high ambient
or a low voltage.

This first commit in the series creates the application and a new
ShutdownAlarmMonitor class, which is where the monitoring will happen.
Initially, it just finds all existing soft and hard shutdown interfaces
on D-Bus and puts them in a map.  It also registers for properties
changed signals for those interfaces.

Note that the interfacesAdded signal doesn't need to be used because all
sensor applications today emit propertiesChanged signals when they first
start up anyway.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ia5b82fcbe32f65d94b23bf26a025202a7f885183
diff --git a/Makefile.am b/Makefile.am
index 1d4ddec..c51d32a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -14,3 +14,6 @@
 if WANT_MONITOR
 SUBDIRS += monitor
 endif
+if WANT_SENSOR_MONITOR
+SUBDIRS += sensor-monitor
+endif
diff --git a/configure.ac b/configure.ac
index 230cd1d..d89fa3e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -71,6 +71,8 @@
     AS_HELP_STRING([--disable-cooling-type], [Disable cooling-type package.]))
 AC_ARG_ENABLE([monitor],
     AS_HELP_STRING([--disable-monitor], [Disable monitor]))
+AC_ARG_ENABLE([sensor-monitor],
+    AS_HELP_STRING([--enable-sensor-monitor], [Enable sensor monitor]))
 
 AM_CONDITIONAL([WANT_JSON], [test "x$enable_json" == "xyes"])
 AM_CONDITIONAL([WANT_JSON_CONTROL], [test "x$enable_json" == "xyes" -a "x$enable_json_control" != "xno"])
@@ -78,6 +80,7 @@
 AM_CONDITIONAL([WANT_CONTROL], [test "x$enable_control" != "xno"])
 AM_CONDITIONAL([WANT_COOLING_TYPE], [test "x$enable_cooling_type" != "xno"])
 AM_CONDITIONAL([WANT_MONITOR], [test "x$enable_monitor" != "xno"])
+AM_CONDITIONAL([WANT_SENSOR_MONITOR], [test "x$enable_sensor_monitor" == "xyes"])
 
 # Package specific checks.
 AS_IF([test "x$enable_presence" != "xno"], [
@@ -228,6 +231,11 @@
     AC_CONFIG_FILES([monitor/Makefile])
 ])
 
+AS_IF([test "x$enable_sensor_monitor" == "xyes"], [
+
+    AC_CONFIG_FILES([sensor-monitor/Makefile])
+])
+
 # Create configured output
 AC_CONFIG_FILES([Makefile test/Makefile presence/test/Makefile monitor/test/Makefile])
 AC_OUTPUT
diff --git a/sensor-monitor/Makefile.am b/sensor-monitor/Makefile.am
new file mode 100644
index 0000000..2cd7e7d
--- /dev/null
+++ b/sensor-monitor/Makefile.am
@@ -0,0 +1,24 @@
+AM_DEFAULT_SOURCE_EXT = .cpp
+AM_CPPFLAGS = -iquote ${top_srcdir}
+
+bin_PROGRAMS = \
+	sensor-monitor
+
+sensor_monitor_SOURCES = \
+    shutdown_alarm_monitor.cpp \
+	main.cpp
+
+sensor_monitor_LDADD = \
+	$(SDBUSPLUS_LIBS) \
+	$(SDEVENTPLUS_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS) \
+	${PHOSPHOR_DBUS_INTERFACES_LIBS} \
+	-lstdc++fs \
+	$(FMT_LIBS)
+
+sensor_monitor_CXXFLAGS = \
+	$(SDBUSPLUS_CFLAGS) \
+	$(SDEVENTPLUS_CFLAGS) \
+	$(PHOSPHOR_LOGGING_CFLAGS) \
+	${PHOSPHOR_DBUS_INTERFACES_CFLAGS} \
+	-flto
diff --git a/sensor-monitor/main.cpp b/sensor-monitor/main.cpp
new file mode 100644
index 0000000..bf1bf17
--- /dev/null
+++ b/sensor-monitor/main.cpp
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2021 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "shutdown_alarm_monitor.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdeventplus/event.hpp>
+
+using namespace sensor::monitor;
+
+int main(int argc, char* argv[])
+{
+    auto event = sdeventplus::Event::get_default();
+    auto bus = sdbusplus::bus::new_default();
+    bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+
+    ShutdownAlarmMonitor shutdownMonitor{bus, event};
+
+    return event.loop();
+}
diff --git a/sensor-monitor/shutdown_alarm_monitor.cpp b/sensor-monitor/shutdown_alarm_monitor.cpp
new file mode 100644
index 0000000..2017770
--- /dev/null
+++ b/sensor-monitor/shutdown_alarm_monitor.cpp
@@ -0,0 +1,142 @@
+/**
+ * Copyright © 2021 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "shutdown_alarm_monitor.hpp"
+
+#include <fmt/format.h>
+
+#include <phosphor-logging/log.hpp>
+
+namespace sensor::monitor
+{
+using namespace phosphor::logging;
+using namespace phosphor::fan::util;
+using namespace phosphor::fan;
+namespace fs = std::filesystem;
+
+const std::map<ShutdownType, std::string> shutdownInterfaces{
+    {ShutdownType::hard, "xyz.openbmc_project.Sensor.Threshold.HardShutdown"},
+    {ShutdownType::soft, "xyz.openbmc_project.Sensor.Threshold.SoftShutdown"}};
+
+const std::map<ShutdownType, std::map<AlarmType, std::string>> alarmProperties{
+    {ShutdownType::hard,
+     {{AlarmType::low, "HardShutdownAlarmLow"},
+      {AlarmType::high, "HardShutdownAlarmHigh"}}},
+    {ShutdownType::soft,
+     {{AlarmType::low, "SoftShutdownAlarmLow"},
+      {AlarmType::high, "SoftShutdownAlarmHigh"}}}};
+
+using namespace sdbusplus::bus::match;
+
+ShutdownAlarmMonitor::ShutdownAlarmMonitor(sdbusplus::bus::bus& bus,
+                                           sdeventplus::Event& event) :
+    bus(bus),
+    event(event),
+    hardShutdownMatch(bus,
+                      "type='signal',member='PropertiesChanged',"
+                      "path_namespace='/xyz/openbmc_project/sensors',"
+                      "arg0='" +
+                          shutdownInterfaces.at(ShutdownType::soft) + "'",
+                      std::bind(&ShutdownAlarmMonitor::propertiesChanged, this,
+                                std::placeholders::_1)),
+    softShutdownMatch(bus,
+                      "type='signal',member='PropertiesChanged',"
+                      "path_namespace='/xyz/openbmc_project/sensors',"
+                      "arg0='" +
+                          shutdownInterfaces.at(ShutdownType::hard) + "'",
+                      std::bind(&ShutdownAlarmMonitor::propertiesChanged, this,
+                                std::placeholders::_1)),
+    _powerState(std::make_unique<PGoodState>(
+        bus, std::bind(&ShutdownAlarmMonitor::powerStateChanged, this,
+                       std::placeholders::_1)))
+{
+    findAlarms();
+
+    if (_powerState->isPowerOn())
+    {
+        checkAlarms();
+    }
+}
+
+void ShutdownAlarmMonitor::findAlarms()
+{
+    // Find all shutdown threshold ifaces currently on D-Bus.
+    for (const auto& [shutdownType, interface] : shutdownInterfaces)
+    {
+        auto paths = SDBusPlus::getSubTreePathsRaw(bus, "/", interface, 0);
+
+        std::for_each(
+            paths.begin(), paths.end(), [this, shutdownType](const auto& path) {
+                alarms.emplace(AlarmKey{path, shutdownType, AlarmType::high},
+                               nullptr);
+                alarms.emplace(AlarmKey{path, shutdownType, AlarmType::low},
+                               nullptr);
+            });
+    }
+}
+
+void ShutdownAlarmMonitor::checkAlarms()
+{
+    for (auto& [alarmKey, timer] : alarms)
+    {
+        const auto& [sensorPath, shutdownType, alarmType] = alarmKey;
+        const auto& interface = shutdownInterfaces.at(shutdownType);
+        auto propertyName = alarmProperties.at(shutdownType).at(alarmType);
+        bool value;
+
+        try
+        {
+            value = SDBusPlus::getProperty<bool>(bus, sensorPath, interface,
+                                                 propertyName);
+        }
+        catch (const DBusServiceError& e)
+        {
+            // The sensor isn't on D-Bus anymore
+            log<level::INFO>(fmt::format("No {} interface on {} anymore.",
+                                         interface, sensorPath)
+                                 .c_str());
+            continue;
+        }
+
+        checkAlarm(value, alarmKey);
+    }
+}
+
+void ShutdownAlarmMonitor::propertiesChanged(
+    sdbusplus::message::message& message)
+{
+    // check the values
+}
+
+void ShutdownAlarmMonitor::checkAlarm(bool value, const AlarmKey& alarmKey)
+{
+    // start or stop a timer possibly
+}
+
+void ShutdownAlarmMonitor::powerStateChanged(bool powerStateOn)
+{
+    if (powerStateOn)
+    {
+        checkAlarms();
+    }
+    else
+    {
+        // Cancel and delete all timers
+    }
+}
+
+} // namespace sensor::monitor
diff --git a/sensor-monitor/shutdown_alarm_monitor.hpp b/sensor-monitor/shutdown_alarm_monitor.hpp
new file mode 100644
index 0000000..6b1c823
--- /dev/null
+++ b/sensor-monitor/shutdown_alarm_monitor.hpp
@@ -0,0 +1,154 @@
+/**
+ * Copyright © 2021 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+#include "power_state.hpp"
+#include "types.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdeventplus/clock.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/utility/timer.hpp>
+
+namespace sensor::monitor
+{
+
+/**
+ * @class ShutdownAlarmMonitor
+ *
+ * This class finds all instances of the D-Bus interfaces
+ *   xyz.openbmc_project.Sensor.Threshold.SoftShutdown
+ *   xyz.openbmc_project.Sensor.Threshold.HardShutdown
+ *
+ * and then watches the high and low alarm properties.  If they trip
+ * the code will start a timer, at the end of which the system will
+ * be shut down.  The timer values can be modified with configure.ac
+ * options.  If the alarm is cleared before the timer expires, then
+ * the timer is stopped.
+ *
+ * Event logs are also created when the alarms trip and clear.
+ *
+ * Note that the SoftShutdown alarm code actually implements a hard shutdown.
+ * This is because in the system this is being written for, the host is
+ * driving the shutdown process (i.e. doing a soft shutdown) based on an alert
+ * it receives via another channel.  If the soft shutdown timer expires, it
+ * means that the host didn't do a soft shutdown in the time allowed and
+ * now a hard shutdown is required.  This behavior could be modified with
+ * compile flags if anyone needs a different behavior in the future.
+ *
+ * It currently uses the PGoodState class to check for power state.
+ * If a different property is ever desired, a new class can be
+ * derived from PowerState and a compile option can be used.
+ *
+ */
+class ShutdownAlarmMonitor
+{
+  public:
+    ShutdownAlarmMonitor() = delete;
+    ~ShutdownAlarmMonitor() = default;
+    ShutdownAlarmMonitor(const ShutdownAlarmMonitor&) = delete;
+    ShutdownAlarmMonitor& operator=(const ShutdownAlarmMonitor&) = delete;
+    ShutdownAlarmMonitor(ShutdownAlarmMonitor&&) = delete;
+    ShutdownAlarmMonitor& operator=(ShutdownAlarmMonitor&&) = delete;
+
+    /**
+     * @brief Constructor
+     *
+     * @param[in] bus - The sdbusplus bus object
+     * @param[in] event - The sdeventplus event object
+     */
+    ShutdownAlarmMonitor(sdbusplus::bus::bus& bus, sdeventplus::Event& event);
+
+  private:
+    /**
+     * @brief The propertiesChanged handled for the shutdown interfaces.
+     *
+     * If the power is on, the new alarm values will be checked to see
+     * if the shutdown timer needs to be started or stopped.
+     */
+    void propertiesChanged(sdbusplus::message::message& message);
+
+    /**
+     * @brief Checks an alarm value to see if a shutdown timer needs
+     *        to be started or stopped.
+     *
+     * If the alarm is on and the timer isn't running, start it.
+     * If the alarm is off and the timer is running, stop it.
+     *
+     * @param[in] value - The alarm property value
+     * @param[in] alarmKey - The alarm key
+     */
+    void checkAlarm(bool value, const AlarmKey& alarmKey);
+
+    /**
+     * @brief Checks all currently known alarm properties on D-Bus.
+     *
+     * May result in starting or stopping shutdown timers.
+     */
+    void checkAlarms();
+
+    /**
+     * @brief Finds all shutdown alarm interfaces currently on
+     *        D-Bus and adds them to the alarms map.
+     */
+    void findAlarms();
+
+    /**
+     * @brief The power state changed handler.
+     *
+     * Checks alarms when power is turned on, and clears
+     * any running timers on a power off.
+     *
+     * @param[in] powerStateOn - If the power is now on or off.
+     */
+    void powerStateChanged(bool powerStateOn);
+
+    /**
+     * @brief The sdbusplus bus object
+     */
+    sdbusplus::bus::bus& bus;
+
+    /**
+     * @brief The sdeventplus Event object
+     */
+    sdeventplus::Event& event;
+
+    /**
+     * @brief The match for properties changing on the HardShutdown
+     *        interface.
+     */
+    sdbusplus::bus::match::match hardShutdownMatch;
+
+    /**
+     * @brief The match for properties changing on the SoftShutdown
+     *        interface.
+     */
+    sdbusplus::bus::match::match softShutdownMatch;
+
+    /**
+     * @brief The PowerState object to track power state changes.
+     */
+    std::unique_ptr<phosphor::fan::PowerState> _powerState;
+
+    /**
+     * @brief The map of alarms.
+     */
+    std::map<AlarmKey, std::unique_ptr<sdeventplus::utility::Timer<
+                           sdeventplus::ClockId::Monotonic>>>
+        alarms;
+};
+
+} // namespace sensor::monitor
diff --git a/sensor-monitor/types.hpp b/sensor-monitor/types.hpp
new file mode 100644
index 0000000..3fd9561
--- /dev/null
+++ b/sensor-monitor/types.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <sdeventplus/clock.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/utility/timer.hpp>
+
+#include <memory>
+#include <string>
+#include <tuple>
+
+namespace sensor::monitor
+{
+
+/**
+ * @brief The enum to represent a hard or soft shutdown
+ */
+enum class ShutdownType
+{
+    hard,
+    soft
+};
+
+/**
+ * @brief The enum to represent a low or high alarm
+ */
+enum class AlarmType
+{
+    low,
+    high
+};
+
+using AlarmKey = std::tuple<std::string, ShutdownType, AlarmType>;
+} // namespace sensor::monitor