regulators: Create DBusSensors class

Create the DBusSensors class.  This is a concrete implementation of the
Sensors abstract base class.  This class manages all the voltage
regulator sensors in the system.

Also add a lastUpdateTime data member to the DBusSensor class.  This
data member is set whenever the sensor is updated.  This enables the
DBusSensors class to detect sensors that were not updated during the
current monitoring cycle.

Sensors that were not updated during the current monitoring cycle are
deleted.  These sensors were likely produced by a hardware device that
was removed or replaced with a different version.

Tested:
* Ran through entire monitoring cycle multiple times
* Tested that lastUpdateTime is set correctly when a sensor is modified
  * Sensor value updated
  * Sensor disabled
  * Sensor put in error state
* Tested where new sensor was created
* Tested where existing sensor was updated
* Tested where all sensors disabled
* Tested where all sensors for a rail put in error state
* Tested where sensors removed due to not being updated this cycle
* Tested where D-Bus exception occurs when trying to create a sensor
* See complete test plan at
  https://gist.github.com/smccarney/69efb813c0005571aee687f67e489278

Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: Ib1fc399f100188cc048ac3ab5892117b74f844e9
diff --git a/phosphor-regulators/src/dbus_sensor.cpp b/phosphor-regulators/src/dbus_sensor.cpp
index 1fd4bf6..9717a12 100644
--- a/phosphor-regulators/src/dbus_sensor.cpp
+++ b/phosphor-regulators/src/dbus_sensor.cpp
@@ -106,6 +106,9 @@
 
     // Now emit signal that object has been created
     dbusObject->emit_object_added();
+
+    // Set the last update time
+    setLastUpdateTime();
 }
 
 void DBusSensor::disable()
@@ -115,6 +118,9 @@
 
     // Set the sensor to unavailable since it is disabled
     dbusObject->available(false);
+
+    // Set the last update time
+    setLastUpdateTime();
 }
 
 void DBusSensor::setToErrorState()
@@ -124,6 +130,9 @@
 
     // Set the sensor to non-functional since it could not be read
     dbusObject->functional(false);
+
+    // Set the last update time
+    setLastUpdateTime();
 }
 
 void DBusSensor::setValue(double value)
@@ -139,6 +148,9 @@
 
     // Set the sensor to available since it is not disabled
     dbusObject->available(true);
+
+    // Set the last update time
+    setLastUpdateTime();
 }
 
 std::vector<AssocationTuple>
diff --git a/phosphor-regulators/src/dbus_sensor.hpp b/phosphor-regulators/src/dbus_sensor.hpp
index 4e39c34..1ac9b61 100644
--- a/phosphor-regulators/src/dbus_sensor.hpp
+++ b/phosphor-regulators/src/dbus_sensor.hpp
@@ -24,6 +24,7 @@
 #include <xyz/openbmc_project/State/Decorator/Availability/server.hpp>
 #include <xyz/openbmc_project/State/Decorator/OperationalStatus/server.hpp>
 
+#include <chrono>
 #include <memory>
 #include <string>
 #include <tuple>
@@ -107,6 +108,8 @@
     /**
      * Constructor.
      *
+     * Throws an exception if an error occurs.
+     *
      * @param bus D-Bus bus object
      * @param name sensor name
      * @param type sensor type
@@ -134,6 +137,16 @@
     void disable();
 
     /**
+     * Return the last time this sensor was updated.
+     *
+     * @return last update time
+     */
+    const std::chrono::system_clock::time_point& getLastUpdateTime() const
+    {
+        return lastUpdateTime;
+    }
+
+    /**
      * Return the sensor name.
      *
      * @return sensor name
@@ -277,6 +290,14 @@
                                 double& minValue, double& maxValue);
 
     /**
+     * Set the last time this sensor was updated.
+     */
+    void setLastUpdateTime()
+    {
+        lastUpdateTime = std::chrono::system_clock::now();
+    }
+
+    /**
      * Set the sensor value on D-Bus to NaN.
      */
     void setValueToNaN();
@@ -327,6 +348,11 @@
      * interfaces via templates and multiple inheritance.
      */
     std::unique_ptr<DBusSensorObject> dbusObject{};
