Add redundancy sensor and cleanup

Add fan redundancy sensor and move some class members
into the base class. Asio now protects against setting
the same number twice, allow it now to simplify code.

Change-Id: Idb6b5ff4746da92be62c4756fe442d5a5ed23f4f
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/sensors/include/ADCSensor.hpp b/sensors/include/ADCSensor.hpp
index e15ba0a..59b4450 100644
--- a/sensors/include/ADCSensor.hpp
+++ b/sensors/include/ADCSensor.hpp
@@ -7,7 +7,6 @@
 class ADCSensor : public Sensor
 {
   public:
-    std::string name;
     std::string configuration;
     ADCSensor(const std::string &path,
               sdbusplus::asio::object_server &objectServer,
@@ -18,7 +17,6 @@
     ~ADCSensor();
 
   private:
-    std::string path;
     sdbusplus::asio::object_server &objServer;
     boost::asio::posix::stream_descriptor inputDev;
     boost::asio::deadline_timer waitTimer;
diff --git a/sensors/include/CPUSensor.hpp b/sensors/include/CPUSensor.hpp
index c4fca5d..41cc02f 100644
--- a/sensors/include/CPUSensor.hpp
+++ b/sensors/include/CPUSensor.hpp
@@ -7,7 +7,6 @@
 class CPUSensor : public Sensor
 {
   public:
-    std::string name;
     std::string configuration;
     CPUSensor(const std::string &path, const std::string &objectType,
               sdbusplus::asio::object_server &objectServer,
@@ -20,7 +19,6 @@
     static constexpr unsigned int sensorPollMs = 1000;
 
   private:
-    std::string path;
     std::string objectType;
     sdbusplus::asio::object_server &objServer;
     std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
diff --git a/sensors/include/HwmonTempSensor.hpp b/sensors/include/HwmonTempSensor.hpp
index 14cb6c7..117f286 100644
--- a/sensors/include/HwmonTempSensor.hpp
+++ b/sensors/include/HwmonTempSensor.hpp
@@ -7,7 +7,6 @@
 class HwmonTempSensor : public Sensor
 {
   public:
-    std::string name;
     std::string configuration;
     HwmonTempSensor(const std::string &path, const std::string &objectType,
                     sdbusplus::asio::object_server &objectServer,
@@ -18,7 +17,6 @@
     ~HwmonTempSensor();
 
   private:
-    std::string path;
     std::string objectType;
     sdbusplus::asio::object_server &objServer;
     boost::asio::posix::stream_descriptor inputDev;
diff --git a/sensors/include/TachSensor.hpp b/sensors/include/TachSensor.hpp
index 2dc6216..36e3eaa 100644
--- a/sensors/include/TachSensor.hpp
+++ b/sensors/include/TachSensor.hpp
@@ -1,6 +1,8 @@
 #pragma once
 
 #include <Thresholds.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
 #include <sdbusplus/asio/object_server.hpp>
 #include <sensor.hpp>
 
@@ -23,23 +25,40 @@
     boost::asio::ip::tcp::socket inputDev;
     int fd;
 };
+
+class RedundancySensor
+{
+  public:
+    RedundancySensor(size_t count, const std::vector<std::string> &children,
+                     sdbusplus::asio::object_server &objectServer);
+    ~RedundancySensor();
+
+    void update(const std::string &name, bool failed);
+
+  private:
+    size_t count;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> iface;
+    sdbusplus::asio::object_server &objectServer;
+    boost::container::flat_map<std::string, bool> statuses;
+};
+
 class TachSensor : public Sensor
 {
   public:
-    std::string name;
     std::string configuration;
     TachSensor(const std::string &path,
                sdbusplus::asio::object_server &objectServer,
                std::shared_ptr<sdbusplus::asio::connection> &conn,
                std::unique_ptr<PresenceSensor> &&presence,
+               std::unique_ptr<RedundancySensor> &redundancy,
                boost::asio::io_service &io, const std::string &fanName,
                std::vector<thresholds::Threshold> &&thresholds,
                const std::string &sensorConfiguration);
     ~TachSensor();
 
   private:
-    std::string path;
     sdbusplus::asio::object_server &objServer;
+    std::unique_ptr<RedundancySensor> &redundancy;
     std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
     std::unique_ptr<PresenceSensor> presence;
     boost::asio::posix::stream_descriptor inputDev;
diff --git a/sensors/include/Thresholds.hpp b/sensors/include/Thresholds.hpp
index 4a07ca2..3f9aa79 100644
--- a/sensors/include/Thresholds.hpp
+++ b/sensors/include/Thresholds.hpp
@@ -48,7 +48,8 @@
                       const thresholds::Threshold &threshold,
                       std::shared_ptr<sdbusplus::asio::connection> &conn);
 
-void checkThresholds(Sensor *sensor);
+// returns false if a critical threshold has been crossed, true otherwise
+bool checkThresholds(Sensor *sensor);
 void assertThresholds(Sensor *sensor, thresholds::Level level,
                       thresholds::Direction direction, bool assert);
 } // namespace thresholds
diff --git a/sensors/include/sensor.hpp b/sensors/include/sensor.hpp
index a19b969..dc9cdbe 100644
--- a/sensors/include/sensor.hpp
+++ b/sensors/include/sensor.hpp
@@ -6,7 +6,15 @@
 constexpr size_t sensorFailedPollTimeMs = 5000;
 struct Sensor
 {
+    Sensor(const std::string& name, const std::string& path,
+           std::vector<thresholds::Threshold>&& thresholdData) :
+        name(name),
+        path(path), thresholds(std::move(thresholdData))
+    {
+    }
     virtual ~Sensor() = default;
+    std::string name;
+    std::string path;
     std::vector<thresholds::Threshold> thresholds;
     std::shared_ptr<sdbusplus::asio::dbus_interface> sensorInterface;
     std::shared_ptr<sdbusplus::asio::dbus_interface> thresholdInterfaceWarning;
diff --git a/sensors/src/ADCSensor.cpp b/sensors/src/ADCSensor.cpp
index 0955994..09c62cf 100644
--- a/sensors/src/ADCSensor.cpp
+++ b/sensors/src/ADCSensor.cpp
@@ -39,15 +39,14 @@
                      std::vector<thresholds::Threshold> &&_thresholds,
                      const double scaleFactor,
                      const std::string &sensorConfiguration) :
-    Sensor(),
-    path(path), objServer(objectServer), configuration(sensorConfiguration),
-    name(boost::replace_all_copy(sensorName, " ", "_")),
+    Sensor(boost::replace_all_copy(sensorName, " ", "_"), path,
+           std::move(_thresholds)),
+    objServer(objectServer), configuration(sensorConfiguration),
     scaleFactor(scaleFactor), inputDev(io, open(path.c_str(), O_RDONLY)),
     waitTimer(io), errCount(0),
     // todo, get these from config
     maxValue(20), minValue(0)
 {
-    thresholds = std::move(_thresholds);
     sensorInterface = objectServer.add_interface(
         "/xyz/openbmc_project/sensors/voltage/" + name,
         "xyz.openbmc_project.Sensor.Value");
@@ -118,15 +117,18 @@
     }
     else
     {
-        std::cerr << "Failure to read sensor " << name << " at " << path
-                  << " ec:" << err << "\n";
 
         errCount++;
     }
-
-    // only send value update once
+    // only print once
     if (errCount == warnAfterErrorCount)
     {
+        std::cerr << "Failure to read sensor " << name << " at " << path
+                  << " ec:" << err << "\n";
+    }
+
+    if (errCount >= warnAfterErrorCount)
+    {
         updateValue(0);
     }
 
diff --git a/sensors/src/CPUSensor.cpp b/sensors/src/CPUSensor.cpp
index 236dd81..eda9a3b 100644
--- a/sensors/src/CPUSensor.cpp
+++ b/sensors/src/CPUSensor.cpp
@@ -35,16 +35,15 @@
                      boost::asio::io_service &io, const std::string &sensorName,
                      std::vector<thresholds::Threshold> &&_thresholds,
                      const std::string &sensorConfiguration) :
-    Sensor(),
-    path(path), objectType(objectType), objServer(objectServer),
-    name(boost::replace_all_copy(sensorName, " ", "_")), dbusConnection(conn),
+    Sensor(boost::replace_all_copy(sensorName, " ", "_"), path,
+           std::move(_thresholds)),
+    objectType(objectType), objServer(objectServer), dbusConnection(conn),
     configuration(sensorConfiguration),
 
     inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), errCount(0),
     // todo, get these from config
     maxValue(127), minValue(-128)
 {
-    thresholds = std::move(_thresholds);
     sensorInterface = objectServer.add_interface(
         "/xyz/openbmc_project/sensors/temperature/" + name,
         "xyz.openbmc_project.Sensor.Value");
@@ -117,14 +116,17 @@
         errCount++;
     }
 
-    // only send value update once
-    if (errCount == warnAfterErrorCount)
+    if (errCount >= warnAfterErrorCount)
     {
         // only an error if power is on
         if (isPowerOn(dbusConnection))
         {
-            std::cerr << "Failure to read sensor " << name << " at " << path
-                      << "\n";
+            // only print once
+            if (errCount == warnAfterErrorCount)
+            {
+                std::cerr << "Failure to read sensor " << name << " at " << path
+                          << "\n";
+            }
             updateValue(0);
             errCount++;
         }
diff --git a/sensors/src/FanMain.cpp b/sensors/src/FanMain.cpp
index 542ca78..8401343 100644
--- a/sensors/src/FanMain.cpp
+++ b/sensors/src/FanMain.cpp
@@ -34,8 +34,13 @@
 namespace variant_ns = sdbusplus::message::variant_ns;
 static constexpr std::array<const char*, 1> sensorTypes = {
     "xyz.openbmc_project.Configuration.AspeedFan"};
+constexpr const char* redundancyConfiguration =
+    "xyz.openbmc_project.Configuration.FanRedundancy";
 static std::regex inputRegex(R"(fan(\d+)_input)");
 
+// todo: power supply fan redundancy
+std::unique_ptr<RedundancySensor> systemRedundancy = nullptr;
+
 void createSensors(
     boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
     boost::container::flat_map<std::string, std::unique_ptr<TachSensor>>&
@@ -241,7 +246,7 @@
 
         tachSensors[sensorName] = std::make_unique<TachSensor>(
             path.string(), objectServer, dbusConnection,
-            std::move(presenceSensor), io, sensorName,
+            std::move(presenceSensor), systemRedundancy, io, sensorName,
             std::move(sensorThresholds), *interfacePath);
     }
     std::vector<fs::path> pwms;
@@ -259,6 +264,56 @@
     }
 }
 
+void createRedundancySensor(
+    const boost::container::flat_map<std::string, std::unique_ptr<TachSensor>>&
+        sensors,
+    std::shared_ptr<sdbusplus::asio::connection> conn,
+    sdbusplus::asio::object_server& objectServer)
+{
+
+    conn->async_method_call(
+        [&objectServer, &sensors](boost::system::error_code& ec,
+                                  const ManagedObjectType managedObj) {
+            if (ec)
+            {
+                std::cerr << "Error calling entity manager \n";
+                return;
+            }
+            for (const auto& pathPair : managedObj)
+            {
+                for (const auto& interfacePair : pathPair.second)
+                {
+                    if (interfacePair.first == redundancyConfiguration)
+                    {
+                        // currently only support one
+                        auto findCount =
+                            interfacePair.second.find("AllowedFailures");
+                        if (findCount == interfacePair.second.end())
+                        {
+                            std::cerr << "Malformed redundancy record \n";
+                            return;
+                        }
+                        std::vector<std::string> sensorList;
+
+                        for (const auto& sensor : sensors)
+                        {
+                            sensorList.push_back(
+                                "/xyz/openbmc_project/sensors/fan_tach/" +
+                                sensor.second->name);
+                        }
+                        systemRedundancy = std::make_unique<RedundancySensor>(
+                            variant_ns::get<uint64_t>(findCount->second),
+                            sensorList, objectServer);
+
+                        return;
+                    }
+                }
+            }
+        },
+        "xyz.openbmc_project.EntityManager", "/",
+        "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+}
+
 int main(int argc, char** argv)
 {
     boost::asio::io_service io;
@@ -276,6 +331,7 @@
     io.post([&]() {
         createSensors(io, objectServer, tachSensors, pwmSensors, systemBus,
                       nullptr);
+        createRedundancySensor(tachSensors, systemBus, objectServer);
     });
 
     boost::asio::deadline_timer filterTimer(io);
@@ -316,5 +372,19 @@
         matches.emplace_back(std::move(match));
     }
 
+    // redundancy sensor
+    std::function<void(sdbusplus::message::message&)> redundancyHandler =
+        [&tachSensors, &systemBus,
+         &objectServer](sdbusplus::message::message& message) {
+            createRedundancySensor(tachSensors, systemBus, objectServer);
+        };
+    auto match = std::make_unique<sdbusplus::bus::match::match>(
+        static_cast<sdbusplus::bus::bus&>(*systemBus),
+        "type='signal',member='PropertiesChanged',path_namespace='" +
+            std::string(inventoryPath) + "',arg0namespace='" +
+            redundancyConfiguration + "'",
+        redundancyHandler);
+    matches.emplace_back(std::move(match));
+
     io.run();
 }
diff --git a/sensors/src/HwmonTempSensor.cpp b/sensors/src/HwmonTempSensor.cpp
index 1d58f07..8510563 100644
--- a/sensors/src/HwmonTempSensor.cpp
+++ b/sensors/src/HwmonTempSensor.cpp
@@ -37,15 +37,14 @@
     boost::asio::io_service &io, const std::string &sensorName,
     std::vector<thresholds::Threshold> &&_thresholds,
     const std::string &sensorConfiguration) :
-    Sensor(),
-    path(path), objectType(objectType), configuration(sensorConfiguration),
-    objServer(objectServer),
-    name(boost::replace_all_copy(sensorName, " ", "_")),
-    inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), errCount(0),
+    Sensor(boost::replace_all_copy(sensorName, " ", "_"), path,
+           std::move(_thresholds)),
+    objectType(objectType), configuration(sensorConfiguration),
+    objServer(objectServer), inputDev(io, open(path.c_str(), O_RDONLY)),
+    waitTimer(io), errCount(0),
     // todo, get these from config
     maxValue(127), minValue(-128)
 {
-    thresholds = std::move(_thresholds);
     sensorInterface = objectServer.add_interface(
         "/xyz/openbmc_project/sensors/temperature/" + name,
         "xyz.openbmc_project.Sensor.Value");
@@ -113,13 +112,18 @@
     }
     else
     {
-        std::cerr << "Failure to read sensor " << name << " at " << path
-                  << "\n";
         errCount++;
     }
