regulators: Create regulators PMBusReadSensorAction class

Create the PMBusReadSensorAction class that implements the
"pmbus_read_sensor" action in the JSON config file.

Only create the .hpp file for PMBusReadSensorAction class first.

Signed-off-by: Bob King <Bob_King@wistron.com>
Change-Id: I689ad9dbefe601d8962c694e7e1b8b7275ba6880
diff --git a/phosphor-regulators/src/actions/pmbus_read_sensor_action.hpp b/phosphor-regulators/src/actions/pmbus_read_sensor_action.hpp
new file mode 100644
index 0000000..8737eba
--- /dev/null
+++ b/phosphor-regulators/src/actions/pmbus_read_sensor_action.hpp
@@ -0,0 +1,179 @@
+/**
+ * Copyright © 2020 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 "action_environment.hpp"
+#include "i2c_action.hpp"
+#include "i2c_interface.hpp"
+#include "pmbus_utils.hpp"
+
+#include <cstdint>
+#include <optional>
+#include <stdexcept>
+#include <string>
+
+namespace phosphor::power::regulators
+{
+
+/**
+ * @class PMBusReadSensorAction
+ *
+ * Reads one sensor for a PMBus regulator rail. Communicates with the device
+ * directly using the I2C interface.
+ *
+ * Implements the pmbus_read_sensor action in the JSON config file.
+ *
+ * Currently supports the linear_11 and linear_16 sensor data formats.
+ *
+ * The linear_16 data format requires an exponent value.  The exponent value
+ * can be specified in the constructor.  Otherwise the exponent value will be
+ * obtained from the PMBus VOUT_MODE command.  Note that some PMBus devices do
+ * not support the VOUT_MODE command.  The exponent value for a device is often
+ * found in the device documentation (data sheet).
+ */
+class PMBusReadSensorAction : public I2CAction
+{
+  public:
+    // Specify which compiler-generated methods we want
+    PMBusReadSensorAction() = delete;
+    PMBusReadSensorAction(const PMBusReadSensorAction&) = delete;
+    PMBusReadSensorAction(PMBusReadSensorAction&&) = delete;
+    PMBusReadSensorAction& operator=(const PMBusReadSensorAction&) = delete;
+    PMBusReadSensorAction& operator=(PMBusReadSensorAction&&) = delete;
+    virtual ~PMBusReadSensorAction() = default;
+
+    /**
+     * Constructor.
+     *
+     * @param type Sensor value type.
+     * @param command PMBus command code.
+     * @param format Data format of the sensor value returned by the device.
+     * @param exponent Exponent value for linear_16 data format.
+     *                 Can be positive or negative. If not specified, the
+     *                 exponent value will be read from VOUT_MODE.
+     *                 Should not be specified if the data format is linear_11.
+     */
+    explicit PMBusReadSensorAction(pmbus_utils::SensorValueType type,
+                                   uint8_t command,
+                                   pmbus_utils::SensorDataFormat format,
+                                   std::optional<int8_t> exponent) :
+        type{type},
+        command{command}, format{format}, exponent{exponent}
+    {
+    }
+
+    /**
+     * Executes this action.
+     *
+     * TODO: Not implemented yet
+     *
+     * @param environment Action execution environment.
+     * @return true
+     */
+    virtual bool execute(ActionEnvironment& /* environment */) override
+    {
+        // TODO: Not implemented yet
+        return true;
+    }
+
+    /**
+     * Returns the PMBus command code.
+     *
+     * @return command
+     */
+    uint8_t getCommand() const
+    {
+        return command;
+    }
+
+    /**
+     * Returns the optional exponent value for linear_16 data format.
+     *
+     * @return optional exponent value
+     */
+    std::optional<int8_t> getExponent() const
+    {
+        return exponent;
+    }
+
+    /**
+     * Returns the data format of the sensor value returned by the device.
+     *
+     * @return data format
+     */
+    pmbus_utils::SensorDataFormat getFormat() const
+    {
+        return format;
+    }
+
+    /**
+     * Returns the sensor value type.
+     *
+     * @return sensor value type.
+     */
+    pmbus_utils::SensorValueType getType() const
+    {
+        return type;
+    }
+
+    /**
+     * Returns a string description of this action.
+     *
+     * @return description of action
+     */
+    virtual std::string toString() const override
+    {
+        std::ostringstream ss;
+        ss << "pmbus_read_sensor: { ";
+        ss << "type: " << pmbus_utils::toString(type) << ", " << std::hex
+           << std::uppercase;
+        ss << "command: 0x" << static_cast<uint16_t>(command) << ", "
+           << std::dec << std::nouppercase;
+        ss << "format: " << pmbus_utils::toString(format);
+
+        if (exponent.has_value())
+        {
+            ss << ", exponent: " << static_cast<int16_t>(exponent.value());
+        }
+
+        ss << " }";
+
+        return ss.str();
+    }
+
+  private:
+    /**
+     * Sensor value type.
+     */
+    const pmbus_utils::SensorValueType type{};
+
+    /**
+     * PMBus command code.
+     */
+    const uint8_t command{};
+
+    /**
+     * Data format of the sensor value returned by the device.
+     */
+    const pmbus_utils::SensorDataFormat format{};
+
+    /**
+     * Optional exponent value for linear_16 data format.
+     */
+    const std::optional<int8_t> exponent{};
+};
+
+} // namespace phosphor::power::regulators
diff --git a/phosphor-regulators/src/actions/pmbus_write_vout_command_action.cpp b/phosphor-regulators/src/actions/pmbus_write_vout_command_action.cpp
index 238e2f2..2a245a9 100644
--- a/phosphor-regulators/src/actions/pmbus_write_vout_command_action.cpp
+++ b/phosphor-regulators/src/actions/pmbus_write_vout_command_action.cpp
@@ -81,8 +81,7 @@
         ss << "volts: " << volts.value() << ", ";
     }
 
