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/FanMain.cpp b/src/fan/FanMain.cpp
index 736d07d..33074b6 100644
--- a/src/fan/FanMain.cpp
+++ b/src/fan/FanMain.cpp
@@ -282,193 +282,194 @@
         sensorsChanged,
     size_t retries = 0)
 {
-    auto getter = std::make_shared<GetSensorConfiguration>(
-        dbusConnection,
-        [&io, &objectServer, &tachSensors, &pwmSensors, &presenceGpios,
-         &dbusConnection,
-         sensorsChanged](const ManagedObjectType& sensorConfigurations) {
-            bool firstScan = sensorsChanged == nullptr;
-            std::vector<fs::path> paths;
-            if (!findFiles(fs::path("/sys/class/hwmon"), R"(fan\d+_input)",
-                           paths))
+    auto getter = std::make_shared<
+        GetSensorConfiguration>(dbusConnection, [&io, &objectServer,
+                                                 &tachSensors, &pwmSensors,
+                                                 &presenceGpios,
+                                                 &dbusConnection,
+                                                 sensorsChanged](
+                                                    const ManagedObjectType&
+                                                        sensorConfigurations) {
+        bool firstScan = sensorsChanged == nullptr;
+        std::vector<fs::path> paths;
+        if (!findFiles(fs::path("/sys/class/hwmon"), R"(fan\d+_input)", paths))
+        {
+            std::cerr << "No fan sensors in system\n";
+            return;
+        }
+
+        // iterate through all found fan sensors, and try to match them with
+        // configuration
+        for (const auto& path : paths)
+        {
+            std::smatch match;
+            std::string pathStr = path.string();
+
+            std::regex_search(pathStr, match, inputRegex);
+            std::string indexStr = *(match.begin() + 1);
+
+            fs::path directory = path.parent_path();
+            FanTypes fanType = getFanType(directory);
+            std::string cfgIntf = configInterfaceName(sensorTypes[fanType]);
+
+            // convert to 0 based
+            size_t index = std::stoul(indexStr) - 1;
+
+            const char* baseType = nullptr;
+            const SensorData* sensorData = nullptr;
+            const std::string* interfacePath = nullptr;
+            const SensorBaseConfiguration* baseConfiguration = nullptr;
+            for (const auto& [path, cfgData] : sensorConfigurations)
             {
-                std::cerr << "No fan sensors in system\n";
-                return;
-            }
-
-            // iterate through all found fan sensors, and try to match them with
-            // configuration
-            for (const auto& path : paths)
-            {
-                std::smatch match;
-                std::string pathStr = path.string();
-
-                std::regex_search(pathStr, match, inputRegex);
-                std::string indexStr = *(match.begin() + 1);
-
-                fs::path directory = path.parent_path();
-                FanTypes fanType = getFanType(directory);
-                std::string cfgIntf = configInterfaceName(sensorTypes[fanType]);
-
-                // convert to 0 based
-                size_t index = std::stoul(indexStr) - 1;
-
-                const char* baseType = nullptr;
-                const SensorData* sensorData = nullptr;
-                const std::string* interfacePath = nullptr;
-                const SensorBaseConfiguration* baseConfiguration = nullptr;
-                for (const auto& [path, cfgData] : sensorConfigurations)
+                // find the base of the configuration to see if indexes
+                // match
+                auto sensorBaseFind = cfgData.find(cfgIntf);
+                if (sensorBaseFind == cfgData.end())
                 {
-                    // find the base of the configuration to see if indexes
-                    // match
-                    auto sensorBaseFind = cfgData.find(cfgIntf);
-                    if (sensorBaseFind == cfgData.end())
+                    continue;
+                }
+
+                baseConfiguration = &(*sensorBaseFind);
+                interfacePath = &path.str;
+                baseType = sensorTypes[fanType];
+
+                auto findIndex = baseConfiguration->second.find("Index");
+                if (findIndex == baseConfiguration->second.end())
+                {
+                    std::cerr << baseConfiguration->first << " missing index\n";
+                    continue;
+                }
+                unsigned int configIndex = std::visit(
+                    VariantToUnsignedIntVisitor(), findIndex->second);
+                if (configIndex != index)
+                {
+                    continue;
+                }
+                if (fanType == FanTypes::aspeed ||
+                    fanType == FanTypes::nuvoton || fanType == FanTypes::hpe)
+                {
+                    // there will be only 1 aspeed or nuvoton or hpe sensor
+                    // object in sysfs, we found the fan
+                    sensorData = &cfgData;
+                    break;
+                }
+                if (fanType == FanTypes::i2c)
+                {
+                    std::string deviceName =
+                        fs::read_symlink(directory / "device").filename();
+
+                    size_t bus = 0;
+                    size_t addr = 0;
+                    if (!getDeviceBusAddr(deviceName, bus, addr))
                     {
                         continue;
                     }
 
-                    baseConfiguration = &(*sensorBaseFind);
-                    interfacePath = &path.str;
-                    baseType = sensorTypes[fanType];
+                    auto findBus = baseConfiguration->second.find("Bus");
+                    auto findAddress =
+                        baseConfiguration->second.find("Address");
+                    if (findBus == baseConfiguration->second.end() ||
+                        findAddress == baseConfiguration->second.end())
+                    {
+                        std::cerr << baseConfiguration->first
+                                  << " missing bus or address\n";
+                        continue;
+                    }
+                    unsigned int configBus = std::visit(
+                        VariantToUnsignedIntVisitor(), findBus->second);
+                    unsigned int configAddress = std::visit(
+                        VariantToUnsignedIntVisitor(), findAddress->second);
 
-                    auto findIndex = baseConfiguration->second.find("Index");
-                    if (findIndex == baseConfiguration->second.end())
+                    if (configBus == bus && configAddress == addr)
                     {
-                        std::cerr
-                            << baseConfiguration->first << " missing index\n";
-                        continue;
-                    }
-                    unsigned int configIndex = std::visit(
-                        VariantToUnsignedIntVisitor(), findIndex->second);
-                    if (configIndex != index)
-                    {
-                        continue;
-                    }
-                    if (fanType == FanTypes::aspeed ||
-                        fanType == FanTypes::nuvoton ||
-                        fanType == FanTypes::hpe)
-                    {
-                        // there will be only 1 aspeed or nuvoton or hpe sensor
-                        // object in sysfs, we found the fan
                         sensorData = &cfgData;
                         break;
                     }
-                    if (fanType == FanTypes::i2c)
+                }
+            }
+            if (sensorData == nullptr)
+            {
+                std::cerr << "failed to find match for " << path.string()
+                          << "\n";
+                continue;
+            }
+
+            auto findSensorName = baseConfiguration->second.find("Name");
+
+            if (findSensorName == baseConfiguration->second.end())
+            {
+                std::cerr << "could not determine configuration name for "
+                          << path.string() << "\n";
+                continue;
+            }
+            std::string sensorName =
+                std::get<std::string>(findSensorName->second);
+
+            // on rescans, only update sensors we were signaled by
+            auto findSensor = tachSensors.find(sensorName);
+            if (!firstScan && findSensor != tachSensors.end())
+            {
+                bool found = false;
+                for (auto it = sensorsChanged->begin();
+                     it != sensorsChanged->end(); it++)
+                {
+                    if (it->ends_with(findSensor->second->name))
                     {
-                        std::string deviceName =
-                            fs::read_symlink(directory / "device").filename();
-
-                        size_t bus = 0;
-                        size_t addr = 0;
-                        if (!getDeviceBusAddr(deviceName, bus, addr))
-                        {
-                            continue;
-                        }
-
-                        auto findBus = baseConfiguration->second.find("Bus");
-                        auto findAddress =
-                            baseConfiguration->second.find("Address");
-                        if (findBus == baseConfiguration->second.end() ||
-                            findAddress == baseConfiguration->second.end())
-                        {
-                            std::cerr << baseConfiguration->first
-                                      << " missing bus or address\n";
-                            continue;
-                        }
-                        unsigned int configBus = std::visit(
-                            VariantToUnsignedIntVisitor(), findBus->second);
-                        unsigned int configAddress = std::visit(
-                            VariantToUnsignedIntVisitor(), findAddress->second);
-
-                        if (configBus == bus && configAddress == addr)
-                        {
-                            sensorData = &cfgData;
-                            break;
-                        }
+                        sensorsChanged->erase(it);
+                        findSensor->second = nullptr;
+                        found = true;
+                        break;
                     }
                 }
-                if (sensorData == nullptr)
+                if (!found)
                 {
-                    std::cerr
-                        << "failed to find match for " << path.string() << "\n";
                     continue;
                 }
+            }
+            std::vector<thresholds::Threshold> sensorThresholds;
+            if (!parseThresholdsFromConfig(*sensorData, sensorThresholds))
+            {
+                std::cerr << "error populating thresholds for " << sensorName
+                          << "\n";
+            }
 
-                auto findSensorName = baseConfiguration->second.find("Name");
+            auto presenceConfig =
+                sensorData->find(cfgIntf + std::string(".Presence"));
 
-                if (findSensorName == baseConfiguration->second.end())
+            std::shared_ptr<PresenceGpio> presenceGpio(nullptr);
+
+            // presence sensors are optional
+            if (presenceConfig != sensorData->end())
+            {
+                auto findPolarity = presenceConfig->second.find("Polarity");
+                auto findPinName = presenceConfig->second.find("PinName");
+
+                if (findPinName == presenceConfig->second.end() ||
+                    findPolarity == presenceConfig->second.end())
                 {
-                    std::cerr << "could not determine configuration name for "
-                              << path.string() << "\n";
-                    continue;
+                    std::cerr << "Malformed Presence Configuration\n";
                 }
-                std::string sensorName =
-                    std::get<std::string>(findSensorName->second);
-
-                // on rescans, only update sensors we were signaled by
-                auto findSensor = tachSensors.find(sensorName);
-                if (!firstScan && findSensor != tachSensors.end())
+                else
                 {
-                    bool found = false;
-                    for (auto it = sensorsChanged->begin();
-                         it != sensorsChanged->end(); it++)
+                    bool inverted =
+                        std::get<std::string>(findPolarity->second) == "Low";
+                    const auto* pinName =
+                        std::get_if<std::string>(&findPinName->second);
+
+                    if (pinName != nullptr)
                     {
-                        if (it->ends_with(findSensor->second->name))
+                        auto findPresenceGpio = presenceGpios.find(*pinName);
+                        if (findPresenceGpio != presenceGpios.end())
                         {
-                            sensorsChanged->erase(it);
-                            findSensor->second = nullptr;
-                            found = true;
-                            break;
-                        }
-                    }
-                    if (!found)
-                    {
-                        continue;
-                    }
-                }
-                std::vector<thresholds::Threshold> sensorThresholds;
-                if (!parseThresholdsFromConfig(*sensorData, sensorThresholds))
-                {
-                    std::cerr << "error populating thresholds for "
-                              << sensorName << "\n";
-                }
-
-                auto presenceConfig =
-                    sensorData->find(cfgIntf + std::string(".Presence"));
-
-                std::shared_ptr<PresenceGpio> presenceGpio(nullptr);
-
-                // presence sensors are optional
-                if (presenceConfig != sensorData->end())
-                {
-                    auto findPolarity = presenceConfig->second.find("Polarity");
-                    auto findPinName = presenceConfig->second.find("PinName");
-
-                    if (findPinName == presenceConfig->second.end() ||
-                        findPolarity == presenceConfig->second.end())
-                    {
-                        std::cerr << "Malformed Presence Configuration\n";
-                    }
-                    else
-                    {
-                        bool inverted = std::get<std::string>(
-                                            findPolarity->second) == "Low";
-                        const auto* pinName =
-                            std::get_if<std::string>(&findPinName->second);
-
-                        if (pinName != nullptr)
-                        {
-                            auto findPresenceGpio =
-                                presenceGpios.find(*pinName);
-                            if (findPresenceGpio != presenceGpios.end())
+                            auto p = findPresenceGpio->second.lock();
+                            if (p)
                             {
-                                auto p = findPresenceGpio->second.lock();
-                                if (p)
-                                {
-                                    presenceGpio = p;
-                                }
+                                presenceGpio = p;
                             }
-                            if (!presenceGpio)
+                        }
+                        if (!presenceGpio)
+                        {
+                            try
                             {
                                 presenceGpio =
                                     std::make_shared<EventPresenceGpio>(
@@ -476,130 +477,135 @@
                                         io);
                                 presenceGpios[*pinName] = presenceGpio;
                             }
-                        }
-                        else
-                        {
-                            std::cerr
-                                << "Malformed Presence pinName for sensor "
-                                << sensorName << " \n";
-                        }
-                    }
-                }
-                std::optional<RedundancySensor>* redundancy = nullptr;
-                if (fanType == FanTypes::aspeed)
-                {
-                    redundancy = &systemRedundancy;
-                }
-
-                PowerState powerState =
-                    getPowerState(baseConfiguration->second);
-
-                constexpr double defaultMaxReading = 25000;
-                constexpr double defaultMinReading = 0;
-                std::pair<double, double> limits =
-                    std::make_pair(defaultMinReading, defaultMaxReading);
-
-                auto connector =
-                    sensorData->find(cfgIntf + std::string(".Connector"));
-
-                std::optional<std::string> led;
-                std::string pwmName;
-                fs::path pwmPath;
-
-                // The Mutable parameter is optional, defaulting to false
-                bool isValueMutable = false;
-                if (connector != sensorData->end())
-                {
-                    auto findPwm = connector->second.find("Pwm");
-                    if (findPwm != connector->second.end())
-                    {
-                        size_t pwm = std::visit(VariantToUnsignedIntVisitor(),
-                                                findPwm->second);
-                        if (!findPwmPath(directory, pwm, pwmPath))
-                        {
-                            std::cerr << "Connector for " << sensorName
-                                      << " no pwm channel found!\n";
-                            continue;
-                        }
-
-                        fs::path pwmEnableFile =
-                            "pwm" + std::to_string(pwm + 1) + "_enable";
-                        fs::path enablePath =
-                            pwmPath.parent_path() / pwmEnableFile;
-                        enablePwm(enablePath);
-
-                        /* use pwm name override if found in configuration else
-                         * use default */
-                        auto findOverride = connector->second.find("PwmName");
-                        if (findOverride != connector->second.end())
-                        {
-                            pwmName = std::visit(VariantToStringVisitor(),
-                                                 findOverride->second);
-                        }
-                        else
-                        {
-                            pwmName = "Pwm_" + std::to_string(pwm + 1);
-                        }
-
-                        // Check PWM sensor mutability
-                        auto findMutable = connector->second.find("Mutable");
-                        if (findMutable != connector->second.end())
-                        {
-                            const auto* ptrMutable =
-                                std::get_if<bool>(&(findMutable->second));
-                            if (ptrMutable != nullptr)
+                            catch (const std::system_error& e)
                             {
-                                isValueMutable = *ptrMutable;
+                                std::cerr
+                                    << "Failed to create GPIO monitor object for "
+                                    << *pinName << " / " << sensorName << ": "
+                                    << e.what() << "\n";
                             }
                         }
                     }
                     else
                     {
+                        std::cerr << "Malformed Presence pinName for sensor "
+                                  << sensorName << " \n";
+                    }
+                }
+            }
+            std::optional<RedundancySensor>* redundancy = nullptr;
+            if (fanType == FanTypes::aspeed)
+            {
+                redundancy = &systemRedundancy;
+            }
+
+            PowerState powerState = getPowerState(baseConfiguration->second);
+
+            constexpr double defaultMaxReading = 25000;
+            constexpr double defaultMinReading = 0;
+            std::pair<double, double> limits =
+                std::make_pair(defaultMinReading, defaultMaxReading);
+
+            auto connector =
+                sensorData->find(cfgIntf + std::string(".Connector"));
+
+            std::optional<std::string> led;
+            std::string pwmName;
+            fs::path pwmPath;
+
+            // The Mutable parameter is optional, defaulting to false
+            bool isValueMutable = false;
+            if (connector != sensorData->end())
+            {
+                auto findPwm = connector->second.find("Pwm");
+                if (findPwm != connector->second.end())
+                {
+                    size_t pwm = std::visit(VariantToUnsignedIntVisitor(),
+                                            findPwm->second);
+                    if (!findPwmPath(directory, pwm, pwmPath))
+                    {
                         std::cerr << "Connector for " << sensorName
-                                  << " missing pwm!\n";
+                                  << " no pwm channel found!\n";
+                        continue;
                     }
 
-                    auto findLED = connector->second.find("LED");
-                    if (findLED != connector->second.end())
+                    fs::path pwmEnableFile =
+                        "pwm" + std::to_string(pwm + 1) + "_enable";
+                    fs::path enablePath = pwmPath.parent_path() / pwmEnableFile;
+                    enablePwm(enablePath);
+
+                    /* use pwm name override if found in configuration else
+                     * use default */
+                    auto findOverride = connector->second.find("PwmName");
+                    if (findOverride != connector->second.end())
                     {
-                        const auto* ledName =
-                            std::get_if<std::string>(&(findLED->second));
-                        if (ledName == nullptr)
+                        pwmName = std::visit(VariantToStringVisitor(),
+                                             findOverride->second);
+                    }
+                    else
+                    {
+                        pwmName = "Pwm_" + std::to_string(pwm + 1);
+                    }
+
+                    // Check PWM sensor mutability
+                    auto findMutable = connector->second.find("Mutable");
+                    if (findMutable != connector->second.end())
+                    {
+                        const auto* ptrMutable =
+                            std::get_if<bool>(&(findMutable->second));
+                        if (ptrMutable != nullptr)
                         {
-                            std::cerr << "Wrong format for LED of "
-                                      << sensorName << "\n";
-                        }
-                        else
-                        {
-                            led = *ledName;
+                            isValueMutable = *ptrMutable;
                         }
                     }
                 }
-
-                findLimits(limits, baseConfiguration);
-
-                enableFanInput(path);
-
-                auto& tachSensor = tachSensors[sensorName];
-                tachSensor = nullptr;
-                tachSensor = std::make_shared<TachSensor>(
-                    path.string(), baseType, objectServer, dbusConnection,
-                    presenceGpio, redundancy, io, sensorName,
-                    std::move(sensorThresholds), *interfacePath, limits,
-                    powerState, led);
-                tachSensor->setupRead();
-
-                if (!pwmPath.empty() && fs::exists(pwmPath) &&
-                    (pwmSensors.count(pwmPath) == 0U))
+                else
                 {
-                    pwmSensors[pwmPath] = std::make_unique<PwmSensor>(
-                        pwmName, pwmPath, dbusConnection, objectServer,
-                        *interfacePath, "Fan", isValueMutable);
+                    std::cerr
+                        << "Connector for " << sensorName << " missing pwm!\n";
+                }
+
+                auto findLED = connector->second.find("LED");
+                if (findLED != connector->second.end())
+                {
+                    const auto* ledName =
+                        std::get_if<std::string>(&(findLED->second));
+                    if (ledName == nullptr)
+                    {
+                        std::cerr
+                            << "Wrong format for LED of " << sensorName << "\n";
+                    }
+                    else
+                    {
+                        led = *ledName;
+                    }
                 }
             }
 
-            createRedundancySensor(tachSensors, dbusConnection, objectServer);
-        });
+            findLimits(limits, baseConfiguration);
+
+            enableFanInput(path);
+
+            auto& tachSensor = tachSensors[sensorName];
+            tachSensor = nullptr;
+            tachSensor = std::make_shared<TachSensor>(
+                path.string(), baseType, objectServer, dbusConnection,
+                presenceGpio, redundancy, io, sensorName,
+                std::move(sensorThresholds), *interfacePath, limits, powerState,
+                led);
+            tachSensor->setupRead();
+
+            if (!pwmPath.empty() && fs::exists(pwmPath) &&
+                (pwmSensors.count(pwmPath) == 0U))
+            {
+                pwmSensors[pwmPath] = std::make_unique<PwmSensor>(
+                    pwmName, pwmPath, dbusConnection, objectServer,
+                    *interfacePath, "Fan", isValueMutable);
+            }
+        }
+
+        createRedundancySensor(tachSensors, dbusConnection, objectServer);
+    });
     getter->getConfiguration(
         std::vector<std::string>{sensorTypes.begin(), sensorTypes.end()},
         retries);
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);
+    });
 }
