PEL: Use class to watch properties in DataIface

Instead of having the DataInterface class explicitly watch for all of
the PropertiesChanged and InterfacesAdded signals for the D-Bus
properties it needs the values of, create some classes to wrap that
functionality.

The PropertyWatcher class will call a user defined function, passing it
the property value for the path/interface/property specified in the
following cases:
1) On construction, by making a property read method call, if the
   property is on D-Bus then.
2) On a properties changed signal for that property.
3) On an interfaces added signal for that property's interface.

The InterfaceWatcher class will call a user defined function, passing it
the property name/value map for all properties in that interface, in the
following cases:
1) On construction, by making a GetAll property read method call, if the
   interface is on D-Bus then.
2) On a properties changed signal for that interface.
3) On an interfaces added signal for that interface.

Both of these are derived from the DBusWatcher class, and the
DataInterface will store a vector of DBusWatcher pointers after it
creates the instances of the PropertyWatcher or InterfaceWatcher classes
in its constructor.

This commit changes the current properties being watched - the system
model, the system serial number, and the operating system status to this
method.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Iade4ac89a9ce1d46bdebf353350bf161722ced9f
diff --git a/extensions/openpower-pels/data_interface.cpp b/extensions/openpower-pels/data_interface.cpp
index 3342569..deb4ba4 100644
--- a/extensions/openpower-pels/data_interface.cpp
+++ b/extensions/openpower-pels/data_interface.cpp
@@ -51,70 +51,50 @@
 
 DataInterface::DataInterface(sdbusplus::bus::bus& bus) : _bus(bus)
 {
-    readMTMS();
-    readHostState();
     readBMCFWVersion();
     readServerFWVersion();
     readBMCFWVersionID();
+
+    // Watch both the Model and SN properties on the system's Asset iface
+    _properties.emplace_back(std::make_unique<InterfaceWatcher<DataInterface>>(
+        bus, object_path::systemInv, interface::invAsset, *this,
+        [this](const auto& properties) {
+            auto model = properties.find("Model");
+            if (model != properties.end())
+            {
+                this->_machineTypeModel = std::get<std::string>(model->second);
+            }
+
+            auto sn = properties.find("SerialNumber");
+            if (sn != properties.end())
+            {
+                this->_machineSerialNumber = std::get<std::string>(sn->second);
+            }
+        }));
+
+    // Watch the OperatingSystemState property
+    _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
+        bus, object_path::hostState, interface::osStatus,
+        "OperatingSystemState", *this, [this](const auto& value) {
+            auto status =
+                Status::convertOSStatusFromString(std::get<std::string>(value));
+
+            if ((status == Status::OSStatus::BootComplete) ||
+                (status == Status::OSStatus::Standby))
+            {
+                setHostState(true);
+            }
+            else
+            {
+                setHostState(false);
+            }
+        }));
 }
 