-    // only send value update once
+
+    // only print once
     if (errCount == warnAfterErrorCount)
     {
+        std::cerr << "Failure to read sensor " << name << " at " << path
+                  << " ec:" << err << "\n";
+    }
+
+    if (errCount >= warnAfterErrorCount)
+    {
         updateValue(0);
     }
     responseStream.clear();
diff --git a/sensors/src/TachSensor.cpp b/sensors/src/TachSensor.cpp
index 4398f61..0f441cd 100644
--- a/sensors/src/TachSensor.cpp
+++ b/sensors/src/TachSensor.cpp
@@ -35,19 +35,19 @@
                        sdbusplus::asio::object_server &objectServer,
                        std::shared_ptr<sdbusplus::asio::connection> &conn,
                        std::unique_ptr<PresenceSensor> &&presence,
+                       std::unique_ptr<RedundancySensor> &redundancy,
                        boost::asio::io_service &io, const std::string &fanName,
                        std::vector<thresholds::Threshold> &&_thresholds,
                        const std::string &sensorConfiguration) :
-    Sensor(),
-    path(path), objServer(objectServer), dbusConnection(conn),
-    presence(std::move(presence)),
-    name(boost::replace_all_copy(fanName, " ", "_")),
+    Sensor(boost::replace_all_copy(fanName, " ", "_"), path,
+           std::move(_thresholds)),
+    objServer(objectServer), dbusConnection(conn),
+    presence(std::move(presence)), redundancy(redundancy),
     configuration(sensorConfiguration),
     inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), errCount(0),
     // todo, get these from config
     maxValue(25000), minValue(0)
 {
-    thresholds = std::move(_thresholds);
     sensorInterface = objectServer.add_interface(
         "/xyz/openbmc_project/sensors/fan_tach/" + name,
         "xyz.openbmc_project.Sensor.Value");
@@ -131,14 +131,17 @@
             pollTime = sensorFailedPollTimeMs;
             errCount++;
         }