diff --git a/src/fan/PresenceGpio.hpp b/src/fan/PresenceGpio.hpp
index 54805cf..627270b 100644
--- a/src/fan/PresenceGpio.hpp
+++ b/src/fan/PresenceGpio.hpp
@@ -8,12 +8,13 @@
 class PresenceGpio
 {
   public:
-    PresenceGpio(const std::string& type, const std::string& name) :
-        deviceType(type), deviceName(name) {};
+    PresenceGpio(const std::string& deviceType, const std::string& deviceName,
+                 const std::string& gpioName);
     PresenceGpio(const PresenceGpio&) = delete;
     PresenceGpio& operator=(const PresenceGpio&) = delete;
     virtual ~PresenceGpio() = 0;
 
+    virtual void monitorPresence() = 0;
     bool isPresent() const
     {
         return status;
@@ -24,8 +25,7 @@
     bool status = false;
     std::string deviceType;
     std::string deviceName;
-
-    virtual void monitorPresence() = 0;
+    std::string gpioName;
 
     void logPresent(const std::string& device)
     {
@@ -43,7 +43,7 @@
                    "REDFISH_MESSAGE_ARGS", device);
     }
 
-    void updateAndTracePresence();
+    void updateAndTracePresence(int newValue);
 };
 
 class EventPresenceGpio :
