Add support for GPIO polling for presence detection

The current PresenceGpio implementation uses event driven detection by
using the gpioLine.event_read method to detect GPIO signal changes, and
then to determine the presence status.

This commit will add support for a PollingPresenceGpio for GPIOs that
do not support events. It will use a pollTimer to periodically read the
GPIO status (every second).

The monitorPresence() function should be called to initiate the
monitoring of the GPIO.

TEST: Tested with multiple GPIOs including disabling to simulate
removing of device and enable to re-detect device. Unable to test Event
driven GPIO due to no hw available.

Change-Id: If46e884ad237dfe909a9373773c8302a0844ac90
Signed-off-by: Chris Cain <cjcain@us.ibm.com>
diff --git a/src/fan/PresenceGpio.cpp b/src/fan/PresenceGpio.cpp
index 6c528d7..d917c82 100644
--- a/src/fan/PresenceGpio.cpp
+++ b/src/fan/PresenceGpio.cpp
@@ -20,20 +20,36 @@
 #include <boost/asio/posix/stream_descriptor.hpp>
 #include <gpiod.hpp>
 
+#include <chrono>
 #include <iostream>
 #include <memory>
 #include <stdexcept>
 #include <string>
 #include <system_error>
 
+static constexpr unsigned int pollIntervalSec = 1;
+
+PresenceGpio::PresenceGpio(const std::string& deviceType,
+                           const std::string& deviceName,
+                           const std::string& gpioName) :
+    deviceType(deviceType), deviceName(deviceName), gpioName(gpioName)
+{
+    gpioLine = gpiod::find_line(gpioName);
+    if (!gpioLine)
+    {
+        std::cerr << "Error requesting gpio: " << gpioName << "\n";
+        throw std::runtime_error("Failed to find GPIO " + gpioName);
+    }
+}
+
 PresenceGpio::~PresenceGpio()
 {
     gpioLine.release();
 }
 
-void PresenceGpio::updateAndTracePresence()
+void PresenceGpio::updateAndTracePresence(int newValue)
 {
-    status = (gpioLine.get_value() != 0);
+    status = (newValue != 0);
     if (status)
     {
         logPresent(deviceName);
@@ -45,41 +61,32 @@
 }
 
 EventPresenceGpio::EventPresenceGpio(
-    const std::string& iDeviceType, const std::string& iDeviceName,
+    const std::string& deviceType, const std::string& deviceName,
     const std::string& gpioName, bool inverted, boost::asio::io_context& io) :
-    PresenceGpio(iDeviceType, iDeviceName), gpioFd(io)
+    PresenceGpio(deviceType, deviceName, gpioName), gpioFd(io)
 {
-    gpioLine = gpiod::find_line(gpioName);
-    if (!gpioLine)
-    {
-        std::cerr << "Error requesting gpio: " << gpioName << "\n";
-        return;
-    }
-
     try
     {
         gpioLine.request(
             {deviceType + "Sensor", gpiod::line_request::EVENT_BOTH_EDGES,
              inverted ? gpiod::line_request::FLAG_ACTIVE_LOW : 0});
-        updateAndTracePresence();
-
-        int gpioLineFd = gpioLine.event_get_fd();
-        if (gpioLineFd < 0)
-        {
-            std::cerr << "Failed to get " << gpioName << " fd\n";
-            throw std::runtime_error("Failed to get GPIO fd " + gpioName);
-        }
-
-        gpioFd.assign(gpioLineFd);
+        updateAndTracePresence(gpioLine.get_value());
     }
     catch (const std::system_error& e)
     {
         std::cerr << "Error reading gpio " << gpioName << ": " << e.what()
                   << "\n";
-        return;
+        throw std::runtime_error("Failed to read GPIO fd " + gpioName);
     }
 
-    monitorPresence();
+    int gpioLineFd = gpioLine.event_get_fd();
+    if (gpioLineFd < 0)
+    {
+        std::cerr << "Failed to get " << gpioName << " fd\n";
+        throw std::runtime_error("Failed to get GPIO fd " + gpioName);
+    }
+
+    gpioFd.assign(gpioLineFd);
 }
 
 void EventPresenceGpio::monitorPresence()
@@ -114,5 +121,65 @@
 {
     // Read is invoked when an edge event is detected by monitorPresence
     gpioLine.event_read();
-    updateAndTracePresence();
+    updateAndTracePresence(gpioLine.get_value());
+}
+
+PollingPresenceGpio::PollingPresenceGpio(
+    const std::string& deviceType, const std::string& deviceName,
+    const std::string& gpioName, bool inverted, boost::asio::io_context& io) :
+    PresenceGpio(deviceType, deviceName, gpioName), pollTimer(io)
+{
+    try
+    {
+        gpioLine.request(
+            {deviceType + "Sensor", gpiod::line_request::DIRECTION_INPUT,
+             inverted ? gpiod::line_request::FLAG_ACTIVE_LOW : 0});
+        updateAndTracePresence(gpioLine.get_value());
+    }
+    catch (const std::system_error& e)
+    {
+        std::cerr << "PollingPresenceGpio: Error reading gpio " << gpioName
+                  << ": " << e.what() << "\n";
+        status = false;
+        throw std::runtime_error("Failed to get Polling GPIO fd " + gpioName);
+    }
+}
+
+inline void PollingPresenceGpio::pollTimerHandler(
+    const std::weak_ptr<PollingPresenceGpio>& weakRef,
+    const boost::system::error_code& ec)
+{
+    std::shared_ptr<PollingPresenceGpio> self = weakRef.lock();
+    if (!self)
+    {
+        std::cerr << "Failed to get lock for pollingPresenceGpio: "
+                  << ec.message() << "\n";
+        return;
+    }
+    if (ec)
+    {
+        if (ec != boost::system::errc::bad_file_descriptor)
+        {
+            std::cerr << "GPIO polling timer failed for " << self->gpioName
+                      << ": " << ec.what() << ")\n";
+        }
+        return;
+    }
+    self->monitorPresence();
+}
+
+void PollingPresenceGpio::monitorPresence()
+{
+    // Determine if the value has changed
+    int newStatus = gpioLine.get_value();
+    if (static_cast<int>(status) != newStatus)
+    {
+        updateAndTracePresence(newStatus);
+    }
+
+    std::weak_ptr<PollingPresenceGpio> weakRef = weak_from_this();
+    pollTimer.expires_after(std::chrono::seconds(pollIntervalSec));
+    pollTimer.async_wait([weakRef](const boost::system::error_code& ec) {
+        pollTimerHandler(weakRef, ec);
+    });
 }