dbus-sdr: support Get Temperature Readings command

This commit support Get Temperature Readings command in the
dynamicsensor library.

The design follows teps:
- Get the list of sensors based on Entity Id
- Sort the list of sensors.
- Loop all of sensors in the list, if its Entity Instance is greater
than the Instance Start then read the temperature via object path.

Tested:
   1. Request to read Temperature of sensors
      "ipmitool dcmi get_temp_reading"
   2. Display Entity Id, Entity Instance, Temperature of sensors

Change-Id: Iaa7d65a763dacb50c1488d2a161b180fed49899b
Signed-off-by: Thang Tran <thuutran@amperecomputing.com>
diff --git a/dbus-sdr/sensorcommands.cpp b/dbus-sdr/sensorcommands.cpp
index 8c9d686..55ba6d9 100644
--- a/dbus-sdr/sensorcommands.cpp
+++ b/dbus-sdr/sensorcommands.cpp
@@ -2601,6 +2601,54 @@
     return std::make_tuple(totalInstSensor, sensorList);
 }
 
+std::tuple<bool,    // Reading result
+           uint7_t, // Temp value
+           bool>    // Sign bit
+    readTemp(ipmi::Context::ptr ctx, const std::string& objectPath)
+{
+    std::string service{};
+    boost::system::error_code ec =
+        ipmi::getService(ctx, sensor::sensorInterface, objectPath, service);
+    if (ec.value())
+    {
+        return std::make_tuple(false, 0, false);
+    }
+
+    ipmi::PropertyMap properties{};
+    ec = ipmi::getAllDbusProperties(ctx, service, objectPath,
+                                    sensor::sensorInterface, properties);
+    if (ec.value())
+    {
+        return std::make_tuple(false, 0, false);
+    }
+
+    auto scaleIt = properties.find("Scale");
+    double scaleVal = 0.0;
+    if (scaleIt != properties.end())
+    {
+        scaleVal = std::visit(ipmi::VariantToDoubleVisitor(), scaleIt->second);
+    }
+
+    auto tempValIt = properties.find("Value");
+    double tempVal = 0.0;
+    if (tempValIt == properties.end())
+    {
+        return std::make_tuple(false, 0, false);
+    }
+
+    const double maxTemp = 127;
+    double absTempVal = 0.0;
+    bool signBit = false;
+
+    tempVal = std::visit(ipmi::VariantToDoubleVisitor(), tempValIt->second);
+    tempVal = std::pow(10, scaleVal) * tempVal;
+    absTempVal = std::abs(tempVal);
+    absTempVal = std::min(absTempVal, maxTemp);
+    signBit = (tempVal < 0) ? true : false;
+
+    return std::make_tuple(true, static_cast<uint7_t>(absTempVal), signBit);
+}
+
 ipmi::RspType<uint8_t,              // No of instances for requested id
               uint8_t,              // No of record ids in the response
               std::vector<uint16_t> // SDR Record ID corresponding to the Entity
@@ -2654,6 +2702,73 @@
     return ipmi::responseSuccess(
         totalSensorInst, static_cast<uint8_t>(sensorRec.size()), sensorRec);
 }
+
+ipmi::RspType<uint8_t,                // No of instances for requested id
+              uint8_t,                // No of record ids in the response
+              std::vector<            // Temperature Data
+                  std::tuple<uint7_t, // Temperature value
+                             bool,    // Sign bit
+                             uint8_t  // Entity Instance of sensor
+                             >>>
+    getTempReadings(ipmi::Context::ptr ctx, uint8_t sensorType,
+                    uint8_t entityId, uint8_t entityInstance,
+                    uint8_t instanceStart)
+{
+    auto match = ipmi::dcmi::validEntityId.find(entityId);
+    if (match == ipmi::dcmi::validEntityId.end())
+    {
+        log<level::ERR>("Unknown Entity ID", entry("ENTITY_ID=%d", entityId));
+
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (sensorType != ipmi::dcmi::temperatureSensorType)
+    {
+        log<level::ERR>("Invalid sensor type",
+                        entry("SENSOR_TYPE=%d", sensorType));
+
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    std::vector<std::tuple<uint7_t, bool, uint8_t>> tempReadingVal{};
+    const auto& [totalSensorInst, sensorList] =
+        getSensorsByEntityId(ctx, entityId, entityInstance, instanceStart);
+
+    if (sensorList.empty())
+    {
+        return ipmi::responseSuccess(totalSensorInst, 0, tempReadingVal);
+    }
+
+    /*
+     * As DCMI specification, the maximum number of Record Ids of response data
+     * is 1 if Entity Instance paramter is not 0. Else the maximum number of
+     * Record Ids of response data is 8. Therefore, not all of sensors are shown
+     * in response data.
+     */
+    uint8_t numOfRec = (entityInstance != 0) ? 1 : ipmi::dcmi::maxRecords;
+
+    for (const auto& sensor : sensorList)
+    {
+        const auto& [readResult, tempVal,
+                     signBit] = readTemp(ctx, sensor.objectPath);
+
+        if (readResult)
+        {
+            tempReadingVal.emplace_back(
+                std::make_tuple(tempVal, signBit, sensor.entityInstance));
+
+            if (tempReadingVal.size() >= numOfRec)
+            {
+                break;
+            }
+        }
+    }
+
+    return ipmi::responseSuccess(totalSensorInst,
+                                 static_cast<uint8_t>(tempReadingVal.size()),
+                                 tempReadingVal);
+}
+
 } // namespace dcmi
 
 /* end storage commands */
@@ -2738,5 +2853,10 @@
                                ipmi::dcmi::cmdGetDcmiSensorInfo,
                                ipmi::Privilege::Operator,
                                ipmi::dcmi::getSensorInfo);
+    // <Get Temperature Readings>
+    ipmi::registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                               ipmi::dcmi::cmdGetTemperatureReadings,
+                               ipmi::Privilege::User,
+                               ipmi::dcmi::getTempReadings);
 }
 } // namespace ipmi
diff --git a/dcmihandler.cpp b/dcmihandler.cpp
index 07f1b63..da6fced 100644
--- a/dcmihandler.cpp
+++ b/dcmihandler.cpp
@@ -1193,11 +1193,6 @@
                          ipmi::dcmi::cmdGetDcmiCapabilitiesInfo,
                          ipmi::Privilege::User, getDCMICapabilities);
 
-    // <Get Temperature Readings>
-    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
-                         ipmi::dcmi::cmdGetTemperatureReadings,
-                         ipmi::Privilege::User, getTempReadings);
-
     // <Get Power Reading>
     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
                          ipmi::dcmi::cmdGetPowerReading, ipmi::Privilege::User,
@@ -1210,6 +1205,11 @@
     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
                          ipmi::dcmi::cmdGetDcmiSensorInfo,
                          ipmi::Privilege::Operator, getSensorInfo);
+
+    // <Get Temperature Readings>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdGetTemperatureReadings,
+                         ipmi::Privilege::User, getTempReadings);
 #endif
     // <Get DCMI Configuration Parameters>
     registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,