Make hwmon work with double or int64 interface

Deduce type based on interface using decltype
and if it is double apply scale to the value.

Tested-by: Noticed that on dbus using busctl and debug
server that doubles were produced and scaled.

Also verifed that hwmon still produced int64_t values when
building with current phosphor-dbus-interfaces.

Change-Id: I00e21d5ef0ea6cee0eb30baa0b39cde95e7f4a86
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/interface.hpp b/interface.hpp
index e8ec28e..4b157dc 100644
--- a/interface.hpp
+++ b/interface.hpp
@@ -29,6 +29,11 @@
     server::OperationalStatus;
 using StatusObject = ServerObject<StatusInterface>;
 
+// I understand this seems like magic, but since decltype doesn't evaluate you
+// can call nullptr https://stackoverflow.com/a/5580411/2784885
+using SensorValueType =
+    decltype((static_cast<ValueInterface*>(nullptr))->value());
+
 enum class InterfaceType
 {
     VALUE,
diff --git a/mainloop.cpp b/mainloop.cpp
index 3c6d232..81faa9b 100644
--- a/mainloop.cpp
+++ b/mainloop.cpp
@@ -214,10 +214,18 @@
 #endif
     }
     auto sensorValue = valueInterface->value();
-    addThreshold<WarningObject>(
-        sensor.first.first, std::get<sensorID>(properties), sensorValue, info);
-    addThreshold<CriticalObject>(
-        sensor.first.first, std::get<sensorID>(properties), sensorValue, info);
+    int64_t scale = 0;
+    // scale the thresholds only if we're using doubles
+    if constexpr (std::is_same<SensorValueType, double>::value)
+    {
+        scale = sensorObj->getScale();
+    }
+    addThreshold<WarningObject>(sensor.first.first,
+                                std::get<sensorID>(properties), sensorValue,
+                                info, scale);
+    addThreshold<CriticalObject>(sensor.first.first,
+                                 std::get<sensorID>(properties), sensorValue,
+                                 info, scale);
 
     auto target =
         addTarget<hwmon::FanSpeed>(sensor.first, ioAccess, _devPath, info);
diff --git a/sensor.cpp b/sensor.cpp
index 0a0971f..5c97e0c 100644
--- a/sensor.cpp
+++ b/sensor.cpp
@@ -8,6 +8,7 @@
 #include "sensorset.hpp"
 #include "sysfs.hpp"
 
+#include <cmath>
 #include <cstring>
 #include <experimental/filesystem>
 #include <phosphor-logging/elog-errors.hpp>
@@ -21,6 +22,19 @@
 using namespace phosphor::logging;
 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
 
+// todo: this can be deleted once we move to double
+// helper class to set the scale on the value iface only when it's available
+template <typename T>
+void setScale(T& iface, int64_t value, double)
+{
+}
+template <typename T>
+void setScale(T& iface, int64_t value, int64_t)
+{
+    iface->scale(value);
+}
+
+// todo: this can be simplified once we move to the double interface
 Sensor::Sensor(const SensorSet::key_type& sensor,
                const hwmonio::HwmonIO& ioAccess, const std::string& devPath) :
     sensor(sensor),
@@ -84,7 +98,7 @@
     }
 }
 
-int64_t Sensor::adjustValue(int64_t value)
+SensorValueType Sensor::adjustValue(SensorValueType value)
 {
 // Because read doesn't have an out pointer to store errors.
 // let's assume negative values are errors if they have this
@@ -100,6 +114,11 @@
     value = static_cast<decltype(value)>(
         static_cast<double>(value) * sensorAdjusts.gain + sensorAdjusts.offset);
 
+    if constexpr (std::is_same<SensorValueType, double>::value)
+    {
+        value *= std::pow(10, scale);
+    }
+
     return value;
 }
 
@@ -113,7 +132,7 @@
     auto& obj = std::get<Object>(info);
     auto& objPath = std::get<std::string>(info);
 
-    int64_t val = 0;
+    SensorValueType val = 0;
     std::shared_ptr<StatusObject> statusIface = nullptr;
     auto it = obj.find(InterfaceType::STATUS);
     if (it != obj.end())
@@ -145,7 +164,10 @@
     if (hwmon::getAttributes(sensor.first, attrs))
     {
         iface->unit(hwmon::getUnit(attrs));
-        iface->scale(hwmon::getScale(attrs));
+
+        setScale(iface, hwmon::getScale(attrs), val);
+
+        scale = hwmon::getScale(attrs);
     }
 
     auto maxValue = env::getEnv("MAXVALUE", sensor);