-        // only send value update once
-        if (errCount == warnAfterErrorCount)
+        if (errCount >= warnAfterErrorCount)
         {
             // only an error if power is on
             if (isPowerOn(dbusConnection))
             {
-                std::cerr << "Failure to read sensor " << name << " at " << path
-                          << "\n";
+                // only print once
+                if (errCount == warnAfterErrorCount)
+                {
+                    std::cerr << "Failure to read sensor " << name << " at "
+                              << path << " ec:" << err << "\n";
+                }
                 updateValue(0);
             }
             else
@@ -168,7 +171,12 @@
 
 void TachSensor::checkThresholds(void)
 {
-    thresholds::checkThresholds(this);
+    bool status = thresholds::checkThresholds(this);
+    if (redundancy)
+    {
+        redundancy->update("/xyz/openbmc_project/sensors/fan_tach/" + name,
+                           !status);
+    }
 }
 
 void TachSensor::updateValue(const double &newValue)
@@ -337,4 +345,47 @@
 bool PresenceSensor::getValue(void)
 {
     return status;
-}
\ No newline at end of file
+}
+
+RedundancySensor::RedundancySensor(
+    size_t count, const std::vector<std::string> &children,
+    sdbusplus::asio::object_server &objectServer) :
+    count(count),
+    iface(objectServer.add_interface(
+        "/xyz/openbmc_project/control/FanRedundancy/Tach",
+        "xyz.openbmc_project.control.FanRedundancy")),
+    objectServer(objectServer)
+{
+    iface->register_property("Collection", children);
+    iface->register_property("Status", std::string("Full"));
+    iface->register_property("AllowedFailures", static_cast<uint8_t>(count));
+    iface->initialize();
+}
+RedundancySensor::~RedundancySensor()
+{
+    objectServer.remove_interface(iface);
+}
+void RedundancySensor::update(const std::string &name, bool failed)
+{
+    statuses[name] = failed;
+    size_t failedCount = 0;
+
+    std::string state = "Full";
+    for (const auto &status : statuses)
+    {
+        if (status.second)
+        {
+            failedCount++;
+        }
+        if (failedCount > count)
+        {
+            state = "Failed";
+            break;
+        }
+        else if (failedCount)
+        {
+            state = "Degraded";
+        }
+    }
+    iface->set_property("Status", state);
+}
diff --git a/sensors/src/Thresholds.cpp b/sensors/src/Thresholds.cpp
index ee4f7d0..10b1674 100644
--- a/sensors/src/Thresholds.cpp
+++ b/sensors/src/Thresholds.cpp
@@ -2,6 +2,7 @@
 #include <VariantVisitors.hpp>
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/lexical_cast.hpp>
+#include <cmath>
 #include <fstream>
 #include <iostream>
 #include <sensor.hpp>
