Add libgpiod support to libnymea-gpio
This commit is contained in:
parent
f151646295
commit
4059bb0172
@ -6,8 +6,10 @@ Standards-Version: 4.6.0
|
|||||||
Vcs-Git: https://github.com/nymea/nymea-gpio.git
|
Vcs-Git: https://github.com/nymea/nymea-gpio.git
|
||||||
Build-Depends: debhelper,
|
Build-Depends: debhelper,
|
||||||
dpkg-dev,
|
dpkg-dev,
|
||||||
|
pkg-config,
|
||||||
qt5-qmake,
|
qt5-qmake,
|
||||||
qtbase5-dev,
|
qtbase5-dev,
|
||||||
|
libgpiod-dev,
|
||||||
qtbase5-dev-tools
|
qtbase5-dev-tools
|
||||||
|
|
||||||
|
|
||||||
@ -38,6 +40,7 @@ Multi-Arch: same
|
|||||||
Depends: ${shlibs:Depends},
|
Depends: ${shlibs:Depends},
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
pkg-config,
|
pkg-config,
|
||||||
|
libgpiod-dev,
|
||||||
libnymea-gpio (= ${binary:Version})
|
libnymea-gpio (= ${binary:Version})
|
||||||
Description: Qt based library for GPIO interaction - development files
|
Description: Qt based library for GPIO interaction - development files
|
||||||
Development files for Qt based GPIO library.
|
Development files for Qt based GPIO library.
|
||||||
|
|||||||
@ -6,7 +6,9 @@ Standards-Version: 4.7.2
|
|||||||
Vcs-Git: https://github.com/nymea/nymea-gpio.git
|
Vcs-Git: https://github.com/nymea/nymea-gpio.git
|
||||||
Build-Depends: debhelper,
|
Build-Depends: debhelper,
|
||||||
dpkg-dev,
|
dpkg-dev,
|
||||||
|
pkg-config,
|
||||||
qt6-base-dev,
|
qt6-base-dev,
|
||||||
|
libgpiod-dev,
|
||||||
qt6-base-dev-tools,
|
qt6-base-dev-tools,
|
||||||
|
|
||||||
|
|
||||||
@ -37,6 +39,7 @@ Multi-Arch: same
|
|||||||
Depends: ${shlibs:Depends},
|
Depends: ${shlibs:Depends},
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
pkg-config,
|
pkg-config,
|
||||||
|
libgpiod-dev,
|
||||||
libnymea-gpio (= ${binary:Version})
|
libnymea-gpio (= ${binary:Version})
|
||||||
Description: Qt based library for GPIO interaction - development files
|
Description: Qt based library for GPIO interaction - development files
|
||||||
Development files for Qt based GPIO library.
|
Development files for Qt based GPIO library.
|
||||||
|
|||||||
@ -126,14 +126,100 @@
|
|||||||
|
|
||||||
#include "gpio.h"
|
#include "gpio.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#ifndef NYMEA_GPIO_USE_SYSFS
|
||||||
|
#include <errno.h>
|
||||||
|
#include <gpiod.h>
|
||||||
|
#include <string.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(dcGpio, "Gpio")
|
Q_LOGGING_CATEGORY(dcGpio, "Gpio")
|
||||||
|
|
||||||
|
#ifndef NYMEA_GPIO_USE_SYSFS
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr const char *kGpioConsumer = "nymea-gpio";
|
||||||
|
|
||||||
|
bool readIntFile(const QString &path, int *value)
|
||||||
|
{
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const QByteArray data = file.readAll();
|
||||||
|
bool ok = false;
|
||||||
|
const int parsed = QString::fromLatin1(data).trimmed().toInt(&ok);
|
||||||
|
if (!ok)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*value = parsed;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool resolveLineFromSysfs(int gpioNumber, QString *chipName, unsigned int *offset)
|
||||||
|
{
|
||||||
|
QDir gpioDir("/sys/class/gpio");
|
||||||
|
if (!gpioDir.exists())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const QStringList entries = gpioDir.entryList(QStringList() << "gpiochip*", QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
for (const QString &entry : entries) {
|
||||||
|
int base = 0;
|
||||||
|
int ngpio = 0;
|
||||||
|
if (!readIntFile(gpioDir.filePath(entry + "/base"), &base) || !readIntFile(gpioDir.filePath(entry + "/ngpio"), &ngpio)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gpioNumber >= base && gpioNumber < base + ngpio) {
|
||||||
|
*chipName = entry;
|
||||||
|
*offset = static_cast<unsigned int>(gpioNumber - base);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool resolveLineSequential(int gpioNumber, QString *chipName, unsigned int *offset)
|
||||||
|
{
|
||||||
|
if (gpioNumber < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
gpiod_chip_iter *iter = gpiod_chip_iter_new();
|
||||||
|
if (!iter)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
gpiod_chip *chip = nullptr;
|
||||||
|
unsigned int base = 0;
|
||||||
|
gpiod_foreach_chip(iter, chip)
|
||||||
|
{
|
||||||
|
const unsigned int numLines = gpiod_chip_num_lines(chip);
|
||||||
|
if (static_cast<unsigned int>(gpioNumber) < base + numLines) {
|
||||||
|
*chipName = QString::fromLatin1(gpiod_chip_name(chip));
|
||||||
|
*offset = static_cast<unsigned int>(gpioNumber) - base;
|
||||||
|
gpiod_chip_iter_free(iter);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
base += numLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpiod_chip_iter_free(iter);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
#endif
|
||||||
|
|
||||||
/*! Constructs a Gpio object to represent a GPIO with the given \a gpio number and \a parent. */
|
/*! Constructs a Gpio object to represent a GPIO with the given \a gpio number and \a parent. */
|
||||||
Gpio::Gpio(int gpio, QObject *parent)
|
Gpio::Gpio(int gpio, QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_gpio(gpio)
|
, m_gpio(gpio)
|
||||||
, m_direction(Gpio::DirectionInvalid)
|
, m_direction(Gpio::DirectionInvalid)
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
, m_gpioDirectory(QDir(QString("/sys/class/gpio/gpio%1").arg(QString::number(gpio))))
|
, m_gpioDirectory(QDir(QString("/sys/class/gpio/gpio%1").arg(QString::number(gpio))))
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
qRegisterMetaType<Gpio::Value>();
|
qRegisterMetaType<Gpio::Value>();
|
||||||
}
|
}
|
||||||
@ -144,16 +230,33 @@ Gpio::~Gpio()
|
|||||||
unexportGpio();
|
unexportGpio();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns true if the directories \tt {/sys/class/gpio} and \tt {/sys/class/gpio/export} do exist. */
|
/*! Returns true if GPIO support is available on this system. */
|
||||||
bool Gpio::isAvailable()
|
bool Gpio::isAvailable()
|
||||||
{
|
{
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
return QFile("/sys/class/gpio/export").exists();
|
return QFile("/sys/class/gpio/export").exists();
|
||||||
|
#else
|
||||||
|
gpiod_chip_iter *iter = gpiod_chip_iter_new();
|
||||||
|
if (!iter)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
gpiod_chip *chip = gpiod_chip_iter_next(iter);
|
||||||
|
const bool available = chip != nullptr;
|
||||||
|
gpiod_chip_iter_free(iter);
|
||||||
|
return available;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns the directory \tt {/sys/class/gpio/gpio<number>} of this Gpio. */
|
/*! Returns the GPIO directory for sysfs builds or the gpiochip device path for libgpiod builds. */
|
||||||
QString Gpio::gpioDirectory() const
|
QString Gpio::gpioDirectory() const
|
||||||
{
|
{
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
return m_gpioDirectory.canonicalPath();
|
return m_gpioDirectory.canonicalPath();
|
||||||
|
#else
|
||||||
|
if (m_chipName.isEmpty())
|
||||||
|
return QString();
|
||||||
|
return QString("/dev/%1").arg(m_chipName);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns the number of this Gpio.
|
/*! Returns the number of this Gpio.
|
||||||
@ -164,10 +267,11 @@ int Gpio::gpioNumber() const
|
|||||||
return m_gpio;
|
return m_gpio;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns true if this Gpio could be exported in the system file \tt {/sys/class/gpio/export}. If this Gpio is already exported, this function will return true. */
|
/*! Returns true if this Gpio could be prepared for use. If this Gpio is already prepared, this function will return true. */
|
||||||
bool Gpio::exportGpio()
|
bool Gpio::exportGpio()
|
||||||
{
|
{
|
||||||
qCDebug(dcGpio()) << "Export GPIO" << m_gpio;
|
qCDebug(dcGpio()) << "Export GPIO" << m_gpio;
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
// Check if already exported
|
// Check if already exported
|
||||||
if (m_gpioDirectory.exists()) {
|
if (m_gpioDirectory.exists()) {
|
||||||
qCDebug(dcGpio()) << "GPIO" << m_gpio << "already exported.";
|
qCDebug(dcGpio()) << "GPIO" << m_gpio << "already exported.";
|
||||||
@ -184,13 +288,41 @@ bool Gpio::exportGpio()
|
|||||||
out << m_gpio;
|
out << m_gpio;
|
||||||
exportFile.close();
|
exportFile.close();
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
if (m_line && m_chip) {
|
||||||
|
qCDebug(dcGpio()) << "GPIO" << m_gpio << "already opened.";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolveLine()) {
|
||||||
|
qCWarning(dcGpio()) << "Could not resolve GPIO" << m_gpio << "to a gpiochip.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray chipName = m_chipName.toLatin1();
|
||||||
|
m_chip = gpiod_chip_open_by_name(chipName.constData());
|
||||||
|
if (!m_chip) {
|
||||||
|
qCWarning(dcGpio()) << "Could not open gpiochip" << m_chipName << ":" << strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_line = gpiod_chip_get_line(m_chip, m_lineOffset);
|
||||||
|
if (!m_line) {
|
||||||
|
qCWarning(dcGpio()) << "Could not get line" << m_lineOffset << "from" << m_chipName << ":" << strerror(errno);
|
||||||
|
gpiod_chip_close(m_chip);
|
||||||
|
m_chip = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns true if this Gpio could be unexported in the system file \tt {/sys/class/gpio/unexport}. */
|
/*! Returns true if this Gpio could be released. */
|
||||||
bool Gpio::unexportGpio()
|
bool Gpio::unexportGpio()
|
||||||
{
|
{
|
||||||
qCDebug(dcGpio()) << "Unexport GPIO" << m_gpio;
|
qCDebug(dcGpio()) << "Unexport GPIO" << m_gpio;
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
QFile unexportFile("/sys/class/gpio/unexport");
|
QFile unexportFile("/sys/class/gpio/unexport");
|
||||||
if (!unexportFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
if (!unexportFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
qCWarning(dcGpio()) << "Could not open GPIO unexport file:" << unexportFile.errorString();
|
qCWarning(dcGpio()) << "Could not open GPIO unexport file:" << unexportFile.errorString();
|
||||||
@ -201,6 +333,22 @@ bool Gpio::unexportGpio()
|
|||||||
out << m_gpio;
|
out << m_gpio;
|
||||||
unexportFile.close();
|
unexportFile.close();
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
if (m_line) {
|
||||||
|
if (gpiod_line_is_requested(m_line))
|
||||||
|
gpiod_line_release(m_line);
|
||||||
|
m_line = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_chip) {
|
||||||
|
gpiod_chip_close(m_chip);
|
||||||
|
m_chip = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_direction = Gpio::DirectionInvalid;
|
||||||
|
m_edge = Gpio::EdgeNone;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns true if the \a direction of this GPIO could be set. \sa Gpio::Direction, */
|
/*! Returns true if the \a direction of this GPIO could be set. \sa Gpio::Direction, */
|
||||||
@ -212,6 +360,7 @@ bool Gpio::setDirection(Gpio::Direction direction)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
QFile directionFile(m_gpioDirectory.path() + QDir::separator() + "direction");
|
QFile directionFile(m_gpioDirectory.path() + QDir::separator() + "direction");
|
||||||
if (!directionFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
if (!directionFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "direction file:" << directionFile.errorString();
|
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "direction file:" << directionFile.errorString();
|
||||||
@ -234,11 +383,35 @@ bool Gpio::setDirection(Gpio::Direction direction)
|
|||||||
|
|
||||||
directionFile.close();
|
directionFile.close();
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
if (!m_line && !exportGpio()) {
|
||||||
|
qCWarning(dcGpio()) << "GPIO" << m_gpio << "is not available.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int outputValue = 0;
|
||||||
|
if (m_line && gpiod_line_is_requested(m_line)) {
|
||||||
|
const int current = gpiod_line_get_value(m_line);
|
||||||
|
if (current >= 0)
|
||||||
|
outputValue = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requestLine(direction, EdgeNone, outputValue)) {
|
||||||
|
qCWarning(dcGpio()) << "Could not request GPIO" << m_gpio << "direction" << direction << ":" << strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_direction = direction;
|
||||||
|
if (direction == DirectionOutput)
|
||||||
|
m_edge = EdgeNone;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns the direction of this Gpio. */
|
/*! Returns the direction of this Gpio. */
|
||||||
Gpio::Direction Gpio::direction()
|
Gpio::Direction Gpio::direction()
|
||||||
{
|
{
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
QFile directionFile(m_gpioDirectory.path() + QDir::separator() + "direction");
|
QFile directionFile(m_gpioDirectory.path() + QDir::separator() + "direction");
|
||||||
if (!directionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!directionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "direction file:" << directionFile.fileName() << directionFile.errorString();
|
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "direction file:" << directionFile.fileName() << directionFile.errorString();
|
||||||
@ -259,6 +432,9 @@ Gpio::Direction Gpio::direction()
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Gpio::DirectionInvalid;
|
return Gpio::DirectionInvalid;
|
||||||
|
#else
|
||||||
|
return m_direction;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns true if the digital \a value of this Gpio could be set correctly. */
|
/*! Returns true if the digital \a value of this Gpio could be set correctly. */
|
||||||
@ -283,6 +459,7 @@ bool Gpio::setValue(Gpio::Value value)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
QFile valueFile(m_gpioDirectory.path() + QDir::separator() + "value");
|
QFile valueFile(m_gpioDirectory.path() + QDir::separator() + "value");
|
||||||
if (!valueFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
if (!valueFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "value file:" << valueFile.errorString();
|
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "value file:" << valueFile.errorString();
|
||||||
@ -304,11 +481,29 @@ bool Gpio::setValue(Gpio::Value value)
|
|||||||
|
|
||||||
valueFile.close();
|
valueFile.close();
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
if (!m_line || !gpiod_line_is_requested(m_line)) {
|
||||||
|
qCWarning(dcGpio()) << "GPIO" << m_gpio << "is not requested.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int physicalValue = logicalToPhysicalValue(value);
|
||||||
|
if (physicalValue < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (gpiod_line_set_value(m_line, physicalValue) < 0) {
|
||||||
|
qCWarning(dcGpio()) << "Could not set GPIO" << m_gpio << "value:" << strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns the current digital value of this Gpio. */
|
/*! Returns the current digital value of this Gpio. */
|
||||||
Gpio::Value Gpio::value()
|
Gpio::Value Gpio::value()
|
||||||
{
|
{
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
QFile valueFile(m_gpioDirectory.path() + QDir::separator() + "value");
|
QFile valueFile(m_gpioDirectory.path() + QDir::separator() + "value");
|
||||||
if (!valueFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!valueFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "value file:" << valueFile.errorString();
|
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "value file:" << valueFile.errorString();
|
||||||
@ -327,6 +522,20 @@ Gpio::Value Gpio::value()
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Gpio::ValueInvalid;
|
return Gpio::ValueInvalid;
|
||||||
|
#else
|
||||||
|
if (!m_line || !gpiod_line_is_requested(m_line)) {
|
||||||
|
qCWarning(dcGpio()) << "GPIO" << m_gpio << "is not requested.";
|
||||||
|
return Gpio::ValueInvalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int value = gpiod_line_get_value(m_line);
|
||||||
|
if (value < 0) {
|
||||||
|
qCWarning(dcGpio()) << "Could not read GPIO" << m_gpio << "value:" << strerror(errno);
|
||||||
|
return Gpio::ValueInvalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return physicalToLogicalValue(value);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! This method allows to invert the logic of this Gpio. Returns true, if the GPIO could be set \a activeLow. */
|
/*! This method allows to invert the logic of this Gpio. Returns true, if the GPIO could be set \a activeLow. */
|
||||||
@ -334,6 +543,7 @@ bool Gpio::setActiveLow(bool activeLow)
|
|||||||
{
|
{
|
||||||
qCDebug(dcGpio()) << "Set GPIO" << m_gpio << "active low" << activeLow;
|
qCDebug(dcGpio()) << "Set GPIO" << m_gpio << "active low" << activeLow;
|
||||||
|
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
QFile activeLowFile(m_gpioDirectory.path() + QDir::separator() + "active_low");
|
QFile activeLowFile(m_gpioDirectory.path() + QDir::separator() + "active_low");
|
||||||
if (!activeLowFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
if (!activeLowFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "active_low file:" << activeLowFile.errorString();
|
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "active_low file:" << activeLowFile.errorString();
|
||||||
@ -349,11 +559,16 @@ bool Gpio::setActiveLow(bool activeLow)
|
|||||||
|
|
||||||
activeLowFile.close();
|
activeLowFile.close();
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
m_activeLow = activeLow;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns true if the logic of this Gpio is inverted (1 = low, 0 = high). */
|
/*! Returns true if the logic of this Gpio is inverted (1 = low, 0 = high). */
|
||||||
bool Gpio::activeLow()
|
bool Gpio::activeLow()
|
||||||
{
|
{
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
QFile activeLowFile(m_gpioDirectory.path() + QDir::separator() + "active_low");
|
QFile activeLowFile(m_gpioDirectory.path() + QDir::separator() + "active_low");
|
||||||
if (!activeLowFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!activeLowFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "active_low file:" << activeLowFile.errorString();
|
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "active_low file:" << activeLowFile.errorString();
|
||||||
@ -369,6 +584,9 @@ bool Gpio::activeLow()
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
#else
|
||||||
|
return m_activeLow;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns true if the \a edge of this GPIO could be set correctly. The \a edge parameter specifies, when an interrupt occurs. */
|
/*! Returns true if the \a edge of this GPIO could be set correctly. The \a edge parameter specifies, when an interrupt occurs. */
|
||||||
@ -380,6 +598,7 @@ bool Gpio::setEdgeInterrupt(Gpio::Edge edge)
|
|||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(dcGpio()) << "Set GPIO" << m_gpio << "edge interrupt" << edge;
|
qCDebug(dcGpio()) << "Set GPIO" << m_gpio << "edge interrupt" << edge;
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
QFile edgeFile(m_gpioDirectory.path() + QDir::separator() + "edge");
|
QFile edgeFile(m_gpioDirectory.path() + QDir::separator() + "edge");
|
||||||
if (!edgeFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
if (!edgeFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "edge file:" << edgeFile.errorString();
|
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "edge file:" << edgeFile.errorString();
|
||||||
@ -404,11 +623,27 @@ bool Gpio::setEdgeInterrupt(Gpio::Edge edge)
|
|||||||
|
|
||||||
edgeFile.close();
|
edgeFile.close();
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
if (!m_line && !exportGpio()) {
|
||||||
|
qCWarning(dcGpio()) << "GPIO" << m_gpio << "is not available.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requestLine(DirectionInput, edge, 0)) {
|
||||||
|
qCWarning(dcGpio()) << "Could not request GPIO" << m_gpio << "edge interrupt:" << strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_direction = DirectionInput;
|
||||||
|
m_edge = edge;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns the edge interrupt of this Gpio. */
|
/*! Returns the edge interrupt of this Gpio. */
|
||||||
Gpio::Edge Gpio::edgeInterrupt()
|
Gpio::Edge Gpio::edgeInterrupt()
|
||||||
{
|
{
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
QFile edgeFile(m_gpioDirectory.path() + QDir::separator() + "edge");
|
QFile edgeFile(m_gpioDirectory.path() + QDir::separator() + "edge");
|
||||||
if (!edgeFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!edgeFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "edge file:" << edgeFile.errorString();
|
qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "edge file:" << edgeFile.errorString();
|
||||||
@ -431,8 +666,88 @@ Gpio::Edge Gpio::edgeInterrupt()
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Gpio::EdgeNone;
|
return Gpio::EdgeNone;
|
||||||
|
#else
|
||||||
|
return m_edge;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef NYMEA_GPIO_USE_SYSFS
|
||||||
|
bool Gpio::resolveLine()
|
||||||
|
{
|
||||||
|
if (!m_chipName.isEmpty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (resolveLineFromSysfs(m_gpio, &m_chipName, &m_lineOffset))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (resolveLineSequential(m_gpio, &m_chipName, &m_lineOffset))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Gpio::requestLine(Gpio::Direction direction, Gpio::Edge edge, int outputValue)
|
||||||
|
{
|
||||||
|
if (!m_line)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (gpiod_line_is_requested(m_line))
|
||||||
|
gpiod_line_release(m_line);
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
if (direction == DirectionOutput) {
|
||||||
|
ret = gpiod_line_request_output_flags(m_line, kGpioConsumer, 0, outputValue);
|
||||||
|
} else {
|
||||||
|
switch (edge) {
|
||||||
|
case EdgeRising:
|
||||||
|
ret = gpiod_line_request_rising_edge_events_flags(m_line, kGpioConsumer, 0);
|
||||||
|
break;
|
||||||
|
case EdgeFalling:
|
||||||
|
ret = gpiod_line_request_falling_edge_events_flags(m_line, kGpioConsumer, 0);
|
||||||
|
break;
|
||||||
|
case EdgeBoth:
|
||||||
|
ret = gpiod_line_request_both_edges_events_flags(m_line, kGpioConsumer, 0);
|
||||||
|
break;
|
||||||
|
case EdgeNone:
|
||||||
|
ret = gpiod_line_request_input_flags(m_line, kGpioConsumer, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Gpio::logicalToPhysicalValue(Gpio::Value value) const
|
||||||
|
{
|
||||||
|
switch (value) {
|
||||||
|
case ValueLow:
|
||||||
|
return m_activeLow ? 1 : 0;
|
||||||
|
case ValueHigh:
|
||||||
|
return m_activeLow ? 0 : 1;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Gpio::Value Gpio::physicalToLogicalValue(int value) const
|
||||||
|
{
|
||||||
|
if (value < 0)
|
||||||
|
return ValueInvalid;
|
||||||
|
|
||||||
|
const bool physicalHigh = value != 0;
|
||||||
|
const bool logicalHigh = m_activeLow ? !physicalHigh : physicalHigh;
|
||||||
|
return logicalHigh ? ValueHigh : ValueLow;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Gpio::eventFd() const
|
||||||
|
{
|
||||||
|
if (!m_line)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return gpiod_line_event_get_fd(m_line);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*! Prints the given \a gpio to \a debug. */
|
/*! Prints the given \a gpio to \a debug. */
|
||||||
QDebug operator<<(QDebug debug, Gpio *gpio)
|
QDebug operator<<(QDebug debug, Gpio *gpio)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -35,10 +35,17 @@
|
|||||||
|
|
||||||
Q_DECLARE_LOGGING_CATEGORY(dcGpio)
|
Q_DECLARE_LOGGING_CATEGORY(dcGpio)
|
||||||
|
|
||||||
|
class GpioMonitor;
|
||||||
|
|
||||||
|
struct gpiod_chip;
|
||||||
|
struct gpiod_line;
|
||||||
|
|
||||||
class Gpio : public QObject
|
class Gpio : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
friend class GpioMonitor;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Direction {
|
enum Direction {
|
||||||
DirectionInvalid,
|
DirectionInvalid,
|
||||||
@ -86,9 +93,25 @@ public:
|
|||||||
Gpio::Edge edgeInterrupt();
|
Gpio::Edge edgeInterrupt();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#ifndef NYMEA_GPIO_USE_SYSFS
|
||||||
|
bool resolveLine();
|
||||||
|
bool requestLine(Gpio::Direction direction, Gpio::Edge edge, int outputValue);
|
||||||
|
int logicalToPhysicalValue(Gpio::Value value) const;
|
||||||
|
Gpio::Value physicalToLogicalValue(int value) const;
|
||||||
|
int eventFd() const;
|
||||||
|
|
||||||
|
QString m_chipName;
|
||||||
|
unsigned int m_lineOffset = 0;
|
||||||
|
gpiod_chip *m_chip = nullptr;
|
||||||
|
gpiod_line *m_line = nullptr;
|
||||||
|
bool m_activeLow = false;
|
||||||
|
Gpio::Edge m_edge = Gpio::EdgeNone;
|
||||||
|
#endif
|
||||||
int m_gpio = 0;
|
int m_gpio = 0;
|
||||||
Gpio::Direction m_direction = Gpio::DirectionOutput;
|
Gpio::Direction m_direction = Gpio::DirectionOutput;
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
QDir m_gpioDirectory;
|
QDir m_gpioDirectory;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
QDebug operator<<(QDebug debug, Gpio *gpio);
|
QDebug operator<<(QDebug debug, Gpio *gpio);
|
||||||
|
|||||||
@ -70,12 +70,20 @@
|
|||||||
|
|
||||||
#include "gpiomonitor.h"
|
#include "gpiomonitor.h"
|
||||||
|
|
||||||
|
#ifndef NYMEA_GPIO_USE_SYSFS
|
||||||
|
#include <errno.h>
|
||||||
|
#include <gpiod.h>
|
||||||
|
#include <string.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/*! Constructs a \l{GpioMonitor} object with the given \a gpio number and \a parent. */
|
/*! Constructs a \l{GpioMonitor} object with the given \a gpio number and \a parent. */
|
||||||
GpioMonitor::GpioMonitor(int gpio, QObject *parent)
|
GpioMonitor::GpioMonitor(int gpio, QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_gpioNumber(gpio)
|
, m_gpioNumber(gpio)
|
||||||
{
|
{
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
m_valueFile.setFileName("/sys/class/gpio/gpio" + QString::number(m_gpioNumber) + "/value");
|
m_valueFile.setFileName("/sys/class/gpio/gpio" + QString::number(m_gpioNumber) + "/value");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns true if this \l{GpioMonitor} could be enabled successfully. With the \a activeLow parameter the values can be inverted.
|
/*! Returns true if this \l{GpioMonitor} could be enabled successfully. With the \a activeLow parameter the values can be inverted.
|
||||||
@ -91,6 +99,7 @@ bool GpioMonitor::enable(bool activeLow, Gpio::Edge edgeInterrupt)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
if (!m_valueFile.open(QFile::ReadOnly)) {
|
if (!m_valueFile.open(QFile::ReadOnly)) {
|
||||||
qWarning(dcGpio()) << "GpioMonitor: Could not open value file for gpio monitor" << m_gpio->gpioNumber();
|
qWarning(dcGpio()) << "GpioMonitor: Could not open value file for gpio monitor" << m_gpio->gpioNumber();
|
||||||
return false;
|
return false;
|
||||||
@ -98,6 +107,23 @@ bool GpioMonitor::enable(bool activeLow, Gpio::Edge edgeInterrupt)
|
|||||||
|
|
||||||
m_notifier = new QSocketNotifier(m_valueFile.handle(), QSocketNotifier::Exception);
|
m_notifier = new QSocketNotifier(m_valueFile.handle(), QSocketNotifier::Exception);
|
||||||
connect(m_notifier, &QSocketNotifier::activated, this, &GpioMonitor::readyReady);
|
connect(m_notifier, &QSocketNotifier::activated, this, &GpioMonitor::readyReady);
|
||||||
|
#else
|
||||||
|
m_eventFd = m_gpio->eventFd();
|
||||||
|
if (m_eventFd < 0) {
|
||||||
|
qCWarning(dcGpio()) << "GpioMonitor: Could not get event file descriptor for GPIO" << m_gpio->gpioNumber();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_notifier = new QSocketNotifier(m_eventFd, QSocketNotifier::Read);
|
||||||
|
connect(m_notifier, &QSocketNotifier::activated, this, &GpioMonitor::readyReady);
|
||||||
|
|
||||||
|
const Gpio::Value initialValue = m_gpio->value();
|
||||||
|
if (initialValue == Gpio::ValueInvalid) {
|
||||||
|
qCWarning(dcGpio()) << "GpioMonitor: Could not read initial value for GPIO" << m_gpio->gpioNumber();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_currentValue = (initialValue == Gpio::ValueHigh);
|
||||||
|
#endif
|
||||||
|
|
||||||
qCDebug(dcGpio()) << "Socket notififier started";
|
qCDebug(dcGpio()) << "Socket notififier started";
|
||||||
m_notifier->setEnabled(true);
|
m_notifier->setEnabled(true);
|
||||||
@ -113,7 +139,11 @@ void GpioMonitor::disable()
|
|||||||
m_notifier = 0;
|
m_notifier = 0;
|
||||||
m_gpio = 0;
|
m_gpio = 0;
|
||||||
|
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
m_valueFile.close();
|
m_valueFile.close();
|
||||||
|
#else
|
||||||
|
m_eventFd = -1;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Returns true if this \l{GpioMonitor} is running. */
|
/*! Returns true if this \l{GpioMonitor} is running. */
|
||||||
@ -141,6 +171,7 @@ void GpioMonitor::readyReady(const int &ready)
|
|||||||
{
|
{
|
||||||
Q_UNUSED(ready)
|
Q_UNUSED(ready)
|
||||||
|
|
||||||
|
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||||
m_valueFile.seek(0);
|
m_valueFile.seek(0);
|
||||||
QByteArray data = m_valueFile.readAll();
|
QByteArray data = m_valueFile.readAll();
|
||||||
|
|
||||||
@ -155,4 +186,22 @@ void GpioMonitor::readyReady(const int &ready)
|
|||||||
|
|
||||||
m_currentValue = value;
|
m_currentValue = value;
|
||||||
emit valueChanged(value);
|
emit valueChanged(value);
|
||||||
|
#else
|
||||||
|
if (m_eventFd < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gpiod_line_event event;
|
||||||
|
if (gpiod_line_event_read_fd(m_eventFd, &event) < 0) {
|
||||||
|
qCWarning(dcGpio()) << "GpioMonitor: Could not read GPIO event:" << strerror(errno);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Gpio::Value current = m_gpio ? m_gpio->value() : Gpio::ValueInvalid;
|
||||||
|
if (current == Gpio::ValueInvalid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const bool value = current == Gpio::ValueHigh;
|
||||||
|
m_currentValue = value;
|
||||||
|
emit valueChanged(value);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,9 @@ private:
|
|||||||
QSocketNotifier *m_notifier;
|
QSocketNotifier *m_notifier;
|
||||||
QFile m_valueFile;
|
QFile m_valueFile;
|
||||||
bool m_currentValue;
|
bool m_currentValue;
|
||||||
|
#ifndef NYMEA_GPIO_USE_SYSFS
|
||||||
|
int m_eventFd = -1;
|
||||||
|
#endif
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void valueChanged(const bool &value);
|
void valueChanged(const bool &value);
|
||||||
|
|||||||
@ -13,6 +13,12 @@ SOURCES += \
|
|||||||
gpiobutton.cpp \
|
gpiobutton.cpp \
|
||||||
gpiomonitor.cpp
|
gpiomonitor.cpp
|
||||||
|
|
||||||
|
!contains(CONFIG, nymea_gpio_sysfs) {
|
||||||
|
CONFIG += link_pkgconfig
|
||||||
|
PKGCONFIG += libgpiod
|
||||||
|
QMAKE_PKGCONFIG_REQUIRES += libgpiod
|
||||||
|
}
|
||||||
|
|
||||||
target.path = $$[QT_INSTALL_LIBS]
|
target.path = $$[QT_INSTALL_LIBS]
|
||||||
INSTALLS += target
|
INSTALLS += target
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,13 @@ greaterThan(QT_MAJOR_VERSION, 5) {
|
|||||||
DEFINES += QT_DISABLE_DEPRECATED_UP_TO=0x050F00
|
DEFINES += QT_DISABLE_DEPRECATED_UP_TO=0x050F00
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contains(CONFIG, nymea_gpio_sysfs) {
|
||||||
|
message("Building with legacy sysfs GPIO backend")
|
||||||
|
DEFINES += NYMEA_GPIO_USE_SYSFS
|
||||||
|
} else {
|
||||||
|
message("Building with libgpiod GPIO backend")
|
||||||
|
}
|
||||||
|
|
||||||
QMAKE_CXXFLAGS += -Werror -g
|
QMAKE_CXXFLAGS += -Werror -g
|
||||||
|
|
||||||
gcc {
|
gcc {
|
||||||
|
|||||||
Reference in New Issue
Block a user