Merge PR #8: Replace GpioMonitor implementation with libnymeas version.
This commit is contained in:
commit
2e58f7f34d
@ -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<Gpio::Value>();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -77,7 +77,7 @@ signals:
|
||||
|
||||
private slots:
|
||||
void onTimeout();
|
||||
void onInterruptOccurred(bool value);
|
||||
void onValueChanged(bool value);
|
||||
|
||||
public slots:
|
||||
bool enable();
|
||||
|
||||
@ -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 <poll.h>
|
||||
#include <QMutexLocker>
|
||||
|
||||
/*! 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);
|
||||
}
|
||||
|
||||
@ -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 <QMutex>
|
||||
#include <QThread>
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QSocketNotifier>
|
||||
#include <QFile>
|
||||
|
||||
#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);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user