@@ -163,16 +164,21 @@
     }
 }
 
-void checkThresholds(Sensor *sensor)
+bool checkThresholds(Sensor *sensor)
 {
+    bool status = true;
 
     if (sensor->thresholds.empty())
     {
-        return;
+        return true;
     }
     for (auto &threshold : sensor->thresholds)
     {
-        if (threshold.direction == thresholds::Direction::HIGH)
+        if (std::isnan(sensor->value))
+        {
+            threshold.asserted = false;
+        }
+        else if (threshold.direction == thresholds::Direction::HIGH)
         {
             if (sensor->value > threshold.value && !threshold.asserted)
             {
@@ -202,7 +208,13 @@
                 threshold.asserted = false;
             }
         }
+        if (threshold.level == thresholds::Level::CRITICAL &&
+            threshold.asserted)
+        {
+            status = false;
+        }
     }
+    return status;
 }
 
 void assertThresholds(Sensor *sensor, thresholds::Level level,
diff --git a/sensors/src/Utils.cpp b/sensors/src/Utils.cpp
index 768f708..a5685d5 100644
--- a/sensors/src/Utils.cpp
+++ b/sensors/src/Utils.cpp
@@ -152,7 +152,7 @@
             powerStatusOn = sdbusplus::message::variant_ns::get<int32_t>(pgood);
         },
         powerInterfaceName, powerObjectName, "org.freedesktop.DBus.Properties",
-        "Get", "pgood");
+        "Get", powerInterfaceName, "pgood");
 
     return powerStatusOn;
 }
\ No newline at end of file