regulators: Implements support for pmbus_read_sensor action

Enhance the configuration file parser to support the pmbus_read_sensor
action element.

Signed-off-by: Bob King <Bob_King@wistron.com>
Change-Id: I7f87a7551469bb494fe17b6d6d79ebb1ca517e86
diff --git a/phosphor-regulators/src/config_file_parser.cpp b/phosphor-regulators/src/config_file_parser.cpp
index 11527b8..6fa34e1 100644
--- a/phosphor-regulators/src/config_file_parser.cpp
+++ b/phosphor-regulators/src/config_file_parser.cpp
@@ -127,9 +127,8 @@
     }
     else if (element.contains("pmbus_read_sensor"))
     {
-        // TODO: Not implemented yet
-        // action = parsePMBusReadSensor(element["pmbus_read_sensor"]);
-        // ++propertyCount;
+        action = parsePMBusReadSensor(element["pmbus_read_sensor"]);
+        ++propertyCount;
     }
     else if (element.contains("pmbus_write_vout_command"))
     {
@@ -672,6 +671,42 @@
     return std::make_unique<OrAction>(std::move(actions));
 }
 
+std::unique_ptr<PMBusReadSensorAction> parsePMBusReadSensor(const json& element)
+{
+    verifyIsObject(element);
+    unsigned int propertyCount{0};
+
+    // Required type property
+    const json& typeElement = getRequiredProperty(element, "type");
+    pmbus_utils::SensorValueType type = parseSensorValueType(typeElement);
+    ++propertyCount;
+
+    // Required command property
+    const json& commandElement = getRequiredProperty(element, "command");
+    uint8_t command = parseHexByte(commandElement);
+    ++propertyCount;
+
+    // Required format property
+    const json& formatElement = getRequiredProperty(element, "format");
+    pmbus_utils::SensorDataFormat format = parseSensorDataFormat(formatElement);
+    ++propertyCount;
+
+    // Optional exponent property
+    std::optional<int8_t> exponent{};
+    auto exponentIt = element.find("exponent");
+    if (exponentIt != element.end())
+    {
+        exponent = parseInt8(*exponentIt);
+        ++propertyCount;
+    }
+
+    // Verify no invalid properties exist
+    verifyPropertyCount(element, propertyCount);
+
+    return std::make_unique<PMBusReadSensorAction>(type, command, format,
+                                                   exponent);
+}
+
 std::unique_ptr<PMBusWriteVoutCommandAction>
     parsePMBusWriteVoutCommand(const json& element)
 {
@@ -903,6 +938,31 @@
     return std::make_unique<RunRuleAction>(ruleID);
 }
 
+pmbus_utils::SensorDataFormat parseSensorDataFormat(const json& element)
+{
+    if (!element.is_string())
+    {
+        throw std::invalid_argument{"Element is not a string"};
+    }
+    std::string value = element.get<std::string>();
+    pmbus_utils::SensorDataFormat format{};
+
+    if (value == "linear_11")
+    {
+        format = pmbus_utils::SensorDataFormat::linear_11;
+    }
+    else if (value == "linear_16")
+    {
+        format = pmbus_utils::SensorDataFormat::linear_16;
+    }
+    else
+    {
+        throw std::invalid_argument{"Element is not a sensor data format"};
+    }
+
+    return format;
+}
+
 std::unique_ptr<SensorMonitoring> parseSensorMonitoring(const json& element)
 {
     verifyIsObject(element);
@@ -925,6 +985,59 @@
     return std::make_unique<SensorMonitoring>(std::move(actions));
 }
 