-void DataInterface::readMTMS()
-{
-    // If this runs when the inventory service isn't running, it will get the
-    // value whenever it starts via the propertiesChanged callback.
-    try
-    {
-        auto inventoryService =
-            getService(object_path::systemInv, interface::invAsset);
-
-        if (!inventoryService.empty())
-        {
-            auto properties = getAllProperties(
-                inventoryService, object_path::systemInv, interface::invAsset);
-
-            _machineTypeModel = std::get<std::string>(properties["Model"]);
-
-            _machineSerialNumber =
-                std::get<std::string>(properties["SerialNumber"]);
-        }
-    }
-    catch (std::exception& e)
-    {
-        // Inventory must not be running at this moment.
-    }
-
-    // Keep up to date by watching for the propertiesChanged signal.
-    _sysInventoryPropMatch = std::make_unique<sdbusplus::bus::match_t>(
-        _bus,
-        sdbusplus::bus::match::rules::propertiesChanged(object_path::systemInv,
-                                                        interface::invAsset),
-        std::bind(std::mem_fn(&DataInterface::sysAssetPropChanged), this,
-                  std::placeholders::_1));
-}
-
-void DataInterface::sysAssetPropChanged(sdbusplus::message::message& msg)
-{
-    DBusInterface interface;
-    DBusPropertyMap properties;
-
-    msg.read(interface, properties);
-
-    auto model = properties.find("Model");
-    if (model != properties.end())
-    {
-        _machineTypeModel = std::get<std::string>(model->second);
-    }
-
-    auto sn = properties.find("SerialNumber");
-    if (sn != properties.end())
-    {
-        _machineSerialNumber = std::get<std::string>(sn->second);
-    }
-}
-
-DBusPropertyMap DataInterface::getAllProperties(const std::string& service,
-                                                const std::string& objectPath,
-                                                const std::string& interface)
+DBusPropertyMap
+    DataInterface::getAllProperties(const std::string& service,
+                                    const std::string& objectPath,
+                                    const std::string& interface) const
 {
     DBusPropertyMap properties;
 
@@ -131,7 +111,8 @@
 void DataInterface::getProperty(const std::string& service,
                                 const std::string& objectPath,
                                 const std::string& interface,
-                                const std::string& property, DBusValue& value)
+                                const std::string& property,
+                                DBusValue& value) const
 {
 
     auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
@@ -164,67 +145,6 @@
     return std::string{};
 }
 