-    // Only linear format is currently supported
-    ss << "format: linear";
+    ss << "format: " << pmbus_utils::toString(format);
 
     if (exponent.has_value())
     {
diff --git a/phosphor-regulators/src/pmbus_utils.cpp b/phosphor-regulators/src/pmbus_utils.cpp
index 87a91b6..eb9e117 100644
--- a/phosphor-regulators/src/pmbus_utils.cpp
+++ b/phosphor-regulators/src/pmbus_utils.cpp
@@ -58,4 +58,76 @@
     parameter = static_cast<int8_t>(parameterField);
 }
 
+std::string toString(SensorDataFormat format)
+{
+    std::string returnValue{};
+    switch (format)
+    {
+        case SensorDataFormat::linear_11:
+            returnValue = "linear_11";
+            break;
+        case SensorDataFormat::linear_16:
+            returnValue = "linear_16";
+            break;
+    }
+    return returnValue;
+}
+
+std::string toString(SensorValueType type)
+{
+    std::string returnValue{};
+    switch (type)
+    {
+        case SensorValueType::iout:
+            returnValue = "iout";
+            break;
+        case SensorValueType::iout_peak:
+            returnValue = "iout_peak";
+            break;
+        case SensorValueType::iout_valley:
+            returnValue = "iout_valley";
+            break;
+        case SensorValueType::pout:
+            returnValue = "pout";
+            break;
+        case SensorValueType::temperature:
+            returnValue = "temperature";
+            break;
+        case SensorValueType::temperature_peak:
+            returnValue = "temperature_peak";
+            break;
+        case SensorValueType::vout:
+            returnValue = "vout";
+            break;
+        case SensorValueType::vout_peak:
+            returnValue = "vout_peak";
+            break;
+        case SensorValueType::vout_valley:
+            returnValue = "vout_valley";
+            break;
+    }
+    return returnValue;
+}
+
+std::string toString(VoutDataFormat format)
+{
+    std::string returnValue{};
+    switch (format)
+    {
+        case VoutDataFormat::linear:
+            returnValue = "linear";
+            break;
+        case VoutDataFormat::vid:
+            returnValue = "vid";
+            break;
+        case VoutDataFormat::direct:
+            returnValue = "direct";
+            break;
+        case VoutDataFormat::ieee:
+            returnValue = "ieee";
+            break;
+    }
+    return returnValue;
+}
+
 } // namespace phosphor::power::regulators::pmbus_utils
diff --git a/phosphor-regulators/src/pmbus_utils.hpp b/phosphor-regulators/src/pmbus_utils.hpp
index 599c236..5e2e270 100644
--- a/phosphor-regulators/src/pmbus_utils.hpp
+++ b/phosphor-regulators/src/pmbus_utils.hpp
@@ -17,6 +17,7 @@
 
 #include <cmath>
 #include <cstdint>