diff --git a/sensor.hpp b/sensor.hpp
index 3f38c33..37cf035 100644
--- a/sensor.hpp
+++ b/sensor.hpp
@@ -73,7 +73,7 @@
      *
      * @return - Adjusted sensor value
      */
-    int64_t adjustValue(int64_t value);
+    SensorValueType adjustValue(SensorValueType value);
 
     /**
      * @brief Add value interface and value property for sensor
@@ -113,6 +113,16 @@
      */
     void lockGpio();
 
+    /**
+     * @brief Get the scale from the sensor.
+     *
+     * @return - Scale value
+     */
+    inline int64_t getScale(void)
+    {
+        return scale;
+    }
+
   private:
     /** @brief Sensor object's identifiers */
     SensorSet::key_type sensor;
@@ -131,6 +141,9 @@
 
     /** @brief default pause after unlocking gpio. */
     static constexpr std::chrono::milliseconds pause{500};
+
+    /** @brief sensor scale from configuration. */
+    int64_t scale;
 };
 
 } // namespace sensor
diff --git a/thresholds.hpp b/thresholds.hpp
index 7e8dbf5..188dc76 100644
--- a/thresholds.hpp
+++ b/thresholds.hpp
@@ -1,6 +1,9 @@
 #pragma once
 
 #include "env.hpp"
+#include "interface.hpp"
+
+#include <cmath>
 
 /** @class Thresholds
  *  @brief Threshold type traits.
@@ -23,10 +26,10 @@
     static constexpr InterfaceType type = InterfaceType::WARN;
     static constexpr const char* envLo = "WARNLO";
     static constexpr const char* envHi = "WARNHI";
-    static int64_t (WarningObject::*const setLo)(int64_t);
-    static int64_t (WarningObject::*const setHi)(int64_t);
-    static int64_t (WarningObject::*const getLo)() const;
-    static int64_t (WarningObject::*const getHi)() const;
+    static SensorValueType (WarningObject::*const setLo)(SensorValueType);
+    static SensorValueType (WarningObject::*const setHi)(SensorValueType);
+    static SensorValueType (WarningObject::*const getLo)() const;
+    static SensorValueType (WarningObject::*const getHi)() const;
     static bool (WarningObject::*const alarmLo)(bool);
     static bool (WarningObject::*const alarmHi)(bool);
 };
@@ -38,10 +41,10 @@
     static constexpr InterfaceType type = InterfaceType::CRIT;
     static constexpr const char* envLo = "CRITLO";
     static constexpr const char* envHi = "CRITHI";
-    static int64_t (CriticalObject::*const setLo)(int64_t);
-    static int64_t (CriticalObject::*const setHi)(int64_t);
-    static int64_t (CriticalObject::*const getLo)() const;
-    static int64_t (CriticalObject::*const getHi)() const;
+    static SensorValueType (CriticalObject::*const setLo)(SensorValueType);
+    static SensorValueType (CriticalObject::*const setHi)(SensorValueType);
+    static SensorValueType (CriticalObject::*const getLo)() const;
+    static SensorValueType (CriticalObject::*const getHi)() const;
     static bool (CriticalObject::*const alarmLo)(bool);
     static bool (CriticalObject::*const alarmHi)(bool);
 };
@@ -57,7 +60,7 @@
  *  @param[in] value - The sensor reading to compare to thresholds.
  */
 template <typename T>
-void checkThresholds(std::any& iface, int64_t value)
+void checkThresholds(std::any& iface, SensorValueType value)
 {
     auto realIface = std::any_cast<std::shared_ptr<T>>(iface);
     auto lo = (*realIface.*Thresholds<T>::getLo)();
@@ -80,7 +83,7 @@
  */
 template <typename T>
 auto addThreshold(const std::string& sensorType, const std::string& sensorID,
-                  int64_t value, ObjectInfo& info)
+                  int64_t value, ObjectInfo& info, int64_t scale)
 {
     auto& objPath = std::get<std::string>(info);
     auto& obj = std::get<Object>(info);
@@ -94,8 +97,8 @@
         auto& bus = *std::get<sdbusplus::bus::bus*>(info);
 
         iface = std::make_shared<T>(bus, objPath.c_str(), deferSignals);
-        auto lo = stoll(tLo);
-        auto hi = stoll(tHi);
+        auto lo = stod(tLo) * std::pow(10, scale);
+        auto hi = stod(tHi) * std::pow(10, scale);
         (*iface.*Thresholds<T>::setLo)(lo);
         (*iface.*Thresholds<T>::setHi)(hi);
         (*iface.*Thresholds<T>::alarmLo)(value <= lo);