diff --git a/.gitignore b/.gitignore index 5291a38..8176148 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,5 @@ target_wrapper.* # QtCreator CMake CMakeLists.txt.user* + +.crossbuilder diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..8008a65 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,3 @@ +nymea-gpio (1.0.0) UNRELEASED; urgency=medium + + -- Simon Stürz Wed, 04 Sep 2019 11:50:53 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..06579e7 --- /dev/null +++ b/debian/control @@ -0,0 +1,48 @@ +Source: nymea-gpio +Section: utils +Priority: optional +Maintainer: Simon Stürz +Build-Depends: debhelper (>= 9.0.0), + dpkg-dev (>= 1.16.1~), + qt5-default, + qt5-qmake, + qtbase5-dev, + qtbase5-dev-tools +Standards-Version: 3.9.7 + +Package: libnymea-gpio +Section: libs +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends} +Description: Qt 5 based library for GPIO interaction. + Qt 5 based library for GPIO interaction. + +Package: nymea-gpio-tool +Section: tools +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, + libnymea-gpio (= ${binary:Version}) +Description: Qt 5 based tool for GPIO interaction. + Qt 5 based tool for GPIO interaction. + +Package: libnymea-gpio-dev +Section: libdevel +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, + pkg-config, + libnymea-gpio (= ${binary:Version}) +Description: Qt 5 based library for GPIO interaction - development files + Development files for Qt 5 based GPIO library. + +Package: libnymea-gpio-dbg +Priority: extra +Architecture: any +Section: debug +Depends: ${shlibs:Depends}, + ${misc:Depends}, + libnymea-gpio (= ${binary:Version}) +Description: Qt 5 based library for GPIO interaction - debug symbols + Debug Symbols for Qt 5 based GPIO library. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..6eb66da --- /dev/null +++ b/debian/copyright @@ -0,0 +1,19 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Author: Simon Stürz +Download: https://github.com/guh/nymea-gpio + +License: LGPL-3 + On Debian systems, the complete text of the GNU General + Public License can be found in `/usr/share/common-licenses/LGPL-3'. + +License: GPL-3+ + On Debian systems, the complete text of the GNU General + Public License can be found in `/usr/share/common-licenses/GPL-3'. + +Files: libnymea-gpio/* +Copyright: (C) 2019 Simon Stürz +License: LGPL-3 + +Files: nymea-gpio-tool/* +Copyright: (C) 2019 Simon Stürz +License: GPL-3+ diff --git a/debian/libnymea-gpio-dev.install.in b/debian/libnymea-gpio-dev.install.in new file mode 100644 index 0000000..a7b9db4 --- /dev/null +++ b/debian/libnymea-gpio-dev.install.in @@ -0,0 +1,3 @@ +usr/lib/@DEB_HOST_MULTIARCH@/libnymea-gpio.so +usr/lib/@DEB_HOST_MULTIARCH@/pkgconfig/nymea-gpio.pc +usr/include/nymea-gpio/* usr/include/nymea-gpio/ diff --git a/debian/libnymea-gpio.install.in b/debian/libnymea-gpio.install.in new file mode 100644 index 0000000..ecbcd05 --- /dev/null +++ b/debian/libnymea-gpio.install.in @@ -0,0 +1,3 @@ +usr/lib/@DEB_HOST_MULTIARCH@/libnymea-gpio.so.1 +usr/lib/@DEB_HOST_MULTIARCH@/libnymea-gpio.so.1.0 +usr/lib/@DEB_HOST_MULTIARCH@/libnymea-gpio.so.1.0.0 diff --git a/debian/nymea-gpio-tool.install.in b/debian/nymea-gpio-tool.install.in new file mode 100644 index 0000000..e63d844 --- /dev/null +++ b/debian/nymea-gpio-tool.install.in @@ -0,0 +1 @@ +nymea-gpio-tool/nymea-gpio-tool usr/bin diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..50a3c52 --- /dev/null +++ b/debian/rules @@ -0,0 +1,22 @@ +#!/usr/bin/make -f + +export DH_VERBOSE=1 +DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) + +PREPROCESS_FILES := $(wildcard debian/*.in) + +$(PREPROCESS_FILES:.in=): %: %.in + sed 's,/@DEB_HOST_MULTIARCH@,$(DEB_HOST_MULTIARCH:%=/%),g' $< > $@ + +%: + dh $@ --buildsystem=qmake --parallel + +override_dh_install: $(PREPROCESS_FILES:.in=) + dh_install + +override_dh_strip: + dh_strip --dbg-package=libnymea-gpio-dbg + +override_dh_auto_clean: + dh_auto_clean + rm -rf $(PREPROCESS_FILES:.in=) diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/docs/config.qdocconf b/docs/config.qdocconf new file mode 100644 index 0000000..ef63711 --- /dev/null +++ b/docs/config.qdocconf @@ -0,0 +1,30 @@ +include(html-template.qdocconf) + +project = nymea-gpio +description = nymea-gpio documentation + +dita.metadata.default.author = Simon Stürz +dita.metadata.default.permissions = all +dita.metadata.default.publisher = guh GmbH +dita.metadata.default.copyryear = 2019 +dita.metadata.default.copyrholder = Simon Stürz +dita.metadata.default.audience = programmer + +outputdir = html +outputformats = HTML + +language = Cpp + +naturallanguage = en_US +outputencoding = UTF-8 +sourceencoding = UTF-8 + +syntaxhighlighting = true + +headerdirs = ../libnymea-gpio +sourcedirs = ../libnymea-gpio ../docs + +headers.fileextensions = "*.h" +sources.fileextensions = "*.cpp *.qdoc" + +Cpp.ignoredirectives = Q_DECLARE_METATYPE Q_DECLARE_LOGGING_CATEGORY Q_LOGGING_CATEGORY Q_ENUM diff --git a/docs/html-template.qdocconf b/docs/html-template.qdocconf new file mode 100644 index 0000000..d5b6643 --- /dev/null +++ b/docs/html-template.qdocconf @@ -0,0 +1,11 @@ +HTML.templatedir = . + +HTML.postpostheader = \ + "
\n" + +HTML.prologue = \ + "
\n" + +HTML.footer = \ + "
\n" \ + "
\n" diff --git a/docs/index.qdoc b/docs/index.qdoc new file mode 100644 index 0000000..917cee1 --- /dev/null +++ b/docs/index.qdoc @@ -0,0 +1,11 @@ +/*! + \page index.html + \title nymea-gpio documentation + + The nymea-gpio library allowes to interact in an confortable way with system GPIOs. + + \chapter Classes + \annotatedlist gpio + +*/ + diff --git a/libnymea-gpio/gpio.cpp b/libnymea-gpio/gpio.cpp new file mode 100644 index 0000000..0b51ed5 --- /dev/null +++ b/libnymea-gpio/gpio.cpp @@ -0,0 +1,478 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-gpio. * + * * + * This library 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 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 this library; 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" + +Q_LOGGING_CATEGORY(dcGpio, "Gpio") + +/*! 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), + m_gpioDirectory(QDir(QString("/sys/class/gpio/gpio%1").arg(QString::number(gpio)))) +{ + +} + +/*! Destroys and unexports the Gpio. */ +Gpio::~Gpio() +{ + unexportGpio(); +} + +/*! Returns true if the directories \tt {/sys/class/gpio} and \tt {/sys/class/gpio/export} do exist. */ +bool Gpio::isAvailable() +{ + return QFile("/sys/class/gpio/export").exists(); +} + +/*! Returns the directory \tt {/sys/class/gpio/gpio} of this Gpio. */ +QString Gpio::gpioDirectory() const +{ + return m_gpioDirectory.canonicalPath(); +} + +/*! 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 exported in the system file \tt {/sys/class/gpio/export}. If this Gpio is already exported, this function will return true. */ +bool Gpio::exportGpio() +{ + qCDebug(dcGpio()) << "Export GPIO" << m_gpio; + // 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; +} + +/*! Returns true if this Gpio could be unexported in the system file \tt {/sys/class/gpio/unexport}. */ +bool Gpio::unexportGpio() +{ + qCDebug(dcGpio()) << "Unexport GPIO" << m_gpio; + + 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; +} + +/*! 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; + } + + 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; +} + +/*! Returns the direction of this Gpio. */ +Gpio::Direction Gpio::direction() +{ + 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; +} + +/*! 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; + } + + 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; +} + +/*! Returns the current digital value of this Gpio. */ +Gpio::Value Gpio::value() +{ + 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; +} + +/*! 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; + + 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 << "0"; + } else { + out << "1"; + } + + activeLowFile.close(); + return true; +} + +/*! Returns true if the logic of this Gpio is inverted (1 = low, 0 = high). */ +bool Gpio::activeLow() +{ + 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 == "0") + return true; + + return false; +} + +/*! 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; + 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; +} + +/*! Returns the edge interrupt of this Gpio. */ +Gpio::Edge Gpio::edgeInterrupt() +{ + 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; +} + + +/*! 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(); +} diff --git a/libnymea-gpio/gpio.h b/libnymea-gpio/gpio.h new file mode 100644 index 0000000..cc32d16 --- /dev/null +++ b/libnymea-gpio/gpio.h @@ -0,0 +1,92 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-gpio. * + * * + * This library 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 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef GPIO_H +#define GPIO_H + +#include +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(dcGpio) + +class Gpio : public QObject +{ + Q_OBJECT + +public: + enum Direction { + DirectionInvalid, + DirectionInput, + DirectionOutput + }; + Q_ENUM(Direction) + + enum Value { + ValueInvalid = -1, + ValueLow = 0, + ValueHigh = 1 + }; + Q_ENUM(Value) + + enum Edge { + EdgeFalling, + EdgeRising, + EdgeBoth, + EdgeNone + }; + Q_ENUM(Edge) + + explicit Gpio(int gpio, QObject *parent = nullptr); + ~Gpio(); + + static bool isAvailable(); + + QString gpioDirectory() const; + int gpioNumber() const; + + bool exportGpio(); + bool unexportGpio(); + + bool setDirection(Gpio::Direction direction); + Gpio::Direction direction(); + + bool setValue(Gpio::Value value); + Gpio::Value value(); + + bool setActiveLow(bool activeLow); + bool activeLow(); + + bool setEdgeInterrupt(Gpio::Edge edge); + Gpio::Edge edgeInterrupt(); + +private: + int m_gpio = 0; + Gpio::Direction m_direction = Gpio::DirectionOutput; + QDir m_gpioDirectory; + +}; + +QDebug operator<< (QDebug debug, Gpio *gpio); + +#endif // GPIO_H diff --git a/libnymea-gpio/gpiobutton.cpp b/libnymea-gpio/gpiobutton.cpp new file mode 100644 index 0000000..8c98980 --- /dev/null +++ b/libnymea-gpio/gpiobutton.cpp @@ -0,0 +1,232 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-gpio. * + * * + * This library 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 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! + \class GpioButton + \brief Represents a GPIO Button with some helper methods. + \inmodule nymea-gpio + \ingroup gpio + + This class represents a Button based on a GPIO. The class takes care about the \l{clicked()} signal handling, debounces the GPIO signal + and offers a nice interface for \l{longPressed()} behaviour. + + In order to get the button signals, the button has to be enabled using \l{enable()}. + + \code + GpioButton *button = new GpioButton(15, this); + button->setName("User button"); + if (!button->enable()) { + qWarning() << "Could not enable the" << this; + button->deleteLater(); + return; + } + + connect(button, &GpioButton::clicked, this, [this, button](){ + qDebug() << button << "clicked"; + }); + + \endcode + +*/ + +/*! + \fn void GpioButton::clicked(); + This signal will be emitted when the button gets clicked. A button will has been clicked, if it was pressed at leased for 10 ms and at most 500 ms. +*/ + +/*! + \fn void GpioButton::pressed(); + This signal will be emitted when the button gets pressed. +*/ + +/*! + \fn void GpioButton::released(); + This signal will be emitted whenever the button gets released. +*/ + +/*! + \fn void GpioButton::longPressed(); + This signal will be emitted whenever the button gets pressed for a certain time. + + \sa longPressedTimeout(), repeateLongPressed() +*/ + +#include "gpiobutton.h" +#include "gpiomonitor.h" + +/*! Constructs a \l{GpioButton} object with the given \a gpio number and \a parent. */ +GpioButton::GpioButton(int gpio, QObject *parent) : + QObject(parent), + m_gpioNumber(gpio) +{ + +} + +/*! Returns the gpio number for this GpioButton. */ +int GpioButton::gpioNumber() const +{ + return m_gpioNumber; +} + +/*! Returns \c true the gpio button is configured as active low. + + \sa Gpio::activeLow() +*/ +bool GpioButton::activeLow() const +{ + return m_activeLow; +} + +/*! Sets the gpio button active low configuration to \a activeLow for this GpioButton. + + \sa Gpio::setActiveLow() +*/ +void GpioButton::setActiveLow(bool activeLow) +{ + m_activeLow = activeLow; +} + +/*! Returns \c true, if the \l{longPressed()} signal will be emited again if the button will be hold down. */ +bool GpioButton::repeateLongPressed() const +{ + return m_repeateLongPressed; +} + +/*! Sets repeate long pressed configuration to \a repeateLongPressed. If \a repeateLongPressed is true, the longPressed() signal will be repeated as long the button will be hold down. + + \sa longPressedTimeout() +*/ +void GpioButton::setRepeateLongPressed(bool repeateLongPressed) +{ + m_repeateLongPressed = repeateLongPressed; +} + +/*! Returns the long pressed timout duration in milliseconds. If the button gets hold down for this duration, the longPressed() signal will be emitted. + + \sa longPressed() +*/ +int GpioButton::longPressedTimeout() const +{ + return m_longPressedTimeout; +} + +/*! Sets the long pressed timout duration to \a longPressedTimeout in milliseconds. If the button gets hold down for this duration, the longPressed() signal will be emitted. + + \sa longPressed() +*/ +void GpioButton::setLongPressedTimeout(int longPressedTimeout) +{ + m_longPressedTimeout = longPressedTimeout; +} + +/*! Returns the \c name for this GpioButton. This is optional, but will be printed in the debug operator. */ +QString GpioButton::name() const +{ + return m_name; +} + +/*! Sets the \a name for this GpioButton. This is optional, but will be printed in the debug operator. */ +void GpioButton::setName(const QString &name) +{ + m_name = name; +} + +void GpioButton::onTimeout() +{ + qCDebug(dcGpio()) << this << "long pressed"; + emit longPressed(); +} + +void GpioButton::onInterruptOccured(bool value) +{ + if (value) { + // Pressed + qCDebug(dcGpio()) << this << "pressed"; + emit pressed(); + + m_timer->setSingleShot(!m_repeateLongPressed); + m_timer->start(m_longPressedTimeout); + m_time.restart(); + } else { + // Released + qCDebug(dcGpio()) << this << "released"; + emit released(); + + m_timer->stop(); + int duration = m_time.elapsed(); + + // Debounce and limit to 500 ms + if (duration >= 10 && duration <= 500) { + qCDebug(dcGpio()) << this << "clicked"; + emit clicked(); + } + } +} + +/*! Returns \c true, if this GpioButton was enabled successfully. */ +bool GpioButton::enable() +{ + // Make sure we have a clean start + disable(); + + m_monitor = new GpioMonitor(m_gpioNumber, this); + m_monitor->setEdge(Gpio::EdgeBoth); + m_monitor->setActiveLow(m_activeLow); + + if (!m_monitor->enable()) { + qCWarning(dcGpio()) << "Could not enable GPIO monitor for" << this; + delete m_monitor; + m_monitor = nullptr; + return false; + } + connect(m_monitor, &GpioMonitor::interruptOccured, this, &GpioButton::onInterruptOccured); + + // Setup timer, if this timer reaches timeout, a long pressed happend + m_timer = new QTimer(this); + m_timer->setTimerType(Qt::PreciseTimer); + m_timer->setSingleShot(!m_repeateLongPressed); + m_timer->setInterval(m_longPressedTimeout); + connect(m_timer, &QTimer::timeout, this, &GpioButton::onTimeout); + return true; +} + +/*! Disable this GpioButton. The Gpio will be unexported. */ +void GpioButton::disable() +{ + if (m_monitor) { + delete m_monitor; + m_monitor = nullptr; + } + + if (m_timer) { + delete m_timer; + m_timer = nullptr; + } +} + +/*! Prints the given \a gpioButton to \a debug. */ +QDebug operator<<(QDebug debug, GpioButton *gpioButton) +{ + debug.nospace() << "GpioButton(" << gpioButton->gpioNumber() << ", "; + debug.nospace() << "name: " << gpioButton->name() << ")"; + return debug.space(); +} diff --git a/libnymea-gpio/gpiobutton.h b/libnymea-gpio/gpiobutton.h new file mode 100644 index 0000000..f80d7ac --- /dev/null +++ b/libnymea-gpio/gpiobutton.h @@ -0,0 +1,83 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-gpio. * + * * + * This library 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 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef GPIOBUTTON_H +#define GPIOBUTTON_H + +#include +#include +#include + +class GpioMonitor; + +class GpioButton : public QObject +{ + Q_OBJECT +public: + explicit GpioButton(int gpio, QObject *parent = nullptr); + + int gpioNumber() const; + + bool activeLow() const; + void setActiveLow(bool activeLow); + + bool repeateLongPressed() const; + void setRepeateLongPressed(bool repeateLongPressed); + + int longPressedTimeout() const; + void setLongPressedTimeout(int longPressedTimeout); + + QString name() const; + void setName(const QString &name); + +private: + int m_gpioNumber; + bool m_activeLow = true; + bool m_repeateLongPressed = false; + int m_longPressedTimeout = 250; + QString m_name; + + GpioMonitor *m_monitor = nullptr; + QTimer *m_timer = nullptr; + + QTime m_time; + +signals: + void clicked(); + void pressed(); + void released(); + void longPressed(); + +private slots: + void onTimeout(); + void onInterruptOccured(bool value); + +public slots: + bool enable(); + void disable(); + +}; + +QDebug operator<< (QDebug debug, GpioButton *gpioButton); + + +#endif // GPIOBUTTON_H diff --git a/libnymea-gpio/gpiomonitor.cpp b/libnymea-gpio/gpiomonitor.cpp new file mode 100644 index 0000000..1fd9d42 --- /dev/null +++ b/libnymea-gpio/gpiomonitor.cpp @@ -0,0 +1,282 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-gpio. * + * * + * This library 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 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! + \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. + + \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; + }); + + \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. +*/ + +#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), + 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); +} + +/*! Destroys and unexports the Gpio. */ +GpioMonitor::~GpioMonitor() +{ + disable(); + wait(200); +} + +/*! 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 interruptOccured(false); + break; + case Gpio::ValueHigh: + emit interruptOccured(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 + QMutexLocker stopLocker(&m_stopMutex); + if (m_stop) break; + + // 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()) << "Enable 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."; + return false; + } + + QMutexLocker locker(&m_stopMutex); + m_stop = false; + + // Everything looks good, lets start the poll thread and inform about the result + start(); + return true; +} + +/*! Disables this GpioMonitor. The \l{interruptOccured()} signal will not be emitted any more and the Gpio will be unexported. */ +void GpioMonitor::disable() +{ + qCDebug(dcGpio()) << "Disable gpio monitor"; + // Stop the thread if not already disabled + QMutexLocker locker(&m_stopMutex); + if (m_stop) return; + m_stop = true; +} diff --git a/libnymea-gpio/gpiomonitor.h b/libnymea-gpio/gpiomonitor.h new file mode 100644 index 0000000..e9653c7 --- /dev/null +++ b/libnymea-gpio/gpiomonitor.h @@ -0,0 +1,82 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-gpio. * + * * + * This library 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 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef GPIOMONITOR_H +#define GPIOMONITOR_H + +#include +#include +#include + +#include "gpio.h" + +class GpioMonitor : public QThread +{ + Q_OBJECT +public: + explicit GpioMonitor(int gpio, QObject *parent = nullptr); + ~GpioMonitor() override; + + Gpio::Edge edge() const; + void setEdge(Gpio::Edge edge); + + bool activeLow() const; + void setActiveLow(bool activeLow); + + Gpio::Value value(); + + bool enabled() const; + +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; + +signals: + void interruptOccured(bool value); + void enabledChanged(bool enabled); + +private slots: + void onThreadStarted(); + void onThreadFinished(); + +public slots: + bool enable(); + void disable(); + +}; + +#endif // GPIOMONITOR_H diff --git a/libnymea-gpio/libnymea-gpio.pro b/libnymea-gpio/libnymea-gpio.pro new file mode 100644 index 0000000..ed3db59 --- /dev/null +++ b/libnymea-gpio/libnymea-gpio.pro @@ -0,0 +1,36 @@ +include(../nymea-gpio.pri) + +TARGET = nymea-gpio +TEMPLATE = lib + +HEADERS += \ + gpio.h \ + gpiobutton.h \ + gpiomonitor.h + +SOURCES += \ + gpio.cpp \ + gpiobutton.cpp \ + gpiomonitor.cpp + +target.path = $$[QT_INSTALL_LIBS] +INSTALLS += target + +# install header file with relative subdirectory +for(header, HEADERS) { + path = $$[QT_INSTALL_PREFIX]/include/nymea-gpio/$${dirname(header)} + eval(headers_$${path}.files += $${header}) + eval(headers_$${path}.path = $${path}) + eval(INSTALLS *= headers_$${path}) +} + +# Create pkgconfig file +CONFIG += create_pc create_prl no_install_prl +QMAKE_PKGCONFIG_NAME = nymea-gpio +QMAKE_PKGCONFIG_DESCRIPTION = nymea gpio development library +QMAKE_PKGCONFIG_PREFIX = $$[QT_INSTALL_PREFIX] +QMAKE_PKGCONFIG_INCDIR = $$[QT_INSTALL_PREFIX]/include/nymea-gpio/ +QMAKE_PKGCONFIG_LIBDIR = $$target.path +QMAKE_PKGCONFIG_VERSION = $$VERSION_STRING +QMAKE_PKGCONFIG_FILE = nymea-gpio +QMAKE_PKGCONFIG_DESTDIR = pkgconfig diff --git a/nymea-gpio-tool/application.cpp b/nymea-gpio-tool/application.cpp new file mode 100644 index 0000000..624d34b --- /dev/null +++ b/nymea-gpio-tool/application.cpp @@ -0,0 +1,68 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-gpio. * + * * + * This library 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 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "application.h" + +#include +#include + +static void catchUnixSignals(const std::vector& quitSignals, const std::vector& ignoreSignals = std::vector()) +{ + auto handler = [](int sig) ->void { + switch (sig) { + case SIGQUIT: + qDebug() << "Cought SIGQUIT quit signal..."; + break; + case SIGINT: + qDebug() << "Cought SIGINT quit signal..."; + break; + case SIGTERM: + qDebug() << "Cought SIGTERM quit signal..."; + break; + case SIGHUP: + qDebug() << "Cought SIGHUP quit signal..."; + break; + case SIGSEGV: { + qCritical() << "Cought SIGSEGV signal. Segmentation fault!"; + exit(EXIT_FAILURE); + } + default: + break; + } + + Application::quit(); + }; + + // all these signals will be ignored. + for (int sig : ignoreSignals) + signal(sig, SIG_IGN); + + for (int sig : quitSignals) + signal(sig, handler); + +} + +Application::Application(int &argc, char **argv) : + QCoreApplication(argc, argv) +{ + catchUnixSignals({SIGQUIT, SIGINT, SIGTERM, SIGHUP, SIGSEGV}); +} diff --git a/nymea-gpio-tool/application.h b/nymea-gpio-tool/application.h new file mode 100644 index 0000000..8fbfa8d --- /dev/null +++ b/nymea-gpio-tool/application.h @@ -0,0 +1,37 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-gpio. * + * * + * This library 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 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef APPLICATION_H +#define APPLICATION_H + +#include +#include + +class Application : public QCoreApplication +{ + Q_OBJECT +public: + explicit Application(int &argc, char **argv); + +}; + +#endif // APPLICATION_H diff --git a/nymea-gpio-tool/main.cpp b/nymea-gpio-tool/main.cpp new file mode 100644 index 0000000..3496cf5 --- /dev/null +++ b/nymea-gpio-tool/main.cpp @@ -0,0 +1,182 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea-gpio. * + * * + * This library 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 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include "application.h" +#include "gpiomonitor.h" + +int main(int argc, char *argv[]) +{ + Application application(argc, argv); + application.setOrganizationName("guh"); + application.setApplicationName("nymea-gpio-tool"); + application.setApplicationVersion(VERSION_STRING); + + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + QString applicationDescription = QString("\nnymea-gpio-tool is a command line tool which allowes to interact with GPIOs.\n" + "Version: %1\n" + "Copyright %2 2019 Simon Stürz \n\n" + "Released under the GNU GENERAL PUBLIC LICENSE Version 3.\n").arg(application.applicationVersion()).arg(QChar(0xA9)); + parser.setApplicationDescription(applicationDescription); + + QCommandLineOption gpioOption(QStringList() << "g" << "gpio", "The gpio number to use.", "GPIO"); + parser.addOption(gpioOption); + + QCommandLineOption interruptOption(QStringList() << "i" << "interrupt", "Configure the input GPIO to the given interrupt. This option is only allowed for monitoring. Allowerd interrupts are: [rising, falling, both, none]. Default is \"both\".", "INTERRUPT"); + parser.addOption(interruptOption); + + QCommandLineOption valueOption(QStringList() << "s" << "set-value", "Configure the GPIO to output and set the value. Allowerd values are: [0, 1].", "VALUE"); + parser.addOption(valueOption); + + QCommandLineOption monitorOption(QStringList() << "m" << "monitor", "Monitor the given GPIO. The GPIO will automatically configured as input and print any change regarding to the given interrupt behaviour."); + parser.addOption(monitorOption); + + QCommandLineOption activeLowOption(QStringList() << "a" << "active-low", "Set the GPIO to active low. Allowerd values are: [0, 1]", "VALUE"); + parser.addOption(activeLowOption); + + parser.process(application); + + // Make sure there is a GPIO number passed + if (!parser.isSet(gpioOption)) { + qCritical() << "No GPIO number specified. Please specify a valid GPIO number using -g, --gpio GPIO"; + parser.showHelp(EXIT_FAILURE); + } + + // Verify GPIO number + bool gpioNumberOk; + int gpioNumber = parser.value(gpioOption).toInt(&gpioNumberOk); + if (!gpioNumberOk || gpioNumber < 0) { + qCritical() << "Invalid GPIO number" << parser.value(gpioOption) << "passed. The GPIO number has to be a positiv integer."; + return EXIT_FAILURE; + } + + // Verify input output operations + if ((parser.isSet(interruptOption) || parser.isSet(monitorOption)) && parser.isSet(valueOption)) { + qCritical() << "Invalid parameter combination. The set value can only be used for output GPIO, the monitor and interrupt parameter can only be used for input GPIO."; + return EXIT_FAILURE; + } + + Gpio::Edge edge = Gpio::EdgeBoth; + if (parser.isSet(interruptOption)) { + if (parser.value(interruptOption).toLower() == "rising") { + edge = Gpio::EdgeRising; + } else if (parser.value(interruptOption).toLower() == "falling") { + edge = Gpio::EdgeFalling; + } else if (parser.value(interruptOption).toLower() == "none") { + edge = Gpio::EdgeNone; + } else if (parser.value(interruptOption).toLower() == "both") { + edge = Gpio::EdgeBoth; + } else { + qCritical() << "Invalid interrupt parameter" << parser.value(interruptOption) << "passed. Valid options are [rising, falling, both, none]."; + return EXIT_FAILURE; + } + } + + bool activeLow = true; + if (parser.isSet(activeLowOption)) { + if (parser.value(activeLowOption) == "1") { + activeLow = false; + } else if (parser.value(activeLowOption) == "0") { + activeLow = true; + } else { + qCritical() << "Invalid active low parameter" << parser.value(activeLowOption) << "passed. Valid options are [0, 1]."; + return EXIT_FAILURE; + } + } + + Gpio::Value value = Gpio::ValueInvalid; + if (parser.isSet(valueOption)) { + if (parser.value(valueOption) == "1") { + value = Gpio::ValueHigh; + } else if (parser.value(valueOption) == "0") { + value = Gpio::ValueLow; + } else { + qCritical() << "Invalid set value parameter" << parser.value(valueOption) << "passed. Valid options are [0, 1]."; + return EXIT_FAILURE; + } + } + + if (!Gpio::isAvailable()) { + qCritical() << "There are no GPIOs available on this platform."; + return EXIT_FAILURE; + } + + // Configure the GPIO + if (parser.isSet(valueOption)) { + Gpio *gpio = new Gpio(gpioNumber); + if (!gpio->exportGpio()) { + qCritical() << "Could not export GPIO" << gpioNumber; + return EXIT_FAILURE; + } + + if (!gpio->setDirection(Gpio::DirectionOutput)) { + qCritical() << "Could not configure GPIO" << gpioNumber << "as output."; + return EXIT_FAILURE; + } + + if (parser.isSet(activeLowOption)) { + if (!gpio->setActiveLow(activeLow)) { + qCritical() << "Could not set GPIO" << gpioNumber << "to active low" << activeLow; + return EXIT_FAILURE; + } + } + + // Finally set the value + if (!gpio->setValue(value)) { + qCritical() << "Could not set GPIO" << gpioNumber << "value to" << value; + return EXIT_FAILURE; + } + + delete gpio; + 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::interruptOccured, [gpioNumber](bool value) { + qDebug() << "GPIO" << gpioNumber << "interrupt occured. Current value:" << (value ? "1" : "0"); + }); + + // Enable the monitor + if (!monitor->enable()) { + qCritical() << "Could not enable GPIO" << gpioNumber << "monitor."; + return EXIT_FAILURE; + } + + // Clean up the gpio once done + QObject::connect(&application, &Application::aboutToQuit, [monitor](){ + delete monitor; + }); + } + + return application.exec(); +} diff --git a/nymea-gpio-tool/nymea-gpio-tool.pro b/nymea-gpio-tool/nymea-gpio-tool.pro new file mode 100644 index 0000000..2e6a605 --- /dev/null +++ b/nymea-gpio-tool/nymea-gpio-tool.pro @@ -0,0 +1,19 @@ +include(../nymea-gpio.pri) + +TARGET = nymea-gpio-tool + +CONFIG += console +CONFIG -= app_bundle +TEMPLATE = app + +INCLUDEPATH += $$top_srcdir/libnymea-gpio/ +LIBS += -L$$top_builddir/libnymea-gpio/ -lnymea-gpio + +HEADERS += \ + application.h + +SOURCES += main.cpp \ + application.cpp + +target.path = /usr/bin +INSTALLS += target diff --git a/nymea-gpio.pri b/nymea-gpio.pri new file mode 100644 index 0000000..f6dcac7 --- /dev/null +++ b/nymea-gpio.pri @@ -0,0 +1,11 @@ + +QMAKE_CXXFLAGS += -Werror -std=c++11 -g +QMAKE_LFLAGS += -std=c++11 + +QT -= gui + +top_srcdir=$$PWD +top_builddir=$$shadowed($$PWD) + +VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p"') +DEFINES += VERSION_STRING=\\\"$${VERSION_STRING}\\\" diff --git a/nymea-gpio.pro b/nymea-gpio.pro new file mode 100644 index 0000000..57b0086 --- /dev/null +++ b/nymea-gpio.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +SUBDIRS = libnymea-gpio nymea-gpio-tool +nymea-gpio-tool.depends = libnymea-gpio +