+#include <string>
 
 /**
  * @namespace pmbus_utils
@@ -38,6 +39,78 @@
 const uint8_t VOUT_COMMAND{0x21u};
 
 /**
+ * Sensor data format.
+ */
+enum class SensorDataFormat
+{
+    /**
+     * Linear data format used for values not related to voltage output, such
+     * as output current, input voltage, and temperature. Two byte value with
+     * an 11-bit, two's complement mantissa and a 5-bit, two's complement
+     * exponent.
+     */
+    linear_11,
+
+    /**
+     * Linear data format used for values related to voltage output. Two
+     * byte (16-bit), unsigned integer that is raised to the power of an
+     * exponent. The exponent is not stored within the two bytes.
+     */
+    linear_16
+};
+
+/**
+ * Sensor Value Type.
+ */
+enum class SensorValueType
+{
+    /**
+     * Output current.
+     */
+    iout,
+
+    /**
+     * Highest output current.
+     */
+    iout_peak,
+
+    /**
+     * Lowest output current.
+     */
+    iout_valley,
+
+    /**
+     * Output power.
+     */
+    pout,
+
+    /**
+     * Temperature.
+     */
+    temperature,
+
+    /**
+     * Highest temperature.
+     */
+    temperature_peak,
+
+    /**
+     * Output voltage.
+     */
+    vout,
+
+    /**
+     * Highest output voltage.
+     */
+    vout_peak,
+
+    /**
+     * Lowest output voltage.
+     */
+    vout_valley
+};
+
+/**
  * Data formats for output voltage.
  *
  * These formats are used for commanding and reading output voltage and related
@@ -88,6 +161,30 @@
                    int8_t& parameter);
 
 /**
+ * Converts the specified SensorDataFormat value to a string.
+ *
+ * @param format SensorDataFormat value
+ * @return string corresponding to the enum value
+ */
+std::string toString(SensorDataFormat format);
+
+/**
+ * Converts the specified SensorValueType value to string.
+ *
+ * @param type SensorValueType type
+ * @return string corresponding to the enum value
+ */
+std::string toString(SensorValueType type);
+
+/**
+ * Converts the specified VoutDataFormat value to string.
+ *
+ * @param format VoutDataFormat format
+ * @return string corresponding to the enum value
+ */
+std::string toString(VoutDataFormat format);
+
+/**
  * Converts a volts value to the linear data format for output voltage.
  *
  * This data format consists of the following:
diff --git a/phosphor-regulators/test/actions/pmbus_read_sensor_action_tests.cpp b/phosphor-regulators/test/actions/pmbus_read_sensor_action_tests.cpp
new file mode 100644
index 0000000..4af095c
--- /dev/null
+++ b/phosphor-regulators/test/actions/pmbus_read_sensor_action_tests.cpp
@@ -0,0 +1,161 @@
+/**
+ * Copyright © 2020 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 "action_environment.hpp"
+#include "i2c_action.hpp"
+#include "i2c_interface.hpp"
+#include "pmbus_read_sensor_action.hpp"
+#include "pmbus_utils.hpp"
+
+#include <cstdint>
+#include <optional>
+#include <stdexcept>
+#include <string>
+
+#include <gtest/gtest.h>
+
+using namespace phosphor::power::regulators;
+
+TEST(PMBusReadSensorActionTests, Constructor)
+{
+    // Test where works: exponent value is specified
+    try
+    {
+        pmbus_utils::SensorValueType type{pmbus_utils::SensorValueType::iout};
+        uint8_t command = 0x8C;
+        pmbus_utils::SensorDataFormat format{
+            pmbus_utils::SensorDataFormat::linear_16};
+        std::optional<int8_t> exponent{-8};
+        PMBusReadSensorAction action{type, command, format, exponent};
+        EXPECT_EQ(action.getType(), pmbus_utils::SensorValueType::iout);
+        EXPECT_EQ(action.getCommand(), 0x8C);
+        EXPECT_EQ(action.getFormat(), pmbus_utils::SensorDataFormat::linear_16);
+        EXPECT_EQ(action.getExponent().has_value(), true);
+        EXPECT_EQ(action.getExponent().value(), -8);
+    }
+    catch (...)
+    {
+        ADD_FAILURE() << "Should not have caught exception.";
+    }
+
+    // Test where works: exponent value is not specified
+    try
+    {
+        pmbus_utils::SensorValueType type{pmbus_utils::SensorValueType::iout};
+        uint8_t command = 0x8C;
+        pmbus_utils::SensorDataFormat format{
+            pmbus_utils::SensorDataFormat::linear_11};
+        std::optional<int8_t> exponent{};
+        PMBusReadSensorAction action{type, command, format, exponent};
+        EXPECT_EQ(action.getType(), pmbus_utils::SensorValueType::iout);
+        EXPECT_EQ(action.getCommand(), 0x8C);
+        EXPECT_EQ(action.getFormat(), pmbus_utils::SensorDataFormat::linear_11);
+        EXPECT_EQ(action.getExponent().has_value(), false);
+    }
+    catch (...)
+    {
+        ADD_FAILURE() << "Should not have caught exception.";
+    }
+}
+
+TEST(PMBusReadSensorActionTests, Execute)
+{
+    // TODO: Not implemented yet
+}
+
+TEST(PMBusReadSensorActionTests, GetCommand)
+{
+    pmbus_utils::SensorValueType type{pmbus_utils::SensorValueType::iout};
+    uint8_t command = 0x8C;
+    pmbus_utils::SensorDataFormat format{
+        pmbus_utils::SensorDataFormat::linear_16};
+    std::optional<int8_t> exponent{-8};
+    PMBusReadSensorAction action{type, command, format, exponent};
+    EXPECT_EQ(action.getCommand(), 0x8C);
+}
+
+TEST(PMBusReadSensorActionTests, GetExponent)
+{
+    pmbus_utils::SensorValueType type{pmbus_utils::SensorValueType::iout};
+    uint8_t command = 0x8C;
+    pmbus_utils::SensorDataFormat format{
+        pmbus_utils::SensorDataFormat::linear_16};
+
+    // Exponent value is specified
+    {
+        std::optional<int8_t> exponent{-9};
+        PMBusReadSensorAction action{type, command, format, exponent};
+        EXPECT_EQ(action.getExponent().has_value(), true);
+        EXPECT_EQ(action.getExponent().value(), -9);
+    }
+
+    // Exponent value is not specified
+    {
+        std::optional<int8_t> exponent{};
+        PMBusReadSensorAction action{type, command, format, exponent};
+        EXPECT_EQ(action.getExponent().has_value(), false);
+    }
+}
+
+TEST(PMBusReadSensorActionTests, GetFormat)
+{
+    pmbus_utils::SensorValueType type{pmbus_utils::SensorValueType::iout};
+    uint8_t command = 0x8C;
+    pmbus_utils::SensorDataFormat format{
+        pmbus_utils::SensorDataFormat::linear_16};
+    std::optional<int8_t> exponent{-8};
+    PMBusReadSensorAction action{type, command, format, exponent};
+    EXPECT_EQ(action.getFormat(), pmbus_utils::SensorDataFormat::linear_16);
+}
+
+TEST(PMBusReadSensorActionTests, GetType)
+{
+    pmbus_utils::SensorValueType type{pmbus_utils::SensorValueType::pout};
+    uint8_t command = 0x8C;
+    pmbus_utils::SensorDataFormat format{
+        pmbus_utils::SensorDataFormat::linear_16};
+    std::optional<int8_t> exponent{-8};
+    PMBusReadSensorAction action{type, command, format, exponent};
+    EXPECT_EQ(action.getType(), pmbus_utils::SensorValueType::pout);
+}
+
+TEST(PMBusReadSensorActionTests, ToString)
+{
+    // Test where exponent value is specified
+    {
+        pmbus_utils::SensorValueType type{
+            pmbus_utils::SensorValueType::temperature_peak};
+        uint8_t command = 0x8C;
+        pmbus_utils::SensorDataFormat format{
+            pmbus_utils::SensorDataFormat::linear_16};
+        std::optional<int8_t> exponent{-8};
+        PMBusReadSensorAction action{type, command, format, exponent};
+        EXPECT_EQ(action.toString(), "pmbus_read_sensor: { type: "
+                                     "temperature_peak, command: 0x8C, format: "
+                                     "linear_16, exponent: -8 }");
+    }
+
+    // Test where exponent value is not specified
+    {
+        pmbus_utils::SensorValueType type{pmbus_utils::SensorValueType::vout};
+        uint8_t command = 0x8C;
+        pmbus_utils::SensorDataFormat format{
+            pmbus_utils::SensorDataFormat::linear_11};
+        std::optional<int8_t> exponent{};
+        PMBusReadSensorAction action{type, command, format, exponent};
+        EXPECT_EQ(action.toString(), "pmbus_read_sensor: { type: vout, "
+                                     "command: 0x8C, format: linear_11 }");
+    }
+}
diff --git a/phosphor-regulators/test/meson.build b/phosphor-regulators/test/meson.build
index a9ac996..b73c3a0 100644
--- a/phosphor-regulators/test/meson.build
+++ b/phosphor-regulators/test/meson.build
@@ -37,6 +37,7 @@
     'actions/if_action_tests.cpp',
     'actions/not_action_tests.cpp',
     'actions/or_action_tests.cpp',
+    'actions/pmbus_read_sensor_action_tests.cpp',
     'actions/pmbus_write_vout_command_action_tests.cpp',
     'actions/run_rule_action_tests.cpp',
     'actions/set_device_action_tests.cpp'
diff --git a/phosphor-regulators/test/pmbus_utils_tests.cpp b/phosphor-regulators/test/pmbus_utils_tests.cpp
index 8c28efd..4aab7db 100644
--- a/phosphor-regulators/test/pmbus_utils_tests.cpp
+++ b/phosphor-regulators/test/pmbus_utils_tests.cpp
@@ -100,6 +100,109 @@
     EXPECT_EQ(parameter, 0);
 }
 
+TEST(PMBusUtilsTests, ToString)
+{
+    // Sensor data format: SensorDataFormat::linear_11
+    {
+        pmbus_utils::SensorDataFormat format =
+            pmbus_utils::SensorDataFormat::linear_11;
+        EXPECT_EQ(pmbus_utils::toString(format), "linear_11");
+    }
+
+    // Sensor data format: SensorDataFormat::linear_16
+    {
+        pmbus_utils::SensorDataFormat format =
+            pmbus_utils::SensorDataFormat::linear_16;
+        EXPECT_EQ(pmbus_utils::toString(format), "linear_16");
+    }
+
+    // Sensor value type: SensorValueType::iout
+    {
+        pmbus_utils::SensorValueType type = pmbus_utils::SensorValueType::iout;
+        EXPECT_EQ(pmbus_utils::toString(type), "iout");
+    }
+
+    // Sensor value type: SensorValueType::iout_peak
+    {
+        pmbus_utils::SensorValueType type =
+            pmbus_utils::SensorValueType::iout_peak;
+        EXPECT_EQ(pmbus_utils::toString(type), "iout_peak");
+    }
+
+    // Sensor value type: SensorValueType::iout_valley
+    {
+        pmbus_utils::SensorValueType type =
+            pmbus_utils::SensorValueType::iout_valley;
+        EXPECT_EQ(pmbus_utils::toString(type), "iout_valley");
+    }
+
+    // Sensor value type: SensorValueType::pout
+    {
+        pmbus_utils::SensorValueType type = pmbus_utils::SensorValueType::pout;
+        EXPECT_EQ(pmbus_utils::toString(type), "pout");
+    }
+
+    // Sensor value type: SensorValueType::temperature
+    {
+        pmbus_utils::SensorValueType type =
+            pmbus_utils::SensorValueType::temperature;
+        EXPECT_EQ(pmbus_utils::toString(type), "temperature");
+    }
+
+    // Sensor value type: SensorValueType::temperature_peak
+    {
+        pmbus_utils::SensorValueType type =
+            pmbus_utils::SensorValueType::temperature_peak;
+        EXPECT_EQ(pmbus_utils::toString(type), "temperature_peak");
+    }
+
+    // Sensor value type: SensorValueType::vout
+    {
+        pmbus_utils::SensorValueType type = pmbus_utils::SensorValueType::vout;
+        EXPECT_EQ(pmbus_utils::toString(type), "vout");
+    }
+
+    // Sensor value type: SensorValueType::vout_peak
+    {
+        pmbus_utils::SensorValueType type =
+            pmbus_utils::SensorValueType::vout_peak;
+        EXPECT_EQ(pmbus_utils::toString(type), "vout_peak");
+    }
+
+    // Sensor value type: SensorValueType::vout_valley
+    {
+        pmbus_utils::SensorValueType type =
+            pmbus_utils::SensorValueType::vout_valley;
+        EXPECT_EQ(pmbus_utils::toString(type), "vout_valley");
+    }
+
+    // Vout data format: VoutDataFormat::linear
+    {
+        pmbus_utils::VoutDataFormat format =
+            pmbus_utils::VoutDataFormat::linear;
+        EXPECT_EQ(pmbus_utils::toString(format), "linear");
+    }
+
+    // Vout data format: VoutDataFormat::vid
+    {
+        pmbus_utils::VoutDataFormat format = pmbus_utils::VoutDataFormat::vid;
+        EXPECT_EQ(pmbus_utils::toString(format), "vid");
+    }
+
+    // Vout data format: VoutDataFormat::direct
+    {
+        pmbus_utils::VoutDataFormat format =
+            pmbus_utils::VoutDataFormat::direct;
+        EXPECT_EQ(pmbus_utils::toString(format), "direct");
+    }
+
+    // Vout data format: VoutDataFormat::ieee
+    {
+        pmbus_utils::VoutDataFormat format = pmbus_utils::VoutDataFormat::ieee;
+        EXPECT_EQ(pmbus_utils::toString(format), "ieee");
+    }
+}
+
 TEST(PMBusUtilsTests, ConvertToVoutLinear)
 {
     double volts;