mirror of https://github.com/nymea/nymea-gpio
Compare commits
9 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
ccc1e6b80a | |
|
|
f9fb5d3f7b | |
|
|
2bcae76bdf | |
|
|
0a76a0beb3 | |
|
|
7ab3f3f329 | |
|
|
4059bb0172 | |
|
|
f151646295 | |
|
|
5e1654d783 | |
|
|
e34ddc8db4 |
|
|
@ -0,0 +1,265 @@
|
|||
---
|
||||
Language: Cpp
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCaseColons: false
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Always
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: true
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterExternBlock: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: true
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
BreakAfterAttributes: Never
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: All
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 180
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- forever
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^<Q.*'
|
||||
Priority: 200
|
||||
SortPriority: 200
|
||||
CaseSensitive: true
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
InsertNewlineAtEOF: false
|
||||
InsertTrailingCommas: None
|
||||
IntegerLiteralSeparator:
|
||||
Binary: 0
|
||||
BinaryMinDigits: 0
|
||||
Decimal: 0
|
||||
DecimalMinDigits: 0
|
||||
Hex: 0
|
||||
HexMinDigits: 0
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
KeepEmptyLinesAtEOF: false
|
||||
LambdaBodyIndentation: Signature
|
||||
LineEnding: DeriveLF
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PackConstructorInitializers: BinPack
|
||||
PenaltyBreakAssignment: 150
|
||||
PenaltyBreakBeforeFirstCallParameter: 300
|
||||
PenaltyBreakComment: 500
|
||||
PenaltyBreakFirstLessLess: 400
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakString: 600
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 50
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 300
|
||||
PointerAlignment: Right
|
||||
PPIndentWidth: -1
|
||||
QualifierAlignment: Leave
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: false
|
||||
RemoveBracesLLVM: false
|
||||
RemoveParentheses: Leave
|
||||
RemoveSemicolon: false
|
||||
RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SortIncludes: CaseSensitive
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: Lexicographic
|
||||
SpaceAfterCStyleCast: true
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeJsonColon: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterForeachMacros: true
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParens: Never
|
||||
SpacesInParensOptions:
|
||||
InCStyleCasts: false
|
||||
InConditionalStatements: false
|
||||
InEmptyParentheses: false
|
||||
Other: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Auto
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
- Q_CLASSINFO
|
||||
- Q_ENUM
|
||||
- Q_ENUM_NS
|
||||
- Q_FLAG
|
||||
- Q_FLAG_NS
|
||||
- Q_GADGET
|
||||
- Q_GADGET_EXPORT
|
||||
- Q_INTERFACES
|
||||
- Q_MOC_INCLUDE
|
||||
- Q_NAMESPACE
|
||||
- Q_NAMESPACE_EXPORT
|
||||
- Q_OBJECT
|
||||
- Q_PROPERTY
|
||||
- Q_REVISION
|
||||
- Q_DISABLE_COPY
|
||||
- Q_SET_OBJECT_NAME
|
||||
- QT_BEGIN_NAMESPACE
|
||||
- QT_END_NAMESPACE
|
||||
- QML_ADDED_IN_MINOR_VERSION
|
||||
- QML_ANONYMOUS
|
||||
- QML_ATTACHED
|
||||
- QML_DECLARE_TYPE
|
||||
- QML_DECLARE_TYPEINFO
|
||||
- QML_ELEMENT
|
||||
- QML_EXTENDED
|
||||
- QML_EXTENDED_NAMESPACE
|
||||
- QML_EXTRA_VERSION
|
||||
- QML_FOREIGN
|
||||
- QML_FOREIGN_NAMESPACE
|
||||
- QML_IMPLEMENTS_INTERFACES
|
||||
- QML_INTERFACE
|
||||
- QML_NAMED_ELEMENT
|
||||
- QML_REMOVED_IN_MINOR_VERSION
|
||||
- QML_SINGLETON
|
||||
- QML_UNAVAILABLE
|
||||
- QML_UNCREATABLE
|
||||
- QML_VALUE_TYPE
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
VerilogBreakBetweenInstancePorts: true
|
||||
WhitespaceSensitiveMacros:
|
||||
- BOOST_PP_STRINGIZE
|
||||
- CF_SWIFT_NAME
|
||||
- NS_SWIFT_NAME
|
||||
- PP_STRINGIZE
|
||||
- STRINGIZE
|
||||
...
|
||||
|
||||
159
README.md
159
README.md
|
|
@ -1,10 +1,161 @@
|
|||
# nymea-gpio
|
||||
|
||||
This repository contains `libnymea-gpio`, a reusable Qt/C++ helper library that allows nymea and other applications to configure and interact with general purpose I/O pins on supported platforms, and `nymea-gpio-tool`, a small CLI utility that exposes the same functionality for scripting and diagnostics.
|
||||
nymea-gpio is a small Qt/C++ GPIO helper library for Linux and a matching CLI tool. The library is used by nymea but can be embedded in other Qt applications, while the CLI is handy for scripting, bring-up, and diagnostics.
|
||||
|
||||
Both components are developed together to keep their pin-handling logic and configuration formats in sync, so the command line tool doubles as a reference implementation for the library.
|
||||
## Components
|
||||
|
||||
- `libnymea-gpio`: Qt library with `Gpio`, `GpioMonitor`, and `GpioButton` helpers.
|
||||
- `nymea-gpio-tool`: CLI wrapper around the same API.
|
||||
|
||||
## Supported GPIO backends
|
||||
|
||||
- Linux libgpiod API v1 or v2 (auto-detected via pkg-config).
|
||||
- Linux sysfs GPIO (deprecated upstream, build-time opt-in).
|
||||
|
||||
## Requirements
|
||||
|
||||
- Linux
|
||||
- Qt 5 or Qt 6 (qmake)
|
||||
- `pkg-config` and `libgpiod` (unless building the legacy sysfs backend)
|
||||
- `dpkg-parsechangelog` (from `dpkg-dev`) to populate `VERSION_STRING` during build
|
||||
|
||||
## Build
|
||||
|
||||
Build both the library and the CLI:
|
||||
|
||||
```sh
|
||||
qmake nymea-gpio.pro
|
||||
make -j
|
||||
```
|
||||
|
||||
If you are building with Qt 6, use `qmake6` instead of `qmake`.
|
||||
|
||||
To force the legacy sysfs backend:
|
||||
|
||||
```sh
|
||||
qmake "CONFIG+=nymea_gpio_sysfs" nymea-gpio.pro
|
||||
make -j
|
||||
```
|
||||
|
||||
Install (optional):
|
||||
|
||||
```sh
|
||||
make install
|
||||
```
|
||||
|
||||
The install step drops the library, headers, and a `nymea-gpio` pkg-config file into your Qt prefix.
|
||||
|
||||
## CLI usage
|
||||
|
||||
`nymea-gpio-tool` requires a GPIO number and then either sets an output value or monitors input changes.
|
||||
|
||||
Set a GPIO output:
|
||||
|
||||
```sh
|
||||
nymea-gpio-tool --gpio 17 --set-value 1
|
||||
```
|
||||
|
||||
Monitor input changes (default edge is `both`):
|
||||
|
||||
```sh
|
||||
nymea-gpio-tool --gpio 17 --monitor --interrupt both
|
||||
```
|
||||
|
||||
Active-low input or output:
|
||||
|
||||
```sh
|
||||
nymea-gpio-tool --gpio 17 --set-value 0 --active-low
|
||||
```
|
||||
|
||||
## Library examples
|
||||
|
||||
Output GPIO:
|
||||
|
||||
```cpp
|
||||
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(Gpio::DirectionOutput)) {
|
||||
qWarning() << "Could not set direction of Gpio" << gpioOut->gpioNumber();
|
||||
gpioOut->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
gpioOut->setValue(Gpio::ValueHigh);
|
||||
```
|
||||
|
||||
Input GPIO:
|
||||
|
||||
```cpp
|
||||
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(Gpio::DirectionInput)) {
|
||||
qWarning() << "Could not set direction of Gpio" << gpioIn->gpioNumber();
|
||||
gpioIn->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Current value" << gpioIn->value();
|
||||
```
|
||||
|
||||
Monitor a button with GpioMonitor:
|
||||
|
||||
```cpp
|
||||
Button::Button(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
m_button = new GpioMonitor(110, this);
|
||||
connect(m_button, &GpioMonitor::valueChanged, this, &Button::stateChanged);
|
||||
}
|
||||
bool Button::init()
|
||||
{
|
||||
return m_button->enable();
|
||||
}
|
||||
void Button::stateChanged(const bool &value)
|
||||
{
|
||||
if (m_pressed != value) {
|
||||
m_pressed = value;
|
||||
if (value) {
|
||||
emit buttonPressed();
|
||||
} else {
|
||||
emit buttonReleased();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Button helper with debounced GpioButton:
|
||||
|
||||
```cpp
|
||||
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";
|
||||
});
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
- `libnymea-gpio` is licensed under the terms of the GNU Lesser General Public License v3.0 or (at your option) any later version. See `LICENSE.LGPL3` for the full text.
|
||||
- `nymea-gpio-tool` is licensed under the terms of the GNU General Public License v3.0 or (at your option) any later version. See `LICENSE.GPL3` for the full text.
|
||||
- `libnymea-gpio` is licensed under the GNU Lesser General Public License v3.0 or (at your option) any later version. See `LICENSE.LGPL3`.
|
||||
- `nymea-gpio-tool` is licensed under the GNU General Public License v3.0 or (at your option) any later version. See `LICENSE.GPL3`.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,21 @@
|
|||
nymea-gpio (1.14.2) noble; urgency=medium
|
||||
|
||||
|
||||
-- jenkins <developer@nymea.io> Thu, 19 Feb 2026 12:13:04 +0100
|
||||
|
||||
nymea-gpio (1.14.1) noble; urgency=medium
|
||||
|
||||
|
||||
-- jenkins <developer@nymea.io> Thu, 29 Jan 2026 21:28:50 +0100
|
||||
|
||||
nymea-gpio (1.14.0) noble; urgency=medium
|
||||
|
||||
[ Simon Stürz ]
|
||||
* Add clang-format
|
||||
* Add libgpiod support to libnymea-gpio
|
||||
|
||||
-- jenkins <developer@nymea.io> Mon, 12 Jan 2026 10:46:54 +0100
|
||||
|
||||
nymea-gpio (1.13.0) jammy; urgency=medium
|
||||
|
||||
[ Simon Stürz ]
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ Standards-Version: 4.6.0
|
|||
Vcs-Git: https://github.com/nymea/nymea-gpio.git
|
||||
Build-Depends: debhelper,
|
||||
dpkg-dev,
|
||||
pkg-config,
|
||||
qt5-qmake,
|
||||
qtbase5-dev,
|
||||
libgpiod-dev,
|
||||
qtbase5-dev-tools
|
||||
|
||||
|
||||
|
|
@ -38,6 +40,7 @@ Multi-Arch: same
|
|||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
pkg-config,
|
||||
libgpiod-dev,
|
||||
libnymea-gpio (= ${binary:Version})
|
||||
Description: Qt based library for GPIO interaction - development files
|
||||
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
|
||||
Build-Depends: debhelper,
|
||||
dpkg-dev,
|
||||
pkg-config,
|
||||
qt6-base-dev,
|
||||
libgpiod-dev,
|
||||
qt6-base-dev-tools,
|
||||
|
||||
|
||||
|
|
@ -37,6 +39,7 @@ Multi-Arch: same
|
|||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
pkg-config,
|
||||
libgpiod-dev,
|
||||
libnymea-gpio (= ${binary:Version})
|
||||
Description: Qt based library for GPIO interaction - development files
|
||||
Development files for Qt based GPIO library.
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@
|
|||
\sa GpioMonitor
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\enum Gpio::Direction
|
||||
This enum type specifies the dirction a Gpio.
|
||||
|
|
@ -127,17 +126,121 @@
|
|||
|
||||
#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")
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
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<unsigned int>(gpioNumber) < base + numLines) {
|
||||
*chipName = entry;
|
||||
*offset = static_cast<unsigned int>(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),
|
||||
m_gpioDirectory(QDir(QString("/sys/class/gpio/gpio%1").arg(QString::number(gpio))))
|
||||
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<Gpio::Value>();
|
||||
|
||||
}
|
||||
|
||||
/*! Destroys and unexports the Gpio. */
|
||||
|
|
@ -146,16 +249,36 @@ Gpio::~Gpio()
|
|||
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()
|
||||
{
|
||||
#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 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
|
||||
{
|
||||
#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.
|
||||
|
|
@ -166,10 +289,11 @@ 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. */
|
||||
/*! 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.";
|
||||
|
|
@ -186,13 +310,49 @@ bool Gpio::exportGpio()
|
|||
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 unexported in the system file \tt {/sys/class/gpio/unexport}. */
|
||||
/*! 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();
|
||||
|
|
@ -203,6 +363,33 @@ bool Gpio::unexportGpio()
|
|||
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, */
|
||||
|
|
@ -214,6 +401,7 @@ bool Gpio::setDirection(Gpio::Direction direction)
|
|||
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();
|
||||
|
|
@ -236,11 +424,49 @@ bool Gpio::setDirection(Gpio::Direction direction)
|
|||
|
||||
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();
|
||||
|
|
@ -261,6 +487,9 @@ Gpio::Direction Gpio::direction()
|
|||
}
|
||||
|
||||
return Gpio::DirectionInvalid;
|
||||
#else
|
||||
return m_direction;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*! Returns true if the digital \a value of this Gpio could be set correctly. */
|
||||
|
|
@ -285,6 +514,7 @@ bool Gpio::setValue(Gpio::Value value)
|
|||
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();
|
||||
|
|
@ -306,11 +536,47 @@ bool Gpio::setValue(Gpio::Value value)
|
|||
|
||||
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();
|
||||
|
|
@ -329,6 +595,36 @@ Gpio::Value Gpio::value()
|
|||
}
|
||||
|
||||
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. */
|
||||
|
|
@ -336,6 +632,7 @@ 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();
|
||||
|
|
@ -351,11 +648,16 @@ bool Gpio::setActiveLow(bool activeLow)
|
|||
|
||||
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();
|
||||
|
|
@ -371,18 +673,21 @@ bool Gpio::activeLow()
|
|||
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();
|
||||
|
|
@ -407,11 +712,33 @@ bool Gpio::setEdgeInterrupt(Gpio::Edge edge)
|
|||
|
||||
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();
|
||||
|
|
@ -434,8 +761,180 @@ Gpio::Edge Gpio::edgeInterrupt()
|
|||
}
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -28,17 +28,30 @@
|
|||
#ifndef GPIO_H
|
||||
#define GPIO_H
|
||||
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
#include <QDir>
|
||||
#include <QLoggingCategory>
|
||||
#include <QObject>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcGpio)
|
||||
|
||||
class GpioMonitor;
|
||||
|
||||
struct gpiod_chip;
|
||||
#ifndef NYMEA_GPIO_USE_SYSFS
|
||||
#if defined(NYMEA_GPIO_LIBGPIOD_V2)
|
||||
struct gpiod_line_request;
|
||||
#else
|
||||
struct gpiod_line;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class Gpio : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class GpioMonitor;
|
||||
|
||||
public:
|
||||
enum Direction {
|
||||
DirectionInvalid,
|
||||
|
|
@ -86,12 +99,31 @@ public:
|
|||
Gpio::Edge edgeInterrupt();
|
||||
|
||||
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;
|
||||
#if defined(NYMEA_GPIO_LIBGPIOD_V2)
|
||||
gpiod_line_request *m_request = nullptr;
|
||||
#else
|
||||
gpiod_line *m_line = nullptr;
|
||||
#endif
|
||||
bool m_activeLow = false;
|
||||
Gpio::Edge m_edge = Gpio::EdgeNone;
|
||||
#endif
|
||||
int m_gpio = 0;
|
||||
Gpio::Direction m_direction = Gpio::DirectionOutput;
|
||||
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||
QDir m_gpioDirectory;
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
QDebug operator<< (QDebug debug, Gpio *gpio);
|
||||
QDebug operator<<(QDebug debug, Gpio *gpio);
|
||||
|
||||
#endif // GPIO_H
|
||||
|
|
|
|||
|
|
@ -79,12 +79,10 @@
|
|||
#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)
|
||||
{
|
||||
|
||||
}
|
||||
GpioButton::GpioButton(int gpio, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_gpioNumber(gpio)
|
||||
{}
|
||||
|
||||
/*! Returns the gpio number for this GpioButton. */
|
||||
int GpioButton::gpioNumber() const
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@
|
|||
#define GPIOBUTTON_H
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
class GpioMonitor;
|
||||
|
||||
|
|
@ -79,10 +79,8 @@ private slots:
|
|||
public slots:
|
||||
bool enable();
|
||||
void disable();
|
||||
|
||||
};
|
||||
|
||||
QDebug operator<< (QDebug debug, GpioButton *gpioButton);
|
||||
|
||||
QDebug operator<<(QDebug debug, GpioButton *gpioButton);
|
||||
|
||||
#endif // GPIOBUTTON_H
|
||||
|
|
|
|||
|
|
@ -70,12 +70,20 @@
|
|||
|
||||
#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. */
|
||||
GpioMonitor::GpioMonitor(int gpio, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_gpioNumber(gpio)
|
||||
GpioMonitor::GpioMonitor(int gpio, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_gpioNumber(gpio)
|
||||
{
|
||||
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||
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.
|
||||
|
|
@ -86,14 +94,12 @@ bool GpioMonitor::enable(bool activeLow, Gpio::Edge edgeInterrupt)
|
|||
return false;
|
||||
|
||||
m_gpio = new Gpio(m_gpioNumber, this);
|
||||
if (!m_gpio->exportGpio() ||
|
||||
!m_gpio->setDirection(Gpio::DirectionInput) ||
|
||||
!m_gpio->setActiveLow(activeLow) ||
|
||||
!m_gpio->setEdgeInterrupt(edgeInterrupt)) {
|
||||
if (!m_gpio->exportGpio() || !m_gpio->setDirection(Gpio::DirectionInput) || !m_gpio->setActiveLow(activeLow) || !m_gpio->setEdgeInterrupt(edgeInterrupt)) {
|
||||
qCWarning(dcGpio()) << "GpioMonitor: Error while initializing GPIO" << m_gpio->gpioNumber();
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||
if (!m_valueFile.open(QFile::ReadOnly)) {
|
||||
qWarning(dcGpio()) << "GpioMonitor: Could not open value file for gpio monitor" << m_gpio->gpioNumber();
|
||||
return false;
|
||||
|
|
@ -101,6 +107,31 @@ bool GpioMonitor::enable(bool activeLow, Gpio::Edge edgeInterrupt)
|
|||
|
||||
m_notifier = new QSocketNotifier(m_valueFile.handle(), QSocketNotifier::Exception);
|
||||
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;
|
||||
}
|
||||
|
||||
#if defined(NYMEA_GPIO_LIBGPIOD_V2)
|
||||
m_eventBuffer = gpiod_edge_event_buffer_new(1);
|
||||
if (!m_eventBuffer) {
|
||||
qCWarning(dcGpio()) << "GpioMonitor: Could not allocate edge event buffer for GPIO" << m_gpio->gpioNumber();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
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";
|
||||
m_notifier->setEnabled(true);
|
||||
|
|
@ -116,7 +147,17 @@ void GpioMonitor::disable()
|
|||
m_notifier = 0;
|
||||
m_gpio = 0;
|
||||
|
||||
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||
m_valueFile.close();
|
||||
#else
|
||||
m_eventFd = -1;
|
||||
#if defined(NYMEA_GPIO_LIBGPIOD_V2)
|
||||
if (m_eventBuffer) {
|
||||
gpiod_edge_event_buffer_free(m_eventBuffer);
|
||||
m_eventBuffer = nullptr;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
/*! Returns true if this \l{GpioMonitor} is running. */
|
||||
|
|
@ -144,6 +185,7 @@ void GpioMonitor::readyReady(const int &ready)
|
|||
{
|
||||
Q_UNUSED(ready)
|
||||
|
||||
#ifdef NYMEA_GPIO_USE_SYSFS
|
||||
m_valueFile.seek(0);
|
||||
QByteArray data = m_valueFile.readAll();
|
||||
|
||||
|
|
@ -158,4 +200,33 @@ void GpioMonitor::readyReady(const int &ready)
|
|||
|
||||
m_currentValue = value;
|
||||
emit valueChanged(value);
|
||||
#else
|
||||
if (m_eventFd < 0)
|
||||
return;
|
||||
|
||||
#if defined(NYMEA_GPIO_LIBGPIOD_V2)
|
||||
if (!m_gpio || !m_gpio->m_request || !m_eventBuffer)
|
||||
return;
|
||||
|
||||
const int eventsRead = gpiod_line_request_read_edge_events(m_gpio->m_request, m_eventBuffer, 1);
|
||||
if (eventsRead < 0) {
|
||||
qCWarning(dcGpio()) << "GpioMonitor: Could not read GPIO edge events:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@
|
|||
#ifndef GPIOMONITOR_H
|
||||
#define GPIOMONITOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QSocketNotifier>
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#include "gpio.h"
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ public:
|
|||
bool isRunning() const;
|
||||
bool value() const;
|
||||
|
||||
Gpio* gpio();
|
||||
Gpio *gpio();
|
||||
|
||||
private:
|
||||
int m_gpioNumber;
|
||||
|
|
@ -56,13 +56,18 @@ private:
|
|||
QSocketNotifier *m_notifier;
|
||||
QFile m_valueFile;
|
||||
bool m_currentValue;
|
||||
#ifndef NYMEA_GPIO_USE_SYSFS
|
||||
int m_eventFd = -1;
|
||||
#if defined(NYMEA_GPIO_LIBGPIOD_V2)
|
||||
struct gpiod_edge_event_buffer *m_eventBuffer = nullptr;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void valueChanged(const bool &value);
|
||||
|
||||
private slots:
|
||||
void readyReady(const int &ready);
|
||||
|
||||
};
|
||||
|
||||
#endif // GPIOMONITOR_H
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ SOURCES += \
|
|||
gpiobutton.cpp \
|
||||
gpiomonitor.cpp
|
||||
|
||||
!contains(CONFIG, nymea_gpio_sysfs) {
|
||||
CONFIG += link_pkgconfig
|
||||
PKGCONFIG += libgpiod
|
||||
QMAKE_PKGCONFIG_REQUIRES += libgpiod
|
||||
}
|
||||
|
||||
target.path = $$[QT_INSTALL_LIBS]
|
||||
INSTALLS += target
|
||||
|
||||
|
|
@ -34,3 +40,5 @@ QMAKE_PKGCONFIG_LIBDIR = $$target.path
|
|||
QMAKE_PKGCONFIG_VERSION = $$VERSION_STRING
|
||||
QMAKE_PKGCONFIG_FILE = nymea-gpio
|
||||
QMAKE_PKGCONFIG_DESTDIR = pkgconfig
|
||||
contains(DEFINES, NYMEA_GPIO_USE_SYSFS): QMAKE_PKGCONFIG_CFLAGS += -DNYMEA_GPIO_USE_SYSFS
|
||||
contains(DEFINES, NYMEA_GPIO_LIBGPIOD_V2): QMAKE_PKGCONFIG_CFLAGS += -DNYMEA_GPIO_LIBGPIOD_V2
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@
|
|||
|
||||
#include "application.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <signal.h>
|
||||
#include <QDebug>
|
||||
|
||||
static void catchUnixSignals(const std::vector<int>& quitSignals, const std::vector<int>& ignoreSignals = std::vector<int>())
|
||||
static void catchUnixSignals(const std::vector<int> &quitSignals, const std::vector<int> &ignoreSignals = std::vector<int>())
|
||||
{
|
||||
auto handler = [](int sig) ->void {
|
||||
auto handler = [](int sig) -> void {
|
||||
switch (sig) {
|
||||
case SIGQUIT:
|
||||
qDebug() << "Cought SIGQUIT quit signal...";
|
||||
|
|
@ -60,11 +60,10 @@ static void catchUnixSignals(const std::vector<int>& quitSignals, const std::vec
|
|||
|
||||
for (int sig : quitSignals)
|
||||
signal(sig, handler);
|
||||
|
||||
}
|
||||
|
||||
Application::Application(int &argc, char **argv) :
|
||||
QCoreApplication(argc, argv)
|
||||
Application::Application(int &argc, char **argv)
|
||||
: QCoreApplication(argc, argv)
|
||||
{
|
||||
catchUnixSignals({SIGQUIT, SIGINT, SIGTERM, SIGHUP, SIGSEGV});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,15 +25,14 @@
|
|||
#ifndef APPLICATION_H
|
||||
#define APPLICATION_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QCoreApplication>
|
||||
#include <QObject>
|
||||
|
||||
class Application : public QCoreApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Application(int &argc, char **argv);
|
||||
|
||||
};
|
||||
|
||||
#endif // APPLICATION_H
|
||||
|
|
|
|||
|
|
@ -42,19 +42,23 @@ int main(int argc, char *argv[])
|
|||
"Copyright (C) 2013 - 2024 nymea GmbH\n"
|
||||
"Copyright (C) 2024 - 2025 chargebyte austria GmbH\n\n"
|
||||
"Released under the GNU General Public License v3.0 or (at your option) any later version.\n")
|
||||
.arg(application.applicationVersion());
|
||||
.arg(application.applicationVersion());
|
||||
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");
|
||||
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.");
|
||||
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() << "l" << "active-low", "Configure the pin as active low (default is active high).");
|
||||
|
|
@ -149,9 +153,7 @@ int main(int argc, char *argv[])
|
|||
GpioMonitor *monitor = new GpioMonitor(gpioNumber);
|
||||
|
||||
// Inform about interrupt
|
||||
QObject::connect(monitor, &GpioMonitor::valueChanged, [gpioNumber](bool value) {
|
||||
qDebug() << "GPIO" << gpioNumber << "value changed:" << (value ? "1" : "0");
|
||||
});
|
||||
QObject::connect(monitor, &GpioMonitor::valueChanged, [gpioNumber](bool value) { qDebug() << "GPIO" << gpioNumber << "value changed:" << (value ? "1" : "0"); });
|
||||
|
||||
// Enable the monitor
|
||||
if (!monitor->enable(activeLow, edge)) {
|
||||
|
|
@ -160,9 +162,7 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
|
||||
// Clean up the gpio once done
|
||||
QObject::connect(&application, &Application::aboutToQuit, [monitor](){
|
||||
delete monitor;
|
||||
});
|
||||
QObject::connect(&application, &Application::aboutToQuit, [monitor]() { delete monitor; });
|
||||
}
|
||||
|
||||
return application.exec();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,20 @@ greaterThan(QT_MAJOR_VERSION, 5) {
|
|||
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 {
|
||||
LIBGPIOD_VERSION = $$system(pkg-config --modversion libgpiod)
|
||||
message("Building with libgpiod GPIO backend $${LIBGPIOD_VERSION}")
|
||||
contains(LIBGPIOD_VERSION, ^2\\..*) {
|
||||
message("Building with libgpiod API V2 support")
|
||||
DEFINES += NYMEA_GPIO_LIBGPIOD_V2
|
||||
} else {
|
||||
message("Building with libgpiod API V1 support")
|
||||
}
|
||||
}
|
||||
|
||||
QMAKE_CXXFLAGS += -Werror -g
|
||||
|
||||
gcc {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
clang-format -i \
|
||||
$(git ls-files '*.cpp' '*.cc' '*.cxx' '*.h' '*.hpp' '*.hh' '*.ipp')
|
||||
Loading…
Reference in New Issue