-void DataInterface::readHostState()
-{
-    _hostUp = false;
-
-    try
-    {
-        auto service = getService(object_path::hostState, interface::osStatus);
-        if (!service.empty())
-        {
-            DBusValue value;
-            getProperty(service, object_path::hostState, interface::osStatus,
-                        "OperatingSystemState", value);
-
-            auto status =
-                Status::convertOSStatusFromString(std::get<std::string>(value));
-
-            if ((status == Status::OSStatus::BootComplete) ||
-                (status == Status::OSStatus::Standby))
-            {
-                _hostUp = true;
-            }
-        }
-    }
-    catch (std::exception& e)
-    {
-        // Not available yet.
-    }
-
-    // Keep up to date by watching for the propertiesChanged signal.
-    _osStateMatch = std::make_unique<sdbusplus::bus::match_t>(
-        _bus,
-        sdbusplus::bus::match::rules::propertiesChanged(object_path::hostState,
-                                                        interface::osStatus),
-        std::bind(std::mem_fn(&DataInterface::osStatePropChanged), this,
-                  std::placeholders::_1));
-}
-
-void DataInterface::osStatePropChanged(sdbusplus::message::message& msg)
-{
-    DBusInterface interface;
-    DBusPropertyMap properties;
-
-    msg.read(interface, properties);
-
-    auto state = properties.find("OperatingSystemState");
-    if (state != properties.end())
-    {
-        auto status = Status::convertOSStatusFromString(
-            std::get<std::string>(state->second));
-
-        bool newHostState = false;
-        if ((status == Status::OSStatus::BootComplete) ||
-            (status == Status::OSStatus::Standby))
-        {
-            newHostState = true;
-        }
-
-        setHostState(newHostState);
-    }
-}
-
 uint8_t DataInterface::getPLDMInstanceID(uint8_t eid) const
 {
     return 0;
diff --git a/extensions/openpower-pels/data_interface.hpp b/extensions/openpower-pels/data_interface.hpp
index e139c07..a275e4d 100644
--- a/extensions/openpower-pels/data_interface.hpp
+++ b/extensions/openpower-pels/data_interface.hpp
@@ -1,5 +1,8 @@
 #pragma once
 
+#include "dbus_types.hpp"
+#include "dbus_watcher.hpp"
+
 #include <filesystem>
 #include <phosphor-logging/log.hpp>
 #include <sdbusplus/bus.hpp>
@@ -10,14 +13,6 @@
 namespace pels
 {
 
-using DBusValue = sdbusplus::message::variant<std::string>;
-using DBusProperty = std::string;
-using DBusInterface = std::string;
-using DBusService = std::string;
-using DBusPath = std::string;
-using DBusInterfaceList = std::vector<DBusInterface>;
-using DBusPropertyMap = std::map<DBusProperty, DBusValue>;
-
 /**
  * @class DataInterface
  *
@@ -269,48 +264,6 @@
      */
     uint8_t getPLDMInstanceID(uint8_t eid) const override;
 
-  private:
-    /**
-     * @brief Reads the machine type/model and SN from D-Bus.
-     *
-     * Looks for them on the 'system' inventory object, and also
-     * places a properties changed watch on them to obtain any changes
-     * (or read them for the first time if the inventory isn't ready
-     * when this function runs.)
-     */
-    void readMTMS();
-
-    /**
-     * @brief Reads the host state from D-Bus.
-     *
-     * For host on, looks for the values of 'BootComplete' or 'Standby'
-     * in the OperatingSystemState property on the
-     * 'xyz.openbmc_project.State.OperatingSystem.Status' interface
-     * on the '/xyz/openbmc_project/state/host0' path.
-     *
-     * Also adds a properties changed watch on it so the code can be
-     * kept up to date on changes.
-     */
-    void readHostState();
-
-    /**
-     * @brief Reads the BMC firmware version string and puts it into
-     *        _bmcFWVersion.
-     */
-    void readBMCFWVersion();
-
-    /**
-     * @brief Reads the server firmware version string and puts it into
-     *        _serverFWVersion.
-     */
-    void readServerFWVersion();
-
-    /**
-     * @brief Reads the BMC firmware version ID and puts it into
-     *        _bmcFWVersionID.
-     */
-    void readBMCFWVersionID();
-
     /**
      * @brief Finds the D-Bus service name that hosts the
      *        passed in path and interface.
@@ -331,7 +284,7 @@
      */
     DBusPropertyMap getAllProperties(const std::string& service,
                                      const std::string& objectPath,
-                                     const std::string& interface);
+                                     const std::string& interface) const;
 
     /**
      * @brief Wrapper for the 'Get' properties method call
@@ -344,33 +297,33 @@
      */
     void getProperty(const std::string& service, const std::string& objectPath,
                      const std::string& interface, const std::string& property,
-                     DBusValue& value);
+                     DBusValue& value) const;
+
+  private:
+    /**
+     * @brief Reads the BMC firmware version string and puts it into
+     *        _bmcFWVersion.
+     */
+    void readBMCFWVersion();
 
     /**
-     * @brief The properties changed callback for the Asset iface
-     *        on the system inventory object.
-     *
-     * @param[in] msg - The sdbusplus message of the signal
+     * @brief Reads the server firmware version string and puts it into
+     *        _serverFWVersion.
      */
-    void sysAssetPropChanged(sdbusplus::message::message& msg);
+    void readServerFWVersion();
 
     /**
-     * @brief The properties changed callback for the OperatingSystemStatus
-     *        interface on the host state object.
-     *
-     * @param[in] msg - The sdbusplus message of the signal
+     * @brief Reads the BMC firmware version ID and puts it into
+     *        _bmcFWVersionID.
      */
-    void osStatePropChanged(sdbusplus::message::message& msg);
+    void readBMCFWVersionID();
 
     /**
-     * @brief The match object for the system path's properties
+     * @brief The D-Bus property or interface watchers that have callbacks
+     *        registered that will set members in this class when
+     *        they change.
      */