+pmbus_utils::SensorValueType parseSensorValueType(const json& element)
+{
+    if (!element.is_string())
+    {
+        throw std::invalid_argument{"Element is not a string"};
+    }
+    std::string value = element.get<std::string>();
+    pmbus_utils::SensorValueType type{};
+
+    if (value == "iout")
+    {
+        type = pmbus_utils::SensorValueType::iout;
+    }
+    else if (value == "iout_peak")
+    {
+        type = pmbus_utils::SensorValueType::iout_peak;
+    }
+    else if (value == "iout_valley")
+    {
+        type = pmbus_utils::SensorValueType::iout_valley;
+    }
+    else if (value == "pout")
+    {
+        type = pmbus_utils::SensorValueType::pout;
+    }
+    else if (value == "temperature")
+    {
+        type = pmbus_utils::SensorValueType::temperature;
+    }
+    else if (value == "temperature_peak")
+    {
+        type = pmbus_utils::SensorValueType::temperature_peak;
+    }
+    else if (value == "vout")
+    {
+        type = pmbus_utils::SensorValueType::vout;
+    }
+    else if (value == "vout_peak")
+    {
+        type = pmbus_utils::SensorValueType::vout_peak;
+    }
+    else if (value == "vout_valley")
+    {
+        type = pmbus_utils::SensorValueType::vout_valley;
+    }
+    else
+    {
+        throw std::invalid_argument{"Element is not a sensor value type"};
+    }
+
+    return type;
+}
+
 std::unique_ptr<SetDeviceAction> parseSetDevice(const json& element)
 {
     // String deviceID
@@ -933,6 +1046,39 @@
     return std::make_unique<SetDeviceAction>(deviceID);
 }
 
+pmbus_utils::VoutDataFormat parseVoutDataFormat(const json& element)
+{
+    if (!element.is_string())
+    {
+        throw std::invalid_argument{"Element is not a string"};
+    }
+    std::string value = element.get<std::string>();
+    pmbus_utils::VoutDataFormat format{};
+
+    if (value == "linear")
+    {
+        format = pmbus_utils::VoutDataFormat::linear;
+    }
+    else if (value == "vid")
+    {
+        format = pmbus_utils::VoutDataFormat::vid;
+    }
+    else if (value == "direct")
+    {
+        format = pmbus_utils::VoutDataFormat::direct;
+    }
+    else if (value == "ieee")
+    {
+        format = pmbus_utils::VoutDataFormat::ieee;
+    }
+    else
+    {
+        throw std::invalid_argument{"Element is not a vout data format"};
+    }
+
+    return format;
+}
+
 } // namespace internal
 
 } // namespace phosphor::power::regulators::config_file_parser
diff --git a/phosphor-regulators/src/config_file_parser.hpp b/phosphor-regulators/src/config_file_parser.hpp
index 205f43a..848ed95 100644
--- a/phosphor-regulators/src/config_file_parser.hpp
+++ b/phosphor-regulators/src/config_file_parser.hpp
@@ -32,6 +32,7 @@
 #include "if_action.hpp"
 #include "not_action.hpp"
 #include "or_action.hpp"
+#include "pmbus_read_sensor_action.hpp"
 #include "pmbus_write_vout_command_action.hpp"
 #include "presence_detection.hpp"
 #include "rail.hpp"
