Add fan presence support

Async montitor GPIO to get fan presence.

Tested-by: Pulled every fan on a 2U and monitored
presence.

Change-Id: Ibad2251e5c6a0ce480812051c103785f79fa60bb
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/sensors/CMakeLists.txt b/sensors/CMakeLists.txt
index 55db096..a1c80e4 100644
--- a/sensors/CMakeLists.txt
+++ b/sensors/CMakeLists.txt
@@ -1,7 +1,7 @@
 cmake_minimum_required (VERSION 2.8.10 FATAL_ERROR)
 set (BUILD_SHARED_LIBRARIES OFF)
 include (ExternalProject)
-set (CMAKE_CXX_STANDARD 14)
+set (CMAKE_CXX_STANDARD 17)
 set (CMAKE_CXX_STANDARD_REQUIRED ON)
 set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -lstdc++fs")
 set (CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
diff --git a/sensors/include/TachSensor.hpp b/sensors/include/TachSensor.hpp
index 33d2da0..2dc6216 100644
--- a/sensors/include/TachSensor.hpp
+++ b/sensors/include/TachSensor.hpp
@@ -4,6 +4,25 @@
 #include <sdbusplus/asio/object_server.hpp>
 #include <sensor.hpp>
 
+constexpr const char *gpioPath = "/sys/class/gpio/";
+class PresenceSensor
+{
+
+  public:
+    PresenceSensor(const size_t index, bool inverted,
+                   boost::asio::io_service &io);
+    ~PresenceSensor();
+
+    void monitorPresence(void);
+    void read(void);
+    bool getValue(void);
+
+  private:
+    bool status = true;
+    bool inverted;
+    boost::asio::ip::tcp::socket inputDev;
+    int fd;
+};
 class TachSensor : public Sensor
 {
   public:
@@ -12,6 +31,7 @@
     TachSensor(const std::string &path,
                sdbusplus::asio::object_server &objectServer,
                std::shared_ptr<sdbusplus::asio::connection> &conn,
+               std::unique_ptr<PresenceSensor> &&presence,
                boost::asio::io_service &io, const std::string &fanName,
                std::vector<thresholds::Threshold> &&thresholds,
                const std::string &sensorConfiguration);
@@ -21,6 +41,7 @@
     std::string path;
     sdbusplus::asio::object_server &objServer;
     std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
+    std::unique_ptr<PresenceSensor> presence;
     boost::asio::posix::stream_descriptor inputDev;
     boost::asio::deadline_timer waitTimer;
     boost::asio::streambuf readBuf;
diff --git a/sensors/src/FanMain.cpp b/sensors/src/FanMain.cpp
index 4fa9ff2..542ca78 100644
--- a/sensors/src/FanMain.cpp
+++ b/sensors/src/FanMain.cpp
@@ -213,8 +213,35 @@
                       << "\n";
         }
 
+        auto presenceConfig =
+            sensorData->find(baseType + std::string(".Presence"));
+
+        std::unique_ptr<PresenceSensor> presenceSensor(nullptr);
+
+        // presence sensors are optional
+        if (presenceConfig != sensorData->end())
+        {
+            auto findIndex = presenceConfig->second.find("Index");
+            auto findPolarity = presenceConfig->second.find("Polarity");
+
+            if (findIndex == presenceConfig->second.end() ||
+                findPolarity == presenceConfig->second.end())
+            {
+                std::cerr << "Malformed Presence Configuration\n";
+            }
+            else
+            {
+                size_t index = variant_ns::get<uint64_t>(findIndex->second);
+                bool inverted =
+                    variant_ns::get<std::string>(findPolarity->second) == "Low";
+                presenceSensor =
+                    std::make_unique<PresenceSensor>(index, inverted, io);
+            }
+        }
+
         tachSensors[sensorName] = std::make_unique<TachSensor>(
-            path.string(), objectServer, dbusConnection, io, sensorName,
+            path.string(), objectServer, dbusConnection,
+            std::move(presenceSensor), io, sensorName,
             std::move(sensorThresholds), *interfacePath);
     }
     std::vector<fs::path> pwms;
diff --git a/sensors/src/TachSensor.cpp b/sensors/src/TachSensor.cpp
index e4ff3fd..f7c6fc0 100644
--- a/sensors/src/TachSensor.cpp
+++ b/sensors/src/TachSensor.cpp
@@ -21,6 +21,7 @@
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
+#include <fstream>
 #include <iostream>
 #include <limits>
 #include <sdbusplus/asio/connection.hpp>
@@ -33,11 +34,13 @@
 TachSensor::TachSensor(const std::string &path,
                        sdbusplus::asio::object_server &objectServer,
                        std::shared_ptr<sdbusplus::asio::connection> &conn,
+                       std::unique_ptr<PresenceSensor> &&presence,
                        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, " ", "_")),
     configuration(sensorConfiguration),
     inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), errCount(0),
@@ -90,46 +93,59 @@
     {
         return; // we're being destroyed
     }
