// SPDX-License-Identifier: LGPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * nymea-gpio * GPIO library for nymea * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of nymea-gpio. * * nymea-gpio is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * nymea-gpio is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with nymea-gpio. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /*! \class Gpio \brief Represents a system GPIO in linux systems. \inmodule nymea-gpio \ingroup gpio A "General Purpose Input/Output" (GPIO) is a flexible software-controlled digital signal. They are provided from many kinds of chip, and are familiar to Linux developers working with embedded and custom hardware. Each GPIO represents a bit connected to a particular pin, or "ball" on Ball Grid Array (BGA) packages. Board schematics show which external hardware connects to which GPIOs. Drivers can be written generically, so that board setup code passes such pin configuration data to drivers (\l{https://www.kernel.org/doc/Documentation/gpio/gpio.txt}{source}). General Purpose Input/Output (a.k.a. GPIO) is a generic pin on a chip whose behavior (including whether it is an input or output pin) can be controlled through this class. An object of of the Gpio class represents a pin. \code Gpio *gpioOut = new Gpio(23, this); // Export Gpio if (!gpioOut->exportGpio()) { qWarning() << "Could not export Gpio" << gpioOut->gpioNumber(); gpioOut->deleteLater(); return; } // Configure Gpio direction if (!gpioOut->setDirection(PiGpio::DirectionOutput)) { qWarning() << "Could not set direction of Gpio" << gpioOut->gpioNumber(); gpioOut->deleteLater(); return; } gpioOut->setValue(Gpio::ValueHigh) \endcode \code Gpio *gpioIn = new Gpio(24, this); // Export Gpio if (!gpioIn->exportGpio()) { qWarning() << "Could not export Gpio" << gpioIn->gpioNumber(); gpioIn->deleteLater(); return; } // Configure Gpio direction if (!gpioIn->setDirection(PiGpio::DirectionInput)) { qWarning() << "Could not set direction of Gpio" << gpioIn->gpioNumber(); gpioIn->deleteLater(); return; } qDebug() << "Current value" << gpioIn->value(); \endcode \sa GpioMonitor */ /*! \enum Gpio::Direction This enum type specifies the dirction a Gpio. \value DirectionInput The Gpio is configured as \b input. \value DirectionOutput The Gpio is configured as \b output. \value DirectionInvalid The direction is not valid. */ /*! \enum Gpio::Value This enum type specifies the value a Gpio. \value ValueInvalid The value is not valid. \value ValueLow The Gpio is low. \value ValueHigh The Gpio is high. */ /*! \enum Gpio::Edge This enum type specifies the edge interrupt type of a Gpio. \value EdgeFalling The Gpio reacts on falling edge interrupt. \value EdgeRising The Gpio reacts on rising edge interrupt. \value EdgeBoth The Gpio reacts on both, rising and falling edge interrupt. \value EdgeNone The Gpio does not react on interrupts. */ #include "gpio.h" #include #include #ifndef NYMEA_GPIO_USE_SYSFS #include #include #include #endif 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(gpioNumber - base); return true; } } return false; } unsigned int chipNumLines(gpiod_chip *chip) { #if defined(NYMEA_GPIO_LIBGPIOD_V2) gpiod_chip_info *info = gpiod_chip_get_info(chip); if (!info) return 0; const unsigned int numLines = gpiod_chip_info_get_num_lines(info); gpiod_chip_info_free(info); return numLines; #else return gpiod_chip_num_lines(chip); #endif } bool resolveLineSequential(int gpioNumber, QString *chipName, unsigned int *offset) { if (gpioNumber < 0) return false; QDir devDir("/dev"); const QStringList entries = devDir.entryList(QStringList() << "gpiochip*", QDir::System | QDir::Files, QDir::Name); unsigned int base = 0; for (const QString &entry : entries) { const QString chipPath = devDir.filePath(entry); gpiod_chip *chip = gpiod_chip_open(chipPath.toLatin1().constData()); if (!chip) continue; const unsigned int numLines = chipNumLines(chip); gpiod_chip_close(chip); if (numLines == 0) continue; if (static_cast(gpioNumber) < base + numLines) { *chipName = entry; *offset = static_cast(gpioNumber) - base; return true; } base += numLines; } return false; } } // namespace #endif /*! Constructs a Gpio object to represent a GPIO with the given \a gpio number and \a parent. */ Gpio::Gpio(int gpio, QObject *parent) : QObject(parent) , m_gpio(gpio) , m_direction(Gpio::DirectionInvalid) #ifdef NYMEA_GPIO_USE_SYSFS , m_gpioDirectory(QDir(QString("/sys/class/gpio/gpio%1").arg(QString::number(gpio)))) #endif { qRegisterMetaType(); } /*! Destroys and unexports the Gpio. */ Gpio::~Gpio() { unexportGpio(); } /*! Returns true if GPIO support is available on this system. */ bool Gpio::isAvailable() { #ifdef NYMEA_GPIO_USE_SYSFS return QFile("/sys/class/gpio/export").exists(); #else QDir devDir("/dev"); const QStringList entries = devDir.entryList(QStringList() << "gpiochip*", QDir::System | QDir::Files, QDir::Name); for (const QString &entry : entries) { const QString chipPath = devDir.filePath(entry); gpiod_chip *chip = gpiod_chip_open(chipPath.toLatin1().constData()); if (chip) { gpiod_chip_close(chip); return true; } } return false; #endif } /*! Returns the GPIO directory for sysfs builds or the gpiochip device path for libgpiod builds. */ QString Gpio::gpioDirectory() const { #ifdef NYMEA_GPIO_USE_SYSFS 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. \note The Gpio number is mostly not equivalent with the pin number. */ int Gpio::gpioNumber() const { return m_gpio; } /*! Returns true if this Gpio could be prepared for use. If this Gpio is already prepared, this function will return true. */ bool Gpio::exportGpio() { qCDebug(dcGpio()) << "Export GPIO" << m_gpio; #ifdef NYMEA_GPIO_USE_SYSFS // Check if already exported if (m_gpioDirectory.exists()) { qCDebug(dcGpio()) << "GPIO" << m_gpio << "already exported."; return true; } QFile exportFile("/sys/class/gpio/export"); if (!exportFile.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(dcGpio()) << "Could not open GPIO export file:" << exportFile.errorString(); return false; } QTextStream out(&exportFile); out << m_gpio; exportFile.close(); return true; #else if ( #if defined(NYMEA_GPIO_LIBGPIOD_V2) m_chip #else m_line && m_chip #endif ) { 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 QString chipPath = QString("/dev/%1").arg(m_chipName); m_chip = gpiod_chip_open(chipPath.toLatin1().constData()); if (!m_chip) { qCWarning(dcGpio()) << "Could not open gpiochip" << chipPath << ":" << strerror(errno); return false; } #if !defined(NYMEA_GPIO_LIBGPIOD_V2) 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; } #endif return true; #endif } /*! Returns true if this Gpio could be released. */ bool Gpio::unexportGpio() { qCDebug(dcGpio()) << "Unexport GPIO" << m_gpio; #ifdef NYMEA_GPIO_USE_SYSFS QFile unexportFile("/sys/class/gpio/unexport"); if (!unexportFile.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(dcGpio()) << "Could not open GPIO unexport file:" << unexportFile.errorString(); return false; } QTextStream out(&unexportFile); out << m_gpio; unexportFile.close(); return true; #else if ( #if defined(NYMEA_GPIO_LIBGPIOD_V2) m_request #else m_line #endif ) { #if defined(NYMEA_GPIO_LIBGPIOD_V2) gpiod_line_request_release(m_request); m_request = nullptr; #else if (gpiod_line_is_requested(m_line)) gpiod_line_release(m_line); m_line = nullptr; #endif } 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, */ bool Gpio::setDirection(Gpio::Direction direction) { qCDebug(dcGpio()) << "Set GPIO" << m_gpio << "direction" << direction; if (direction == Gpio::DirectionInvalid) { qCWarning(dcGpio()) << "Setting an invalid direction is forbidden."; return false; } #ifdef NYMEA_GPIO_USE_SYSFS QFile directionFile(m_gpioDirectory.path() + QDir::separator() + "direction"); if (!directionFile.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "direction file:" << directionFile.errorString(); return false; } m_direction = direction; QTextStream out(&directionFile); switch (m_direction) { case DirectionInput: out << "in"; break; case DirectionOutput: out << "out"; break; default: break; } directionFile.close(); return true; #else if ( #if defined(NYMEA_GPIO_LIBGPIOD_V2) !m_chip #else !m_line #endif && !exportGpio()) { qCWarning(dcGpio()) << "GPIO" << m_gpio << "is not available."; return false; } int outputValue = 0; #if defined(NYMEA_GPIO_LIBGPIOD_V2) if (m_request) { const Gpio::Value currentValue = value(); if (currentValue != Gpio::ValueInvalid) outputValue = logicalToPhysicalValue(currentValue); } #else if (m_line && gpiod_line_is_requested(m_line)) { const int current = gpiod_line_get_value(m_line); if (current >= 0) outputValue = current; } #endif 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. */ Gpio::Direction Gpio::direction() { #ifdef NYMEA_GPIO_USE_SYSFS QFile directionFile(m_gpioDirectory.path() + QDir::separator() + "direction"); if (!directionFile.open(QIODevice::ReadOnly | QIODevice::Text)) { qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "direction file:" << directionFile.fileName() << directionFile.errorString(); return Gpio::DirectionInvalid; } QString direction; QTextStream in(&directionFile); in >> direction; directionFile.close(); if (direction == "in") { m_direction = DirectionInput; return Gpio::DirectionInput; } else if (direction == "out") { m_direction = DirectionOutput; return Gpio::DirectionOutput; } return Gpio::DirectionInvalid; #else return m_direction; #endif } /*! Returns true if the digital \a value of this Gpio could be set correctly. */ bool Gpio::setValue(Gpio::Value value) { qCDebug(dcGpio()) << "Set GPIO" << m_gpio << "value" << value; // Check given value if (value == Gpio::ValueInvalid) { qCWarning(dcGpio()) << "Setting an invalid value is forbidden."; return false; } // Check current direction if (m_direction == Gpio::DirectionInput) { qCWarning(dcGpio()) << "Setting the value of an input GPIO is forbidden."; return false; } if (m_direction == Gpio::DirectionInvalid) { qCWarning(dcGpio()) << "The direction of GPIO" << m_gpio << "is invalid."; return false; } #ifdef NYMEA_GPIO_USE_SYSFS QFile valueFile(m_gpioDirectory.path() + QDir::separator() + "value"); if (!valueFile.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "value file:" << valueFile.errorString(); return false; } QTextStream out(&valueFile); switch (value) { case ValueLow: out << "0"; break; case ValueHigh: out << "1"; break; default: valueFile.close(); return false; } valueFile.close(); return true; #else #if defined(NYMEA_GPIO_LIBGPIOD_V2) if (!m_request) { qCWarning(dcGpio()) << "GPIO" << m_gpio << "is not requested."; return false; } const int physicalValue = logicalToPhysicalValue(value); if (physicalValue < 0) return false; if (gpiod_line_request_set_value(m_request, m_lineOffset, physicalValue ? GPIOD_LINE_VALUE_ACTIVE : GPIOD_LINE_VALUE_INACTIVE) < 0) { qCWarning(dcGpio()) << "Could not set GPIO" << m_gpio << "value:" << strerror(errno); return false; } 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 #endif } /*! Returns the current digital value of this Gpio. */ Gpio::Value Gpio::value() { #ifdef NYMEA_GPIO_USE_SYSFS QFile valueFile(m_gpioDirectory.path() + QDir::separator() + "value"); if (!valueFile.open(QIODevice::ReadOnly | QIODevice::Text)) { qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "value file:" << valueFile.errorString(); return Gpio::ValueInvalid; } QString value; QTextStream in(&valueFile); in >> value; valueFile.close(); if (value == "0") { return Gpio::ValueLow; } else if (value == "1") { return Gpio::ValueHigh; } return Gpio::ValueInvalid; #else #if defined(NYMEA_GPIO_LIBGPIOD_V2) if (!m_request) { qCWarning(dcGpio()) << "GPIO" << m_gpio << "is not requested."; return Gpio::ValueInvalid; } const gpiod_line_value value = gpiod_line_request_get_value(m_request, m_lineOffset); if (value == GPIOD_LINE_VALUE_ERROR) { qCWarning(dcGpio()) << "Could not read GPIO" << m_gpio << "value:" << strerror(errno); return Gpio::ValueInvalid; } const int physicalValue = (value == GPIOD_LINE_VALUE_ACTIVE) ? 1 : 0; return physicalToLogicalValue(physicalValue); #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 #endif } /*! This method allows to invert the logic of this Gpio. Returns true, if the GPIO could be set \a activeLow. */ bool Gpio::setActiveLow(bool activeLow) { qCDebug(dcGpio()) << "Set GPIO" << m_gpio << "active low" << activeLow; #ifdef NYMEA_GPIO_USE_SYSFS QFile activeLowFile(m_gpioDirectory.path() + QDir::separator() + "active_low"); if (!activeLowFile.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "active_low file:" << activeLowFile.errorString(); return false; } QTextStream out(&activeLowFile); if (activeLow) { out << "1"; } else { out << "0"; } activeLowFile.close(); return true; #else m_activeLow = activeLow; return true; #endif } /*! Returns true if the logic of this Gpio is inverted (1 = low, 0 = high). */ bool Gpio::activeLow() { #ifdef NYMEA_GPIO_USE_SYSFS QFile activeLowFile(m_gpioDirectory.path() + QDir::separator() + "active_low"); if (!activeLowFile.open(QIODevice::ReadOnly | QIODevice::Text)) { qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "active_low file:" << activeLowFile.errorString(); return false; } QString value; QTextStream in(&activeLowFile); in >> value; activeLowFile.close(); if (value == "1") return true; 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. */ bool Gpio::setEdgeInterrupt(Gpio::Edge edge) { if (m_direction == Gpio::DirectionOutput) { qCWarning(dcGpio()) << "Could not set edge interrupt, GPIO is configured as an output."; return false; } qCDebug(dcGpio()) << "Set GPIO" << m_gpio << "edge interrupt" << edge; #ifdef NYMEA_GPIO_USE_SYSFS QFile edgeFile(m_gpioDirectory.path() + QDir::separator() + "edge"); if (!edgeFile.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "edge file:" << edgeFile.errorString(); return false; } QTextStream out(&edgeFile); switch (edge) { case EdgeFalling: out << "falling"; break; case EdgeRising: out << "rising"; break; case EdgeBoth: out << "both"; break; case EdgeNone: out << "none"; break; } edgeFile.close(); return true; #else if ( #if defined(NYMEA_GPIO_LIBGPIOD_V2) !m_chip #else !m_line #endif && !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. */ Gpio::Edge Gpio::edgeInterrupt() { #ifdef NYMEA_GPIO_USE_SYSFS QFile edgeFile(m_gpioDirectory.path() + QDir::separator() + "edge"); if (!edgeFile.open(QIODevice::ReadOnly | QIODevice::Text)) { qCWarning(dcGpio()) << "Could not open GPIO" << m_gpio << "edge file:" << edgeFile.errorString(); return Gpio::EdgeNone; } QString edge; QTextStream in(&edgeFile); in >> edge; edgeFile.close(); if (edge.contains("falling")) { return Gpio::EdgeFalling; } else if (edge.contains("rising")) { return Gpio::EdgeRising; } else if (edge.contains("both")) { return Gpio::EdgeBoth; } else if (edge.contains("none")) { 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 defined(NYMEA_GPIO_LIBGPIOD_V2) if (!m_chip) return false; if (m_request) { gpiod_line_request_release(m_request); m_request = nullptr; } gpiod_line_settings *settings = gpiod_line_settings_new(); if (!settings) return false; gpiod_line_config *lineConfig = gpiod_line_config_new(); if (!lineConfig) { gpiod_line_settings_free(settings); return false; } gpiod_request_config *requestConfig = gpiod_request_config_new(); if (!requestConfig) { gpiod_line_config_free(lineConfig); gpiod_line_settings_free(settings); return false; } gpiod_request_config_set_consumer(requestConfig, kGpioConsumer); if (gpiod_line_settings_set_direction(settings, direction == DirectionOutput ? GPIOD_LINE_DIRECTION_OUTPUT : GPIOD_LINE_DIRECTION_INPUT) < 0) { gpiod_request_config_free(requestConfig); gpiod_line_config_free(lineConfig); gpiod_line_settings_free(settings); return false; } if (direction == DirectionOutput) { if (gpiod_line_settings_set_output_value(settings, outputValue ? GPIOD_LINE_VALUE_ACTIVE : GPIOD_LINE_VALUE_INACTIVE) < 0) { gpiod_request_config_free(requestConfig); gpiod_line_config_free(lineConfig); gpiod_line_settings_free(settings); return false; } } else { enum gpiod_line_edge gpiodEdge = GPIOD_LINE_EDGE_NONE; switch (edge) { case EdgeRising: gpiodEdge = GPIOD_LINE_EDGE_RISING; break; case EdgeFalling: gpiodEdge = GPIOD_LINE_EDGE_FALLING; break; case EdgeBoth: gpiodEdge = GPIOD_LINE_EDGE_BOTH; break; case EdgeNone: gpiodEdge = GPIOD_LINE_EDGE_NONE; break; } if (gpiod_line_settings_set_edge_detection(settings, gpiodEdge) < 0) { gpiod_request_config_free(requestConfig); gpiod_line_config_free(lineConfig); gpiod_line_settings_free(settings); return false; } } const unsigned int offsets[] = {m_lineOffset}; if (gpiod_line_config_add_line_settings(lineConfig, offsets, 1, settings) < 0) { gpiod_request_config_free(requestConfig); gpiod_line_config_free(lineConfig); gpiod_line_settings_free(settings); return false; } m_request = gpiod_chip_request_lines(m_chip, requestConfig, lineConfig); gpiod_request_config_free(requestConfig); gpiod_line_config_free(lineConfig); gpiod_line_settings_free(settings); if (!m_request) return false; return true; #else 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; #endif } 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 defined(NYMEA_GPIO_LIBGPIOD_V2) if (!m_request) return -1; return gpiod_line_request_get_fd(m_request); #else if (!m_line) return -1; return gpiod_line_event_get_fd(m_line); #endif } #endif /*! Prints the given \a gpio to \a debug. */ QDebug operator<<(QDebug debug, Gpio *gpio) { debug.nospace() << "Gpio(" << gpio->gpioNumber() << ", "; if (gpio->direction() == Gpio::DirectionInput) { debug.nospace() << "input, "; switch (gpio->edgeInterrupt()) { case Gpio::EdgeFalling: debug.nospace() << "edge: falling, "; break; case Gpio::EdgeRising: debug.nospace() << "edge: rising, "; break; case Gpio::EdgeBoth: debug.nospace() << "edge: both, "; break; case Gpio::EdgeNone: debug.nospace() << "edge: none, "; break; } } else if (gpio->direction() == Gpio::DirectionOutput) { debug.nospace() << "output, "; } else { debug.nospace() << "invalid, "; } if (gpio->activeLow()) { debug.nospace() << "active low: 1, "; } else { debug.nospace() << "active low: 0, "; } if (gpio->value() == Gpio::ValueHigh) { debug.nospace() << "value: 1"; } else if (gpio->value() == Gpio::ValueLow) { debug.nospace() << "value: 0"; } else { debug.nospace() << "value: invalid"; } debug.nospace() << ")"; return debug.space(); }