-    std::unique_ptr<sdbusplus::bus::match_t> _sysInventoryPropMatch;
-
-    /**
-     * @brief The match object for the operating system status.
-     */
-    std::unique_ptr<sdbusplus::bus::match_t> _osStateMatch;
+    std::vector<std::unique_ptr<DBusWatcher>> _properties;
 
     /**
      * @brief The sdbusplus bus object for making D-Bus calls.
diff --git a/extensions/openpower-pels/dbus_types.hpp b/extensions/openpower-pels/dbus_types.hpp
new file mode 100644
index 0000000..d263958
--- /dev/null
+++ b/extensions/openpower-pels/dbus_types.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <map>
+#include <sdbusplus/bus.hpp>
+#include <string>
+#include <variant>
+#include <vector>
+
+namespace openpower::pels
+{
+
+using DBusValue =
+    sdbusplus::message::variant<std::string, bool, std::vector<uint8_t>>;
+using DBusProperty = std::string;
+using DBusInterface = std::string;
+using DBusService = std::string;
+using DBusPath = std::string;
+using DBusInterfaceList = std::vector<DBusInterface>;
+using DBusPathList = std::vector<DBusPath>;
+using DBusPropertyMap = std::map<DBusProperty, DBusValue>;
+using DBusInterfaceMap = std::map<DBusInterface, DBusPropertyMap>;
+
+} // namespace openpower::pels
diff --git a/extensions/openpower-pels/dbus_watcher.hpp b/extensions/openpower-pels/dbus_watcher.hpp
new file mode 100644
index 0000000..bdd3b2c
--- /dev/null
+++ b/extensions/openpower-pels/dbus_watcher.hpp
@@ -0,0 +1,339 @@
+#pragma once
+
+#include "dbus_types.hpp"
+
+#include <sdbusplus/bus/match.hpp>
+
+namespace openpower::pels
+{
+
+using sdbusplus::exception::SdBusError;
+namespace match_rules = sdbusplus::bus::match::rules;
+
+/**
+ * @class DBusWatcher
+ *
+ * The base class for the PropertyWatcher and InterfaceWatcher classes.
+ */
+class DBusWatcher
+{
+  public:
+    DBusWatcher() = delete;
+    virtual ~DBusWatcher() = default;
+    DBusWatcher(const DBusWatcher&) = default;
+    DBusWatcher& operator=(const DBusWatcher&) = default;
+    DBusWatcher(DBusWatcher&&) = default;
+    DBusWatcher& operator=(DBusWatcher&&) = default;
+
+    /**
+     * @brief Constructor
+     *
+     * @param[in] path - The D-Bus path that will be watched
+     * @param[in] interface - The D-Bus interface that will be watched
+     */
+    DBusWatcher(const std::string& path, const std::string& interface) :
+        _path(path), _interface(interface)
+    {
+    }
+
+  protected:
+    /**
+     * @brief The D-Bus path
+     */
+    std::string _path;
+
+    /**
+     * @brief The D-Bus interface
+     */
+    std::string _interface;
+
+    /**
+     * @brief The match objects for the propertiesChanged and
+     *        interfacesAdded signals.
+     */
+    std::vector<sdbusplus::bus::match_t> _matches;
+};
+
+/**
+ * @class PropertyWatcher
+ *
+ * This class allows the user to be kept up to data with a D-Bus
+ * property's value.  It does this by calling a user specified function
+ * that is passed the variant that contains the property's value when:
+ *
+ * 1) The property is read when the class is constructed, if
+ *    the property is on D-Bus at the time.
+ * 2) The property changes (via a property changed signal).
+ * 3) An interfacesAdded signal is received with that property.
+ *
+ * The DataInterface class is used to access D-Bus, and is a template
+ * to avoid any circular include issues as that class is one of the
+ * users of this one.
+ */
+template <typename DataIface>
+class PropertyWatcher : public DBusWatcher
+{
+  public:
+    PropertyWatcher() = delete;
+    ~PropertyWatcher() = default;
+    PropertyWatcher(const PropertyWatcher&) = delete;
+    PropertyWatcher& operator=(const PropertyWatcher&) = delete;
+    PropertyWatcher(PropertyWatcher&&) = delete;
+    PropertyWatcher& operator=(PropertyWatcher&&) = delete;
+
+    using PropertySetFunc = std::function<void(const DBusValue&)>;
+
+    /**
+     * @brief Constructor
+     *
+     * Reads the property if it is on D-Bus, and sets up the match
+     * objects for the propertiesChanged and interfacesAdded signals.
+     *
+     * @param[in] bus - The sdbusplus bus object
+     * @param[in] path - The D-Bus path of the property
+     * @param[in] interface - The D-Bus interface that contains the property
+     * @param[in] propertyName - The property name
+     * @param[in] dataIface - The DataInterface object
+     * @param[in] func - The callback used any time the property is read
+     */
+    PropertyWatcher(sdbusplus::bus::bus& bus, const std::string& path,
+                    const std::string& interface,
+                    const std::string& propertyName, const DataIface& dataIface,
+                    PropertySetFunc func) :
+        DBusWatcher(path, interface),
+        _name(propertyName), _setFunc(func)
+    {
+        try
+        {
+            read(dataIface);
+        }
+        catch (const SdBusError& e)
+        {
+            // Path doesn't exist now
+        }
+
+        _matches.emplace_back(
+            bus, match_rules::propertiesChanged(_path, _interface),
+            std::bind(std::mem_fn(&PropertyWatcher::propChanged), this,
+                      std::placeholders::_1));
+
+        _matches.emplace_back(
+            bus,
+            match_rules::interfacesAdded() + match_rules::argNpath(0, _path),
+            std::bind(std::mem_fn(&PropertyWatcher::interfaceAdded), this,
+                      std::placeholders::_1));
+    }
+
+    /**
+     * @brief Reads the property on D-Bus, and calls
+     *        the user defined function with the value.
+     *
+     * @param[in] dataIface - The DataInterface object
+     */
+    void read(const DataIface& dataIface)
+    {
+        auto service = dataIface.getService(_path, _interface);
+        if (!service.empty())
+        {
+            DBusValue value;
+            dataIface.getProperty(service, _path, _interface, _name, value);
+
+            _setFunc(value);
+        }
+    }
+
+    /**
+     * @brief The propertiesChanged callback
+     *
+     * Calls the user defined function with the property value
+     *
+     * @param[in] msg - The sdbusplus message object
+     */
+    void propChanged(sdbusplus::message::message& msg)
+    {
+        DBusInterface interface;
+        DBusPropertyMap properties;
+
+        msg.read(interface, properties);
+
+        auto prop = properties.find(_name);
+        if (prop != properties.end())
+        {
+            _setFunc(prop->second);
+        }
+    }
+
+    /**
+     * @brief The interfacesAdded callback
+     *
+     * Calls the user defined function with the property value
+     *
+     * @param[in] msg - The sdbusplus message object
+     */
+    void interfaceAdded(sdbusplus::message::message& msg)
+    {
+        sdbusplus::message::object_path path;
+        DBusInterfaceMap interfaces;
+
+        msg.read(path, interfaces);
+
+        auto iface = interfaces.find(_interface);
+        if (iface != interfaces.end())
+        {
+            auto prop = iface->second.find(_name);
+            if (prop != iface->second.end())
+            {
+                _setFunc(prop->second);
+            }
+        }
+    }
+
+  private:
+    /**
+     * @brief The D-Bus property name
+     */
+    std::string _name;
+
+    /**
+     * @brief The function that will be called any time the
+     *        property is read.
+     */
+    PropertySetFunc _setFunc;
+};
+
+/**
+ * @class InterfaceWatcher
+ *
+ * This class allows the user to be kept up to data with a D-Bus
+ * interface's properties..  It does this by calling a user specified
+ * function that is passed a map of the D-Bus property names and values
+ * on that interface when:
+ *
+ * 1) The interface is read when the class is constructed, if
+ *    the interface is on D-Bus at the time.
+ * 2) The interface has a property that changes (via a property changed signal).
+ * 3) An interfacesAdded signal is received.
+ *
+ * The DataInterface class is used to access D-Bus, and is a template
+ * to avoid any circular include issues as that class is one of the
+ * users of this one.
+ */
+template <typename DataIface>
+class InterfaceWatcher : public DBusWatcher
+{
+  public:
+    InterfaceWatcher() = delete;
+    ~InterfaceWatcher() = default;
+    InterfaceWatcher(const InterfaceWatcher&) = delete;
+    InterfaceWatcher& operator=(const InterfaceWatcher&) = delete;
+    InterfaceWatcher(InterfaceWatcher&&) = delete;
+    InterfaceWatcher& operator=(InterfaceWatcher&&) = delete;
+
+    using InterfaceSetFunc = std::function<void(const DBusPropertyMap&)>;
+
+    /**
+     * @brief Constructor
+     *
+     * Reads all properties on the interface if it is on D-Bus,
+     * and sets up the match objects for the propertiesChanged
+     * and interfacesAdded signals.
+     *
+     * @param[in] bus - The sdbusplus bus object
+     * @param[in] path - The D-Bus path of the property
+     * @param[in] interface - The D-Bus interface that contains the property
+     * @param[in] dataIface - The DataInterface object
+     * @param[in] func - The callback used any time the property is read
+     */
+    InterfaceWatcher(sdbusplus::bus::bus& bus, const std::string& path,
+                     const std::string& interface, const DataIface& dataIface,
+                     InterfaceSetFunc func) :
+        DBusWatcher(path, interface),
+        _setFunc(func)
+    {
+        try
+        {
+            read(dataIface);
+        }
+        catch (const SdBusError& e)
+        {
+            // Path doesn't exist now
+        }
+
+        _matches.emplace_back(
+            bus, match_rules::propertiesChanged(_path, _interface),
+            std::bind(std::mem_fn(&InterfaceWatcher::propChanged), this,
+                      std::placeholders::_1));
+
+        _matches.emplace_back(
+            bus,
+            match_rules::interfacesAdded() + match_rules::argNpath(0, _path),
+            std::bind(std::mem_fn(&InterfaceWatcher::interfaceAdded), this,
+                      std::placeholders::_1));
+    }
+
+    /**
+     * @brief Reads the interface's properties on D-Bus, and
+     * calls the the user defined function with the property map.
+     *
+     * @param[in] dataIface - The DataInterface object
+     */
+    void read(const DataIface& dataIface)
+    {
+        auto service = dataIface.getService(_path, _interface);
+        if (!service.empty())
+        {
+            auto properties =
+                dataIface.getAllProperties(service, _path, _interface);
+
+            _setFunc(properties);
+        }
+    }
+
+    /**
+     * @brief The propertiesChanged callback
+     *
+     * Calls the user defined function with the property map.  Only the
+     * properties that changed will be in the map.
+     *
+     * @param[in] msg - The sdbusplus message object
+     */
+    void propChanged(sdbusplus::message::message& msg)
+    {
+        DBusInterface interface;
+        DBusPropertyMap properties;
+
+        msg.read(interface, properties);
+
+        _setFunc(properties);
+    }
+
+    /**
+     * @brief The interfacesAdded callback
+     *
+     * Calls the user defined function with the property map
+     *
+     * @param[in] msg - The sdbusplus message object
+     */
+    void interfaceAdded(sdbusplus::message::message& msg)
+    {
+        sdbusplus::message::object_path path;
+        DBusInterfaceMap interfaces;
+
+        msg.read(path, interfaces);
+
+        auto iface = interfaces.find(_interface);
+        if (iface != interfaces.end())
+        {
+            _setFunc(iface->second);
+        }
+    }
+
+  private:
+    /**
+     * @brief The function that will be called any time the
+     *        interface is read.
+     */
+    InterfaceSetFunc _setFunc;
+};
+
+} // namespace openpower::pels