@@ -51,14 +51,39 @@
     public std::enable_shared_from_this<EventPresenceGpio>
 {
   public:
-    EventPresenceGpio(const std::string& iDeviceType,
-                      const std::string& iDeviceName,
+    EventPresenceGpio(const std::string& deviceType,
+                      const std::string& deviceName,
                       const std::string& gpioName, bool inverted,
                       boost::asio::io_context& io);
 
+    void monitorPresence() override;
+
   private:
     boost::asio::posix::stream_descriptor gpioFd;
 
-    void monitorPresence() override;
     void read();
 };
+
+class PollingPresenceGpio :
+    public PresenceGpio,
+    public std::enable_shared_from_this<PollingPresenceGpio>
+{
+  public:
+    PollingPresenceGpio(const std::string& deviceType,
+                        const std::string& deviceName,
+                        const std::string& gpioName, bool inverted,
+                        boost::asio::io_context& io);
+    ~PollingPresenceGpio() override
+    {
+        // GPIO no longer being used so release/remove
+        gpioLine.release();
+    }
+    void monitorPresence() override;
+
+  private:
+    boost::asio::steady_timer pollTimer;
+
+    static inline void
+        pollTimerHandler(const std::weak_ptr<PollingPresenceGpio>& weakRef,
+                         const boost::system::error_code& ec);
+};
diff --git a/src/fan/TachSensor.cpp b/src/fan/TachSensor.cpp
index 7908488..10a34a4 100644
--- a/src/fan/TachSensor.cpp
+++ b/src/fan/TachSensor.cpp
@@ -78,6 +78,7 @@
 
     if (presence)
     {
+        presence->monitorPresence();
         itemIface =
             objectServer.add_interface("/xyz/openbmc_project/inventory/" + name,
                                        "xyz.openbmc_project.Inventory.Item");