diff --git a/libnymea-gpio/gpio.cpp b/libnymea-gpio/gpio.cpp index 28a0e2d..596251a 100644 --- a/libnymea-gpio/gpio.cpp +++ b/libnymea-gpio/gpio.cpp @@ -139,6 +139,7 @@ Gpio::Gpio(int gpio, QObject *parent) : m_direction(Gpio::DirectionInvalid), m_gpioDirectory(QDir(QString("/sys/class/gpio/gpio%1").arg(QString::number(gpio)))) { + qRegisterMetaType(); } diff --git a/libnymea-gpio/gpiobutton.cpp b/libnymea-gpio/gpiobutton.cpp index bb28780..ac6b5e0 100644 --- a/libnymea-gpio/gpiobutton.cpp +++ b/libnymea-gpio/gpiobutton.cpp @@ -164,7 +164,7 @@ void GpioButton::onTimeout() emit longPressed(); } -void GpioButton::onInterruptOccurred(bool value) +void GpioButton::onValueChanged(bool value) { if (value) { // Pressed @@ -197,16 +197,14 @@ bool GpioButton::enable() disable(); m_monitor = new GpioMonitor(m_gpioNumber, this); - m_monitor->setEdge(Gpio::EdgeBoth); - m_monitor->setActiveLow(m_activeLow); - if (!m_monitor->enable()) { + if (!m_monitor->enable(m_activeLow, Gpio::EdgeBoth)) { qCWarning(dcGpio()) << "Could not enable GPIO monitor for" << this; delete m_monitor; m_monitor = nullptr; return false; } - connect(m_monitor, &GpioMonitor::interruptOccurred, this, &GpioButton::onInterruptOccurred); + connect(m_monitor, &GpioMonitor::valueChanged, this, &GpioButton::onValueChanged); // Setup timer, if this timer reaches timeout, a long pressed happend m_timer = new QTimer(this); diff --git a/libnymea-gpio/gpiobutton.h b/libnymea-gpio/gpiobutton.h index 8270383..a4e9516 100644 --- a/libnymea-gpio/gpiobutton.h +++ b/libnymea-gpio/gpiobutton.h @@ -77,7 +77,7 @@ signals: private slots: void onTimeout(); - void onInterruptOccurred(bool value); + void onValueChanged(bool value); public slots: bool enable(); diff --git a/libnymea-gpio/gpiomonitor.cpp b/libnymea-gpio/gpiomonitor.cpp index e1ee24e..ef6af85 100644 --- a/libnymea-gpio/gpiomonitor.cpp +++ b/libnymea-gpio/gpiomonitor.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2020, nymea GmbH +* Copyright 2013 - 2021, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -30,270 +30,135 @@ /*! \class GpioMonitor - \brief Monitor for GPIO interrupts. - \inmodule nymea-gpio - \ingroup gpio - - This class allows to monitor an input GPIO for the interrupts depending on the edge interrupt configuration. - - This class will start a poll thread in the background. Depending on the Gpio::Edge configuration, the \l{interruptOccured()} signal - will be emitted. Default is Gpio::EdgeBoth which means the interrupt will be on rising and falling signal of the Gpio. - - The behavior of the interrupt can also be inverted using the \l{activeLow()} parameter. - + \brief The GpioMonitor class allows to monitor GPIOs. + \ingroup hardware + \inmodule libnymea + An instance of this class creates a thread, which monitors each of the added GPIOs. The object + emits a signal if one of the added GPIOs changes its value. The GpioMonitor configures a GPIO as an + input, with the edge interrupt EDGE_BOTH (\l{Gpio::setEdgeInterrupt()}{setEdgeInterrupt}). + \chapter Example + Following example shows how to use the GpioMonitor class for a button on the Raspberry Pi. There are two possibilitys + to connect a button. Following picture shows the schematics: + \image Raspberry_Pi_Button_Example.png "Raspberry Pi button example" + Button A represents a clean solutin with a 10 k\unicode{0x2126} resistor (set up as activeLow = false). + Button B represents a "dirty" solution, were the 3.3V will be directly connected to the GPIO if the button is pressed (set activeLow = true). + Here is the code example for a button class: \code - GpioMonitor *monitor = new GpioMonitor(112, this); - - if (!monitor->enable()) { - qWarning() << "Could not enable GPIO monitor"; - monitor->deleteLater(); - return; - } - - connect(monitor, &GpioMonitor::interruptOccured, this, [this, monitor](bool value){ - qDebug() << "GPIO value changed" << value; - }); - + Button::Button(QObject *parent) : + QObject(parent) + { + m_button = new GpioMonitor(110, this); + connect(m_button, &GpioMonitor::valueChanged, this, &Button::stateChanged); + } + bool Button::init() + { + return m_button->enable(); + } + void Button::stateChanged(const bool &value) + { + if (m_pressed != value) { + m_pressed = value; + if (value) { + emit buttonPressed(); + } else { + emit buttonReleased(); + } + } + } \endcode - */ -/*! - \fn void GpioMonitor::interruptOccured(bool value); - This signal will be emitted, if an interrupt on the monitored Gpio occured with the new \a value. This event depends on the Gpio::Edge configuration of the Gpio. - - \sa edge(), setEdge() -*/ - -/*! - \fn void GpioMonitor::enabledChanged(bool enabled); - This signal will be emitted when the GpioMonitor \a enabled changed. -*/ +/*! \fn void GpioMonitor::valueChanged(const bool &value); + * This signal will be emitted, if the monitored \l{Gpio}{Gpios} changed his \a value. */ #include "gpiomonitor.h" -#include -#include - /*! Constructs a \l{GpioMonitor} object with the given \a gpio number and \a parent. */ GpioMonitor::GpioMonitor(int gpio, QObject *parent) : - QThread(parent), + QObject(parent), m_gpioNumber(gpio) { - // Inform about the thread status - connect(this, &GpioMonitor::started, this, &GpioMonitor::onThreadStarted, Qt::DirectConnection); - connect(this, &GpioMonitor::finished, this, &GpioMonitor::onThreadFinished, Qt::DirectConnection); + m_valueFile.setFileName("/sys/class/gpio/gpio" + QString::number(m_gpioNumber) + "/value"); } -/*! Destroys and unexports the Gpio. */ -GpioMonitor::~GpioMonitor() +/*! Returns true if this \l{GpioMonitor} could be enabled successfully. With the \a activeLow parameter the values can be inverted. + With the \a edgeInterrupt parameter the interrupt type can be specified. */ +bool GpioMonitor::enable(bool activeLow, Gpio::Edge edgeInterrupt) { - disable(); - wait(200); -} + if (!Gpio::isAvailable()) + return false; -int GpioMonitor::gpioNumber() const -{ - return m_gpioNumber; -} - -/*! Returns the edge interrupt configuration for this GpioMonitor. */ -Gpio::Edge GpioMonitor::edge() const -{ - return m_edge; -} - -/*! Sets the edge interrupt configuration for this GpioMonitor to the given \a edge. */ -void GpioMonitor::setEdge(Gpio::Edge edge) -{ - if (m_edge == edge) - return; - - m_edge = edge; -} - -/*! Returns true, if the monitor is configured as active low. If active low is true, the GPIO values and interrupt behavior will be inverted. */ -bool GpioMonitor::activeLow() const -{ - return m_activeLow; -} - -/*! Sets the the monitor to \a activeLow. If active low is true, the GPIO values and interrupt behavior will be inverted. */ -void GpioMonitor::setActiveLow(bool activeLow) -{ - if (m_activeLow == activeLow) - return; - - m_activeLow = activeLow; -} - -/*! Returns the current value of the Gpio. */ -Gpio::Value GpioMonitor::value() -{ - QMutexLocker valueLocker(&m_valueMutex); - return m_value; -} - -/*! Returns true if this GpioMonitor is enabled. */ -bool GpioMonitor::enabled() const -{ - return m_enabled; -} - -void GpioMonitor::setValue(Gpio::Value value) -{ - QMutexLocker valueLocker(&m_valueMutex); - m_value = value; - - switch (m_value) { - case Gpio::ValueLow: - emit interruptOccurred(false); - break; - case Gpio::ValueHigh: - emit interruptOccurred(true); - break; - default: - break; - } -} - -void GpioMonitor::setEnabled(bool enabled) -{ - if (m_enabled == enabled) - return; - - m_enabled = enabled; - emit enabledChanged(m_enabled); -} - -/*! Reimplementation of the QThread run() method. Within the thread the Gpio value will be polled using poll() 2. */ -void GpioMonitor::run() -{ - // Create GPIO in the thread for initialisation - Gpio inputGpio(m_gpioNumber); - if (!inputGpio.exportGpio()) { - qCWarning(dcGpio()) << "Could not enable GPIO monitor."; - return; - } - - if (!inputGpio.setDirection(Gpio::DirectionInput)) { - qCWarning(dcGpio()) << "Could not enable GPIO monitor."; - return; - } - - if (!inputGpio.setEdgeInterrupt(m_edge)) { - qCWarning(dcGpio()) << "Could not set interrupt for the GPIO monitor."; - return; - } - - if (!inputGpio.setActiveLow(m_activeLow)) { - qCWarning(dcGpio()) << "Could not set active low for the GPIO monitor."; - return; - } - - - // In order to do correctly, use poll (2) according to the kernel documentation - // https://www.kernel.org/doc/Documentation/gpio/sysfs.txt - QFile valueFile(inputGpio.gpioDirectory() + QDir::separator() + "value"); - if (!valueFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - qCWarning(dcGpio()) << "Could not open GPIO" << &inputGpio << "value file:" << valueFile.errorString(); - return; - } - - struct pollfd fdset[1]; - int rc = -1; - uint nfds = 1; - int timeout = 100; // ms - fdset[0].fd = valueFile.handle(); - fdset[0].events = POLLPRI; - - // Poll the GPIO value until stop is true - while (true) { - // Poll the value file - rc = poll(fdset, nfds, timeout); - - // Poll failed... - if (rc < 0) { - qCWarning(dcGpio()) << "Failed to poll" << &inputGpio; - break; - } - - // Check if we should stop the thread - m_stopMutex.lock(); - if (m_stop) { - m_stopMutex.unlock(); - break; - } - m_stopMutex.unlock(); - - // No interrupt occured - if (rc == 0) - continue; - - // Interrupt occured - if (fdset[0].revents & POLLPRI) { - QString valueString; - QTextStream readStream(&valueFile); - if (!readStream.seek(0)) { - qCWarning(dcGpio()) << "Failed to seek value file of" << &inputGpio; - continue; - } - - // Notify the main thread about the interrupt - readStream >> valueString; - if (valueString == "0") { - setValue(Gpio::ValueLow); - } else { - setValue(Gpio::ValueHigh); - } - } - } - - // Clean up once done - valueFile.close(); -} - -void GpioMonitor::onThreadStarted() -{ - qCDebug(dcGpio()) << "Monitor thread started"; - setEnabled(true); -} - -void GpioMonitor::onThreadFinished() -{ - qCDebug(dcGpio()) << "Monitor thread finished"; - setEnabled(false); -} - -/*! Returns true, if this GpioMonitor was enabled successfully. */ -bool GpioMonitor::enable() -{ - qCDebug(dcGpio()) << "Enabling gpio monitor"; - if (isRunning()) { - qCWarning(dcGpio()) << "This GPIO monitor is already running."; - return true; - } - - // Init the GPIO - if (!Gpio::isAvailable()) { - qCWarning(dcGpio()) << "Could not enable GPIO monitor. There are no GPIOs available on this platform."; + m_gpio = new Gpio(m_gpioNumber, this); + if (!m_gpio->exportGpio() || + !m_gpio->setDirection(Gpio::DirectionInput) || + !m_gpio->setActiveLow(activeLow) || + !m_gpio->setEdgeInterrupt(edgeInterrupt)) { + qCWarning(dcGpio()) << "GpioMonitor: Error while initializing GPIO" << m_gpio->gpioNumber(); return false; } - QMutexLocker locker(&m_stopMutex); - m_stop = false; + if (!m_valueFile.open(QFile::ReadOnly)) { + qWarning(dcGpio()) << "GpioMonitor: Could not open value file for gpio monitor" << m_gpio->gpioNumber(); + return false; + } - // Everything looks good, lets start the poll thread and inform about the result - start(); + m_notifier = new QSocketNotifier(m_valueFile.handle(), QSocketNotifier::Exception); + connect(m_notifier, &QSocketNotifier::activated, this, &GpioMonitor::readyReady); + + qCDebug(dcGpio()) << "Socket notififier started"; + m_notifier->setEnabled(true); return true; } -/*! Disables this GpioMonitor. The \l{interruptOccured()} signal will not be emitted any more and the Gpio will be unexported. */ +/*! Disables this \l{GpioMonitor}. */ void GpioMonitor::disable() { - qCDebug(dcGpio()) << "Disabling gpio monitor"; - // Stop the thread if not already disabled - QMutexLocker locker(&m_stopMutex); - if (m_stop) return; - m_stop = true; + delete m_notifier; + delete m_gpio; + + m_notifier = 0; + m_gpio = 0; + + m_valueFile.close(); +} + +/*! Returns true if this \l{GpioMonitor} is running. */ +bool GpioMonitor::isRunning() const +{ + if (!m_notifier) + return false; + + return m_notifier->isEnabled(); +} + +/*! Returns the current value of this \l{GpioMonitor}. */ +bool GpioMonitor::value() const +{ + return m_currentValue; +} + +/*! Returns the \l{Gpio} of this \l{GpioMonitor}. */ +Gpio *GpioMonitor::gpio() +{ + return m_gpio; +} + +void GpioMonitor::readyReady(const int &ready) +{ + Q_UNUSED(ready) + + m_valueFile.seek(0); + QByteArray data = m_valueFile.readAll(); + + bool value = false; + if (data[0] == '1') { + value = true; + } else if (data[0] == '0') { + value = false; + } else { + return; + } + + m_currentValue = value; + emit valueChanged(value); } diff --git a/libnymea-gpio/gpiomonitor.h b/libnymea-gpio/gpiomonitor.h index a960244..da641a8 100644 --- a/libnymea-gpio/gpiomonitor.h +++ b/libnymea-gpio/gpiomonitor.h @@ -1,6 +1,5 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ** +* Copyright 2013 - 2021, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -31,61 +30,40 @@ #ifndef GPIOMONITOR_H #define GPIOMONITOR_H -#include -#include #include +#include +#include +#include #include "gpio.h" -class GpioMonitor : public QThread +class GpioMonitor : public QObject { Q_OBJECT + public: explicit GpioMonitor(int gpio, QObject *parent = nullptr); - ~GpioMonitor() override; - int gpioNumber() const; + bool enable(bool activeLow = false, Gpio::Edge edgeInterrupt = Gpio::EdgeBoth); + void disable(); - Gpio::Edge edge() const; - void setEdge(Gpio::Edge edge); + bool isRunning() const; + bool value() const; - bool activeLow() const; - void setActiveLow(bool activeLow); - - Gpio::Value value(); - - bool enabled() const; + Gpio* gpio(); private: - int m_gpioNumber = -1; - Gpio::Edge m_edge = Gpio::EdgeBoth; - bool m_activeLow = true; - bool m_enabled = false; - - // Thread stuff - QMutex m_valueMutex; - Gpio::Value m_value = Gpio::ValueInvalid; - - QMutex m_stopMutex; - bool m_stop = false; - - void setValue(Gpio::Value value); - void setEnabled(bool enabled); - -protected: - void run() override; + int m_gpioNumber; + Gpio *m_gpio; + QSocketNotifier *m_notifier; + QFile m_valueFile; + bool m_currentValue; signals: - void interruptOccurred(bool value); - void enabledChanged(bool enabled); + void valueChanged(const bool &value); private slots: - void onThreadStarted(); - void onThreadFinished(); - -public slots: - bool enable(); - void disable(); + void readyReady(const int &ready); }; diff --git a/nymea-gpio-tool/main.cpp b/nymea-gpio-tool/main.cpp index 08d27b7..15ea030 100644 --- a/nymea-gpio-tool/main.cpp +++ b/nymea-gpio-tool/main.cpp @@ -151,21 +151,14 @@ int main(int argc, char *argv[]) return EXIT_SUCCESS; } else { GpioMonitor *monitor = new GpioMonitor(gpioNumber); - monitor->setEdge(edge); - monitor->setActiveLow(activeLow); - - // Inform about enabled changed - QObject::connect(monitor, &GpioMonitor::enabledChanged, [gpioNumber](bool enabled) { - qDebug() << "GPIO" << gpioNumber << "monitor" << (enabled ? "enabled" : "disabled"); - }); // Inform about interrupt - QObject::connect(monitor, &GpioMonitor::interruptOccurred, [gpioNumber](bool value) { - qDebug() << "GPIO" << gpioNumber << "interrupt occurred. Current value:" << (value ? "1" : "0"); + QObject::connect(monitor, &GpioMonitor::valueChanged, [gpioNumber](bool value) { + qDebug() << "GPIO" << gpioNumber << "value changed:" << (value ? "1" : "0"); }); // Enable the monitor - if (!monitor->enable()) { + if (!monitor->enable(activeLow, edge)) { qCritical() << "Could not enable GPIO" << gpioNumber << "monitor."; return EXIT_FAILURE; }