+    bool missing = false;
+    if (presence)
+    {
+        if (!presence->getValue())
+        {
+            sensorInterface->set_property(
+                "Value", std::numeric_limits<double>::quiet_NaN());
+            missing = true;
+        }
+    }
     std::istream responseStream(&readBuf);
-    if (!err)
+    if (!missing)
     {
-        std::string response;
-        try
+        if (!err)
         {
-            std::getline(responseStream, response);
-            float nvalue = std::stof(response);
-            responseStream.clear();
-            if (nvalue != value)
+            std::string response;
+            try
             {
-                updateValue(nvalue);
+                std::getline(responseStream, response);
+                float nvalue = std::stof(response);
+                responseStream.clear();
+                if (nvalue != value)
+                {
+                    updateValue(nvalue);
+                }
+                errCount = 0;
             }
-            errCount = 0;
-        }
-        catch (const std::invalid_argument &)
-        {
-            errCount++;
-        }
-    }
-    else
-    {
-
-        errCount++;
-    }
-    // only send value update once
-    if (errCount == warnAfterErrorCount)
-    {
-        // only an error if power is on
-        if (isPowerOn(dbusConnection))
-        {
-            std::cerr << "Failure to read sensor " << name << " at " << path
-                      << "\n";
-            updateValue(0);
+            catch (const std::invalid_argument &)
+            {
+                errCount++;
+            }
         }
         else
         {
-            errCount = 0; // check power again in 10 cycles
-            sensorInterface->set_property(
-                "Value", std::numeric_limits<double>::quiet_NaN());
+
+            errCount++;
+        }
+        // only send value update once
+        if (errCount == warnAfterErrorCount)
+        {
+            // only an error if power is on
+            if (isPowerOn(dbusConnection))
+            {
+                std::cerr << "Failure to read sensor " << name << " at " << path
+                          << "\n";
+                updateValue(0);
+            }
+            else
+            {
+                errCount = 0; // check power again in 10 cycles
+                sensorInterface->set_property(
+                    "Value", std::numeric_limits<double>::quiet_NaN());
+            }
         }
     }
     responseStream.clear();
@@ -140,7 +156,12 @@
         return; // we're no longer valid
     }
     inputDev.assign(fd);
-    waitTimer.expires_from_now(boost::posix_time::milliseconds(pwmPollMs));
+    size_t pollTime = pwmPollMs;
+    if (missing)
+    {
+        pollTime *= 10;
+    }
+    waitTimer.expires_from_now(boost::posix_time::milliseconds(pollTime));
     waitTimer.async_wait([&](const boost::system::error_code &ec) {
         if (ec == boost::asio::error::operation_aborted)
         {
@@ -240,3 +261,85 @@
         std::cerr << "error initializing critical threshold interface\n";
     }
 }
+
+PresenceSensor::PresenceSensor(const size_t index, bool inverted,
+                               boost::asio::io_service &io) :
+    inverted(inverted),
+    inputDev(io)
+{
+    // todo: use gpiodaemon
+    std::string device = gpioPath + std::string("gpio") + std::to_string(index);
+    fd = open((device + "/value").c_str(), O_RDONLY);
+    if (fd < 0)
+    {
+        std::cerr << "Error opening gpio " << index << "\n";
+        return;
+    }
+
+    std::ofstream deviceFile(device + "/edge");
+    if (!deviceFile.good())
+    {
+        std::cerr << "Error setting edge " << device << "\n";
+        return;
+    }
+    deviceFile << "both";
+    deviceFile.close();
+
+    inputDev.assign(boost::asio::ip::tcp::v4(), fd);
+    monitorPresence();
+    read();
+}
+
+PresenceSensor::~PresenceSensor()
+{
+    inputDev.close();
+    close(fd);
+}
+
+void PresenceSensor::monitorPresence(void)
+{
+    inputDev.async_wait(boost::asio::ip::tcp::socket::wait_error,
+                        [this](const boost::system::error_code &ec) {
+                            if (ec == boost::system::errc::bad_file_descriptor)
+                            {
+                                return; // we're being destroyed
+                            }
+                            else if (ec)
+                            {
+                                std::cerr
+                                    << "Error on presence sensor socket\n";
+                            }
+                            else
+                            {
+                                read();
+                            }
+                            monitorPresence();
+                        });
+}
+
+void PresenceSensor::read(void)
+{
+    constexpr size_t readSize = sizeof("0");
+    std::string readBuf;
+    readBuf.resize(readSize);
+    lseek(fd, 0, SEEK_SET);
+    size_t r = ::read(fd, readBuf.data(), readSize);
+    if (r != 1)
+    {
+        std::cerr << "Error reading gpio\n";
+    }
+    else
+    {
+        bool value = std::stoi(readBuf);
+        if (inverted)
+        {
+            value = !value;
+        }
+        status = value;
+    }
+}
+
+bool PresenceSensor::getValue(void)
+{
+    return status;
+}
\ No newline at end of file