regulators: Create DBusSensor class

Create the DBusSensor class that represents a voltage regulator sensor
on D-Bus.

Each voltage rail in the system may provide multiple types of sensor
data, such as temperature, output voltage, and output current.  A
DBusSensor tracks one of these data types for a voltage rail.

Class supports the following sensor operations:
* Create sensor
* Set sensor value
* Disable sensor (when system is powered off)
* Set sensor to error state (when unable to read value due to I2C error)
* Delete sensor

Note: A different class will implement the ObjectManager interface for
all voltage regulator sensors.  That will be in a future commit.

Tested:
* Created a sensor of each supported type (vout, iout, etc.)
* Set the value of a sensor
  * Tested update policies: hysteresis, highest, lowest
* Disabled a sensor
* Set a sensor to the error state
* Verified all D-Bus signals emitted
  * InterfacesAdded signal emitted with all info when sensor created
  * PropertiesChanged signal emitted when sensor properties change
  * InterfacesRemoved signal emitted when sensor deleted
* See https://gist.github.com/smccarney/5ae989da1977162ae1a21c208d5302fc
  for complete test plan.

Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: I655277e6bf0a31a8778a54e87d5864f9951b045e
diff --git a/phosphor-regulators/src/dbus_sensor.cpp b/phosphor-regulators/src/dbus_sensor.cpp
new file mode 100644
index 0000000..1fd4bf6
--- /dev/null
+++ b/phosphor-regulators/src/dbus_sensor.cpp
@@ -0,0 +1,308 @@
+/**
+ * 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_sensor.hpp"
+
+#include <cmath>
+#include <limits>
+#include <utility>
+
+namespace phosphor::power::regulators
+{
+
+/**
+ * Constants for current sensors.
+ *
+ * Values are in amperes.
+ */
+constexpr double currentMinValue = 0.0;
+constexpr double currentMaxValue = 500.0;
+constexpr double currentHysteresis = 1.0;
+constexpr const char* currentNamespace = "current";
+
+/**
+ * Constants for power sensors.
+ *
+ * Values are in watts.
+ */
+constexpr double powerMinValue = 0.0;
+constexpr double powerMaxValue = 1000.0;
+constexpr double powerHysteresis = 1.0;
+constexpr const char* powerNamespace = "power";
+
+/**
+ * Constants for temperature sensors.
+ *
+ * Values are in degrees Celsius.
+ */
+constexpr double temperatureMinValue = -50.0;
+constexpr double temperatureMaxValue = 250.0;
+constexpr double temperatureHysteresis = 1.0;
+constexpr const char* temperatureNamespace = "temperature";
+
+/**
+ * Constants for voltage sensors.
+ *
+ * Values are in volts.
+ *
+ * Note the hysteresis value is very low.  Small voltage changes can have a big
+ * impact in some systems.  The sensors need to reflect these small changes.
+ */
+constexpr double voltageMinValue = -15.0;
+constexpr double voltageMaxValue = 15.0;
+constexpr double voltageHysteresis = 0.001;
+constexpr const char* voltageNamespace = "voltage";
+
+DBusSensor::DBusSensor(sdbusplus::bus::bus& bus, const std::string& name,
+                       SensorType type, double value, const std::string& rail,
+                       const std::string& deviceInventoryPath,
+                       const std::string& chassisInventoryPath) :
+    bus{bus},
+    name{name}, type{type}, rail{rail}
+{
+    // Get sensor properties that are based on the sensor type
+    std::string objectPath;
+    Unit unit;
+    double minValue, maxValue;
+    getTypeBasedProperties(objectPath, unit, minValue, maxValue);
+
+    // Get the D-Bus associations to create for this sensor
+    std::vector<AssocationTuple> associations =
+        getAssociations(deviceInventoryPath, chassisInventoryPath);
+
+    // Create the sdbusplus object that implements the D-Bus sensor interfaces.
+    // Skip emitting D-Bus signals until the object has been fully created.
+    bool skipSignal{true};
+    dbusObject =
+        std::make_unique<DBusSensorObject>(bus, objectPath.c_str(), skipSignal);
+
+    // Set properties of the Value interface
+    dbusObject->value(value, skipSignal);
+    dbusObject->maxValue(maxValue, skipSignal);
+    dbusObject->minValue(minValue, skipSignal);
+    dbusObject->unit(unit, skipSignal);
+
+    // Set properties of the OperationalStatus interface
+    dbusObject->functional(true, skipSignal);
+
+    // Set properties of the Availability interface
+    dbusObject->available(true, skipSignal);
+
+    // Set properties on the Association.Definitions interface
+    dbusObject->associations(std::move(associations), skipSignal);
+
+    // Now emit signal that object has been created
+    dbusObject->emit_object_added();
+}
+
+void DBusSensor::disable()
+{
+    // Set sensor value to NaN
+    setValueToNaN();
+
+    // Set the sensor to unavailable since it is disabled
+    dbusObject->available(false);
+}
+
+void DBusSensor::setToErrorState()
+{
+    // Set sensor value to NaN
+    setValueToNaN();
+
+    // Set the sensor to non-functional since it could not be read
+    dbusObject->functional(false);
+}
+
+void DBusSensor::setValue(double value)
+{
+    // Update value on D-Bus if necessary
+    if (shouldUpdateValue(value))
+    {
+        dbusObject->value(value);
+    }
+
+    // Set the sensor to functional since it has a valid value
+    dbusObject->functional(true);
+
+    // Set the sensor to available since it is not disabled
+    dbusObject->available(true);
+}
+
+std::vector<AssocationTuple>
+    DBusSensor::getAssociations(const std::string& deviceInventoryPath,
+                                const std::string& chassisInventoryPath)
+{
+    std::vector<AssocationTuple> associations{};
+
+    // Add an association between the sensor and the chassis.  This is used by
+    // the Redfish support to find all the sensors in a chassis.
+    associations.emplace_back(
+        std::make_tuple("chassis", "all_sensors", chassisInventoryPath));
+
+    // Add an association between the sensor and the voltage regulator device.
+    // This is used by the Redfish support to find the hardware/inventory item
+    // associated with a sensor.
+    associations.emplace_back(
+        std::make_tuple("inventory", "sensors", deviceInventoryPath));
+
+    return associations;
+}
+
+void DBusSensor::getTypeBasedProperties(std::string& objectPath, Unit& unit,
+                                        double& minValue, double& maxValue)
+{
+    const char* typeNamespace{""};
+    switch (type)
+    {
+        case SensorType::iout:
+            typeNamespace = currentNamespace;
+            unit = Unit::Amperes;
+            minValue = currentMinValue;
+            maxValue = currentMaxValue;
+            updatePolicy = ValueUpdatePolicy::hysteresis;
+            hysteresis = currentHysteresis;
+            break;
+
+        case SensorType::iout_peak:
+            typeNamespace = currentNamespace;
+            unit = Unit::Amperes;
+            minValue = currentMinValue;
+            maxValue = currentMaxValue;
+            updatePolicy = ValueUpdatePolicy::highest;
+            break;
+
+        case SensorType::iout_valley:
+            typeNamespace = currentNamespace;
+            unit = Unit::Amperes;
+            minValue = currentMinValue;
+            maxValue = currentMaxValue;
+            updatePolicy = ValueUpdatePolicy::lowest;
+            break;
+
+        case SensorType::pout:
+            typeNamespace = powerNamespace;
+            unit = Unit::Watts;
+            minValue = powerMinValue;
+            maxValue = powerMaxValue;
+            updatePolicy = ValueUpdatePolicy::hysteresis;
+            hysteresis = powerHysteresis;
+            break;
+
+        case SensorType::temperature:
+            typeNamespace = temperatureNamespace;
+            unit = Unit::DegreesC;
+            minValue = temperatureMinValue;
+            maxValue = temperatureMaxValue;
+            updatePolicy = ValueUpdatePolicy::hysteresis;
+            hysteresis = temperatureHysteresis;
+            break;
+
+        case SensorType::temperature_peak:
+            typeNamespace = temperatureNamespace;
+            unit = Unit::DegreesC;
+            minValue = temperatureMinValue;
+            maxValue = temperatureMaxValue;
+            updatePolicy = ValueUpdatePolicy::highest;
+            break;
+
+        case SensorType::vout:
+            typeNamespace = voltageNamespace;
+            unit = Unit::Volts;
+            minValue = voltageMinValue;
+            maxValue = voltageMaxValue;
+            updatePolicy = ValueUpdatePolicy::hysteresis;
+            hysteresis = voltageHysteresis;
+            break;
+
+        case SensorType::vout_peak:
+            typeNamespace = voltageNamespace;
+            unit = Unit::Volts;
+            minValue = voltageMinValue;
+            maxValue = voltageMaxValue;
+            updatePolicy = ValueUpdatePolicy::highest;
+            break;
+
+        case SensorType::vout_valley:
+        default:
+            typeNamespace = voltageNamespace;
+            unit = Unit::Volts;
+            minValue = voltageMinValue;
+            maxValue = voltageMaxValue;
+            updatePolicy = ValueUpdatePolicy::lowest;
+            break;
+    }
+
+    // Build object path
+    objectPath = sensorsObjectPath;
+    objectPath += '/';
+    objectPath += typeNamespace;
+    objectPath += '/';
+    objectPath += name;
+}
+
+void DBusSensor::setValueToNaN()
+{
+    // Get current value published on D-Bus
+    double currentValue = dbusObject->value();
+
+    // Check if current value is already NaN.  We want to avoid an unnecessary
+    // PropertiesChanged signal.  The generated C++ code for the Value interface
+    // does check whether the new value is different from the old one.  However,
+    // it uses the equality operator, and NaN always returns false when compared
+    // to another NaN value.
+    if (!std::isnan(currentValue))
+    {
+        // Set value to NaN
+        dbusObject->value(std::numeric_limits<double>::quiet_NaN());
+    }
+}
+
+bool DBusSensor::shouldUpdateValue(double value)
+{
+    // Initially assume we should update the value
+    bool shouldUpdate{true};
+
+    // Get current value published on D-Bus
+    double currentValue = dbusObject->value();
+
+    // Update sensor if the current value is NaN.  This indicates it was
+    // disabled or in an error state.  Note: you cannot compare a variable to
+    // NaN directly using the equality operator; it will always return false.
+    if (std::isnan(currentValue))
+    {
+        shouldUpdate = true;
+    }
+    else
+    {
+        // Determine whether to update based on policy used by this sensor
+        switch (updatePolicy)
+        {
+            case ValueUpdatePolicy::hysteresis:
+                shouldUpdate = (std::abs(value - currentValue) >= hysteresis);
+                break;
+            case ValueUpdatePolicy::highest:
+                shouldUpdate = (value > currentValue);
+                break;
+            case ValueUpdatePolicy::lowest:
+                shouldUpdate = (value < currentValue);
+                break;
+        }
+    }
+
+    return shouldUpdate;
+}
+
+} // namespace phosphor::power::regulators
diff --git a/phosphor-regulators/src/dbus_sensor.hpp b/phosphor-regulators/src/dbus_sensor.hpp
new file mode 100644
index 0000000..4e39c34
--- /dev/null
+++ b/phosphor-regulators/src/dbus_sensor.hpp
@@ -0,0 +1,332 @@
+/**
+ * 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 "sensors.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
+#include <xyz/openbmc_project/Sensor/Value/server.hpp>
+#include <xyz/openbmc_project/State/Decorator/Availability/server.hpp>
+#include <xyz/openbmc_project/State/Decorator/OperationalStatus/server.hpp>
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace phosphor::power::regulators
+{
+
+/**
+ * Define simple name for the generated C++ class that implements the
+ * xyz.openbmc_project.Sensor.Value interface.
+ */
+using ValueInterface = sdbusplus::xyz::openbmc_project::Sensor::server::Value;
+
+/**
+ * Define simple name for the generated C++ class that implements the
+ * xyz.openbmc_project.State.Decorator.OperationalStatus interface.
+ */
+using OperationalStatusInterface = sdbusplus::xyz::openbmc_project::State::
+    Decorator::server::OperationalStatus;
+
+/**
+ * Define simple name for the generated C++ class that implements the
+ * xyz.openbmc_project.State.Decorator.Availability interface.
+ */
+using AvailabilityInterface =
+    sdbusplus::xyz::openbmc_project::State::Decorator::server::Availability;
+
+/**
+ * Define simple name for the generated C++ class that implements the
+ * xyz.openbmc_project.Association.Definitions interface.
+ */
+using AssociationDefinitionsInterface =
+    sdbusplus::xyz::openbmc_project::Association::server::Definitions;
+
+/**
+ * Define simple name for the sdbusplus object_t class that implements all
+ * the necessary D-Bus interfaces via templates/multiple inheritance.
+ */
+using DBusSensorObject =
+    sdbusplus::server::object_t<ValueInterface, OperationalStatusInterface,
+                                AvailabilityInterface,
+                                AssociationDefinitionsInterface>;
+
+/**
+ * Define simple name for the generated C++ enum that implements the
+ * valid sensor Unit values on D-Bus.
+ */
+using Unit = sdbusplus::xyz::openbmc_project::Sensor::server::Value::Unit;
+
+/**
+ * Define simple name for the tuple used to create D-Bus associations.
+ */
+using AssocationTuple = std::tuple<std::string, std::string, std::string>;
+
+/**
+ * Root object path for sensors.
+ */
+constexpr const char* sensorsObjectPath = "/xyz/openbmc_project/sensors";
+
+/**
+ * @class DBusSensor
+ *
+ * This class represents a voltage regulator sensor on D-Bus.
+ *
+ * Each voltage rail in the system may provide multiple types of sensor data,
+ * such as temperature, output voltage, and output current.  A DBusSensor tracks
+ * one of these data types for a voltage rail.
+ */
+class DBusSensor
+{
+  public:
+    // Specify which compiler-generated methods we want
+    DBusSensor() = delete;
+    DBusSensor(const DBusSensor&) = delete;
+    DBusSensor(DBusSensor&&) = delete;
+    DBusSensor& operator=(const DBusSensor&) = delete;
+    DBusSensor& operator=(DBusSensor&&) = delete;
+    virtual ~DBusSensor() = default;
+
+    /**
+     * Constructor.
+     *
+     * @param bus D-Bus bus object
+     * @param name sensor name
+     * @param type sensor type
+     * @param value sensor value
+     * @param rail voltage rail associated with this sensor
+     * @param deviceInventoryPath D-Bus inventory path of the voltage regulator
+     *                            device that produces the rail
+     * @param chassisInventoryPath D-Bus inventory path of the chassis that
+     *                             contains the voltage regulator device
+     */
+    explicit DBusSensor(sdbusplus::bus::bus& bus, const std::string& name,
+                        SensorType type, double value, const std::string& rail,
+                        const std::string& deviceInventoryPath,
+                        const std::string& chassisInventoryPath);
+
+    /**
+     * Disable this sensor.
+     *
+     * Updates the sensor properties on D-Bus to indicate it is no longer
+     * receiving value updates.
+     *
+     * This method is normally called when the system is being powered off.
+     * Sensors are not read when the system is powered off.
+     */
+    void disable();
+
+    /**
+     * Return the sensor name.
+     *
+     * @return sensor name
+     */
+    const std::string& getName() const
+    {
+        return name;
+    }
+
+    /**
+     * Return the voltage regulator rail associated with this sensor.
+     *
+     * @return rail
+     */
+    const std::string& getRail() const
+    {
+        return rail;
+    }
+
+    /**
+     * Return the sensor type.
+     *
+     * @return sensor type
+     */
+    SensorType getType() const
+    {
+        return type;
+    }
+
+    /**
+     * Set this sensor to the error state.
+     *
+     * Updates the sensor properties on D-Bus to indicate an error occurred and
+     * the sensor value could not be read.
+     */
+    void setToErrorState();
+
+    /**
+     * Set the value of this sensor.
+     *
+     * Do not specify the value NaN.  This special value is used internally to
+     * indicate the sensor has been disabled or is in the error state.  Call the
+     * disable() or setToErrorState() method instead so that all affected D-Bus
+     * interfaces are updated correctly.
+     *
+     * @param value new sensor value
+     */
+    void setValue(double value);
+
+  private:
+    /**
+     * Sensor value update policy.
+     *
+     * Determines whether a new sensor value should replace the current value on
+     * D-Bus.
+     */
+    enum class ValueUpdatePolicy : unsigned char
+    {
+        /**
+         * Hysteresis value update policy.
+         *
+         * The sensor value will only be updated if the new value differs from
+         * the current value by at least the hysteresis amount.  This avoids
+         * constant D-Bus traffic due to insignificant value changes.
+         */
+        hysteresis,
+
+        /**
+         * Highest value update policy.
+         *
+         * The sensor value will only be updated if the new value is higher than
+         * the current value.
+         *
+         * Some sensors contain the highest value observed by the voltage
+         * regulator, such as the highest temperature or highest output voltage.
+         * The regulator internally calculates this value since it can poll the
+         * value very quickly and can catch transient events.
+         *
+         * When the sensor is read from the regulator, the regulator will often
+         * clear its internal value.  It will begin calculating a new highest
+         * value.  For this reason, the D-Bus sensor value is set to the highest
+         * value that has been read across all monitoring cycles.
+         *
+         * The D-Bus sensor value is cleared when the sensor is disabled.  This
+         * normally occurs when the system is powered off.  Thus, the D-Bus
+         * sensor value is normally the highest value read since the system was
+         * powered on.
+         */
+        highest,
+
+        /**
+         * Lowest value update policy.
+         *
+         * The sensor value will only be updated if the new value is lower than
+         * the current value.
+         *
+         * Some sensors contain the lowest value observed by the voltage
+         * regulator, such as the lowest output current or lowest output
+         * voltage.  The regulator internally calculates this value since it can
+         * poll the value very quickly and can catch transient events.
+         *
+         * When the sensor is read from the regulator, the regulator will often
+         * clear its internal value.  It will begin calculating a new lowest
+         * value.  For this reason, the D-Bus sensor value is set to the lowest
+         * value that has been read across all monitoring cycles.
+         *
+         * The D-Bus sensor value is cleared when the sensor is disabled.  This
+         * normally occurs when the system is powered off.  Thus, the D-Bus
+         * sensor value is normally the lowest value read since the system was
+         * powered on.
+         */
+        lowest
+    };
+
+    /**
+     * Get the D-Bus associations to create for this sensor.
+     *
+     * @param deviceInventoryPath D-Bus inventory path of the voltage regulator
+     *                            device that produces the rail
+     * @param chassisInventoryPath D-Bus inventory path of the chassis that
+     *                             contains the voltage regulator device
+     */
+    std::vector<AssocationTuple>
+        getAssociations(const std::string& deviceInventoryPath,
+                        const std::string& chassisInventoryPath);
+
+    /**
+     * Get sensor properties that are based on the sensor type.
+     *
+     * The properties are returned in output parameters.
+     *
+     * Also initializes some data members whose value is based on the sensor
+     * type.
+     *
+     * @param objectPath returns the object path of this sensor
+     * @param unit returns the D-Bus unit for the sensor value
+     * @param minValue returns the minimum sensor value
+     * @param maxValue returns the maximum sensor value
+     */
+    void getTypeBasedProperties(std::string& objectPath, Unit& unit,
+                                double& minValue, double& maxValue);
+
+    /**
+     * Set the sensor value on D-Bus to NaN.
+     */
+    void setValueToNaN();
+
+    /**
+     * Returns whether to update the sensor value on D-Bus with the specified
+     * new value.
+     *
+     * @param value new sensor value
+     * @return true if value should be updated on D-Bus, false otherwise
+     */
+    bool shouldUpdateValue(double value);
+
+    /**
+     * D-Bus bus object.
+     */
+    sdbusplus::bus::bus& bus;
+
+    /**
+     * Sensor name.
+     */
+    std::string name{};
+
+    /**
+     * Sensor type.
+     */
+    SensorType type;
+
+    /**
+     * Voltage regulator rail associated with this sensor.
+     */
+    std::string rail{};
+
+    /**
+     * Sensor value update policy.
+     */
+    ValueUpdatePolicy updatePolicy{ValueUpdatePolicy::hysteresis};
+
+    /**
+     * Hysteresis value.
+     *
+     * Only used when updatePolicy is hysteresis.
+     */
+    double hysteresis{0.0};
+
+    /**
+     * sdbusplus object_t class that implements all the necessary D-Bus
+     * interfaces via templates and multiple inheritance.
+     */
+    std::unique_ptr<DBusSensorObject> dbusObject{};
+};
+
+} // namespace phosphor::power::regulators
diff --git a/phosphor-regulators/src/meson.build b/phosphor-regulators/src/meson.build
index 40cb3e8..f842a3b 100644
--- a/phosphor-regulators/src/meson.build
+++ b/phosphor-regulators/src/meson.build
@@ -8,6 +8,7 @@
     'chassis.cpp',
     'config_file_parser.cpp',
     'configuration.cpp',
+    'dbus_sensor.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 d23a2b7..b37c8c3 100644
--- a/phosphor-regulators/src/sensors.hpp
+++ b/phosphor-regulators/src/sensors.hpp
@@ -25,7 +25,7 @@
 /**
  * Voltage regulator sensor type.
  */
-enum class SensorType
+enum class SensorType : unsigned char
 {
     /**
      * Output current.
@@ -90,7 +90,7 @@
  * @param type sensor type
  * @return sensor type name
  */
-std::string toString(SensorType type)
+inline std::string toString(SensorType type)
 {
     std::string name{};
     switch (type)