+
+    /**
+     * Last time this sensor was updated.
+     */
+    std::chrono::system_clock::time_point lastUpdateTime{};
 };
 
 } // namespace phosphor::power::regulators
diff --git a/phosphor-regulators/src/dbus_sensors.cpp b/phosphor-regulators/src/dbus_sensors.cpp
new file mode 100644
index 0000000..eee7bc2
--- /dev/null
+++ b/phosphor-regulators/src/dbus_sensors.cpp
@@ -0,0 +1,122 @@
+/**
+ * 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 "dbus_sensors.hpp"
+
+#include <utility>
+
+namespace phosphor::power::regulators
+{
+
+void DBusSensors::enable(Services& /*services*/)
+{
+    // Currently nothing to do here.  The next monitoring cycle will set the
+    // values of all sensors, and that will set them to the enabled state.
+}
+
+void DBusSensors::endCycle(Services& services)
+{
+    // Delete any sensors that were not updated during this monitoring cycle.
+    // This can happen if the hardware device producing the sensors was removed
+    // or replaced with a different version.
+    auto it = sensors.begin();
+    while (it != sensors.end())
+    {
+        // Increment iterator before potentially deleting the sensor.
+        // map::erase() invalidates iterators/references to the erased element.
+        auto& [sensorName, sensor] = *it;
+        ++it;
+
+        // Check if last update time for sensor is before cycle start time
+        if (sensor->getLastUpdateTime() < cycleStartTime)
+        {
+            services.getJournal().logDebug("Deleted sensor " + sensorName);
+            sensors.erase(sensorName);
+        }
+    }
+}
+
+void DBusSensors::endRail(bool errorOccurred, Services& /*services*/)
+{
+    // If an error occurred, set all sensors for current rail to the error state
+    if (errorOccurred)
+    {
+        for (auto& [sensorName, sensor] : sensors)
+        {
+            if (sensor->getRail() == rail)
+            {
+                sensor->setToErrorState();
+            }
+        }
+    }
+
+    // Clear current rail information
+    rail.clear();
+    deviceInventoryPath.clear();
+    chassisInventoryPath.clear();
+}
+
+void DBusSensors::disable(Services& /*services*/)
+{
+    // Disable all sensors
+    for (auto& [sensorName, sensor] : sensors)
+    {
+        sensor->disable();
+    }
+}
+
+void DBusSensors::setValue(SensorType type, double value, Services& services)
+{
+    // Build unique sensor name based on rail and sensor type
+    std::string sensorName{rail + '_' + sensors::toString(type)};
+
+    // Check to see if the sensor already exists
+    auto it = sensors.find(sensorName);
+    if (it != sensors.end())
+    {
+        // Sensor exists; update value
+        it->second->setValue(value);
+    }
+    else
+    {
+        // Sensor doesn't exist; create it and add it to the map
+        auto sensor = std::make_unique<DBusSensor>(bus, sensorName, type, value,
+                                                   rail, deviceInventoryPath,
+                                                   chassisInventoryPath);
+        sensors.emplace(sensorName, std::move(sensor));
+        services.getJournal().logDebug("Created sensor " + sensorName);
+    }
+}
+
+void DBusSensors::startCycle(Services& /*services*/)
+{
+    // Store the time when this monitoring cycle started.  This is used to
+    // detect sensors that were not updated during this cycle.
+    cycleStartTime = std::chrono::system_clock::now();
+}
+
+void DBusSensors::startRail(const std::string& rail,
+                            const std::string& deviceInventoryPath,
+                            const std::string& chassisInventoryPath,
+                            Services& /*services*/)
+{
+    // Store current rail information; used later by setValue() and endRail()
+    this->rail = rail;
+    this->deviceInventoryPath = deviceInventoryPath;
+    this->chassisInventoryPath = chassisInventoryPath;
+}
+
+} // namespace phosphor::power::regulators
diff --git a/phosphor-regulators/src/dbus_sensors.hpp b/phosphor-regulators/src/dbus_sensors.hpp
new file mode 100644
index 0000000..4ffa70b
--- /dev/null
+++ b/phosphor-regulators/src/dbus_sensors.hpp
@@ -0,0 +1,130 @@
+/**
+ * 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 "dbus_sensor.hpp"
+#include "sensors.hpp"
+#include "services.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/manager.hpp>
+
+#include <chrono>
+#include <map>
+#include <memory>
+#include <string>
+
+namespace phosphor::power::regulators
+{
+
+/**
+ * @class DBusSensors
+ *
+ * Implementation of the Sensors interface using D-Bus.
+ */
+class DBusSensors : public Sensors
+{
+  public:
+    // Specify which compiler-generated methods we want
+    DBusSensors() = delete;
+    DBusSensors(const DBusSensors&) = delete;
+    DBusSensors(DBusSensors&&) = delete;
+    DBusSensors& operator=(const DBusSensors&) = delete;
+    DBusSensors& operator=(DBusSensors&&) = delete;
+    virtual ~DBusSensors() = default;
+
+    /**
+     * Constructor.
+     *
+     * @param bus D-Bus bus object
+     */
+    explicit DBusSensors(sdbusplus::bus::bus& bus) :
+        bus{bus}, manager{bus, sensorsObjectPath}
+    {
+    }
+
+    /** @copydoc Sensors::enable() */
+    virtual void enable(Services& services) override;
+
+    /** @copydoc Sensors::endCycle() */
+    virtual void endCycle(Services& services) override;
+
+    /** @copydoc Sensors::endRail() */
+    virtual void endRail(bool errorOccurred, Services& services) override;
+
+    /** @copydoc Sensors::disable() */
+    virtual void disable(Services& services) override;
+
+    /** @copydoc Sensors::setValue() */
+    virtual void setValue(SensorType type, double value,
+                          Services& services) override;
+
+    /** @copydoc Sensors::startCycle() */
+    virtual void startCycle(Services& services) override;
+
+    /** @copydoc Sensors::startRail() */
+    virtual void startRail(const std::string& rail,
+                           const std::string& deviceInventoryPath,
+                           const std::string& chassisInventoryPath,
+                           Services& services) override;
+
+  private:
+    /**
+     * D-Bus bus object.
+     */
+    sdbusplus::bus::bus& bus;
+
+    /**
+     * D-Bus object manager.
+     *
+     * Causes this application to implement the
+     * org.freedesktop.DBus.ObjectManager interface.
+     */
+    sdbusplus::server::manager_t manager;
+
+    /**
+     * Map from sensor names to DBusSensor objects.
+     */
+    std::map<std::string, std::unique_ptr<DBusSensor>> sensors{};
+
+    /**
+     * Time that current monitoring cycle started.
+     */
+    std::chrono::system_clock::time_point cycleStartTime{};
+
+    /**
+     * Current voltage rail.
+     *
+     * This is set by startRail().
+     */
+    std::string rail{};
+
+    /**
+     * Current device inventory path.
+     *
+     * This is set by startRail().
+     */
+    std::string deviceInventoryPath{};
+
+    /**
+     * Current chassis inventory path.
+     *
+     * This is set by startRail().
+     */
+    std::string chassisInventoryPath{};
+};
+
+} // namespace phosphor::power::regulators
diff --git a/phosphor-regulators/src/meson.build b/phosphor-regulators/src/meson.build
index f842a3b..9c289ae 100644
--- a/phosphor-regulators/src/meson.build
+++ b/phosphor-regulators/src/meson.build
@@ -9,6 +9,7 @@
     'config_file_parser.cpp',
     'configuration.cpp',
     'dbus_sensor.cpp',
+    'dbus_sensors.cpp',
     'device.cpp',
     'error_logging.cpp',
     'error_logging_utils.cpp',
diff --git a/phosphor-regulators/src/sensors.hpp b/phosphor-regulators/src/sensors.hpp
index b37c8c3..1fe0daf 100644
--- a/phosphor-regulators/src/sensors.hpp
+++ b/phosphor-regulators/src/sensors.hpp
@@ -213,6 +213,8 @@
     /**
      * Sets the value of one sensor for the current voltage rail.
      *
+     * Throws an exception if an error occurs.
+     *
      * @param type sensor type
      * @param value sensor value
      * @param services system services