@@ -508,6 +509,19 @@
 std::unique_ptr<OrAction> parseOr(const nlohmann::json& element);
 
 /**
+ * Parses a JSON element containing a pmbus_read_sensor action.
+ *
+ * Returns the corresponding C++ PMBusReadSensorAction object.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return PMBusReadSensorAction object
+ */
+std::unique_ptr<PMBusReadSensorAction>
+    parsePMBusReadSensor(const nlohmann::json& element);
+
+/**
  * Parses a JSON element containing a pmbus_write_vout_command action.
  *
  * Returns the corresponding C++ PMBusWriteVoutCommandAction object.
@@ -629,6 +643,19 @@
 std::unique_ptr<RunRuleAction> parseRunRule(const nlohmann::json& element);
 
 /**
+ * Parses a JSON element containing a SensorDataFormat expressed as a string.
+ *
+ * Returns the corresponding SensorDataFormat enum value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return SensorDataFormat enum value
+ */
+pmbus_utils::SensorDataFormat
+    parseSensorDataFormat(const nlohmann::json& element);
+
+/**
  * Parses a JSON element containing a sensor monitoring operation.
  *
  * Returns the corresponding C++ SensorMonitoring object.
@@ -642,6 +669,19 @@
     parseSensorMonitoring(const nlohmann::json& element);
 
 /**
+ * Parses a JSON element containing a SensorValueType expressed as a string.
+ *
+ * Returns the corresponding SensorValueType enum value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return SensorValueType enum value
+ */
+pmbus_utils::SensorValueType
+    parseSensorValueType(const nlohmann::json& element);
+
+/**
  * Parses a JSON element containing a set_device action.
  *
  * Returns the corresponding C++ SetDeviceAction object.
@@ -725,6 +765,18 @@
 }
 
 /**
+ * Parses a JSON element containing a VoutDataFormat expressed as a string.
+ *
+ * Returns the corresponding VoutDataFormat enum value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return VoutDataFormat enum value
+ */
+pmbus_utils::VoutDataFormat parseVoutDataFormat(const nlohmann::json& element);
+
+/**
  * Verifies that the specified JSON element is a JSON array.
  *
  * Throws an invalid_argument exception if the element is not an array.
diff --git a/phosphor-regulators/test/config_file_parser_tests.cpp b/phosphor-regulators/test/config_file_parser_tests.cpp
index c6db12d..80e65ba 100644
--- a/phosphor-regulators/test/config_file_parser_tests.cpp
+++ b/phosphor-regulators/test/config_file_parser_tests.cpp
@@ -31,6 +31,7 @@
 #include "i2c_write_bytes_action.hpp"
 #include "not_action.hpp"
 #include "or_action.hpp"
+#include "pmbus_read_sensor_action.hpp"
 #include "pmbus_utils.hpp"
 #include "pmbus_write_vout_command_action.hpp"
 #include "presence_detection.hpp"
@@ -419,7 +420,19 @@
     }
 
     // Test where works: pmbus_read_sensor action type specified
-    // TODO: Not implemented yet
+    {
+        const json element = R"(
+            {
+              "pmbus_read_sensor": {
+                "type": "iout",
+                "command": "0x8C",
+                "format": "linear_11"
+              }
+            }
+        )"_json;
+        std::unique_ptr<Action> action = parseAction(element);
+        EXPECT_NE(action.get(), nullptr);
+    }
 
     // Test where works: pmbus_write_vout_command action type specified
     {
@@ -3110,6 +3123,202 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParsePMBusReadSensor)
+{
+    // Test where works: Only required properties specified
+    {
+        const json element = R"(
+            {
+              "type": "iout",
+              "command": "0x8C",
+              "format": "linear_11"
+            }
+        )"_json;
+        std::unique_ptr<PMBusReadSensorAction> action =
+            parsePMBusReadSensor(element);
+        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);
+    }
+
+    // Test where works: All properties specified
+    {
+        const json element = R"(
+            {
+              "type": "temperature",
+              "command": "0x7A",
+              "format": "linear_16",
+              "exponent": -8
+            }
+        )"_json;
+        std::unique_ptr<PMBusReadSensorAction> action =
+            parsePMBusReadSensor(element);
+        EXPECT_EQ(action->getType(), pmbus_utils::SensorValueType::temperature);
+        EXPECT_EQ(action->getCommand(), 0x7A);
+        EXPECT_EQ(action->getFormat(),
+                  pmbus_utils::SensorDataFormat::linear_16);
+        EXPECT_EQ(action->getExponent().has_value(), true);
+        EXPECT_EQ(action->getExponent().value(), -8);
+    }
+
+    // Test where fails: Element is not an object
+    try
+    {
+        const json element = R"( [ "0xFF", "0x01" ] )"_json;
+        parsePMBusReadSensor(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an object");
+    }
+
+    // Test where fails: Invalid property specified
+    try
+    {
+        const json element = R"(
+            {
+              "type": "iout",
+              "command": "0x8C",
+              "format": "linear_11",
+              "foo": 1
+            }
+        )"_json;
+        parsePMBusReadSensor(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element contains an invalid property");
+    }
+
+    // Test where fails: Required type property not specified
+    try
+    {
+        const json element = R"(
+            {
+              "command": "0x8C",
+              "format": "linear_11"
+            }
+        )"_json;
+        parsePMBusReadSensor(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Required property missing: type");
+    }
+
+    // Test where fails: Required command property not specified
+    try
+    {
+        const json element = R"(
+            {
+              "type": "iout",
+              "format": "linear_11"
+            }
+        )"_json;
+        parsePMBusReadSensor(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Required property missing: command");
+    }
+
+    // Test where fails: Required format property not specified
+    try
+    {
+        const json element = R"(
+            {
+              "type": "iout",
+              "command": "0x8C"
+            }
+        )"_json;
+        parsePMBusReadSensor(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Required property missing: format");
+    }
+
+    // Test where fails: type value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "type": 1,
+              "command": "0x7A",
+              "format": "linear_16"
+            }
+        )"_json;
+        parsePMBusReadSensor(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a string");
+    }
+
+    // Test where fails: command value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "type": "temperature",
+              "command": 0,
+              "format": "linear_16"
+            }
+        )"_json;
+        parsePMBusReadSensor(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a string");
+    }
+
+    // Test where fails: format value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "type": "temperature",
+              "command": "0x7A",
+              "format": 1
+            }
+        )"_json;
+        parsePMBusReadSensor(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a string");
+    }
+
+    // Test where fails: exponent value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "type": "temperature",
+              "command": "0x7A",
+              "format": "linear_16",
+              "exponent": 1.3
+            }
+        )"_json;
+        parsePMBusReadSensor(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an integer");
+    }
+}
+
 TEST(ConfigFileParserTests, ParsePMBusWriteVoutCommand)
 {
     // Test where works: Only required properties specified
@@ -4021,6 +4230,51 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParseSensorDataFormat)
+{
+    // Test where works: linear_11
+    {
+        const json element = "linear_11";
+        pmbus_utils::SensorDataFormat value = parseSensorDataFormat(element);
+        pmbus_utils::SensorDataFormat format =
+            pmbus_utils::SensorDataFormat::linear_11;
+        EXPECT_EQ(value, format);
+    }
+
+    // Test where works: linear_16
+    {
+        const json element = "linear_16";
+        pmbus_utils::SensorDataFormat value = parseSensorDataFormat(element);
+        pmbus_utils::SensorDataFormat format =
+            pmbus_utils::SensorDataFormat::linear_16;
+        EXPECT_EQ(value, format);
+    }
+
+    // Test where fails: Element is not a sensor data format
+    try
+    {
+        const json element = "foo";
+        parseSensorDataFormat(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a sensor data format");
+    }
+
+    // Test where fails: Element is not a string
+    try
+    {
+        const json element = R"( { "foo": "bar" } )"_json;
+        parseSensorDataFormat(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a string");
+    }
+}
+
 TEST(ConfigFileParserTests, ParseSensorMonitoring)
 {
     // Test where works: actions property specified
@@ -4151,6 +4405,111 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParseSensorValueType)
+{
+    // Test where works: iout
+    {
+        const json element = "iout";
+        pmbus_utils::SensorValueType value = parseSensorValueType(element);
+        pmbus_utils::SensorValueType type = pmbus_utils::SensorValueType::iout;
+        EXPECT_EQ(value, type);
+    }
+
+    // Test where works: iout_peak
+    {
+        const json element = "iout_peak";
+        pmbus_utils::SensorValueType value = parseSensorValueType(element);
+        pmbus_utils::SensorValueType type =
+            pmbus_utils::SensorValueType::iout_peak;
+        EXPECT_EQ(value, type);
+    }
+
+    // Test where works: iout_valley
+    {
+        const json element = "iout_valley";
+        pmbus_utils::SensorValueType value = parseSensorValueType(element);
+        pmbus_utils::SensorValueType type =
+            pmbus_utils::SensorValueType::iout_valley;
+        EXPECT_EQ(value, type);
+    }
+
+    // Test where works: pout
+    {
+        const json element = "pout";
+        pmbus_utils::SensorValueType value = parseSensorValueType(element);
+        pmbus_utils::SensorValueType type = pmbus_utils::SensorValueType::pout;
+        EXPECT_EQ(value, type);
+    }
+
+    // Test where works: temperature
+    {
+        const json element = "temperature";
+        pmbus_utils::SensorValueType value = parseSensorValueType(element);
+        pmbus_utils::SensorValueType type =
+            pmbus_utils::SensorValueType::temperature;
+        EXPECT_EQ(value, type);
+    }
+
+    // Test where works: temperature_peak
+    {
+        const json element = "temperature_peak";
+        pmbus_utils::SensorValueType value = parseSensorValueType(element);
+        pmbus_utils::SensorValueType type =
+            pmbus_utils::SensorValueType::temperature_peak;
+        EXPECT_EQ(value, type);
+    }
+
+    // Test where works: vout
+    {
+        const json element = "vout";
+        pmbus_utils::SensorValueType value = parseSensorValueType(element);
+        pmbus_utils::SensorValueType type = pmbus_utils::SensorValueType::vout;
+        EXPECT_EQ(value, type);
+    }
+
+    // Test where works: vout_peak
+    {
+        const json element = "vout_peak";
+        pmbus_utils::SensorValueType value = parseSensorValueType(element);
+        pmbus_utils::SensorValueType type =
+            pmbus_utils::SensorValueType::vout_peak;
+        EXPECT_EQ(value, type);
+    }
+
+    // Test where works: vout_valley
+    {
+        const json element = "vout_valley";
+        pmbus_utils::SensorValueType value = parseSensorValueType(element);
+        pmbus_utils::SensorValueType type =
+            pmbus_utils::SensorValueType::vout_valley;
+        EXPECT_EQ(value, type);
+    }
+
+    // Test where fails: Element is not a sensor value type
+    try
+    {
+        const json element = "foo";
+        parseSensorValueType(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a sensor value type");
+    }
+
+    // Test where fails: Element is not a string
+    try
+    {
+        const json element = R"( { "foo": "bar" } )"_json;
+        parseSensorValueType(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a string");
+    }
+}
+
 TEST(ConfigFileParserTests, ParseSetDevice)
 {
     // Test where works
@@ -4313,6 +4672,67 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParseVoutDataFormat)
+{
+    // Test where works: linear
+    {
+        const json element = "linear";
+        pmbus_utils::VoutDataFormat value = parseVoutDataFormat(element);
+        pmbus_utils::VoutDataFormat format =
+            pmbus_utils::VoutDataFormat::linear;
+        EXPECT_EQ(value, format);
+    }
+
+    // Test where works: vid
+    {
+        const json element = "vid";
+        pmbus_utils::VoutDataFormat value = parseVoutDataFormat(element);
+        pmbus_utils::VoutDataFormat format = pmbus_utils::VoutDataFormat::vid;
+        EXPECT_EQ(value, format);
+    }
+
+    // Test where works: direct
+    {
+        const json element = "direct";
+        pmbus_utils::VoutDataFormat value = parseVoutDataFormat(element);
+        pmbus_utils::VoutDataFormat format =
+            pmbus_utils::VoutDataFormat::direct;
+        EXPECT_EQ(value, format);
+    }
+
+    // Test where works: ieee
+    {
+        const json element = "ieee";
+        pmbus_utils::VoutDataFormat value = parseVoutDataFormat(element);
+        pmbus_utils::VoutDataFormat format = pmbus_utils::VoutDataFormat::ieee;
+        EXPECT_EQ(value, format);
+    }
+
+    // Test where fails: Element is not a vout data format
+    try
+    {
+        const json element = "foo";
+        parseVoutDataFormat(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a vout data format");
+    }
+
+    // Test where fails: Element is not a string
+    try
+    {
+        const json element = R"( { "foo": "bar" } )"_json;
+        parseVoutDataFormat(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a string");
+    }
+}
+
 TEST(ConfigFileParserTests, VerifyIsArray)
 {
     // Test where element is an array