1
0
mirror of https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git synced 2026-10-24 00:48:53 +03:00

treewide: libgpiod v2 implementation

This commit is the result of a squash of all the commits created during
the development of libgpiod v2. This is done to preserve bisectability
of the git tree. It contains significant re-writes of the core C library
as well as C++ and python bindings. The tools have been adjusted to work
with the new API but have not undergone significant changes yet.

This commit contains the bulk of the work on libgpiod v2 but the
development continues with more changes planned.

Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
[Ben: fix format specifier for uint64_t]
Signed-off-by: Ben Hutchings <ben.hutchings@mind.be>
[Viresh: Fix ioctl number for gpiod_line_request_reconfigure_lines()]
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
[Kent: bug fixes, improvements, better pointer contracts, type strictness]
Signed-off-by: Kent Gibson <warthog618@gmail.com>
This commit is contained in:
Bartosz Golaszewski
2021-03-25 09:26:37 +01:00
parent 7311e1d5b9
commit b7ba732e6a
170 changed files with 18266 additions and 12497 deletions

5
.gitignore vendored
View File

@@ -26,7 +26,12 @@ config.h.in~
config.log
config.status
configure
configure~
libtool
*-libtool
m4/
stamp-h1
# profiling
*.gcda
*.gcno

View File

@@ -44,7 +44,9 @@ WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_FORMAT =
WARN_LOGFILE =
INPUT = @top_srcdir@/include/gpiod.h @top_srcdir@/bindings/cxx/gpiod.hpp
INPUT = @top_srcdir@/include/gpiod.h \
@top_srcdir@/bindings/cxx/gpiod.hpp \
@top_srcdir@/bindings/cxx/gpiodcxx/
SOURCE_BROWSER = YES
INLINE_SOURCES = NO
REFERENCED_BY_RELATION = YES

View File

@@ -2,18 +2,38 @@
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
lib_LTLIBRARIES = libgpiodcxx.la
libgpiodcxx_la_SOURCES = chip.cpp internal.h iter.cpp line.cpp line_bulk.cpp
libgpiodcxx_la_CPPFLAGS = -Wall -Wextra -g -std=gnu++11
libgpiodcxx_la_CPPFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
libgpiodcxx_la_SOURCES = \
chip.cpp \
chip-info.cpp \
edge-event-buffer.cpp \
edge-event.cpp \
exception.cpp \
info-event.cpp \
internal.cpp \
internal.hpp \
line.cpp \
line-config.cpp \
line-info.cpp \
line-request.cpp \
line-settings.cpp \
misc.cpp \
request-builder.cpp \
request-config.cpp
libgpiodcxx_la_CXXFLAGS = -Wall -Wextra -g -std=gnu++17
libgpiodcxx_la_CXXFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
libgpiodcxx_la_CXXFLAGS += $(PROFILING_CFLAGS)
libgpiodcxx_la_CXXFLAGS += -DGPIOD_CXX_BUILD
libgpiodcxx_la_LDFLAGS = -version-info $(subst .,:,$(ABI_CXX_VERSION))
libgpiodcxx_la_LDFLAGS += -lgpiod -L$(top_builddir)/lib
libgpiodcxx_la_LDFLAGS += $(PROFILING_LDFLAGS)
include_HEADERS = gpiod.hpp
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libgpiodcxx.pc
SUBDIRS = .
SUBDIRS = gpiodcxx .
if WITH_TESTS

View File

@@ -0,0 +1,77 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <ostream>
#include <utility>
#include "internal.hpp"
namespace gpiod {
void chip_info::impl::set_info_ptr(chip_info_ptr& new_info)
{
this->info = ::std::move(new_info);
}
GPIOD_CXX_API chip_info::chip_info()
: _m_priv(new impl)
{
}
GPIOD_CXX_API chip_info::chip_info(const chip_info& other)
: _m_priv(other._m_priv)
{
}
GPIOD_CXX_API chip_info::chip_info(chip_info&& other) noexcept
: _m_priv(::std::move(other._m_priv))
{
}
GPIOD_CXX_API chip_info::~chip_info()
{
}
GPIOD_CXX_API chip_info& chip_info::operator=(const chip_info& other)
{
this->_m_priv = other._m_priv;
return *this;
}
GPIOD_CXX_API chip_info& chip_info::operator=(chip_info&& other) noexcept
{
this->_m_priv = ::std::move(other._m_priv);
return *this;
}
GPIOD_CXX_API ::std::string chip_info::name() const noexcept
{
return ::gpiod_chip_info_get_name(this->_m_priv->info.get());
}
GPIOD_CXX_API ::std::string chip_info::label() const noexcept
{
return ::gpiod_chip_info_get_label(this->_m_priv->info.get());
}
GPIOD_CXX_API ::std::size_t chip_info::num_lines() const noexcept
{
return ::gpiod_chip_info_get_num_lines(this->_m_priv->info.get());
}
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const chip_info& info)
{
out << "gpiod::chip_info(name=\"" << info.name() <<
"\", label=\"" << info.label() <<
"\", num_lines=" << info.num_lines() << ")";
return out;
}
} /* namespace gpiod */

View File

@@ -1,10 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <functional>
#include <gpiod.hpp>
#include <map>
#include <system_error>
#include <ostream>
#include <utility>
#include "internal.hpp"
@@ -13,145 +10,193 @@ namespace gpiod {
namespace {
GPIOD_CXX_API void chip_deleter(::gpiod_chip* chip)
chip_ptr open_chip(const ::std::filesystem::path& path)
{
::gpiod_chip_unref(chip);
chip_ptr chip(::gpiod_chip_open(path.c_str()));
if (!chip)
throw_from_errno("unable to open the GPIO device " + path.string());
return chip;
}
} /* namespace */
GPIOD_CXX_API bool is_gpiochip_device(const ::std::string& path)
{
return ::gpiod_is_gpiochip_device(path.c_str());
}
GPIOD_CXX_API chip::chip(const ::std::string& path)
: _m_chip()
{
this->open(path);
}
GPIOD_CXX_API chip::chip(::gpiod_chip* chip)
: _m_chip(chip, chip_deleter)
chip::impl::impl(const ::std::filesystem::path& path)
: chip(open_chip(path))
{
}
GPIOD_CXX_API chip::chip(const ::std::weak_ptr<::gpiod_chip>& chip_ptr)
: _m_chip(chip_ptr)
void chip::impl::throw_if_closed() const
{
if (!this->chip)
throw chip_closed("GPIO chip has been closed");
}
GPIOD_CXX_API chip::chip(const ::std::filesystem::path& path)
: _m_priv(new impl(path))
{
}
GPIOD_CXX_API void chip::open(const ::std::string& path)
chip::chip(const chip& other)
: _m_priv(other._m_priv)
{
::gpiod_chip *chip = ::gpiod_chip_open(path.c_str());
if (!chip)
throw ::std::system_error(errno, ::std::system_category(),
"cannot open GPIO device " + path);
this->_m_chip.reset(chip, chip_deleter);
}
GPIOD_CXX_API void chip::reset(void) noexcept
GPIOD_CXX_API chip::chip(chip&& other) noexcept
: _m_priv(::std::move(other._m_priv))
{
this->_m_chip.reset();
}
GPIOD_CXX_API ::std::string chip::name(void) const
GPIOD_CXX_API chip::~chip()
{
this->throw_if_noref();
return ::std::string(::gpiod_chip_get_name(this->_m_chip.get()));
}
GPIOD_CXX_API ::std::string chip::label(void) const
GPIOD_CXX_API chip& chip::operator=(chip&& other) noexcept
{
this->throw_if_noref();
this->_m_priv = ::std::move(other._m_priv);
return ::std::string(::gpiod_chip_get_label(this->_m_chip.get()));
return *this;
}
GPIOD_CXX_API unsigned int chip::num_lines(void) const
GPIOD_CXX_API chip::operator bool() const noexcept
{
this->throw_if_noref();
return ::gpiod_chip_get_num_lines(this->_m_chip.get());
return this->_m_priv->chip.get() != nullptr;
}
GPIOD_CXX_API line chip::get_line(unsigned int offset) const
GPIOD_CXX_API void chip::close()
{
this->throw_if_noref();
this->_m_priv->throw_if_closed();
if (offset >= this->num_lines())
throw ::std::out_of_range("line offset greater than the number of lines");
::gpiod_line* line_handle = ::gpiod_chip_get_line(this->_m_chip.get(), offset);
if (!line_handle)
throw ::std::system_error(errno, ::std::system_category(),
"error getting GPIO line from chip");
return line(line_handle, *this);
this->_m_priv->chip.reset();
}
GPIOD_CXX_API int chip::find_line(const ::std::string& name) const
GPIOD_CXX_API ::std::filesystem::path chip::path() const
{
this->throw_if_noref();
this->_m_priv->throw_if_closed();
for (unsigned int offset = 0; offset < this->num_lines(); offset++) {
auto line = this->get_line(offset);
return ::gpiod_chip_get_path(this->_m_priv->chip.get());
}
if (line.name() == name)
return offset;
GPIOD_CXX_API chip_info chip::get_info() const
{
this->_m_priv->throw_if_closed();
chip_info_ptr info(::gpiod_chip_get_info(this->_m_priv->chip.get()));
if (!info)
throw_from_errno("failed to retrieve GPIO chip info");
chip_info ret;
ret._m_priv->set_info_ptr(info);
return ret;
}
GPIOD_CXX_API line_info chip::get_line_info(line::offset offset) const
{
this->_m_priv->throw_if_closed();
line_info_ptr info(::gpiod_chip_get_line_info(this->_m_priv->chip.get(), offset));
if (!info)
throw_from_errno("unable to retrieve GPIO line info");
line_info ret;
ret._m_priv->set_info_ptr(info);
return ret;
}
GPIOD_CXX_API line_info chip::watch_line_info(line::offset offset) const
{
this->_m_priv->throw_if_closed();
line_info_ptr info(::gpiod_chip_watch_line_info(this->_m_priv->chip.get(), offset));
if (!info)
throw_from_errno("unable to start watching GPIO line info changes");
line_info ret;
ret._m_priv->set_info_ptr(info);
return ret;
}
GPIOD_CXX_API void chip::unwatch_line_info(line::offset offset) const
{
this->_m_priv->throw_if_closed();
int ret = ::gpiod_chip_unwatch_line_info(this->_m_priv->chip.get(), offset);
if (ret)
throw_from_errno("unable to unwatch line status changes");
}
GPIOD_CXX_API int chip::fd() const
{
this->_m_priv->throw_if_closed();
return ::gpiod_chip_get_fd(this->_m_priv->chip.get());
}
GPIOD_CXX_API bool chip::wait_info_event(const ::std::chrono::nanoseconds& timeout) const
{
this->_m_priv->throw_if_closed();
int ret = ::gpiod_chip_wait_info_event(this->_m_priv->chip.get(), timeout.count());
if (ret < 0)
throw_from_errno("error waiting for info events");
return ret;
}
GPIOD_CXX_API info_event chip::read_info_event() const
{
this->_m_priv->throw_if_closed();
info_event_ptr event(gpiod_chip_read_info_event(this->_m_priv->chip.get()));
if (!event)
throw_from_errno("error reading the line info event_handle");
info_event ret;
ret._m_priv->set_info_event_ptr(event);
return ret;
}
GPIOD_CXX_API int chip::get_line_offset_from_name(const ::std::string& name) const
{
this->_m_priv->throw_if_closed();
int ret = ::gpiod_chip_get_line_offset_from_name(this->_m_priv->chip.get(), name.c_str());
if (ret < 0) {
if (errno == ENOENT)
return -1;
throw_from_errno("error looking up line by name");
}
return -1;
return ret;
}
GPIOD_CXX_API line_bulk chip::get_lines(const ::std::vector<unsigned int>& offsets) const
GPIOD_CXX_API request_builder chip::prepare_request()
{
line_bulk lines;
for (auto& it: offsets)
lines.append(this->get_line(it));
return lines;
return request_builder(*this);
}
GPIOD_CXX_API line_bulk chip::get_all_lines(void) const
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const chip& chip)
{
line_bulk lines;
if (!chip)
out << "gpiod::chip(closed)";
else
out << "gpiod::chip(path=" << chip.path() <<
", info=" << chip.get_info() << ")";
for (unsigned int i = 0; i < this->num_lines(); i++)
lines.append(this->get_line(i));
return lines;
}
GPIOD_CXX_API bool chip::operator==(const chip& rhs) const noexcept
{
return this->_m_chip.get() == rhs._m_chip.get();
}
GPIOD_CXX_API bool chip::operator!=(const chip& rhs) const noexcept
{
return this->_m_chip.get() != rhs._m_chip.get();
}
GPIOD_CXX_API chip::operator bool(void) const noexcept
{
return this->_m_chip.get() != nullptr;
}
GPIOD_CXX_API bool chip::operator!(void) const noexcept
{
return this->_m_chip.get() == nullptr;
}
GPIOD_CXX_API void chip::throw_if_noref(void) const
{
if (!this->_m_chip.get())
throw ::std::logic_error("object not associated with an open GPIO chip");
return out;
}
} /* namespace gpiod */

View File

@@ -0,0 +1,117 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <iterator>
#include <ostream>
#include <utility>
#include "internal.hpp"
namespace gpiod {
namespace {
::gpiod_edge_event_buffer* make_edge_event_buffer(unsigned int capacity)
{
::gpiod_edge_event_buffer* buffer = ::gpiod_edge_event_buffer_new(capacity);
if (!buffer)
throw_from_errno("unable to allocate the edge event buffer");
return buffer;
}
} /* namespace */
edge_event_buffer::impl::impl(unsigned int capacity)
: buffer(make_edge_event_buffer(capacity)),
events()
{
events.reserve(capacity);
for (unsigned int i = 0; i < capacity; i++) {
events.push_back(edge_event());
events.back()._m_priv.reset(new edge_event::impl_external);
}
}
int edge_event_buffer::impl::read_events(const line_request_ptr& request, unsigned int max_events)
{
int ret = ::gpiod_line_request_read_edge_event(request.get(),
this->buffer.get(), max_events);
if (ret < 0)
throw_from_errno("error reading edge events from file descriptor");
for (int i = 0; i < ret; i++) {
::gpiod_edge_event* event = ::gpiod_edge_event_buffer_get_event(this->buffer.get(), i);
dynamic_cast<edge_event::impl_external&>(*this->events[i]._m_priv).event = event;
}
return ret;
}
GPIOD_CXX_API edge_event_buffer::edge_event_buffer(::std::size_t capacity)
: _m_priv(new impl(capacity))
{
}
GPIOD_CXX_API edge_event_buffer::edge_event_buffer(edge_event_buffer&& other) noexcept
: _m_priv(::std::move(other._m_priv))
{
}
GPIOD_CXX_API edge_event_buffer::~edge_event_buffer()
{
}
GPIOD_CXX_API edge_event_buffer& edge_event_buffer::operator=(edge_event_buffer&& other) noexcept
{
this->_m_priv = ::std::move(other._m_priv);
return *this;
}
GPIOD_CXX_API const edge_event& edge_event_buffer::get_event(unsigned int index) const
{
return this->_m_priv->events.at(index);
}
GPIOD_CXX_API ::std::size_t edge_event_buffer::num_events() const
{
return ::gpiod_edge_event_buffer_get_num_events(this->_m_priv->buffer.get());
}
GPIOD_CXX_API ::std::size_t edge_event_buffer::capacity() const noexcept
{
return ::gpiod_edge_event_buffer_get_capacity(this->_m_priv->buffer.get());
}
GPIOD_CXX_API edge_event_buffer::const_iterator edge_event_buffer::begin() const noexcept
{
return this->_m_priv->events.begin();
}
GPIOD_CXX_API edge_event_buffer::const_iterator edge_event_buffer::end() const noexcept
{
return this->_m_priv->events.begin() + this->num_events();
}
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const edge_event_buffer& buf)
{
out << "gpiod::edge_event_buffer(num_events=" << buf.num_events() <<
", capacity=" << buf.capacity() <<
", events=[";
::std::copy(buf.begin(), ::std::prev(buf.end()),
::std::ostream_iterator<edge_event>(out, ", "));
out << *(::std::prev(buf.end()));
out << "])";
return out;
}
} /* namespace gpiod */

137
bindings/cxx/edge-event.cpp Normal file
View File

@@ -0,0 +1,137 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <map>
#include <ostream>
#include <utility>
#include "internal.hpp"
namespace gpiod {
namespace {
const ::std::map<int, edge_event::event_type> event_type_mapping = {
{ GPIOD_EDGE_EVENT_RISING_EDGE, edge_event::event_type::RISING_EDGE },
{ GPIOD_EDGE_EVENT_FALLING_EDGE, edge_event::event_type::FALLING_EDGE }
};
const ::std::map<edge_event::event_type, ::std::string> event_type_names = {
{ edge_event::event_type::RISING_EDGE, "RISING_EDGE" },
{ edge_event::event_type::FALLING_EDGE, "FALLING_EDGE" }
};
} /* namespace */
::gpiod_edge_event* edge_event::impl_managed::get_event_ptr() const noexcept
{
return this->event.get();
}
::std::shared_ptr<edge_event::impl>
edge_event::impl_managed::copy(const ::std::shared_ptr<impl>& self) const
{
return self;
}
edge_event::impl_external::impl_external()
: impl(),
event(nullptr)
{
}
::gpiod_edge_event* edge_event::impl_external::get_event_ptr() const noexcept
{
return this->event;
}
::std::shared_ptr<edge_event::impl>
edge_event::impl_external::copy(const ::std::shared_ptr<impl>& self GPIOD_CXX_UNUSED) const
{
::std::shared_ptr<impl> ret(new impl_managed);
impl_managed& managed = dynamic_cast<impl_managed&>(*ret);
managed.event.reset(::gpiod_edge_event_copy(this->event));
if (!managed.event)
throw_from_errno("unable to copy the edge event object");
return ret;
}
edge_event::edge_event()
: _m_priv()
{
}
GPIOD_CXX_API edge_event::edge_event(const edge_event& other)
: _m_priv(other._m_priv->copy(other._m_priv))
{
}
GPIOD_CXX_API edge_event::edge_event(edge_event&& other) noexcept
: _m_priv(::std::move(other._m_priv))
{
}
GPIOD_CXX_API edge_event::~edge_event()
{
}
GPIOD_CXX_API edge_event& edge_event::operator=(const edge_event& other)
{
this->_m_priv = other._m_priv->copy(other._m_priv);
return *this;
}
GPIOD_CXX_API edge_event& edge_event::operator=(edge_event&& other) noexcept
{
this->_m_priv = ::std::move(other._m_priv);
return *this;
}
GPIOD_CXX_API edge_event::event_type edge_event::type() const
{
int evtype = ::gpiod_edge_event_get_event_type(this->_m_priv->get_event_ptr());
return map_int_to_enum(evtype, event_type_mapping);
}
GPIOD_CXX_API timestamp edge_event::timestamp_ns() const noexcept
{
return ::gpiod_edge_event_get_timestamp_ns(this->_m_priv->get_event_ptr());
}
GPIOD_CXX_API line::offset edge_event::line_offset() const noexcept
{
return ::gpiod_edge_event_get_line_offset(this->_m_priv->get_event_ptr());
}
GPIOD_CXX_API unsigned long edge_event::global_seqno() const noexcept
{
return ::gpiod_edge_event_get_global_seqno(this->_m_priv->get_event_ptr());
}
GPIOD_CXX_API unsigned long edge_event::line_seqno() const noexcept
{
return ::gpiod_edge_event_get_line_seqno(this->_m_priv->get_event_ptr());
}
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const edge_event& event)
{
out << "gpiod::edge_event(type='" << event_type_names.at(event.type()) <<
"', timestamp=" << event.timestamp_ns() <<
", line_offset=" << event.line_offset() <<
", global_seqno=" << event.global_seqno() <<
", line_seqno=" << event.line_seqno() << ")";
return out;
}
} /* namespace gpiod */

View File

@@ -1,16 +1,16 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
AM_CPPFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include
AM_CPPFLAGS += -Wall -Wextra -g -std=gnu++17
AM_LDFLAGS = -lgpiodcxx -L$(top_builddir)/bindings/cxx/ -lstdc++fs
AM_CXXFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include
AM_CXXFLAGS += -Wall -Wextra -g -std=gnu++17
AM_LDFLAGS = -lgpiodcxx -L$(top_builddir)/bindings/cxx/
noinst_PROGRAMS = \
gpiodetectcxx \
gpiofindcxx \
gpiogetcxx \
gpioinfocxx \
gpiomoncxx \
noinst_PROGRAMS = \
gpiodetectcxx \
gpiofindcxx \
gpiogetcxx \
gpioinfocxx \
gpiomoncxx \
gpiosetcxx
gpiodetectcxx_SOURCES = gpiodetectcxx.cpp

View File

@@ -3,10 +3,9 @@
/* C++ reimplementation of the gpiodetect tool. */
#include <gpiod.hpp>
#include <cstdlib>
#include <filesystem>
#include <gpiod.hpp>
#include <iostream>
int main(int argc, char **argv)
@@ -19,10 +18,11 @@ int main(int argc, char **argv)
for (const auto& entry: ::std::filesystem::directory_iterator("/dev/")) {
if (::gpiod::is_gpiochip_device(entry.path())) {
::gpiod::chip chip(entry.path());
auto info = chip.get_info();
::std::cout << chip.name() << " ["
<< chip.label() << "] ("
<< chip.num_lines() << " lines)" << ::std::endl;
::std::cout << info.name() << " [" <<
info.label() << "] (" <<
info.num_lines() << " lines)" << ::std::endl;
}
}

View File

@@ -20,9 +20,9 @@ int main(int argc, char **argv)
if (::gpiod::is_gpiochip_device(entry.path())) {
::gpiod::chip chip(entry.path());
auto offset = chip.find_line(argv[1]);
auto offset = chip.get_line_offset_from_name(argv[1]);
if (offset >= 0) {
::std::cout << chip.name() << " " << offset << ::std::endl;
::std::cout << chip.get_info().name() << " " << offset << ::std::endl;
return EXIT_SUCCESS;
}
}

View File

@@ -15,24 +15,25 @@ int main(int argc, char **argv)
return EXIT_FAILURE;
}
::std::vector<unsigned int> offsets;
::gpiod::line::offsets offsets;
for (int i = 2; i < argc; i++)
offsets.push_back(::std::stoul(argv[i]));
::gpiod::chip chip(argv[1]);
auto lines = chip.get_lines(offsets);
auto request = ::gpiod::chip(argv[1])
.prepare_request()
.set_consumer("gpiogetcxx")
.add_line_settings(
offsets,
::gpiod::line_settings()
.set_direction(::gpiod::line::direction::INPUT)
)
.do_request();
lines.request({
argv[0],
::gpiod::line_request::DIRECTION_INPUT,
0
});
auto vals = lines.get_values();
auto vals = request.get_values();
for (auto& it: vals)
::std::cout << it << ' ';
::std::cout << (it == ::gpiod::line::value::ACTIVE ? "1" : "0") << ' ';
::std::cout << ::std::endl;
return EXIT_SUCCESS;

View File

@@ -9,6 +9,42 @@
#include <filesystem>
#include <iostream>
namespace {
void show_chip(const ::gpiod::chip& chip)
{
auto info = chip.get_info();
::std::cout << info.name() << " - " << info.num_lines() << " lines:" << ::std::endl;
for (unsigned int offset = 0; offset < info.num_lines(); offset++) {
auto info = chip.get_line_info(offset);
::std::cout << "\tline ";
::std::cout.width(3);
::std::cout << info.offset() << ": ";
::std::cout.width(12);
::std::cout << (info.name().empty() ? "unnamed" : info.name());
::std::cout << " ";
::std::cout.width(12);
::std::cout << (info.consumer().empty() ? "unused" : info.consumer());
::std::cout << " ";
::std::cout.width(8);
::std::cout << (info.direction() == ::gpiod::line::direction::INPUT ? "input" : "output");
::std::cout << " ";
::std::cout.width(10);
::std::cout << (info.active_low() ? "active-low" : "active-high");
::std::cout << ::std::endl;
}
}
} /* namespace */
int main(int argc, char **argv)
{
if (argc != 1) {
@@ -17,34 +53,8 @@ int main(int argc, char **argv)
}
for (const auto& entry: ::std::filesystem::directory_iterator("/dev/")) {
if (::gpiod::is_gpiochip_device(entry.path())) {
::gpiod::chip chip(entry.path());
::std::cout << chip.name() << " - " << chip.num_lines() << " lines:" << ::std::endl;
for (auto& lit: ::gpiod::line_iter(chip)) {
::std::cout << "\tline ";
::std::cout.width(3);
::std::cout << lit.offset() << ": ";
::std::cout.width(12);
::std::cout << (lit.name().empty() ? "unnamed" : lit.name());
::std::cout << " ";
::std::cout.width(12);
::std::cout << (lit.consumer().empty() ? "unused" : lit.consumer());
::std::cout << " ";
::std::cout.width(8);
::std::cout << (lit.direction() == ::gpiod::line::DIRECTION_INPUT ? "input" : "output");
::std::cout << " ";
::std::cout.width(10);
::std::cout << (lit.is_active_low() ? "active-low" : "active-high");
::std::cout << ::std::endl;
}
}
if (::gpiod::is_gpiochip_device(entry.path()))
show_chip(::gpiod::chip(entry.path()));
}
return EXIT_SUCCESS;

View File

@@ -3,29 +3,27 @@
/* Simplified C++ reimplementation of the gpiomon tool. */
#include <gpiod.hpp>
#include <chrono>
#include <cstdlib>
#include <gpiod.hpp>
#include <iostream>
namespace {
void print_event(const ::gpiod::line_event& event)
void print_event(const ::gpiod::edge_event& event)
{
if (event.event_type == ::gpiod::line_event::RISING_EDGE)
if (event.type() == ::gpiod::edge_event::event_type::RISING_EDGE)
::std::cout << " RISING EDGE";
else if (event.event_type == ::gpiod::line_event::FALLING_EDGE)
::std::cout << "FALLING EDGE";
else
throw ::std::logic_error("invalid event type");
::std::cout << "FALLING EDGE";
::std::cout << " ";
::std::cout << ::std::chrono::duration_cast<::std::chrono::seconds>(event.timestamp).count();
::std::cout << event.timestamp_ns() / 1000000000;
::std::cout << ".";
::std::cout << event.timestamp.count() % 1000000000;
::std::cout << event.timestamp_ns() % 1000000000;
::std::cout << " line: " << event.source.offset();
::std::cout << " line: " << event.line_offset();
::std::cout << ::std::endl;
}
@@ -39,26 +37,29 @@ int main(int argc, char **argv)
return EXIT_FAILURE;
}
::std::vector<unsigned int> offsets;
::gpiod::line::offsets offsets;
offsets.reserve(argc);
for (int i = 2; i < argc; i++)
offsets.push_back(::std::stoul(argv[i]));
::gpiod::chip chip(argv[1]);
auto lines = chip.get_lines(offsets);
auto request = ::gpiod::chip(argv[1])
.prepare_request()
.set_consumer("gpiomoncxx")
.add_line_settings(
offsets,
::gpiod::line_settings()
.set_direction(::gpiod::line::direction::INPUT)
.set_edge_detection(::gpiod::line::edge::BOTH)
)
.do_request();
lines.request({
argv[0],
::gpiod::line_request::EVENT_BOTH_EDGES,
0,
});
::gpiod::edge_event_buffer buffer;
for (;;) {
auto events = lines.event_wait(::std::chrono::seconds(1));
if (events) {
for (auto& it: events)
print_event(it.event_read());
}
request.read_edge_event(buffer);
for (const auto& event: buffer)
print_event(event);
}
return EXIT_SUCCESS;

View File

@@ -11,12 +11,13 @@
int main(int argc, char **argv)
{
if (argc < 3) {
::std::cerr << "usage: " << argv[0] << " <chip> <line_offset0>=<value0> ..." << ::std::endl;
::std::cerr << "usage: " << argv[0] <<
" <chip> <line_offset0>=<value0> ..." << ::std::endl;
return EXIT_FAILURE;
}
::std::vector<unsigned int> offsets;
::std::vector<int> values;
::gpiod::line::offsets offsets;
::gpiod::line::values values;
for (int i = 2; i < argc; i++) {
::std::string arg(argv[i]);
@@ -27,20 +28,25 @@ int main(int argc, char **argv)
::std::string value(arg.substr(pos + 1, ::std::string::npos));
if (offset.empty() || value.empty())
throw ::std::invalid_argument("invalid argument: " + ::std::string(argv[i]));
throw ::std::invalid_argument("invalid offset=value mapping: " +
::std::string(argv[i]));
offsets.push_back(::std::stoul(offset));
values.push_back(::std::stoul(value));
values.push_back(::std::stoul(value) ? ::gpiod::line::value::ACTIVE :
::gpiod::line::value::INACTIVE);
}
::gpiod::chip chip(argv[1]);
auto lines = chip.get_lines(offsets);
auto request = ::gpiod::chip(argv[1])
.prepare_request()
.set_consumer("gpiosetcxx")
.add_line_settings(
offsets,
::gpiod::line_settings()
.set_direction(::gpiod::line::direction::OUTPUT)
)
.do_request();
lines.request({
argv[0],
::gpiod::line_request::DIRECTION_OUTPUT,
0
}, values);
request.set_values(values);
::std::cin.get();

119
bindings/cxx/exception.cpp Normal file
View File

@@ -0,0 +1,119 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include "internal.hpp"
namespace gpiod {
GPIOD_CXX_API chip_closed::chip_closed(const ::std::string& what)
: ::std::logic_error(what)
{
}
GPIOD_CXX_API chip_closed::chip_closed(const chip_closed& other) noexcept
: ::std::logic_error(other)
{
}
GPIOD_CXX_API chip_closed::chip_closed(chip_closed&& other) noexcept
: ::std::logic_error(other)
{
}
GPIOD_CXX_API chip_closed& chip_closed::operator=(const chip_closed& other) noexcept
{
::std::logic_error::operator=(other);
return *this;
}
GPIOD_CXX_API chip_closed& chip_closed::operator=(chip_closed&& other) noexcept
{
::std::logic_error::operator=(other);
return *this;
}
GPIOD_CXX_API chip_closed::~chip_closed()
{
}
GPIOD_CXX_API request_released::request_released(const ::std::string& what)
: ::std::logic_error(what)
{
}
GPIOD_CXX_API request_released::request_released(const request_released& other) noexcept
: ::std::logic_error(other)
{
}
GPIOD_CXX_API request_released::request_released(request_released&& other) noexcept
: ::std::logic_error(other)
{
}
GPIOD_CXX_API request_released& request_released::operator=(const request_released& other) noexcept
{
::std::logic_error::operator=(other);
return *this;
}
GPIOD_CXX_API request_released& request_released::operator=(request_released&& other) noexcept
{
::std::logic_error::operator=(other);
return *this;
}
GPIOD_CXX_API request_released::~request_released()
{
}
GPIOD_CXX_API bad_mapping::bad_mapping(const ::std::string& what)
: ::std::runtime_error(what)
{
}
GPIOD_CXX_API bad_mapping::bad_mapping(const bad_mapping& other) noexcept
: ::std::runtime_error(other)
{
}
GPIOD_CXX_API bad_mapping::bad_mapping(bad_mapping&& other) noexcept
: ::std::runtime_error(other)
{
}
GPIOD_CXX_API bad_mapping& bad_mapping::operator=(const bad_mapping& other) noexcept
{
::std::runtime_error::operator=(other);
return *this;
}
GPIOD_CXX_API bad_mapping& bad_mapping::operator=(bad_mapping&& other) noexcept
{
::std::runtime_error::operator=(other);
return *this;
}
GPIOD_CXX_API bad_mapping::~bad_mapping()
{
}
} /* namespace gpiod */

View File

@@ -1,940 +1,52 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
#ifndef __LIBGPIOD_GPIOD_CXX_HPP__
#define __LIBGPIOD_GPIOD_CXX_HPP__
#include <bitset>
#include <chrono>
#include <gpiod.h>
#include <memory>
#include <string>
#include <vector>
namespace gpiod {
class line;
class line_bulk;
class line_iter;
class chip_iter;
struct line_event;
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file gpiod.hpp
*/
#ifndef __LIBGPIOD_GPIOD_CXX_HPP__
#define __LIBGPIOD_GPIOD_CXX_HPP__
/**
* @defgroup gpiod_cxx C++ bindings
* @{
*/
/**
* @brief Check if the file pointed to by path is a GPIO chip character device.
* @param path Path to check.
* @return True if the file exists and is a GPIO chip character device or a
* symbolic link to it.
*/
bool is_gpiochip_device(const ::std::string& path);
/**
* @brief Represents a GPIO chip.
*
* Internally this class holds a smart pointer to an open GPIO chip descriptor.
* Multiple objects of this class can reference the same chip. The chip is
* closed and all resources freed when the last reference is dropped.
* C++ bindings for libgpiod.
*/
class chip
{
public:
/**
* @brief Default constructor. Creates an empty GPIO chip object.
*/
chip(void) = default;
/**
* @brief Constructor. Opens the chip using chip::open.
* @param path Path to the GPIO chip device.
*/
chip(const ::std::string& path);
/**
* @brief Copy constructor. References the object held by other.
* @param other Other chip object.
*/
chip(const chip& other) = default;
/**
* @brief Move constructor. References the object held by other.
* @param other Other chip object.
*/
chip(chip&& other) = default;
/**
* @brief Assignment operator. References the object held by other.
* @param other Other chip object.
* @return Reference to this object.
*/
chip& operator=(const chip& other) = default;
/**
* @brief Move assignment operator. References the object held by other.
* @param other Other chip object.
* @return Reference to this object.
*/
chip& operator=(chip&& other) = default;
/**
* @brief Destructor. Unreferences the internal chip object.
*/
~chip(void) = default;
/**
* @brief Open a GPIO chip.
* @param path Path to the GPIO chip device.
*
* If the object already holds a reference to an open chip, it will be
* closed and the reference reset.
*/
void open(const ::std::string &path);
/**
* @brief Reset the internal smart pointer owned by this object.
*/
void reset(void) noexcept;
/**
* @brief Return the name of the chip held by this object.
* @return Name of the GPIO chip.
*/
::std::string name(void) const;
/**
* @brief Return the label of the chip held by this object.
* @return Label of the GPIO chip.
*/
::std::string label(void) const;
/**
* @brief Return the number of lines exposed by this chip.
* @return Number of lines.
*/
unsigned int num_lines(void) const;
/**
* @brief Get the line exposed by this chip at given offset.
* @param offset Offset of the line.
* @return Line object.
*/
line get_line(unsigned int offset) const;
/**
* @brief Map a GPIO line's name to its offset within the chip.
* @param name Name of the GPIO line to map.
* @return Offset of the line within the chip or -1 if a line with
* given name is not exposed by the chip.
*/
int find_line(const ::std::string& name) const;
/**
* @brief Get a set of lines exposed by this chip at given offsets.
* @param offsets Vector of line offsets.
* @return Set of lines held by a line_bulk object.
*/
line_bulk get_lines(const ::std::vector<unsigned int>& offsets) const;
/**
* @brief Get all lines exposed by this chip.
* @return All lines exposed by this chip held by a line_bulk object.
*/
line_bulk get_all_lines(void) const;
/**
* @brief Equality operator.
* @param rhs Right-hand side of the equation.
* @return True if rhs references the same chip. False otherwise.
*/
bool operator==(const chip& rhs) const noexcept;
/**
* @brief Inequality operator.
* @param rhs Right-hand side of the equation.
* @return False if rhs references the same chip. True otherwise.
*/
bool operator!=(const chip& rhs) const noexcept;
/**
* @brief Check if this object holds a reference to a GPIO chip.
* @return True if this object references a GPIO chip, false otherwise.
*/
explicit operator bool(void) const noexcept;
/**
* @brief Check if this object doesn't hold a reference to a GPIO chip.
* @return False if this object references a GPIO chip, true otherwise.
*/
bool operator!(void) const noexcept;
private:
chip(::gpiod_chip* chip);
chip(const ::std::weak_ptr<::gpiod_chip>& chip_ptr);
void throw_if_noref(void) const;
::std::shared_ptr<::gpiod_chip> _m_chip;
friend line;
friend chip_iter;
friend line_iter;
};
/**
* @brief Stores the configuration for line requests.
* @cond
*/
struct line_request
{
/**
* @brief Request types.
*/
enum : int {
DIRECTION_AS_IS = 1,
/**< Request for values, don't change the direction. */
DIRECTION_INPUT,
/**< Request for reading line values. */
DIRECTION_OUTPUT,
/**< Request for driving the GPIO lines. */
EVENT_FALLING_EDGE,
/**< Listen for falling edge events. */
EVENT_RISING_EDGE,
/**< Listen for rising edge events. */
EVENT_BOTH_EDGES,
/**< Listen for all types of events. */
};
static const ::std::bitset<32> FLAG_ACTIVE_LOW;
/**< Set the active state to 'low' (high is the default). */
static const ::std::bitset<32> FLAG_OPEN_SOURCE;
/**< The line is an open-source port. */
static const ::std::bitset<32> FLAG_OPEN_DRAIN;
/**< The line is an open-drain port. */
static const ::std::bitset<32> FLAG_BIAS_DISABLED;
/**< The line has neither pull-up nor pull-down resistor enabled. */
static const ::std::bitset<32> FLAG_BIAS_PULL_DOWN;
/**< The line has a configurable pull-down resistor enabled. */
static const ::std::bitset<32> FLAG_BIAS_PULL_UP;
/**< The line has a configurable pull-up resistor enabled. */
/*
* We don't make this symbol private because it needs to be accessible by
* the declarations in exception.hpp in order to expose the symbols of classes
* inheriting from standard exceptions.
*/
#ifdef GPIOD_CXX_BUILD
#define GPIOD_CXX_API __attribute__((visibility("default")))
#else
#define GPIOD_CXX_API __attribute__((visibility("hidden")))
#endif
::std::string consumer;
/**< Consumer name to pass to the request. */
int request_type;
/**< Type of the request. */
::std::bitset<32> flags;
/**< Additional request flags. */
};
#define __LIBGPIOD_GPIOD_CXX_INSIDE__
#include "gpiodcxx/chip.hpp"
#include "gpiodcxx/chip-info.hpp"
#include "gpiodcxx/edge-event.hpp"
#include "gpiodcxx/edge-event-buffer.hpp"
#include "gpiodcxx/exception.hpp"
#include "gpiodcxx/info-event.hpp"
#include "gpiodcxx/line.hpp"
#include "gpiodcxx/line-config.hpp"
#include "gpiodcxx/line-info.hpp"
#include "gpiodcxx/line-request.hpp"
#include "gpiodcxx/line-settings.hpp"
#include "gpiodcxx/request-builder.hpp"
#include "gpiodcxx/request-config.hpp"
#undef __LIBGPIOD_GPIOD_CXX_INSIDE__
/**
* @brief Represents a single GPIO line.
*
* Internally this class holds a raw pointer to a GPIO line descriptor and a
* reference to the parent chip. All line resources are freed when the last
* reference to the parent chip is dropped.
* @endcond
*/
class line
{
public:
/**
* @brief Default constructor. Creates an empty line object.
*/
line(void);
/**
* @brief Copy constructor.
* @param other Other line object.
*/
line(const line& other) = default;
/**
* @brief Move constructor.
* @param other Other line object.
*/
line(line&& other) = default;
/**
* @brief Assignment operator.
* @param other Other line object.
* @return Reference to this object.
*/
line& operator=(const line& other) = default;
/**
* @brief Move assignment operator.
* @param other Other line object.
* @return Reference to this object.
*/
line& operator=(line&& other) = default;
/**
* @brief Destructor.
*/
~line(void) = default;
/**
* @brief Get the offset of this line.
* @return Offet of this line.
*/
unsigned int offset(void) const;
/**
* @brief Get the name of this line (if any).
* @return Name of this line or an empty string if it is unnamed.
*/
::std::string name(void) const;
/**
* @brief Get the consumer of this line (if any).
* @return Name of the consumer of this line or an empty string if it
* is unused.
*/
::std::string consumer(void) const;
/**
* @brief Get current direction of this line.
* @return Current direction setting.
*/
int direction(void) const;
/**
* @brief Check if this line's signal is inverted.
* @return True if this line is "active-low", false otherwise.
*/
bool is_active_low(void) const;
/**
* @brief Get current bias of this line.
* @return Current bias setting.
*/
int bias(void) const;
/**
* @brief Check if this line is used by the kernel or other user space
* process.
* @return True if this line is in use, false otherwise.
*/
bool is_used(void) const;
/**
* @brief Get current drive setting of this line.
* @return Current drive setting.
*/
int drive(void) const;
/**
* @brief Request this line.
* @param config Request config (see gpiod::line_request).
* @param default_val Default value - only matters for OUTPUT direction.
*/
void request(const line_request& config, int default_val = 0) const;
/**
* @brief Release the line if it was previously requested.
*/
void release(void) const;
/**
* @brief Read the line value.
* @return Current value (0 or 1).
*/
int get_value(void) const;
/**
* @brief Set the value of this line.
* @param val New value (0 or 1).
*/
void set_value(int val) const;
/**
* @brief Set configuration of this line.
* @param direction New direction.
* @param flags Replacement flags.
* @param value New value (0 or 1) - only matters for OUTPUT direction.
*/
void set_config(int direction, ::std::bitset<32> flags, int value = 0) const;
/**
* @brief Set configuration flags of this line.
* @param flags Replacement flags.
*/
void set_flags(::std::bitset<32> flags) const;
/**
* @brief Change the direction this line to input.
*/
void set_direction_input() const;
/**
* @brief Change the direction this lines to output.
* @param value New value (0 or 1).
*/
void set_direction_output(int value = 0) const;
/**
* @brief Wait for an event on this line.
* @param timeout Time to wait before returning if no event occurred.
* @return True if an event occurred and can be read, false if the wait
* timed out.
*/
bool event_wait(const ::std::chrono::nanoseconds& timeout) const;
/**
* @brief Read a line event.
* @return Line event object.
*/
line_event event_read(void) const;
/**
* @brief Read multiple line events.
* @return Vector of line event objects.
*/
::std::vector<line_event> event_read_multiple(void) const;
/**
* @brief Get the event file descriptor associated with this line.
* @return File descriptor number.
*/
int event_get_fd(void) const;
/**
* @brief Get the parent chip.
* @return Parent chip of this line.
*/
const chip get_chip(void) const;
/**
* @brief Reset the state of this object.
*
* This is useful when the user needs to e.g. keep the line_event object
* but wants to drop the reference to the GPIO chip indirectly held by
* the line being the source of the event.
*/
void reset(void);
/**
* @brief Check if two line objects reference the same GPIO line.
* @param rhs Right-hand side of the equation.
* @return True if both objects reference the same line, fale otherwise.
*/
bool operator==(const line& rhs) const noexcept;
/**
* @brief Check if two line objects reference different GPIO lines.
* @param rhs Right-hand side of the equation.
* @return False if both objects reference the same line, true otherwise.
*/
bool operator!=(const line& rhs) const noexcept;
/**
* @brief Check if this object holds a reference to any GPIO line.
* @return True if this object references a GPIO line, false otherwise.
*/
explicit operator bool(void) const noexcept;
/**
* @brief Check if this object doesn't reference any GPIO line.
* @return True if this object doesn't reference any GPIO line, true
* otherwise.
*/
bool operator!(void) const noexcept;
/**
* @brief Possible direction settings.
*/
enum : int {
DIRECTION_INPUT = 1,
/**< Line's direction setting is input. */
DIRECTION_OUTPUT,
/**< Line's direction setting is output. */
};
/**
* @brief Possible drive settings.
*/
enum : int {
DRIVE_PUSH_PULL = 1,
/**< Drive setting is unknown. */
DRIVE_OPEN_DRAIN,
/**< Line output is open-drain. */
DRIVE_OPEN_SOURCE,
/**< Line output is open-source. */
};
/**
* @brief Possible bias settings.
*/
enum : int {
BIAS_UNKNOWN = 1,
/**< Line's bias state is unknown. */
BIAS_DISABLED,
/**< Line's internal bias is disabled. */
BIAS_PULL_UP,
/**< Line's internal pull-up bias is enabled. */
BIAS_PULL_DOWN,
/**< Line's internal pull-down bias is enabled. */
};
private:
line(::gpiod_line* line, const chip& owner);
void throw_if_null(void) const;
line_event make_line_event(const ::gpiod_line_event& event) const noexcept;
::gpiod_line* _m_line;
::std::weak_ptr<::gpiod_chip> _m_owner;
class chip_guard
{
public:
chip_guard(const line& line);
~chip_guard(void) = default;
chip_guard(const chip_guard& other) = delete;
chip_guard(chip_guard&& other) = delete;
chip_guard& operator=(const chip_guard&& other) = delete;
chip_guard& operator=(chip_guard&& other) = delete;
private:
::std::shared_ptr<::gpiod_chip> _m_chip;
};
friend chip;
friend line_bulk;
friend line_iter;
};
/**
* @brief Describes a single GPIO line event.
*/
struct line_event
{
/**
* @brief Possible event types.
*/
enum : int {
RISING_EDGE = 1,
/**< Rising edge event. */
FALLING_EDGE,
/**< Falling edge event. */
};
::std::chrono::nanoseconds timestamp;
/**< Best estimate of time of event occurrence in nanoseconds. */
int event_type;
/**< Type of the event that occurred. */
line source;
/**< Line object referencing the GPIO line on which the event occurred. */
};
/**
* @brief Represents a set of GPIO lines.
*
* Internally an object of this class stores an array of line objects
* owned by a single chip.
*/
class line_bulk
{
public:
/**
* @brief Default constructor. Creates an empty line_bulk object.
*/
line_bulk(void) = default;
/**
* @brief Construct a line_bulk from a vector of lines.
* @param lines Vector of gpiod::line objects.
* @note All lines must be owned by the same GPIO chip.
*/
line_bulk(const ::std::vector<line>& lines);
/**
* @brief Copy constructor.
* @param other Other line_bulk object.
*/
line_bulk(const line_bulk& other) = default;
/**
* @brief Move constructor.
* @param other Other line_bulk object.
*/
line_bulk(line_bulk&& other) = default;
/**
* @brief Assignment operator.
* @param other Other line_bulk object.
* @return Reference to this object.
*/
line_bulk& operator=(const line_bulk& other) = default;
/**
* @brief Move assignment operator.
* @param other Other line_bulk object.
* @return Reference to this object.
*/
line_bulk& operator=(line_bulk&& other) = default;
/**
* @brief Destructor.
*/
~line_bulk(void) = default;
/**
* @brief Add a line to this line_bulk object.
* @param new_line Line to add.
* @note The new line must be owned by the same chip as all the other
* lines already held by this line_bulk object.
*/
void append(const line& new_line);
/**
* @brief Get the line at given offset.
* @param index Index of the line to get.
* @return Reference to the line object.
* @note This method will throw if index is equal or greater than the
* number of lines currently held by this bulk.
*/
line& get(unsigned int index);
/**
* @brief Get the line at given offset without bounds checking.
* @param index Offset of the line to get.
* @return Reference to the line object.
* @note No bounds checking is performed.
*/
line& operator[](unsigned int index);
/**
* @brief Get the number of lines currently held by this object.
* @return Number of elements in this line_bulk.
*/
unsigned int size(void) const noexcept;
/**
* @brief Check if this line_bulk doesn't hold any lines.
* @return True if this object is empty, false otherwise.
*/
bool empty(void) const noexcept;
/**
* @brief Remove all lines from this object.
*/
void clear(void);
/**
* @brief Request all lines held by this object.
* @param config Request config (see gpiod::line_request).
* @param default_vals Vector of default values. Only relevant for
* output direction requests.
*/
void request(const line_request& config,
const ::std::vector<int> default_vals = ::std::vector<int>()) const;
/**
* @brief Release all lines held by this object.
*/
void release(void) const;
/**
* @brief Read values from all lines held by this object.
* @return Vector containing line values the order of which corresponds
* with the order of lines in the internal array.
*/
::std::vector<int> get_values(void) const;
/**
* @brief Set values of all lines held by this object.
* @param values Vector of values to set. Must be the same size as the
* number of lines held by this line_bulk.
*/
void set_values(const ::std::vector<int>& values) const;
/**
* @brief Set configuration of all lines held by this object.
* @param direction New direction.
* @param flags Replacement flags.
* @param values Vector of values to set. Must be the same size as the
* number of lines held by this line_bulk.
* Only relevant for output direction requests.
*/
void set_config(int direction, ::std::bitset<32> flags,
const ::std::vector<int> values = ::std::vector<int>()) const;
/**
* @brief Set configuration flags of all lines held by this object.
* @param flags Replacement flags.
*/
void set_flags(::std::bitset<32> flags) const;
/**
* @brief Change the direction all lines held by this object to input.
*/
void set_direction_input() const;
/**
* @brief Change the direction all lines held by this object to output.
* @param values Vector of values to set. Must be the same size as the
* number of lines held by this line_bulk.
*/
void set_direction_output(const ::std::vector<int>& values) const;
/**
* @brief Poll the set of lines for line events.
* @param timeout Number of nanoseconds to wait before returning an
* empty line_bulk.
* @return Returns a line_bulk object containing lines on which events
* occurred.
*/
line_bulk event_wait(const ::std::chrono::nanoseconds& timeout) const;
/**
* @brief Check if this object holds any lines.
* @return True if this line_bulk holds at least one line, false otherwise.
*/
explicit operator bool(void) const noexcept;
/**
* @brief Check if this object doesn't hold any lines.
* @return True if this line_bulk is empty, false otherwise.
*/
bool operator!(void) const noexcept;
/**
* @brief Max number of lines that this object can hold.
*/
static const unsigned int MAX_LINES;
/**
* @brief Iterator for iterating over lines held by line_bulk.
*/
class iterator
{
public:
/**
* @brief Default constructor. Builds an empty iterator object.
*/
iterator(void) = default;
/**
* @brief Copy constructor.
* @param other Other line_bulk iterator.
*/
iterator(const iterator& other) = default;
/**
* @brief Move constructor.
* @param other Other line_bulk iterator.
*/
iterator(iterator&& other) = default;
/**
* @brief Assignment operator.
* @param other Other line_bulk iterator.
* @return Reference to this iterator.
*/
iterator& operator=(const iterator& other) = default;
/**
* @brief Move assignment operator.
* @param other Other line_bulk iterator.
* @return Reference to this iterator.
*/
iterator& operator=(iterator&& other) = default;
/**
* @brief Destructor.
*/
~iterator(void) = default;
/**
* @brief Advance the iterator by one element.
* @return Reference to this iterator.
*/
iterator& operator++(void);
/**
* @brief Dereference current element.
* @return Current GPIO line by reference.
*/
const line& operator*(void) const;
/**
* @brief Member access operator.
* @return Current GPIO line by pointer.
*/
const line* operator->(void) const;
/**
* @brief Check if this operator points to the same element.
* @param rhs Right-hand side of the equation.
* @return True if this iterator points to the same GPIO line,
* false otherwise.
*/
bool operator==(const iterator& rhs) const noexcept;
/**
* @brief Check if this operator doesn't point to the same element.
* @param rhs Right-hand side of the equation.
* @return True if this iterator doesn't point to the same GPIO
* line, false otherwise.
*/
bool operator!=(const iterator& rhs) const noexcept;
private:
iterator(const ::std::vector<line>::iterator& it);
::std::vector<line>::iterator _m_iter;
friend line_bulk;
};
/**
* @brief Returns an iterator to the first line.
* @return A line_bulk iterator.
*/
iterator begin(void) noexcept;
/**
* @brief Returns an iterator to the element following the last line.
* @return A line_bulk iterator.
*/
iterator end(void) noexcept;
private:
struct line_bulk_deleter
{
void operator()(::gpiod_line_bulk *bulk);
};
void throw_if_empty(void) const;
using line_bulk_ptr = ::std::unique_ptr<::gpiod_line_bulk, line_bulk_deleter>;
line_bulk_ptr make_line_bulk_ptr(void) const;
line_bulk_ptr to_line_bulk(void) const;
::std::vector<line> _m_bulk;
};
/**
* @brief Support for range-based loops for line iterators.
* @param iter A line iterator.
* @return Iterator unchanged.
*/
line_iter begin(line_iter iter) noexcept;
/**
* @brief Support for range-based loops for line iterators.
* @param iter A line iterator.
* @return New end iterator.
*/
line_iter end(const line_iter& iter) noexcept;
/**
* @brief Allows to iterate over all lines owned by a GPIO chip.
*/
class line_iter
{
public:
/**
* @brief Default constructor. Creates the end iterator.
*/
line_iter(void) = default;
/**
* @brief Constructor. Creates the begin iterator.
* @param owner Chip owning the GPIO lines over which we want to iterate.
*/
line_iter(const chip& owner);
/**
* @brief Copy constructor.
* @param other Other line iterator.
*/
line_iter(const line_iter& other) = default;
/**
* @brief Move constructor.
* @param other Other line iterator.
*/
line_iter(line_iter&& other) = default;
/**
* @brief Assignment operator.
* @param other Other line iterator.
* @return Reference to this line_iter.
*/
line_iter& operator=(const line_iter& other) = default;
/**
* @brief Move assignment operator.
* @param other Other line iterator.
* @return Reference to this line_iter.
*/
line_iter& operator=(line_iter&& other) = default;
/**
* @brief Destructor.
*/
~line_iter(void) = default;
/**
* @brief Advance the iterator by one element.
* @return Reference to this iterator.
*/
line_iter& operator++(void);
/**
* @brief Dereference current element.
* @return Current GPIO line by reference.
*/
const line& operator*(void) const;
/**
* @brief Member access operator.
* @return Current GPIO line by pointer.
*/
const line* operator->(void) const;
/**
* @brief Check if this operator points to the same element.
* @param rhs Right-hand side of the equation.
* @return True if this iterator points to the same line_iter,
* false otherwise.
*/
bool operator==(const line_iter& rhs) const noexcept;
/**
* @brief Check if this operator doesn't point to the same element.
* @param rhs Right-hand side of the equation.
* @return True if this iterator doesn't point to the same line_iter,
* false otherwise.
*/
bool operator!=(const line_iter& rhs) const noexcept;
private:
line _m_current;
};
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_GPIOD_CXX_HPP__ */

View File

@@ -0,0 +1,20 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
otherincludedir = $(includedir)/gpiodcxx
otherinclude_HEADERS = \
chip.hpp \
chip-info.hpp \
edge-event-buffer.hpp \
edge-event.hpp \
exception.hpp \
info-event.hpp \
line.hpp \
line-config.hpp \
line-info.hpp \
line-request.hpp \
line-settings.hpp \
misc.hpp \
request-builder.hpp \
request-config.hpp \
timestamp.hpp

View File

@@ -0,0 +1,105 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file chip-info.hpp
*/
#ifndef __LIBGPIOD_CXX_CHIP_INFO_HPP__
#define __LIBGPIOD_CXX_CHIP_INFO_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <memory>
#include <ostream>
namespace gpiod {
class chip;
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Represents an immutable snapshot of GPIO chip information.
*/
class chip_info
{
public:
/**
* @brief Copy constructor.
* @param other Object to copy.
*/
chip_info(const chip_info& other);
/**
* @brief Move constructor.
* @param other Object to move.
*/
chip_info(chip_info&& other) noexcept;
~chip_info();
/**
* @brief Assignment operator.
* @param other Object to copy.
* @return Reference to self.
*/
chip_info& operator=(const chip_info& other);
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
chip_info& operator=(chip_info&& other) noexcept;
/**
* @brief Get the name of this GPIO chip.
* @return String containing the chip name.
*/
::std::string name() const noexcept;
/**
* @brief Get the label of this GPIO chip.
* @return String containing the chip name.
*/
::std::string label() const noexcept;
/**
* @brief Return the number of lines exposed by this chip.
* @return Number of lines.
*/
::std::size_t num_lines() const noexcept;
private:
chip_info();
struct impl;
::std::shared_ptr<impl> _m_priv;
friend chip;
};
/**
* @brief Stream insertion operator for GPIO chip objects.
* @param out Output stream to write to.
* @param chip GPIO chip to insert into the output stream.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const chip_info& chip);
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_CHIP_INFO_HPP__ */

View File

@@ -0,0 +1,179 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file chip.hpp
*/
#ifndef __LIBGPIOD_CXX_CHIP_HPP__
#define __LIBGPIOD_CXX_CHIP_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <chrono>
#include <cstddef>
#include <iostream>
#include <filesystem>
#include <memory>
#include "line.hpp"
namespace gpiod {
class chip_info;
class info_event;
class line_config;
class line_info;
class line_request;
class request_builder;
class request_config;
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Represents a GPIO chip.
*/
class chip
{
public:
/**
* @brief Instantiates a new chip object by opening the device file
* indicated by the path argument.
* @param path Path to the device file to open.
*/
explicit chip(const ::std::filesystem::path& path);
/**
* @brief Move constructor.
* @param other Object to move.
*/
chip(chip&& other) noexcept;
~chip();
chip& operator=(const chip& other) = delete;
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
chip& operator=(chip&& other) noexcept;
/**
* @brief Check if this object is valid.
* @return True if this object's methods can be used, false otherwise.
* False usually means the chip was closed. If the user calls
* any of the methods of this class on an object for which this
* operator returned false, a logic_error will be thrown.
*/
explicit operator bool() const noexcept;
/**
* @brief Close the GPIO chip device file and free associated resources.
* @note The chip object can live after calling this method but any of
* the chip's mutators will throw a logic_error exception.
*/
void close();
/**
* @brief Get the filesystem path that was used to open this GPIO chip.
* @return Path to the underlying character device file.
*/
::std::filesystem::path path() const;
/**
* @brief Get information about the chip.
* @return New chip_info object.
*/
chip_info get_info() const;
/**
* @brief Retrieve the current snapshot of line information for a
* single line.
* @param offset Offset of the line to get the info for.
* @return New ::gpiod::line_info object.
*/
line_info get_line_info(line::offset offset) const;
/**
* @brief Wrapper around ::gpiod::chip::get_line_info that retrieves
* the line info and starts watching the line for changes.
* @param offset Offset of the line to get the info for.
* @return New ::gpiod::line_info object.
*/
line_info watch_line_info(line::offset offset) const;
/**
* @brief Stop watching the line at given offset for info events.
* @param offset Offset of the line to get the info for.
*/
void unwatch_line_info(line::offset offset) const;
/**
* @brief Get the file descriptor associated with this chip.
* @return File descriptor number.
*/
int fd() const;
/**
* @brief Wait for line status events on any of the watched lines
* exposed by this chip.
* @param timeout Wait time limit in nanoseconds.
* @return True if at least one event is ready to be read. False if the
* wait timed out.
*/
bool wait_info_event(const ::std::chrono::nanoseconds& timeout) const;
/**
* @brief Read a single line status change event from this chip.
* @return New info_event object.
*/
info_event read_info_event() const;
/**
* @brief Map a GPIO line's name to its offset within the chip.
* @param name Name of the GPIO line to map.
* @return Offset of the line within the chip or -1 if the line with
* given name is not exposed by this chip.
*/
int get_line_offset_from_name(const ::std::string& name) const;
/**
* @brief Create a request_builder associated with this chip.
* @return New request_builder object.
*/
request_builder prepare_request();
private:
struct impl;
::std::shared_ptr<impl> _m_priv;
chip(const chip& other);
friend request_builder;
};
/**
* @brief Stream insertion operator for GPIO chip objects.
* @param out Output stream to write to.
* @param chip GPIO chip to insert into the output stream.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const chip& chip);
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_CHIP_HPP__ */

View File

@@ -0,0 +1,129 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file edge-event-buffer.hpp
*/
#ifndef __LIBGPIOD_CXX_EDGE_EVENT_BUFFER_HPP__
#define __LIBGPIOD_CXX_EDGE_EVENT_BUFFER_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <cstddef>
#include <iostream>
#include <memory>
#include <vector>
namespace gpiod {
class edge_event;
class line_request;
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Object into which edge events are read for better performance.
*
* The edge_event_buffer allows reading edge_event objects into an existing
* buffer which improves the performance by avoiding needless memory
* allocations.
*/
class edge_event_buffer
{
public:
/**
* @brief Constant iterator for iterating over edge events stored in
* the buffer.
*/
using const_iterator = ::std::vector<edge_event>::const_iterator;
/**
* @brief Constructor. Creates a new edge event buffer with given
* capacity.
* @param capacity Capacity of the new buffer.
*/
explicit edge_event_buffer(::std::size_t capacity = 64);
edge_event_buffer(const edge_event_buffer& other) = delete;
/**
* @brief Move constructor.
* @param other Object to move.
*/
edge_event_buffer(edge_event_buffer&& other) noexcept;
~edge_event_buffer();
edge_event_buffer& operator=(const edge_event_buffer& other) = delete;
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
edge_event_buffer& operator=(edge_event_buffer&& other) noexcept;
/**
* @brief Get the constant reference to the edge event at given index.
* @param index Index of the event in the buffer.
* @return Constant reference to the edge event.
*/
const edge_event& get_event(unsigned int index) const;
/**
* @brief Get the number of edge events currently stored in the buffer.
* @return Number of edge events in the buffer.
*/
::std::size_t num_events() const;
/**
* @brief Maximum capacity of the buffer.
* @return Buffer capacity.
*/
::std::size_t capacity() const noexcept;
/**
* @brief Get a constant iterator to the first edge event currently
* stored in the buffer.
* @return Constant iterator to the first element.
*/
const_iterator begin() const noexcept;
/**
* @brief Get a constant iterator to the element after the last edge
* event in the buffer.
* @return Constant iterator to the element after the last edge event.
*/
const_iterator end() const noexcept;
private:
struct impl;
::std::unique_ptr<impl> _m_priv;
friend line_request;
};
/**
* @brief Stream insertion operator for GPIO edge event buffer objects.
* @param out Output stream to write to.
* @param buf GPIO edge event buffer object to insert into the output stream.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const edge_event_buffer& buf);
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_EDGE_EVENT_BUFFER_HPP__ */

View File

@@ -0,0 +1,137 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file edge-event.hpp
*/
#ifndef __LIBGPIOD_CXX_EDGE_EVENT_HPP__
#define __LIBGPIOD_CXX_EDGE_EVENT_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <cstdint>
#include <iostream>
#include <memory>
#include "timestamp.hpp"
namespace gpiod {
class edge_event_buffer;
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Immutable object containing data about a single edge event.
*/
class edge_event
{
public:
/**
* @brief Edge event types.
*/
enum class event_type
{
RISING_EDGE = 1,
/**< This is a rising edge event. */
FALLING_EDGE
/**< This is falling edge event. */
};
/**
* @brief Copy constructor.
* @param other Object to copy.
*/
edge_event(const edge_event& other);
/**
* @brief Move constructor.
* @param other Object to move.
*/
edge_event(edge_event&& other) noexcept;
~edge_event();
/**
* @brief Copy assignment operator.
* @param other Object to copy.
* @return Reference to self.
*/
edge_event& operator=(const edge_event& other);
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
edge_event& operator=(edge_event&& other) noexcept;
/**
* @brief Retrieve the event type.
* @return Event type (rising or falling edge).
*/
event_type type() const;
/**
* @brief Retrieve the event time-stamp.
* @return Time-stamp in nanoseconds as registered by the kernel using
* the configured edge event clock.
*/
timestamp timestamp_ns() const noexcept;
/**
* @brief Read the offset of the line on which this event was
* registered.
* @return Line offset.
*/
line::offset line_offset() const noexcept;
/**
* @brief Get the global sequence number of this event.
* @return Sequence number of the event relative to all lines in the
* associated line request.
*/
unsigned long global_seqno() const noexcept;
/**
* @brief Get the event sequence number specific to the concerned line.
* @return Sequence number of the event relative to this line within
* the lifetime of the associated line request.
*/
unsigned long line_seqno() const noexcept;
private:
edge_event();
struct impl;
struct impl_managed;
struct impl_external;
::std::shared_ptr<impl> _m_priv;
friend edge_event_buffer;
};
/**
* @brief Stream insertion operator for edge events.
* @param out Output stream to write to.
* @param event Edge event to insert into the output stream.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const edge_event& event);
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_EDGE_EVENT_HPP__ */

View File

@@ -0,0 +1,158 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file exception.hpp
*/
#ifndef __LIBGPIOD_CXX_EXCEPTION_HPP__
#define __LIBGPIOD_CXX_EXCEPTION_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <stdexcept>
#include <string>
namespace gpiod {
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Exception thrown when an already closed chip is used.
*/
class GPIOD_CXX_API chip_closed : public ::std::logic_error
{
public:
/**
* @brief Constructor.
* @param what Human readable reason for error.
*/
explicit chip_closed(const ::std::string& what);
/**
* @brief Copy constructor.
* @param other Object to copy from.
*/
chip_closed(const chip_closed& other) noexcept;
/**
* @brief Move constructor.
* @param other Object to move.
*/
chip_closed(chip_closed&& other) noexcept;
/**
* @brief Assignment operator.
* @param other Object to copy from.
* @return Reference to self.
*/
chip_closed& operator=(const chip_closed& other) noexcept;
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
chip_closed& operator=(chip_closed&& other) noexcept;
virtual ~chip_closed();
};
/**
* @brief Exception thrown when an already released line request is used.
*/
class GPIOD_CXX_API request_released : public ::std::logic_error
{
public:
/**
* @brief Constructor.
* @param what Human readable reason for error.
*/
explicit request_released(const ::std::string& what);
/**
* @brief Copy constructor.
* @param other Object to copy from.
*/
request_released(const request_released& other) noexcept;
/**
* @brief Move constructor.
* @param other Object to move.
*/
request_released(request_released&& other) noexcept;
/**
* @brief Assignment operator.
* @param other Object to copy from.
* @return Reference to self.
*/
request_released& operator=(const request_released& other) noexcept;
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
request_released& operator=(request_released&& other) noexcept;
virtual ~request_released();
};
/**
* @brief Exception thrown when the core C library returns an invalid value
* for any of the line_info properties.
*/
class GPIOD_CXX_API bad_mapping : public ::std::runtime_error
{
public:
/**
* @brief Constructor.
* @param what Human readable reason for error.
*/
explicit bad_mapping(const ::std::string& what);
/**
* @brief Copy constructor.
* @param other Object to copy from.
*/
bad_mapping(const bad_mapping& other) noexcept;
/**
* @brief Move constructor.
* @param other Object to move.
*/
bad_mapping(bad_mapping&& other) noexcept;
/**
* @brief Assignment operator.
* @param other Object to copy from.
* @return Reference to self.
*/
bad_mapping& operator=(const bad_mapping& other) noexcept;
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
bad_mapping& operator=(bad_mapping&& other) noexcept;
virtual ~bad_mapping();
};
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_EXCEPTION_HPP__ */

View File

@@ -0,0 +1,123 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file gpiod.h
*/
#ifndef __LIBGPIOD_CXX_INFO_EVENT_HPP__
#define __LIBGPIOD_CXX_INFO_EVENT_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <cstdint>
#include <iostream>
#include <memory>
#include "timestamp.hpp"
namespace gpiod {
class chip;
class line_info;
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Immutable object containing data about a single line info event.
*/
class info_event
{
public:
/**
* @brief Types of info events.
*/
enum class event_type
{
LINE_REQUESTED = 1,
/**< Line has been requested. */
LINE_RELEASED,
/**< Previously requested line has been released. */
LINE_CONFIG_CHANGED
/**< Line configuration has changed. */
};
/**
* @brief Copy constructor.
* @param other Object to copy.
*/
info_event(const info_event& other);
/**
* @brief Move constructor.
* @param other Object to move.
*/
info_event(info_event&& other) noexcept;
~info_event();
/**
* @brief Copy assignment operator.
* @param other Object to copy.
* @return Reference to self.
*/
info_event& operator=(const info_event& other);
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
info_event& operator=(info_event&& other) noexcept;
/**
* @brief Type of this event.
* @return Event type.
*/
event_type type() const;
/**
* @brief Timestamp of the event as returned by the kernel.
* @return Timestamp as a 64-bit unsigned integer.
*/
::std::uint64_t timestamp_ns() const noexcept;
/**
* @brief Get the new line information.
* @return Constant reference to the line info object containing the
* line data as read at the time of the info event.
*/
const line_info& get_line_info() const noexcept;
private:
info_event();
struct impl;
::std::shared_ptr<impl> _m_priv;
friend chip;
};
/**
* @brief Stream insertion operator for info events.
* @param out Output stream to write to.
* @param event GPIO line info event to insert into the output stream.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const info_event& event);
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_INFO_EVENT_HPP__ */

View File

@@ -0,0 +1,113 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file line-config.hpp
*/
#ifndef __LIBGPIOD_CXX_LINE_CONFIG_HPP__
#define __LIBGPIOD_CXX_LINE_CONFIG_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <map>
#include <memory>
namespace gpiod {
class chip;
class line_request;
class line_settings;
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Contains a set of line config options used in line requests and
* reconfiguration.
*/
class line_config
{
public:
line_config();
line_config(const line_config& other) = delete;
/**
* @brief Move constructor.
* @param other Object to move.
*/
line_config(line_config&& other) noexcept;
~line_config();
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
line_config& operator=(line_config&& other) noexcept;
/**
* @brief Reset the line config object.
* @return Reference to self.
*/
line_config& reset() noexcept;
/**
* @brief Add line settings for a single offset.
* @param offset Offset for which to add settings.
* @param settings Line settings to add.
* @return Reference to self.
*/
line_config& add_line_settings(line::offset offset, const line_settings& settings);
/**
* @brief Add line settings for a set of offsets.
* @param offsets Offsets for which to add settings.
* @param settings Line settings to add.
* @return Reference to self.
*/
line_config& add_line_settings(const line::offsets& offsets, const line_settings& settings);
/**
* @brief Get a mapping of offsets to line settings stored by this
* object.
* @return Map in which keys represent line offsets and values are
* the settings corresponding with them.
*/
::std::map<line::offset, line_settings> get_line_settings() const;
private:
struct impl;
::std::shared_ptr<impl> _m_priv;
line_config& operator=(const line_config& other);
friend line_request;
friend request_builder;
};
/**
* @brief Stream insertion operator for GPIO line config objects.
* @param out Output stream to write to.
* @param config Line config object to insert into the output stream.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const line_config& config);
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_LINE_CONFIG_HPP__ */

View File

@@ -0,0 +1,176 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file line-info.hpp
*/
#ifndef __LIBGPIOD_CXX_LINE_INFO_HPP__
#define __LIBGPIOD_CXX_LINE_INFO_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <chrono>
#include <iostream>
#include <memory>
#include <string>
namespace gpiod {
class chip;
class info_event;
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Contains an immutable snapshot of the line's state at the
* time when the object of this class was instantiated.
*/
class line_info
{
public:
/**
* @brief Copy constructor.
* @param other Object to copy.
*/
line_info(const line_info& other) noexcept;
/**
* @brief Move constructor.
* @param other Object to move.
*/
line_info(line_info&& other) noexcept;
~line_info();
/**
* @brief Copy assignment operator.
* @param other Object to copy.
* @return Reference to self.
*/
line_info& operator=(const line_info& other) noexcept;
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
line_info& operator=(line_info&& other) noexcept;
/**
* @brief Get the hardware offset of the line.
* @return Offset of the line within the parent chip.
*/
line::offset offset() const noexcept;
/**
* @brief Get the GPIO line name.
* @return Name of the GPIO line as it is represented in the kernel.
* This routine returns an empty string if the line is unnamed.
*/
::std::string name() const noexcept;
/**
* @brief Check if the line is currently in use.
* @return True if the line is in use, false otherwise.
*
* The user space can't know exactly why a line is busy. It may have
* been requested by another process or hogged by the kernel. It only
* matters that the line is used and we can't request it.
*/
bool used() const noexcept;
/**
* @brief Read the GPIO line consumer name.
* @return Name of the GPIO consumer name as it is represented in the
* kernel. This routine returns an empty string if the line is
* not used.
*/
::std::string consumer() const noexcept;
/**
* @brief Read the GPIO line direction setting.
* @return Returns DIRECTION_INPUT or DIRECTION_OUTPUT.
*/
line::direction direction() const;
/**
* @brief Read the current edge detection setting of this line.
* @return Returns EDGE_NONE, EDGE_RISING, EDGE_FALLING or EDGE_BOTH.
*/
line::edge edge_detection() const;
/**
* @brief Read the GPIO line bias setting.
* @return Returns BIAS_PULL_UP, BIAS_PULL_DOWN, BIAS_DISABLE or
* BIAS_UNKNOWN.
*/
line::bias bias() const;
/**
* @brief Read the GPIO line drive setting.
* @return Returns DRIVE_PUSH_PULL, DRIVE_OPEN_DRAIN or
* DRIVE_OPEN_SOURCE.
*/
line::drive drive() const;
/**
* @brief Check if the signal of this line is inverted.
* @return True if this line is "active-low", false otherwise.
*/
bool active_low() const noexcept;
/**
* @brief Check if this line is debounced (either by hardware or by the
* kernel software debouncer).
* @return True if the line is debounced, false otherwise.
*/
bool debounced() const noexcept;
/**
* @brief Read the current debounce period in microseconds.
* @return Current debounce period in microseconds, 0 if the line is
* not debounced.
*/
::std::chrono::microseconds debounce_period() const noexcept;
/**
* @brief Read the current event clock setting used for edge event
* timestamps.
* @return Returns MONOTONIC, REALTIME or HTE.
*/
line::clock event_clock() const;
private:
line_info();
struct impl;
::std::shared_ptr<impl> _m_priv;
friend chip;
friend info_event;
};
/**
* @brief Stream insertion operator for GPIO line info objects.
* @param out Output stream to write to.
* @param info GPIO line info object to insert into the output stream.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const line_info& info);
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_LINE_INFO_HPP__ */

View File

@@ -0,0 +1,227 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file line-request.hpp
*/
#ifndef __LIBGPIOD_CXX_LINE_REQUEST_HPP__
#define __LIBGPIOD_CXX_LINE_REQUEST_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <chrono>
#include <cstddef>
#include <iostream>
#include <memory>
#include "misc.hpp"
namespace gpiod {
class chip;
class edge_event;
class edge_event_buffer;
class line_config;
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Stores the context of a set of requested GPIO lines.
*/
class line_request
{
public:
line_request(const line_request& other) = delete;
/**
* @brief Move constructor.
* @param other Object to move.
*/
line_request(line_request&& other) noexcept;
~line_request();
line_request& operator=(const line_request& other) = delete;
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
line_request& operator=(line_request&& other) noexcept;
/**
* @brief Check if this object is valid.
* @return True if this object's methods can be used, false otherwise.
* False usually means the request was released. If the user
* calls any of the methods of this class on an object for
* which this operator returned false, a logic_error will be
* thrown.
*/
explicit operator bool() const noexcept;
/**
* @brief Release the GPIO chip and free all associated resources.
* @note The object can still be used after this method is called but
* using any of the mutators will result in throwing
* a logic_error exception.
*/
void release();
/**
* @brief Get the number of requested lines.
* @return Number of lines in this request.
*/
::std::size_t num_lines() const;
/**
* @brief Get the list of offsets of requested lines.
* @return List of hardware offsets of the lines in this request.
*/
line::offsets offsets() const;
/**
* @brief Get the value of a single requested line.
* @param offset Offset of the line to read within the chip.
* @return Current line value.
*/
line::value get_value(line::offset offset);
/**
* @brief Get the values of a subset of requested lines.
* @param offsets Vector of line offsets
* @return Vector of lines values with indexes of values corresponding
* to those of the offsets.
*/
line::values get_values(const line::offsets& offsets);
/**
* @brief Get the values of all requested lines.
* @return List of read values.
*/
line::values get_values();
/**
* @brief Get the values of a subset of requested lines into a vector
* supplied by the caller.
* @param offsets Vector of line offsets.
* @param values Vector for storing the values. Its size must be at
* least that of the offsets vector. The indexes of read
* values will correspond with those in the offsets
* vector.
*/
void get_values(const line::offsets& offsets, line::values& values);
/**
* @brief Get the values of all requested lines.
* @param values Array in which the values will be stored. Must hold
* at least the number of elements returned by
* line_request::num_lines.
*/
void get_values(line::values& values);
/**
* @brief Set the value of a single requested line.
* @param offset Offset of the line to set within the chip.
* @param value New line value.
* @return Reference to self.
*/
line_request& set_value(line::offset offset, line::value value);
/**
* @brief Set the values of a subset of requested lines.
* @param values Vector containing a set of offset->value mappings.
* @return Reference to self.
*/
line_request& set_values(const line::value_mappings& values);
/**
* @brief Set the values of a subset of requested lines.
* @param offsets Vector containing the offsets of lines to set.
* @param values Vector containing new values with indexes
* corresponding with those in the offsets vector.
* @return Reference to self.
*/
line_request& set_values(const line::offsets& offsets, const line::values& values);
/**
* @brief Set the values of all requested lines.
* @param values Array of new line values. The size must be equal to
* the value returned by line_request::num_lines.
* @return Reference to self.
*/
line_request& set_values(const line::values& values);
/**
* @brief Apply new config options to requested lines.
* @param config New configuration.
* @return Reference to self.
*/
line_request& reconfigure_lines(const line_config& config);
/**
* @brief Get the file descriptor number associated with this line
* request.
* @return File descriptor number.
*/
int fd() const;
/**
* @brief Wait for edge events on any of the lines requested with edge
* detection enabled.
* @param timeout Wait time limit in nanoseconds.
* @return True if at least one event is ready to be read. False if the
* wait timed out.
*/
bool wait_edge_event(const ::std::chrono::nanoseconds& timeout) const;
/**
* @brief Read a number of edge events from this request up to the
* maximum capacity of the buffer.
* @param buffer Edge event buffer to read events into.
* @return Number of events read.
*/
::std::size_t read_edge_event(edge_event_buffer& buffer);
/**
* @brief Read a number of edge events from this request.
* @param buffer Edge event buffer to read events into.
* @param max_events Maximum number of events to read. Limited by the
* capacity of the buffer.
* @return Number of events read.
*/
::std::size_t read_edge_event(edge_event_buffer& buffer, ::std::size_t max_events);
private:
line_request();
struct impl;
::std::unique_ptr<impl> _m_priv;
friend request_builder;
};
/**
* @brief Stream insertion operator for line requests.
* @param out Output stream to write to.
* @param request Line request object to insert into the output stream.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const line_request& request);
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_LINE_REQUEST_HPP__ */

View File

@@ -0,0 +1,193 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file request-config.hpp
*/
#ifndef __LIBGPIOD_CXX_LINE_SETTINGS_HPP__
#define __LIBGPIOD_CXX_LINE_SETTINGS_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <chrono>
#include <memory>
#include "line.hpp"
namespace gpiod {
class line_config;
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Stores GPIO line settings.
*/
class line_settings
{
public:
/**
* @brief Initializes the line_settings object with default values.
*/
line_settings();
line_settings(const line_settings& other) = delete;
/**
* @brief Move constructor.
* @param other Object to move.
*/
line_settings(line_settings&& other) noexcept;
~line_settings();
line_settings& operator=(const line_settings& other) = delete;
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
line_settings& operator=(line_settings&& other);
/**
* @brief Reset the line settings to default values.
* @return Reference to self.
*/
line_settings& reset(void) noexcept;
/**
* @brief Set direction.
* @param direction New direction.
* @return Reference to self.
*/
line_settings& set_direction(line::direction direction);
/**
* @brief Get direction.
* @return Current direction setting.
*/
line::direction direction() const;
/**
* @brief Set edge detection.
* @param edge New edge detection setting.
* @return Reference to self.
*/
line_settings& set_edge_detection(line::edge edge);
/**
* @brief Get edge detection.
* @return Current edge detection setting.
*/
line::edge edge_detection() const;
/**
* @brief Set bias setting.
* @param bias New bias.
* @return Reference to self.
*/
line_settings& set_bias(line::bias bias);
/**
* @brief Get bias setting.
* @return Current bias.
*/
line::bias bias() const;
/**
* @brief Set drive setting.
* @param drive New drive.
* @return Reference to self.
*/
line_settings& set_drive(line::drive drive);
/**
* @brief Get drive setting.
* @return Current drive.
*/
line::drive drive() const;
/**
* @brief Set the active-low setting.
* @param active_low New active-low setting.
* @return Reference to self.
*/
line_settings& set_active_low(bool active_low);
/**
* @brief Get the active-low setting.
* @return Current active-low setting.
*/
bool active_low() const noexcept;
/**
* @brief Set debounce period.
* @param period New debounce period in microseconds.
* @return Reference to self.
*/
line_settings& set_debounce_period(const ::std::chrono::microseconds& period);
/**
* @brief Get debounce period.
* @return Current debounce period.
*/
::std::chrono::microseconds debounce_period() const noexcept;
/**
* @brief Set the event clock to use for edge event timestamps.
* @param event_clock Clock to use.
* @return Reference to self.
*/
line_settings& set_event_clock(line::clock event_clock);
/**
* @brief Get the event clock used for edge event timestamps.
* @return Current event clock type.
*/
line::clock event_clock() const;
/**
* @brief Set the output value.
* @param value New output value.
* @return Reference to self.
*/
line_settings& set_output_value(line::value value);
/**
* @brief Get the output value.
* @return Current output value.
*/
line::value output_value() const;
private:
struct impl;
::std::unique_ptr<impl> _m_priv;
friend line_config;
};
/**
* @brief Stream insertion operator for line settings.
* @param out Output stream to write to.
* @param settings Line settings object to insert into the output stream.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const line_settings& settings);
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_LINE_SETTINGS_HPP__ */

View File

@@ -0,0 +1,276 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file line.hpp
*/
#ifndef __LIBGPIOD_CXX_LINE_HPP__
#define __LIBGPIOD_CXX_LINE_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <ostream>
#include <utility>
#include <vector>
namespace gpiod {
/**
* @brief Namespace containing various type definitions for GPIO lines.
*/
namespace line {
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Wrapper around unsigned int for representing line offsets.
*/
class offset
{
public:
/**
* @brief Constructor with implicit conversion from unsigned int.
* @param off Line offset.
*/
offset(unsigned int off = 0) : _m_offset(off) { }
/**
* @brief Copy constructor.
* @param other Object to copy.
*/
offset(const offset& other) = default;
/**
* @brief Move constructor.
* @param other Object to move.
*/
offset(offset&& other) = default;
~offset() = default;
/**
* @brief Assignment operator.
* @param other Object to copy.
* @return Reference to self.
*/
offset& operator=(const offset& other) = default;
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
offset& operator=(offset&& other) noexcept = default;
/**
* @brief Conversion operator to `unsigned int`.
*/
operator unsigned int() const noexcept
{
return this->_m_offset;
}
private:
unsigned int _m_offset;
};
/**
* @brief Logical line states.
*/
enum class value
{
INACTIVE = 0,
/**< Line is inactive. */
ACTIVE = 1,
/**< Line is active. */
};
/**
* @brief Direction settings.
*/
enum class direction
{
AS_IS = 1,
/**< Request the line(s), but don't change current direction. */
INPUT,
/**< Direction is input - we're reading the state of a GPIO line. */
OUTPUT
/**< Direction is output - we're driving the GPIO line. */
};
/**
* @brief Edge detection settings.
*/
enum class edge
{
NONE = 1,
/**< Line edge detection is disabled. */
RISING,
/**< Line detects rising edge events. */
FALLING,
/**< Line detect falling edge events. */
BOTH
/**< Line detects both rising and falling edge events. */
};
/**
* @brief Internal bias settings.
*/
enum class bias
{
AS_IS = 1,
/**< Don't change the bias setting when applying line config. */
UNKNOWN,
/**< The internal bias state is unknown. */
DISABLED,
/**< The internal bias is disabled. */
PULL_UP,
/**< The internal pull-up bias is enabled. */
PULL_DOWN
/**< The internal pull-down bias is enabled. */
};
/**
* @brief Drive settings.
*/
enum class drive
{
PUSH_PULL = 1,
/**< Drive setting is push-pull. */
OPEN_DRAIN,
/**< Line output is open-drain. */
OPEN_SOURCE
/**< Line output is open-source. */
};
/**
* @brief Event clock settings.
*/
enum class clock
{
MONOTONIC = 1,
/**< Line uses the monotonic clock for edge event timestamps. */
REALTIME,
/**< Line uses the realtime clock for edge event timestamps. */
HTE,
/*<< Line uses the hardware timestamp engine for event timestamps. */
};
/**
* @brief Vector of line offsets.
*/
using offsets = ::std::vector<offset>;
/**
* @brief Vector of line values.
*/
using values = ::std::vector<value>;
/**
* @brief Represents a mapping of a line offset to line logical state.
*/
using value_mapping = ::std::pair<offset, value>;
/**
* @brief Vector of offset->value mappings. Each mapping is defined as a pair
* of an unsigned and signed integers.
*/
using value_mappings = ::std::vector<value_mapping>;
/**
* @brief Stream insertion operator for logical line values.
* @param out Output stream.
* @param val Value to insert into the output stream in a human-readable form.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, value val);
/**
* @brief Stream insertion operator for direction values.
* @param out Output stream.
* @param dir Value to insert into the output stream in a human-readable form.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, direction dir);
/**
* @brief Stream insertion operator for edge detection values.
* @param out Output stream.
* @param edge Value to insert into the output stream in a human-readable form.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, edge edge);
/**
* @brief Stream insertion operator for bias values.
* @param out Output stream.
* @param bias Value to insert into the output stream in a human-readable form.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, bias bias);
/**
* @brief Stream insertion operator for drive values.
* @param out Output stream.
* @param drive Value to insert into the output stream in a human-readable form.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, drive drive);
/**
* @brief Stream insertion operator for event clock values.
* @param out Output stream.
* @param clock Value to insert into the output stream in a human-readable form.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, clock clock);
/**
* @brief Stream insertion operator for the list of output values.
* @param out Output stream.
* @param vals Object to insert into the output stream in a human-readable form.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const values& vals);
/**
* @brief Stream insertion operator for the list of line offsets.
* @param out Output stream.
* @param offs Object to insert into the output stream in a human-readable form.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const offsets& offs);
/**
* @brief Stream insertion operator for the offset-to-value mapping.
* @param out Output stream.
* @param mapping Value to insert into the output stream in a human-readable
* form.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const value_mapping& mapping);
/**
* @brief Stream insertion operator for the list of offset-to-value mappings.
* @param out Output stream.
* @param mappings Object to insert into the output stream in a human-readable
* form.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const value_mappings& mappings);
/**
* @}
*/
} /* namespace line */
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_LINE_HPP__ */

View File

@@ -0,0 +1,44 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file misc.hpp
*/
#ifndef __LIBGPIOD_CXX_MISC_HPP__
#define __LIBGPIOD_CXX_MISC_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <string>
namespace gpiod {
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Check if the file pointed to by path is a GPIO chip character device.
* @param path Path to check.
* @return True if the file exists and is a GPIO chip character device or a
* symbolic link to it.
*/
bool is_gpiochip_device(const ::std::filesystem::path& path);
/**
* @brief Get the human readable version string for libgpiod API
* @return String containing the library version.
*/
const ::std::string& version_string();
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_MISC_HPP__ */

View File

@@ -0,0 +1,149 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file request-builder.hpp
*/
#ifndef __LIBGPIOD_CXX_REQUEST_BUILDER_HPP__
#define __LIBGPIOD_CXX_REQUEST_BUILDER_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <memory>
#include <ostream>
namespace gpiod {
class chip;
class line_config;
class line_request;
class request_config;
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Intermediate object storing the configuration for a line request.
*/
class request_builder
{
public:
request_builder(const request_builder& other) = delete;
/**
* @brief Move constructor.
* @param other Object to be moved.
*/
request_builder(request_builder&& other) noexcept;
~request_builder();
request_builder& operator=(const request_builder& other) = delete;
/**
* @brief Move assignment operator.
* @param other Object to be moved.
* @return Reference to self.
*/
request_builder& operator=(request_builder&& other) noexcept;
/**
* @brief Set the request config for the request.
* @param req_cfg Request config to use.
* @return Reference to self.
*/
request_builder& set_request_config(request_config& req_cfg);
/**
* @brief Get the current request config.
* @return Const reference to the current request config stored by this
* object.
*/
const request_config& get_request_config() const noexcept;
/**
* @brief Set consumer in the request config stored by this object.
* @param consumer New consumer string.
* @return Reference to self.
*/
request_builder& set_consumer(const ::std::string& consumer) noexcept;
/**
* @brief Set the event buffer size in the request config stored by
* this object.
* @param event_buffer_size New event buffer size.
* @return Reference to self.
*/
request_builder& set_event_buffer_size(::std::size_t event_buffer_size) noexcept;
/**
* @brief Set the line config for this request.
* @param line_cfg Line config to use.
* @return Reference to self.
*/
request_builder& set_line_config(line_config &line_cfg);
/**
* @brief Get the current line config.
* @return Const reference to the current line config stored by this
* object.
*/
const line_config& get_line_config() const noexcept;
/**
* @brief Add line settings to the line config stored by this object
* for a single offset.
* @param offset Offset for which to add settings.
* @param settings Line settings to use.
* @return Reference to self.
*/
request_builder& add_line_settings(line::offset offset, const line_settings& settings);
/**
* @brief Add line settings to the line config stored by this object
* for a set of offsets.
* @param offsets Offsets for which to add settings.
* @param settings Settings to add.
* @return Reference to self.
*/
request_builder& add_line_settings(const line::offsets& offsets, const line_settings& settings);
/**
* @brief Make the line request.
* @return New line_request object.
*/
line_request do_request();
private:
struct impl;
request_builder(chip& chip);
::std::unique_ptr<impl> _m_priv;
friend chip;
friend ::std::ostream& operator<<(::std::ostream& out, const request_builder& builder);
};
/**
* @brief Stream insertion operator for GPIO request builder objects.
* @param out Output stream to write to.
* @param builder Request builder object to insert into the output stream.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const request_builder& builder);
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_REQUEST_BUILDER_HPP__ */

View File

@@ -0,0 +1,114 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file request-config.hpp
*/
#ifndef __LIBGPIOD_CXX_REQUEST_CONFIG_HPP__
#define __LIBGPIOD_CXX_REQUEST_CONFIG_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <cstddef>
#include <iostream>
#include <memory>
#include <string>
#include "line.hpp"
namespace gpiod {
class chip;
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Stores a set of options passed to the kernel when making a line
* request.
*/
class request_config
{
public:
/**
* @brief Constructor.
*/
request_config();
request_config(const request_config& other) = delete;
/**
* @brief Move constructor.
* @param other Object to move.
*/
request_config(request_config&& other) noexcept;
~request_config();
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
request_config& operator=(request_config&& other) noexcept;
/**
* @brief Set the consumer name.
* @param consumer New consumer name.
* @return Reference to self.
*/
request_config& set_consumer(const ::std::string& consumer) noexcept;
/**
* @brief Get the consumer name.
* @return Currently configured consumer name. May be an empty string.
*/
::std::string consumer() const noexcept;
/**
* @brief Set the size of the kernel event buffer.
* @param event_buffer_size New event buffer size.
* @return Reference to self.
* @note The kernel may adjust the value if it's too high. If set to 0,
* the default value will be used.
*/
request_config& set_event_buffer_size(::std::size_t event_buffer_size) noexcept;
/**
* @brief Get the edge event buffer size from this request config.
* @return Current edge event buffer size setting.
*/
::std::size_t event_buffer_size() const noexcept;
private:
struct impl;
::std::shared_ptr<impl> _m_priv;
request_config& operator=(const request_config& other);
friend request_builder;
};
/**
* @brief Stream insertion operator for request_config objects.
* @param out Output stream to write to.
* @param config request_config to insert into the output stream.
* @return Reference to out.
*/
::std::ostream& operator<<(::std::ostream& out, const request_config& config);
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_REQUEST_CONFIG_HPP__ */

View File

@@ -0,0 +1,122 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
/**
* @file timestamp.hpp
*/
#ifndef __LIBGPIOD_CXX_TIMESTAMP_HPP__
#define __LIBGPIOD_CXX_TIMESTAMP_HPP__
#if !defined(__LIBGPIOD_GPIOD_CXX_INSIDE__)
#error "Only gpiod.hpp can be included directly."
#endif
#include <chrono>
#include <cstdint>
namespace gpiod {
/**
* @ingroup gpiod_cxx
* @{
*/
/**
* @brief Stores the edge and info event timestamps as returned by the kernel
* and allows to convert them to std::chrono::time_point.
*/
class timestamp
{
public:
/**
* @brief Monotonic time_point.
*/
using time_point_monotonic = ::std::chrono::time_point<::std::chrono::steady_clock>;
/**
* @brief Real-time time_point.
*/
using time_point_realtime = ::std::chrono::time_point<::std::chrono::system_clock>;
/**
* @brief Constructor with implicit conversion from `uint64_t`.
* @param ns Timestamp in nanoseconds.
*/
timestamp(::std::uint64_t ns) : _m_ns(ns) { }
/**
* @brief Copy constructor.
* @param other Object to copy.
*/
timestamp(const timestamp& other) noexcept = default;
/**
* @brief Move constructor.
* @param other Object to move.
*/
timestamp(timestamp&& other) noexcept = default;
/**
* @brief Assignment operator.
* @param other Object to copy.
* @return Reference to self.
*/
timestamp& operator=(const timestamp& other) noexcept = default;
/**
* @brief Move assignment operator.
* @param other Object to move.
* @return Reference to self.
*/
timestamp& operator=(timestamp&& other) noexcept = default;
~timestamp() = default;
/**
* @brief Conversion operator to `std::uint64_t`.
*/
operator ::std::uint64_t() noexcept
{
return this->ns();
}
/**
* @brief Get the timestamp in nanoseconds.
* @return Timestamp in nanoseconds.
*/
::std::uint64_t ns() const noexcept
{
return this->_m_ns;
}
/**
* @brief Convert the timestamp to a monotonic time_point.
* @return time_point associated with the steady clock.
*/
time_point_monotonic to_time_point_monotonic() const
{
return time_point_monotonic(::std::chrono::nanoseconds(this->ns()));
}
/**
* @brief Convert the timestamp to a real-time time_point.
* @return time_point associated with the system clock.
*/
time_point_realtime to_time_point_realtime() const
{
return time_point_realtime(::std::chrono::nanoseconds(this->ns()));
}
private:
::std::uint64_t _m_ns;
};
/**
* @}
*/
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_TIMESTAMP_HPP__ */

103
bindings/cxx/info-event.cpp Normal file
View File

@@ -0,0 +1,103 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <map>
#include <ostream>
#include "internal.hpp"
namespace gpiod {
namespace {
const ::std::map<int, info_event::event_type> event_type_mapping = {
{ GPIOD_INFO_EVENT_LINE_REQUESTED, info_event::event_type::LINE_REQUESTED },
{ GPIOD_INFO_EVENT_LINE_RELEASED, info_event::event_type::LINE_RELEASED },
{ GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED, info_event::event_type::LINE_CONFIG_CHANGED }
};
const ::std::map<info_event::event_type, ::std::string> event_type_names = {
{ info_event::event_type::LINE_REQUESTED, "LINE_REQUESTED" },
{ info_event::event_type::LINE_RELEASED, "LINE_RELEASED" },
{ info_event::event_type::LINE_CONFIG_CHANGED, "LINE_CONFIG_CHANGED" }
};
} /* namespace */
void info_event::impl::set_info_event_ptr(info_event_ptr& new_event)
{
::gpiod_line_info* info = ::gpiod_info_event_get_line_info(new_event.get());
line_info_ptr copy(::gpiod_line_info_copy(info));
if (!copy)
throw_from_errno("unable to copy the line info object");
this->event = ::std::move(new_event);
this->info._m_priv->set_info_ptr(copy);
}
info_event::info_event()
: _m_priv(new impl)
{
}
GPIOD_CXX_API info_event::info_event(const info_event& other)
: _m_priv(other._m_priv)
{
}
GPIOD_CXX_API info_event::info_event(info_event&& other) noexcept
: _m_priv(::std::move(other._m_priv))
{
}
GPIOD_CXX_API info_event::~info_event()
{
}
GPIOD_CXX_API info_event& info_event::operator=(const info_event& other)
{
this->_m_priv = other._m_priv;
return *this;
}
GPIOD_CXX_API info_event& info_event::operator=(info_event&& other) noexcept
{
this->_m_priv = ::std::move(other._m_priv);
return *this;
}
GPIOD_CXX_API info_event::event_type info_event::type() const
{
int type = ::gpiod_info_event_get_event_type(this->_m_priv->event.get());
return map_int_to_enum(type, event_type_mapping);
}
GPIOD_CXX_API ::std::uint64_t info_event::timestamp_ns() const noexcept
{
return ::gpiod_info_event_get_timestamp_ns(this->_m_priv->event.get());
}
GPIOD_CXX_API const line_info& info_event::get_line_info() const noexcept
{
return this->_m_priv->info;
}
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const info_event& event)
{
out << "gpiod::info_event(event_type='" << event_type_names.at(event.type()) <<
"', timestamp=" << event.timestamp_ns() <<
", line_info=" << event.get_line_info() <<
")";
return out;
}
} /* namespace gpiod */

28
bindings/cxx/internal.cpp Normal file
View File

@@ -0,0 +1,28 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <map>
#include <stdexcept>
#include <system_error>
#include "internal.hpp"
namespace gpiod {
void throw_from_errno(const ::std::string& what)
{
switch (errno) {
case EINVAL:
throw ::std::invalid_argument(what);
case E2BIG:
throw ::std::length_error(what);
case ENOMEM:
throw ::std::bad_alloc();
case EDOM:
throw ::std::domain_error(what);
default:
throw ::std::system_error(errno, ::std::system_category(), what);
}
}
} /* namespace gpiod */

View File

@@ -1,9 +1,235 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <bgolaszewski@baylibre.com> */
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
#ifndef __LIBGPIOD_GPIOD_CXX_INTERNAL_HPP__
#define __LIBGPIOD_GPIOD_CXX_INTERNAL_HPP__
#ifndef __LIBGPIOD_CXX_INTERNAL_HPP__
#define __LIBGPIOD_CXX_INTERNAL_HPP__
#define GPIOD_CXX_API __attribute__((visibility("default")))
#include <gpiod.h>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#endif /* __LIBGPIOD_GPIOD_CXX_INTERNAL_HPP__ */
#include "gpiod.hpp"
#define GPIOD_CXX_UNUSED __attribute__((unused))
#define GPIOD_CXX_NORETURN __attribute__((noreturn))
namespace gpiod {
template<class enum_type> enum_type
map_int_to_enum(int value, const ::std::map<int, enum_type>& mapping)
{
try {
return mapping.at(value);
} catch (const ::std::out_of_range& err) {
/* FIXME Demangle the name. */
throw bad_mapping(::std::string("invalid value for ") +
typeid(enum_type).name());
}
}
void throw_from_errno(const ::std::string& what);
template<class T, void F(T*)> struct deleter
{
void operator()(T* ptr)
{
F(ptr);
}
};
using chip_deleter = deleter<::gpiod_chip, ::gpiod_chip_close>;
using chip_info_deleter = deleter<::gpiod_chip_info, ::gpiod_chip_info_free>;
using line_info_deleter = deleter<::gpiod_line_info, ::gpiod_line_info_free>;
using info_event_deleter = deleter<::gpiod_info_event, ::gpiod_info_event_free>;
using line_settings_deleter = deleter<::gpiod_line_settings, ::gpiod_line_settings_free>;
using line_config_deleter = deleter<::gpiod_line_config, ::gpiod_line_config_free>;
using request_config_deleter = deleter<::gpiod_request_config, ::gpiod_request_config_free>;
using line_request_deleter = deleter<::gpiod_line_request, ::gpiod_line_request_release>;
using edge_event_deleter = deleter<::gpiod_edge_event, ::gpiod_edge_event_free>;
using edge_event_buffer_deleter = deleter<::gpiod_edge_event_buffer,
::gpiod_edge_event_buffer_free>;
using chip_ptr = ::std::unique_ptr<::gpiod_chip, chip_deleter>;
using chip_info_ptr = ::std::unique_ptr<::gpiod_chip_info, chip_info_deleter>;
using line_info_ptr = ::std::unique_ptr<::gpiod_line_info, line_info_deleter>;
using info_event_ptr = ::std::unique_ptr<::gpiod_info_event, info_event_deleter>;
using line_settings_ptr = ::std::unique_ptr<::gpiod_line_settings, line_settings_deleter>;
using line_config_ptr = ::std::unique_ptr<::gpiod_line_config, line_config_deleter>;
using request_config_ptr = ::std::unique_ptr<::gpiod_request_config, request_config_deleter>;
using line_request_ptr = ::std::unique_ptr<::gpiod_line_request, line_request_deleter>;
using edge_event_ptr = ::std::unique_ptr<::gpiod_edge_event, edge_event_deleter>;
using edge_event_buffer_ptr = ::std::unique_ptr<::gpiod_edge_event_buffer,
edge_event_buffer_deleter>;
struct chip::impl
{
impl(const ::std::filesystem::path& path);
impl(const impl& other) = delete;
impl(impl&& other) = delete;
impl& operator=(const impl& other) = delete;
impl& operator=(impl&& other) = delete;
void throw_if_closed() const;
chip_ptr chip;
};
struct chip_info::impl
{
impl() = default;
impl(const impl& other) = delete;
impl(impl&& other) = delete;
impl& operator=(const impl& other) = delete;
impl& operator=(impl&& other) = delete;
void set_info_ptr(chip_info_ptr& new_info);
chip_info_ptr info;
};
struct line_info::impl
{
impl() = default;
impl(const impl& other) = delete;
impl(impl&& other) = delete;
impl& operator=(const impl& other) = delete;
impl& operator=(impl&& other) = delete;
void set_info_ptr(line_info_ptr& new_info);
line_info_ptr info;
};
struct info_event::impl
{
impl() = default;
impl(const impl& other) = delete;
impl(impl&& other) = delete;
impl& operator=(const impl& other) = delete;
impl& operator=(impl&& other) = delete;
void set_info_event_ptr(info_event_ptr& new_event);
info_event_ptr event;
line_info info;
};
struct line_settings::impl
{
impl();
impl(const impl& other) = delete;
impl(impl&& other) = delete;
impl& operator=(const impl& other) = delete;
impl& operator=(impl&& other) = delete;
line_settings_ptr settings;
};
struct line_config::impl
{
impl();
impl(const impl& other) = delete;
impl(impl&& other) = delete;
impl& operator=(const impl& other) = delete;
impl& operator=(impl&& other) = delete;
line_config_ptr config;
};
struct request_config::impl
{
impl();
impl(const impl& other) = delete;
impl(impl&& other) = delete;
impl& operator=(const impl& other) = delete;
impl& operator=(impl&& other) = delete;
request_config_ptr config;
};
struct line_request::impl
{
impl() = default;
impl(const impl& other) = delete;
impl(impl&& other) = delete;
impl& operator=(const impl& other) = delete;
impl& operator=(impl&& other) = delete;
void throw_if_released() const;
void set_request_ptr(line_request_ptr& ptr);
void fill_offset_buf(const line::offsets& offsets);
line_request_ptr request;
/*
* Used when reading/setting the line values in order to avoid
* allocating a new buffer on every call. We're not doing it for
* offsets in the line & request config structures because they don't
* require high performance unlike the set/get value calls.
*/
::std::vector<unsigned int> offset_buf;
};
struct edge_event::impl
{
impl() = default;
impl(const impl& other) = delete;
impl(impl&& other) = delete;
virtual ~impl() = default;
impl& operator=(const impl& other) = delete;
impl& operator=(impl&& other) = delete;
virtual ::gpiod_edge_event* get_event_ptr() const noexcept = 0;
virtual ::std::shared_ptr<impl> copy(const ::std::shared_ptr<impl>& self) const = 0;
};
struct edge_event::impl_managed : public edge_event::impl
{
impl_managed() = default;
impl_managed(const impl_managed& other) = delete;
impl_managed(impl_managed&& other) = delete;
virtual ~impl_managed() = default;
impl_managed& operator=(const impl_managed& other) = delete;
impl_managed& operator=(impl_managed&& other) = delete;
::gpiod_edge_event* get_event_ptr() const noexcept override;
::std::shared_ptr<impl> copy(const ::std::shared_ptr<impl>& self) const override;
edge_event_ptr event;
};
struct edge_event::impl_external : public edge_event::impl
{
impl_external();
impl_external(const impl_external& other) = delete;
impl_external(impl_external&& other) = delete;
virtual ~impl_external() = default;
impl_external& operator=(const impl_external& other) = delete;
impl_external& operator=(impl_external&& other) = delete;
::gpiod_edge_event* get_event_ptr() const noexcept override;
::std::shared_ptr<impl> copy(const ::std::shared_ptr<impl>& self) const override;
::gpiod_edge_event *event;
};
struct edge_event_buffer::impl
{
impl(unsigned int capacity);
impl(const impl& other) = delete;
impl(impl&& other) = delete;
impl& operator=(const impl& other) = delete;
impl& operator=(impl&& other) = delete;
int read_events(const line_request_ptr& request, unsigned int max_events);
edge_event_buffer_ptr buffer;
::std::vector<edge_event> events;
};
} /* namespace gpiod */
#endif /* __LIBGPIOD_CXX_INTERNAL_HPP__ */

View File

@@ -1,60 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
#include <gpiod.hpp>
#include <system_error>
#include "internal.hpp"
namespace gpiod {
GPIOD_CXX_API line_iter begin(line_iter iter) noexcept
{
return iter;
}
GPIOD_CXX_API line_iter end(const line_iter&) noexcept
{
return line_iter();
}
GPIOD_CXX_API line_iter::line_iter(const chip& owner)
: _m_current(owner.get_line(0))
{
}
GPIOD_CXX_API line_iter& line_iter::operator++(void)
{
unsigned int offset = this->_m_current.offset() + 1;
chip owner = this->_m_current.get_chip();
if (offset == owner.num_lines())
this->_m_current = line(); /* Last element */
else
this->_m_current = owner.get_line(offset);
return *this;
}
GPIOD_CXX_API const line& line_iter::operator*(void) const
{
return this->_m_current;
}
GPIOD_CXX_API const line* line_iter::operator->(void) const
{
return ::std::addressof(this->_m_current);
}
GPIOD_CXX_API bool line_iter::operator==(const line_iter& rhs) const noexcept
{
return this->_m_current._m_line == rhs._m_current._m_line;
}
GPIOD_CXX_API bool line_iter::operator!=(const line_iter& rhs) const noexcept
{
return this->_m_current._m_line != rhs._m_current._m_line;
}
} /* namespace gpiod */

View File

@@ -0,0 +1,164 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <cstdlib>
#include <iterator>
#include <ostream>
#include <sstream>
#include <utility>
#include <vector>
#include "internal.hpp"
namespace gpiod {
namespace {
::gpiod_line_config* make_line_config()
{
::gpiod_line_config *config = ::gpiod_line_config_new();
if (!config)
throw_from_errno("Unable to allocate the line config object");
return config;
}
struct malloc_deleter
{
void operator()(void* ptr)
{
::free(ptr);
}
};
} /* namespace */
line_config::impl::impl()
: config(make_line_config())
{
}
GPIOD_CXX_API line_config::line_config()
: _m_priv(new impl)
{
}
GPIOD_CXX_API line_config::line_config(line_config&& other) noexcept
: _m_priv(::std::move(other._m_priv))
{
}
GPIOD_CXX_API line_config::~line_config()
{
}
line_config& line_config::operator=(const line_config& other)
{
this->_m_priv = other._m_priv;
return *this;
}
GPIOD_CXX_API line_config& line_config::operator=(line_config&& other) noexcept
{
this->_m_priv = ::std::move(other._m_priv);
return *this;
}
GPIOD_CXX_API line_config& line_config::reset() noexcept
{
::gpiod_line_config_reset(this->_m_priv->config.get());
return *this;
}
GPIOD_CXX_API line_config& line_config::add_line_settings(line::offset offset,
const line_settings& settings)
{
return this->add_line_settings(line::offsets({offset}), settings);
}
GPIOD_CXX_API line_config& line_config::add_line_settings(const line::offsets& offsets,
const line_settings& settings)
{
::std::vector<unsigned int> raw_offsets(offsets.size());
for (unsigned int i = 0; i < offsets.size(); i++)
raw_offsets[i] = offsets[i];
auto ret = ::gpiod_line_config_add_line_settings(this->_m_priv->config.get(),
raw_offsets.data(), raw_offsets.size(),
settings._m_priv->settings.get());
if (ret)
throw_from_errno("unable to add line settings");
return *this;
}
GPIOD_CXX_API ::std::map<line::offset, line_settings> line_config::get_line_settings() const
{
::std::map<line::offset, line_settings> settings_map;
::std::size_t num_offsets;
unsigned int *offsets_ptr;
int ret;
ret = ::gpiod_line_config_get_offsets(this->_m_priv->config.get(),
&num_offsets, &offsets_ptr);
if (ret)
throw_from_errno("unable to retrieve line offsets");
if (num_offsets == 0)
return settings_map;
::std::unique_ptr<unsigned int, malloc_deleter> offsets(offsets_ptr);
for (size_t i = 0; i < num_offsets; i++) {
line_settings settings;
settings._m_priv->settings.reset(::gpiod_line_config_get_line_settings(
this->_m_priv->config.get(),
offsets.get()[i]));
if (!settings._m_priv->settings)
throw_from_errno("unable to retrieve line settings");
settings_map[offsets.get()[i]] = ::std::move(settings);
}
return settings_map;
}
GPIOD_CXX_API ::std::ostream&
operator<<(::std::ostream& out, const line_config& config)
{
auto settings_map = config.get_line_settings();
::std::vector<::std::string> vec;
out << "gpiod::line_config(num_settings=" << settings_map.size();
if (settings_map.size() == 0) {
out << ")";
return out;
}
for (const auto& [offset, settings]: settings_map) {
::std::stringstream str;
str << offset << ": " << settings;
vec.push_back(str.str());
}
out << ", settings=[";
::std::copy(vec.begin(), ::std::prev(vec.end()),
::std::ostream_iterator<::std::string>(out, ", "));
out << vec.back();
out << "])";
return out;
}
} /* namespace gpiod */

191
bindings/cxx/line-info.cpp Normal file
View File

@@ -0,0 +1,191 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <map>
#include <ostream>
#include <utility>
#include "internal.hpp"
namespace gpiod {
namespace {
const ::std::map<int, line::direction> direction_mapping = {
{ GPIOD_LINE_DIRECTION_INPUT, line::direction::INPUT },
{ GPIOD_LINE_DIRECTION_OUTPUT, line::direction::OUTPUT }
};
const ::std::map<int, line::bias> bias_mapping = {
{ GPIOD_LINE_BIAS_UNKNOWN, line::bias::UNKNOWN },
{ GPIOD_LINE_BIAS_DISABLED, line::bias::DISABLED },
{ GPIOD_LINE_BIAS_PULL_UP, line::bias::PULL_UP },
{ GPIOD_LINE_BIAS_PULL_DOWN, line::bias::PULL_DOWN }
};
const ::std::map<int, line::drive> drive_mapping = {
{ GPIOD_LINE_DRIVE_PUSH_PULL, line::drive::PUSH_PULL },
{ GPIOD_LINE_DRIVE_OPEN_DRAIN, line::drive::OPEN_DRAIN },
{ GPIOD_LINE_DRIVE_OPEN_SOURCE, line::drive::OPEN_SOURCE }
};
const ::std::map<int, line::edge> edge_mapping = {
{ GPIOD_LINE_EDGE_NONE, line::edge::NONE },
{ GPIOD_LINE_EDGE_RISING, line::edge::RISING },
{ GPIOD_LINE_EDGE_FALLING, line::edge::FALLING },
{ GPIOD_LINE_EDGE_BOTH, line::edge::BOTH }
};
const ::std::map<int, line::clock> clock_mapping = {
{ GPIOD_LINE_EVENT_CLOCK_MONOTONIC, line::clock::MONOTONIC },
{ GPIOD_LINE_EVENT_CLOCK_REALTIME, line::clock::REALTIME },
{ GPIOD_LINE_EVENT_CLOCK_HTE, line::clock::HTE }
};
} /* namespace */
void line_info::impl::set_info_ptr(line_info_ptr& new_info)
{
this->info = ::std::move(new_info);
}
line_info::line_info()
: _m_priv(new impl)
{
}
GPIOD_CXX_API line_info::line_info(const line_info& other) noexcept
: _m_priv(other._m_priv)
{
}
GPIOD_CXX_API line_info::line_info(line_info&& other) noexcept
: _m_priv(::std::move(other._m_priv))
{
}
GPIOD_CXX_API line_info::~line_info()
{
}
GPIOD_CXX_API line_info& line_info::operator=(const line_info& other) noexcept
{
this->_m_priv = other._m_priv;
return *this;
}
GPIOD_CXX_API line_info& line_info::operator=(line_info&& other) noexcept
{
this->_m_priv = ::std::move(other._m_priv);
return *this;
}
GPIOD_CXX_API line::offset line_info::offset() const noexcept
{
return ::gpiod_line_info_get_offset(this->_m_priv->info.get());
}
GPIOD_CXX_API ::std::string line_info::name() const noexcept
{
const char* name = ::gpiod_line_info_get_name(this->_m_priv->info.get());
return name ?: "";
}
GPIOD_CXX_API bool line_info::used() const noexcept
{
return ::gpiod_line_info_is_used(this->_m_priv->info.get());
}
GPIOD_CXX_API ::std::string line_info::consumer() const noexcept
{
const char* consumer = ::gpiod_line_info_get_consumer(this->_m_priv->info.get());
return consumer ?: "";
}
GPIOD_CXX_API line::direction line_info::direction() const
{
int direction = ::gpiod_line_info_get_direction(this->_m_priv->info.get());
return map_int_to_enum(direction, direction_mapping);
}
GPIOD_CXX_API bool line_info::active_low() const noexcept
{
return ::gpiod_line_info_is_active_low(this->_m_priv->info.get());
}
GPIOD_CXX_API line::bias line_info::bias() const
{
int bias = ::gpiod_line_info_get_bias(this->_m_priv->info.get());
return bias_mapping.at(bias);
}
GPIOD_CXX_API line::drive line_info::drive() const
{
int drive = ::gpiod_line_info_get_drive(this->_m_priv->info.get());
return drive_mapping.at(drive);
}
GPIOD_CXX_API line::edge line_info::edge_detection() const
{
int edge = ::gpiod_line_info_get_edge_detection(this->_m_priv->info.get());
return edge_mapping.at(edge);
}
GPIOD_CXX_API line::clock line_info::event_clock() const
{
int clock = ::gpiod_line_info_get_event_clock(this->_m_priv->info.get());
return clock_mapping.at(clock);
}
GPIOD_CXX_API bool line_info::debounced() const noexcept
{
return ::gpiod_line_info_is_debounced(this->_m_priv->info.get());
}
GPIOD_CXX_API ::std::chrono::microseconds line_info::debounce_period() const noexcept
{
return ::std::chrono::microseconds(
::gpiod_line_info_get_debounce_period_us(this->_m_priv->info.get()));
}
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line_info& info)
{
::std::string name, consumer;
name = info.name().empty() ? "unnamed" : ::std::string("'") + info.name() + "'";
consumer = info.consumer().empty() ? "unused" : ::std::string("'") + info.name() + "'";
out << "gpiod::line_info(offset=" << info.offset() <<
", name=" << name <<
", used=" << ::std::boolalpha << info.used() <<
", consumer=" << consumer <<
", direction=" << info.direction() <<
", active_low=" << ::std::boolalpha << info.active_low() <<
", bias=" << info.bias() <<
", drive=" << info.drive() <<
", edge_detection=" << info.edge_detection() <<
", event_clock=" << info.event_clock() <<
", debounced=" << ::std::boolalpha << info.debounced();
if (info.debounced())
out << ", debounce_period=" << info.debounce_period().count() << "us";
out << ")";
return out;
}
} /* namespace gpiod */

View File

@@ -0,0 +1,231 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <iterator>
#include <ostream>
#include <utility>
#include "internal.hpp"
namespace gpiod {
void line_request::impl::throw_if_released() const
{
if (!this->request)
throw request_released("GPIO lines have been released");
}
void line_request::impl::set_request_ptr(line_request_ptr& ptr)
{
this->request = ::std::move(ptr);
this->offset_buf.resize(::gpiod_line_request_get_num_lines(this->request.get()));
}
void line_request::impl::fill_offset_buf(const line::offsets& offsets)
{
for (unsigned int i = 0; i < offsets.size(); i++)
this->offset_buf[i] = offsets[i];
}
line_request::line_request()
: _m_priv(new impl)
{
}
GPIOD_CXX_API line_request::line_request(line_request&& other) noexcept
: _m_priv(::std::move(other._m_priv))
{
}
GPIOD_CXX_API line_request::~line_request()
{
}
GPIOD_CXX_API line_request& line_request::operator=(line_request&& other) noexcept
{
this->_m_priv = ::std::move(other._m_priv);
return *this;
}
GPIOD_CXX_API line_request::operator bool() const noexcept
{
return this->_m_priv->request.get() != nullptr;
}
GPIOD_CXX_API void line_request::release()
{
this->_m_priv->throw_if_released();
this->_m_priv->request.reset();
}
GPIOD_CXX_API ::std::size_t line_request::num_lines() const
{
this->_m_priv->throw_if_released();
return ::gpiod_line_request_get_num_lines(this->_m_priv->request.get());
}
GPIOD_CXX_API line::offsets line_request::offsets() const
{
this->_m_priv->throw_if_released();
auto num_lines = this->num_lines();
::std::vector<unsigned int> buf(num_lines);
line::offsets offsets(num_lines);
::gpiod_line_request_get_offsets(this->_m_priv->request.get(), buf.data());
for (unsigned int i = 0; i < num_lines; i++)
offsets[i] = buf[i];
return offsets;
}
GPIOD_CXX_API line::value line_request::get_value(line::offset offset)
{
return this->get_values({ offset }).front();
}
GPIOD_CXX_API line::values
line_request::get_values(const line::offsets& offsets)
{
line::values vals(offsets.size());
this->get_values(offsets, vals);
return vals;
}
GPIOD_CXX_API line::values line_request::get_values()
{
return this->get_values(this->offsets());
}
GPIOD_CXX_API void line_request::get_values(const line::offsets& offsets, line::values& values)
{
this->_m_priv->throw_if_released();
if (offsets.size() != values.size())
throw ::std::invalid_argument("values must have the same size as the offsets");
this->_m_priv->fill_offset_buf(offsets);
int ret = ::gpiod_line_request_get_values_subset(this->_m_priv->request.get(),
offsets.size(), this->_m_priv->offset_buf.data(),
reinterpret_cast<int*>(values.data()));
if (ret)
throw_from_errno("unable to retrieve line values");
}
GPIOD_CXX_API void line_request::get_values(line::values& values)
{
this->get_values(this->offsets(), values);
}
GPIOD_CXX_API line_request&
line_request::line_request::set_value(line::offset offset, line::value value)
{
return this->set_values({ offset }, { value });
}
GPIOD_CXX_API line_request&
line_request::set_values(const line::value_mappings& values)
{
line::offsets offsets(values.size());
line::values vals(values.size());
for (unsigned int i = 0; i < values.size(); i++) {
offsets[i] = values[i].first;
vals[i] = values[i].second;
}
return this->set_values(offsets, vals);
}
GPIOD_CXX_API line_request& line_request::set_values(const line::offsets& offsets,
const line::values& values)
{
this->_m_priv->throw_if_released();
if (offsets.size() != values.size())
throw ::std::invalid_argument("values must have the same size as the offsets");
this->_m_priv->fill_offset_buf(offsets);
int ret = ::gpiod_line_request_set_values_subset(this->_m_priv->request.get(),
offsets.size(), this->_m_priv->offset_buf.data(),
reinterpret_cast<const int*>(values.data()));
if (ret)
throw_from_errno("unable to set line values");
return *this;
}
GPIOD_CXX_API line_request& line_request::set_values(const line::values& values)
{
return this->set_values(this->offsets(), values);
}
GPIOD_CXX_API line_request& line_request::reconfigure_lines(const line_config& config)
{
this->_m_priv->throw_if_released();
int ret = ::gpiod_line_request_reconfigure_lines(this->_m_priv->request.get(),
config._m_priv->config.get());
if (ret)
throw_from_errno("unable to reconfigure GPIO lines");
return *this;
}
GPIOD_CXX_API int line_request::fd() const
{
this->_m_priv->throw_if_released();
return ::gpiod_line_request_get_fd(this->_m_priv->request.get());
}
GPIOD_CXX_API bool line_request::wait_edge_event(const ::std::chrono::nanoseconds& timeout) const
{
this->_m_priv->throw_if_released();
int ret = ::gpiod_line_request_wait_edge_event(this->_m_priv->request.get(),
timeout.count());
if (ret < 0)
throw_from_errno("error waiting for edge events");
return ret;
}
GPIOD_CXX_API ::std::size_t line_request::read_edge_event(edge_event_buffer& buffer)
{
return this->read_edge_event(buffer, buffer.capacity());
}
GPIOD_CXX_API ::std::size_t
line_request::read_edge_event(edge_event_buffer& buffer, ::std::size_t max_events)
{
this->_m_priv->throw_if_released();
return buffer._m_priv->read_events(this->_m_priv->request, max_events);
}
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line_request& request)
{
if (!request)
out << "gpiod::line_request(released)";
else
out << "gpiod::line_request(num_lines=" << request.num_lines() <<
", line_offsets=" << request.offsets() <<
", fd=" << request.fd() <<
")";
return out;
}
} /* namespace gpiod */

View File

@@ -0,0 +1,304 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <map>
#include <ostream>
#include "internal.hpp"
namespace gpiod {
namespace {
template<class enum_type>
::std::map<int, enum_type> make_reverse_maping(const ::std::map<enum_type, int>& mapping)
{
::std::map<int, enum_type> ret;
for (const auto &item: mapping)
ret[item.second] = item.first;
return ret;
}
const ::std::map<line::direction, int> direction_mapping = {
{ line::direction::AS_IS, GPIOD_LINE_DIRECTION_AS_IS },
{ line::direction::INPUT, GPIOD_LINE_DIRECTION_INPUT },
{ line::direction::OUTPUT, GPIOD_LINE_DIRECTION_OUTPUT }
};
const ::std::map<int, line::direction> reverse_direction_mapping = make_reverse_maping(direction_mapping);
const ::std::map<line::edge, int> edge_mapping = {
{ line::edge::NONE, GPIOD_LINE_EDGE_NONE },
{ line::edge::FALLING, GPIOD_LINE_EDGE_FALLING },
{ line::edge::RISING, GPIOD_LINE_EDGE_RISING },
{ line::edge::BOTH, GPIOD_LINE_EDGE_BOTH }
};
const ::std::map<int, line::edge> reverse_edge_mapping = make_reverse_maping(edge_mapping);
const ::std::map<line::bias, int> bias_mapping = {
{ line::bias::AS_IS, GPIOD_LINE_BIAS_AS_IS },
{ line::bias::DISABLED, GPIOD_LINE_BIAS_DISABLED },
{ line::bias::PULL_UP, GPIOD_LINE_BIAS_PULL_UP },
{ line::bias::PULL_DOWN, GPIOD_LINE_BIAS_PULL_DOWN }
};
const ::std::map<int, line::bias> reverse_bias_mapping = make_reverse_maping(bias_mapping);
const ::std::map<line::drive, int> drive_mapping = {
{ line::drive::PUSH_PULL, GPIOD_LINE_DRIVE_PUSH_PULL },
{ line::drive::OPEN_DRAIN, GPIOD_LINE_DRIVE_OPEN_DRAIN },
{ line::drive::OPEN_SOURCE, GPIOD_LINE_DRIVE_OPEN_SOURCE }
};
const ::std::map<int, line::drive> reverse_drive_mapping = make_reverse_maping(drive_mapping);
const ::std::map<line::clock, int> clock_mapping = {
{ line::clock::MONOTONIC, GPIOD_LINE_EVENT_CLOCK_MONOTONIC },
{ line::clock::REALTIME, GPIOD_LINE_EVENT_CLOCK_REALTIME },
{ line::clock::HTE, GPIOD_LINE_EVENT_CLOCK_HTE }
};
const ::std::map<int, line::clock> reverse_clock_mapping = make_reverse_maping(clock_mapping);
const ::std::map<line::value, int> value_mapping = {
{ line::value::INACTIVE, GPIOD_LINE_VALUE_INACTIVE },
{ line::value::ACTIVE, GPIOD_LINE_VALUE_ACTIVE }
};
const ::std::map<int, line::value> reverse_value_mapping = make_reverse_maping(value_mapping);
line_settings_ptr make_line_settings()
{
line_settings_ptr settings(::gpiod_line_settings_new());
if (!settings)
throw_from_errno("Unable to allocate the line settings object");
return settings;
}
template<class key_type, class value_type, class exception_type>
value_type map_setting(const key_type& key, const ::std::map<key_type, value_type>& mapping)
{
value_type ret;
try {
ret = mapping.at(key);
} catch (const ::std::out_of_range& err) {
/* FIXME Demangle the name. */
throw exception_type(::std::string("invalid value for ") +
typeid(key_type).name());
}
return ret;
}
template<class enum_type>
int do_map_value(enum_type value, const ::std::map<enum_type, int>& mapping)
{
return map_setting<enum_type, int, ::std::invalid_argument>(value, mapping);
}
template<class enum_type, int set_func(::gpiod_line_settings*, int)>
void set_mapped_value(::gpiod_line_settings* settings, enum_type value,
const ::std::map<enum_type, int>& mapping)
{
auto mapped_val = do_map_value(value, mapping);
auto ret = set_func(settings, mapped_val);
if (ret)
throw_from_errno("unable to set property");
}
template<class ret_type, int get_func(::gpiod_line_settings*)>
ret_type get_mapped_value(::gpiod_line_settings* settings,
const ::std::map<int, ret_type>& mapping)
{
int mapped_val = get_func(settings);
return map_int_to_enum(mapped_val, mapping);
}
} /* namespace */
line_settings::impl::impl()
: settings(make_line_settings())
{
}
GPIOD_CXX_API line_settings::line_settings()
: _m_priv(new impl)
{
}
GPIOD_CXX_API line_settings::line_settings(line_settings&& other) noexcept
: _m_priv(::std::move(other._m_priv))
{
}
GPIOD_CXX_API line_settings::~line_settings()
{
}
GPIOD_CXX_API line_settings& line_settings::operator=(line_settings&& other)
{
this->_m_priv = ::std::move(other._m_priv);
return *this;
}
GPIOD_CXX_API line_settings& line_settings::reset(void) noexcept
{
::gpiod_line_settings_reset(this->_m_priv->settings.get());
return *this;
}
GPIOD_CXX_API line_settings& line_settings::set_direction(line::direction direction)
{
set_mapped_value<line::direction,
::gpiod_line_settings_set_direction>(this->_m_priv->settings.get(),
direction, direction_mapping);
return *this;
}
GPIOD_CXX_API line::direction line_settings::direction() const
{
return get_mapped_value<line::direction,
::gpiod_line_settings_get_direction>(
this->_m_priv->settings.get(),
reverse_direction_mapping);
}
GPIOD_CXX_API line_settings& line_settings::set_edge_detection(line::edge edge)
{
set_mapped_value<line::edge,
::gpiod_line_settings_set_edge_detection>(this->_m_priv->settings.get(),
edge, edge_mapping);
return *this;
}
GPIOD_CXX_API line::edge line_settings::edge_detection() const
{
return get_mapped_value<line::edge,
::gpiod_line_settings_get_edge_detection>(
this->_m_priv->settings.get(),
reverse_edge_mapping);
}
GPIOD_CXX_API line_settings& line_settings::set_bias(line::bias bias)
{
set_mapped_value<line::bias,
::gpiod_line_settings_set_bias>(this->_m_priv->settings.get(),
bias, bias_mapping);
return *this;
}
GPIOD_CXX_API line::bias line_settings::bias() const
{
return get_mapped_value<line::bias,
::gpiod_line_settings_get_bias>(this->_m_priv->settings.get(),
reverse_bias_mapping);
}
GPIOD_CXX_API line_settings& line_settings::set_drive(line::drive drive)
{
set_mapped_value<line::drive,
::gpiod_line_settings_set_drive>(this->_m_priv->settings.get(),
drive, drive_mapping);
return *this;
}
GPIOD_CXX_API line::drive line_settings::drive() const
{
return get_mapped_value<line::drive,
::gpiod_line_settings_get_drive>(this->_m_priv->settings.get(),
reverse_drive_mapping);
}
GPIOD_CXX_API line_settings& line_settings::set_active_low(bool active_low)
{
::gpiod_line_settings_set_active_low(this->_m_priv->settings.get(), active_low);
return *this;
}
GPIOD_CXX_API bool line_settings::active_low() const noexcept
{
return ::gpiod_line_settings_get_active_low(this->_m_priv->settings.get());
}
GPIOD_CXX_API line_settings&
line_settings::set_debounce_period(const ::std::chrono::microseconds& period)
{
::gpiod_line_settings_set_debounce_period_us(this->_m_priv->settings.get(), period.count());
return *this;
}
GPIOD_CXX_API ::std::chrono::microseconds line_settings::debounce_period() const noexcept
{
return ::std::chrono::microseconds(
::gpiod_line_settings_get_debounce_period_us(this->_m_priv->settings.get()));
}
GPIOD_CXX_API line_settings& line_settings::set_event_clock(line::clock event_clock)
{
set_mapped_value<line::clock,
::gpiod_line_settings_set_event_clock>(this->_m_priv->settings.get(),
event_clock, clock_mapping);
return *this;
}
GPIOD_CXX_API line::clock line_settings::event_clock() const
{
return get_mapped_value<line::clock,
::gpiod_line_settings_get_event_clock>(
this->_m_priv->settings.get(),
reverse_clock_mapping);
}
GPIOD_CXX_API line_settings& line_settings::set_output_value(line::value value)
{
set_mapped_value<line::value,
::gpiod_line_settings_set_output_value>(this->_m_priv->settings.get(),
value, value_mapping);
return *this;
}
GPIOD_CXX_API line::value line_settings::output_value() const
{
return get_mapped_value<line::value,
::gpiod_line_settings_get_output_value>(
this->_m_priv->settings.get(),
reverse_value_mapping);
}
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line_settings& settings)
{
out << "gpiod::line_settings(direction=" << settings.direction() <<
", edge_detection=" << settings.edge_detection() <<
", bias=" << settings.bias() <<
", drive=" << settings.drive() <<
", " << (settings.active_low() ? "active-low" : "active-high") <<
", debounce_period=" << settings.debounce_period().count() <<
", event_clock=" << settings.event_clock() <<
", output_value=" << settings.output_value() <<
")";
return out;
}
} /* namespace gpiod */

View File

@@ -1,321 +1,132 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <gpiod.hpp>
#include <array>
#include <map>
#include <system_error>
#include <iterator>
#include <ostream>
#include "internal.hpp"
namespace gpiod {
namespace line {
namespace {
const ::std::map<int, int> drive_mapping = {
{ GPIOD_LINE_DRIVE_PUSH_PULL, line::DRIVE_PUSH_PULL, },
{ GPIOD_LINE_DRIVE_OPEN_DRAIN, line::DRIVE_OPEN_DRAIN, },
{ GPIOD_LINE_DRIVE_OPEN_SOURCE, line::DRIVE_OPEN_SOURCE, },
const ::std::map<line::value, ::std::string> value_names = {
{ line::value::INACTIVE, "INACTIVE" },
{ line::value::ACTIVE, "ACTIVE" }
};
const ::std::map<int, int> bias_mapping = {
{ GPIOD_LINE_BIAS_UNKNOWN, line::BIAS_UNKNOWN, },
{ GPIOD_LINE_BIAS_DISABLED, line::BIAS_DISABLED, },
{ GPIOD_LINE_BIAS_PULL_UP, line::BIAS_PULL_UP, },
{ GPIOD_LINE_BIAS_PULL_DOWN, line::BIAS_PULL_DOWN, },
const ::std::map<line::direction, ::std::string> direction_names = {
{ line::direction::AS_IS, "AS_IS" },
{ line::direction::INPUT, "INPUT" },
{ line::direction::OUTPUT, "OUTPUT" }
};
const ::std::map<line::bias, ::std::string> bias_names = {
{ line::bias::AS_IS, "AS_IS" },
{ line::bias::UNKNOWN, "UNKNOWN" },
{ line::bias::DISABLED, "DISABLED" },
{ line::bias::PULL_UP, "PULL_UP" },
{ line::bias::PULL_DOWN, "PULL_DOWN" }
};
const ::std::map<line::drive, ::std::string> drive_names = {
{ line::drive::PUSH_PULL, "PUSH_PULL" },
{ line::drive::OPEN_DRAIN, "OPEN_DRAIN" },
{ line::drive::OPEN_SOURCE, "OPEN_SOURCE" }
};
const ::std::map<line::edge, ::std::string> edge_names = {
{ line::edge::NONE, "NONE" },
{ line::edge::RISING, "RISING_EDGE" },
{ line::edge::FALLING, "FALLING_EDGE" },
{ line::edge::BOTH, "BOTH_EDGES" }
};
const ::std::map<line::clock, ::std::string> clock_names = {
{ line::clock::MONOTONIC, "MONOTONIC" },
{ line::clock::REALTIME, "REALTIME" },
{ line::clock::HTE, "HTE" }
};
} /* namespace */
GPIOD_CXX_API line::line(void)
: _m_line(nullptr),
_m_owner()
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::value val)
{
out << value_names.at(val);
return out;
}
GPIOD_CXX_API line::line(::gpiod_line* line, const chip& owner)
: _m_line(line),
_m_owner(owner._m_chip)
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::direction dir)
{
out << direction_names.at(dir);
return out;
}
GPIOD_CXX_API unsigned int line::offset(void) const
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::edge edge)
{
this->throw_if_null();
line::chip_guard lock_chip(*this);
out << edge_names.at(edge);
return ::gpiod_line_offset(this->_m_line);
return out;
}
GPIOD_CXX_API ::std::string line::name(void) const
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::bias bias)
{
this->throw_if_null();
line::chip_guard lock_chip(*this);
out << bias_names.at(bias);
const char* name = ::gpiod_line_name(this->_m_line);
return name ? ::std::string(name) : ::std::string();
return out;
}
GPIOD_CXX_API ::std::string line::consumer(void) const
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::drive drive)
{
this->throw_if_null();
line::chip_guard lock_chip(*this);
out << drive_names.at(drive);
const char* consumer = ::gpiod_line_consumer(this->_m_line);
return consumer ? ::std::string(consumer) : ::std::string();
return out;
}
GPIOD_CXX_API int line::direction(void) const
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, line::clock clock)
{
this->throw_if_null();
line::chip_guard lock_chip(*this);
out << clock_names.at(clock);
int dir = ::gpiod_line_direction(this->_m_line);
return dir == GPIOD_LINE_DIRECTION_INPUT ? DIRECTION_INPUT : DIRECTION_OUTPUT;
return out;
}
GPIOD_CXX_API bool line::is_active_low(void) const
template<typename T>
::std::ostream& insert_vector(::std::ostream& out,
const ::std::string& name, const ::std::vector<T>& vec)
{
this->throw_if_null();
line::chip_guard lock_chip(*this);
out << name << "(";
::std::copy(vec.begin(), ::std::prev(vec.end()),
::std::ostream_iterator<T>(out, ", "));
out << vec.back();
out << ")";
return ::gpiod_line_is_active_low(this->_m_line);
return out;
}
GPIOD_CXX_API int line::bias(void) const
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const offsets& offs)
{
this->throw_if_null();
line::chip_guard lock_chip(*this);
return bias_mapping.at(::gpiod_line_bias(this->_m_line));
return insert_vector(out, "gpiod::offsets", offs);
}
GPIOD_CXX_API bool line::is_used(void) const
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line::values& vals)
{
this->throw_if_null();
line::chip_guard lock_chip(*this);
return ::gpiod_line_is_used(this->_m_line);
return insert_vector(out, "gpiod::values", vals);
}
GPIOD_CXX_API int line::drive(void) const
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line::value_mapping& mapping)
{
this->throw_if_null();
line::chip_guard lock_chip(*this);
out << "gpiod::value_mapping(" << mapping.first << ": " << mapping.second << ")";
return drive_mapping.at(::gpiod_line_drive(this->_m_line));
return out;
}
GPIOD_CXX_API void line::request(const line_request& config, int default_val) const
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const line::value_mappings& mappings)
{
this->throw_if_null();
line_bulk bulk({ *this });
bulk.request(config, { default_val });
}
GPIOD_CXX_API void line::release(void) const
{
this->throw_if_null();
line_bulk bulk({ *this });
bulk.release();
}
/*
* REVISIT: Check the performance of get/set_value & event_wait compared to
* the C API. Creating a line_bulk object involves a memory allocation every
* time this method if called. If the performance is significantly lower,
* switch to calling the C functions for setting/getting line values and
* polling for events on single lines directly.
*/
GPIOD_CXX_API int line::get_value(void) const
{
this->throw_if_null();
line_bulk bulk({ *this });
return bulk.get_values()[0];
}
GPIOD_CXX_API void line::set_value(int val) const
{
this->throw_if_null();
line_bulk bulk({ *this });
bulk.set_values({ val });
}
GPIOD_CXX_API void line::set_config(int direction, ::std::bitset<32> flags,
int value) const
{
this->throw_if_null();
line_bulk bulk({ *this });
bulk.set_config(direction, flags, { value });
}
GPIOD_CXX_API void line::set_flags(::std::bitset<32> flags) const
{
this->throw_if_null();
line_bulk bulk({ *this });
bulk.set_flags(flags);
}
GPIOD_CXX_API void line::set_direction_input() const
{
this->throw_if_null();
line_bulk bulk({ *this });
bulk.set_direction_input();
}
GPIOD_CXX_API void line::set_direction_output(int value) const
{
this->throw_if_null();
line_bulk bulk({ *this });
bulk.set_direction_output({ value });
}
GPIOD_CXX_API bool line::event_wait(const ::std::chrono::nanoseconds& timeout) const
{
this->throw_if_null();
line_bulk bulk({ *this });
line_bulk event_bulk = bulk.event_wait(timeout);
return !!event_bulk;
}
GPIOD_CXX_API line_event line::make_line_event(const ::gpiod_line_event& event) const noexcept
{
line_event ret;
if (event.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
ret.event_type = line_event::RISING_EDGE;
else if (event.event_type == GPIOD_LINE_EVENT_FALLING_EDGE)
ret.event_type = line_event::FALLING_EDGE;
ret.timestamp = ::std::chrono::duration_cast<::std::chrono::nanoseconds>(
::std::chrono::seconds(event.ts.tv_sec)) +
::std::chrono::nanoseconds(event.ts.tv_nsec);
ret.source = *this;
return ret;
}
GPIOD_CXX_API line_event line::event_read(void) const
{
this->throw_if_null();
line::chip_guard lock_chip(*this);
::gpiod_line_event event_buf;
line_event event;
int rv;
rv = ::gpiod_line_event_read(this->_m_line, ::std::addressof(event_buf));
if (rv < 0)
throw ::std::system_error(errno, ::std::system_category(),
"error reading line event");
return this->make_line_event(event_buf);
}
GPIOD_CXX_API ::std::vector<line_event> line::event_read_multiple(void) const
{
this->throw_if_null();
line::chip_guard lock_chip(*this);
/* 16 is the maximum number of events stored in the kernel FIFO. */
::std::array<::gpiod_line_event, 16> event_buf;
::std::vector<line_event> events;
int rv;
rv = ::gpiod_line_event_read_multiple(this->_m_line,
event_buf.data(), event_buf.size());
if (rv < 0)
throw ::std::system_error(errno, ::std::system_category(),
"error reading multiple line events");
events.reserve(rv);
for (int i = 0; i < rv; i++)
events.push_back(this->make_line_event(event_buf[i]));
return events;
}
GPIOD_CXX_API int line::event_get_fd(void) const
{
this->throw_if_null();
line::chip_guard lock_chip(*this);
int ret = ::gpiod_line_event_get_fd(this->_m_line);
if (ret < 0)
throw ::std::system_error(errno, ::std::system_category(),
"unable to get the line event file descriptor");
return ret;
}
GPIOD_CXX_API const chip line::get_chip(void) const
{
return chip(this->_m_owner);
}
GPIOD_CXX_API void line::reset(void)
{
this->_m_line = nullptr;
this->_m_owner.reset();
}
GPIOD_CXX_API bool line::operator==(const line& rhs) const noexcept
{
return this->_m_line == rhs._m_line;
}
GPIOD_CXX_API bool line::operator!=(const line& rhs) const noexcept
{
return this->_m_line != rhs._m_line;
}
GPIOD_CXX_API line::operator bool(void) const noexcept
{
return this->_m_line != nullptr;
}
GPIOD_CXX_API bool line::operator!(void) const noexcept
{
return this->_m_line == nullptr;
}
GPIOD_CXX_API void line::throw_if_null(void) const
{
if (!this->_m_line)
throw ::std::logic_error("object not holding a GPIO line handle");
}
GPIOD_CXX_API line::chip_guard::chip_guard(const line& line)
: _m_chip(line._m_owner)
{
return insert_vector(out, "gpiod::value_mappings", mappings);
}
} /* namespace line */
} /* namespace gpiod */

View File

@@ -1,366 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
#include <gpiod.hpp>
#include <map>
#include <system_error>
#include "internal.hpp"
namespace gpiod {
GPIOD_CXX_API const ::std::bitset<32> line_request::FLAG_ACTIVE_LOW(GPIOD_BIT(0));
GPIOD_CXX_API const ::std::bitset<32> line_request::FLAG_OPEN_SOURCE(GPIOD_BIT(1));
GPIOD_CXX_API const ::std::bitset<32> line_request::FLAG_OPEN_DRAIN(GPIOD_BIT(2));
GPIOD_CXX_API const ::std::bitset<32> line_request::FLAG_BIAS_DISABLED(GPIOD_BIT(3));
GPIOD_CXX_API const ::std::bitset<32> line_request::FLAG_BIAS_PULL_DOWN(GPIOD_BIT(4));
GPIOD_CXX_API const ::std::bitset<32> line_request::FLAG_BIAS_PULL_UP(GPIOD_BIT(5));
namespace {
const ::std::map<int, int> reqtype_mapping = {
{ line_request::DIRECTION_AS_IS, GPIOD_LINE_REQUEST_DIRECTION_AS_IS, },
{ line_request::DIRECTION_INPUT, GPIOD_LINE_REQUEST_DIRECTION_INPUT, },
{ line_request::DIRECTION_OUTPUT, GPIOD_LINE_REQUEST_DIRECTION_OUTPUT, },
{ line_request::EVENT_FALLING_EDGE, GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE, },
{ line_request::EVENT_RISING_EDGE, GPIOD_LINE_REQUEST_EVENT_RISING_EDGE, },
{ line_request::EVENT_BOTH_EDGES, GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES, },
};
struct bitset_cmp
{
bool operator()(const ::std::bitset<32>& lhs, const ::std::bitset<32>& rhs) const
{
return lhs.to_ulong() < rhs.to_ulong();
}
};
const ::std::map<::std::bitset<32>, int, bitset_cmp> reqflag_mapping = {
{ line_request::FLAG_ACTIVE_LOW, GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW, },
{ line_request::FLAG_OPEN_DRAIN, GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN, },
{ line_request::FLAG_OPEN_SOURCE, GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE, },
{ line_request::FLAG_BIAS_DISABLED, GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED, },
{ line_request::FLAG_BIAS_PULL_DOWN, GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN, },
{ line_request::FLAG_BIAS_PULL_UP, GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP, },
};
} /* namespace */
GPIOD_CXX_API const unsigned int line_bulk::MAX_LINES = 64;
GPIOD_CXX_API line_bulk::line_bulk(const ::std::vector<line>& lines)
: _m_bulk()
{
this->_m_bulk.reserve(lines.size());
for (auto& it: lines)
this->append(it);
}
GPIOD_CXX_API void line_bulk::append(const line& new_line)
{
if (!new_line)
throw ::std::logic_error("line_bulk cannot hold empty line objects");
if (this->_m_bulk.size() >= MAX_LINES)
throw ::std::logic_error("maximum number of lines reached");
if (this->_m_bulk.size() >= 1 && this->_m_bulk.begin()->get_chip() != new_line.get_chip())
throw ::std::logic_error("line_bulk cannot hold GPIO lines from different chips");
this->_m_bulk.push_back(new_line);
}
GPIOD_CXX_API line& line_bulk::get(unsigned int index)
{
return this->_m_bulk.at(index);
}
GPIOD_CXX_API line& line_bulk::operator[](unsigned int index)
{
return this->_m_bulk[index];
}
GPIOD_CXX_API unsigned int line_bulk::size(void) const noexcept
{
return this->_m_bulk.size();
}
GPIOD_CXX_API bool line_bulk::empty(void) const noexcept
{
return this->_m_bulk.empty();
}
GPIOD_CXX_API void line_bulk::clear(void)
{
this->_m_bulk.clear();
}
GPIOD_CXX_API void line_bulk::request(const line_request& config, const ::std::vector<int> default_vals) const
{
this->throw_if_empty();
line::chip_guard lock_chip(this->_m_bulk.front());
if (!default_vals.empty() && this->size() != default_vals.size())
throw ::std::invalid_argument("the number of default values must correspond with the number of lines");
::gpiod_line_request_config conf;
auto bulk = this->to_line_bulk();
int rv;
conf.consumer = config.consumer.c_str();
conf.request_type = reqtype_mapping.at(config.request_type);
conf.flags = 0;
for (auto& it: reqflag_mapping) {
if ((it.first & config.flags).to_ulong())
conf.flags |= it.second;
}
rv = ::gpiod_line_request_bulk(bulk.get(),
::std::addressof(conf),
default_vals.empty() ? NULL : default_vals.data());
if (rv)
throw ::std::system_error(errno, ::std::system_category(),
"error requesting GPIO lines");
}
GPIOD_CXX_API void line_bulk::release(void) const
{
this->throw_if_empty();
line::chip_guard lock_chip(this->_m_bulk.front());
auto bulk = this->to_line_bulk();
::gpiod_line_release_bulk(bulk.get());
}
GPIOD_CXX_API ::std::vector<int> line_bulk::get_values(void) const
{
this->throw_if_empty();
line::chip_guard lock_chip(this->_m_bulk.front());
auto bulk = this->to_line_bulk();
::std::vector<int> values;
int rv;
values.resize(this->_m_bulk.size());
rv = ::gpiod_line_get_value_bulk(bulk.get(), values.data());
if (rv)
throw ::std::system_error(errno, ::std::system_category(),
"error reading GPIO line values");
return values;
}
GPIOD_CXX_API void line_bulk::set_values(const ::std::vector<int>& values) const
{
this->throw_if_empty();
line::chip_guard lock_chip(this->_m_bulk.front());
if (values.size() != this->_m_bulk.size())
throw ::std::invalid_argument("the size of values array must correspond with the number of lines");
auto bulk = this->to_line_bulk();
int rv;
rv = ::gpiod_line_set_value_bulk(bulk.get(), values.data());
if (rv)
throw ::std::system_error(errno, ::std::system_category(),
"error setting GPIO line values");
}
GPIOD_CXX_API void line_bulk::set_config(int direction, ::std::bitset<32> flags,
const ::std::vector<int> values) const
{
this->throw_if_empty();
line::chip_guard lock_chip(this->_m_bulk.front());
if (!values.empty() && this->_m_bulk.size() != values.size())
throw ::std::invalid_argument("the number of default values must correspond with the number of lines");
auto bulk = this->to_line_bulk();
int rv, gflags;
gflags = 0;
for (auto& it: reqflag_mapping) {
if ((it.first & flags).to_ulong())
gflags |= it.second;
}
rv = ::gpiod_line_set_config_bulk(bulk.get(), direction,
gflags, values.data());
if (rv)
throw ::std::system_error(errno, ::std::system_category(),
"error setting GPIO line config");
}
GPIOD_CXX_API void line_bulk::set_flags(::std::bitset<32> flags) const
{
this->throw_if_empty();
line::chip_guard lock_chip(this->_m_bulk.front());
auto bulk = this->to_line_bulk();
int rv, gflags;
gflags = 0;
for (auto& it: reqflag_mapping) {
if ((it.first & flags).to_ulong())
gflags |= it.second;
}
rv = ::gpiod_line_set_flags_bulk(bulk.get(), gflags);
if (rv)
throw ::std::system_error(errno, ::std::system_category(),
"error setting GPIO line flags");
}
GPIOD_CXX_API void line_bulk::set_direction_input() const
{
this->throw_if_empty();
line::chip_guard lock_chip(this->_m_bulk.front());
auto bulk = this->to_line_bulk();
int rv;
rv = ::gpiod_line_set_direction_input_bulk(bulk.get());
if (rv)
throw ::std::system_error(errno, ::std::system_category(),
"error setting GPIO line direction to input");
}
GPIOD_CXX_API void line_bulk::set_direction_output(const ::std::vector<int>& values) const
{
this->throw_if_empty();
line::chip_guard lock_chip(this->_m_bulk.front());
if (values.size() != this->_m_bulk.size())
throw ::std::invalid_argument("the size of values array must correspond with the number of lines");
auto bulk = this->to_line_bulk();
int rv;
rv = ::gpiod_line_set_direction_output_bulk(bulk.get(), values.data());
if (rv)
throw ::std::system_error(errno, ::std::system_category(),
"error setting GPIO line direction to output");
}
GPIOD_CXX_API line_bulk line_bulk::event_wait(const ::std::chrono::nanoseconds& timeout) const
{
this->throw_if_empty();
line::chip_guard lock_chip(this->_m_bulk.front());
auto ev_bulk = this->make_line_bulk_ptr();
auto bulk = this->to_line_bulk();
::timespec ts;
line_bulk ret;
int rv;
ts.tv_sec = timeout.count() / 1000000000ULL;
ts.tv_nsec = timeout.count() % 1000000000ULL;
rv = ::gpiod_line_event_wait_bulk(bulk.get(), ::std::addressof(ts), ev_bulk.get());
if (rv < 0) {
throw ::std::system_error(errno, ::std::system_category(),
"error polling for events");
} else if (rv > 0) {
auto chip = this->_m_bulk[0].get_chip();
auto num_lines = ::gpiod_line_bulk_num_lines(ev_bulk.get());
for (unsigned int i = 0; i < num_lines; i++)
ret.append(line(::gpiod_line_bulk_get_line(ev_bulk.get(), i), chip));
}
return ret;
}
GPIOD_CXX_API line_bulk::operator bool(void) const noexcept
{
return !this->_m_bulk.empty();
}
GPIOD_CXX_API bool line_bulk::operator!(void) const noexcept
{
return this->_m_bulk.empty();
}
GPIOD_CXX_API line_bulk::iterator::iterator(const ::std::vector<line>::iterator& it)
: _m_iter(it)
{
}
GPIOD_CXX_API line_bulk::iterator& line_bulk::iterator::operator++(void)
{
this->_m_iter++;
return *this;
}
GPIOD_CXX_API const line& line_bulk::iterator::operator*(void) const
{
return *this->_m_iter;
}
GPIOD_CXX_API const line* line_bulk::iterator::operator->(void) const
{
return this->_m_iter.operator->();
}
GPIOD_CXX_API bool line_bulk::iterator::operator==(const iterator& rhs) const noexcept
{
return this->_m_iter == rhs._m_iter;
}
GPIOD_CXX_API bool line_bulk::iterator::operator!=(const iterator& rhs) const noexcept
{
return this->_m_iter != rhs._m_iter;
}
GPIOD_CXX_API line_bulk::iterator line_bulk::begin(void) noexcept
{
return line_bulk::iterator(this->_m_bulk.begin());
}
GPIOD_CXX_API line_bulk::iterator line_bulk::end(void) noexcept
{
return line_bulk::iterator(this->_m_bulk.end());
}
GPIOD_CXX_API void line_bulk::throw_if_empty(void) const
{
if (this->_m_bulk.empty())
throw ::std::logic_error("line_bulk not holding any GPIO lines");
}
GPIOD_CXX_API line_bulk::line_bulk_ptr line_bulk::make_line_bulk_ptr(void) const
{
line_bulk_ptr bulk(::gpiod_line_bulk_new(this->size()));
if (!bulk)
throw ::std::system_error(errno, ::std::system_category(),
"unable to allocate new bulk object");
return bulk;
}
GPIOD_CXX_API line_bulk::line_bulk_ptr line_bulk::to_line_bulk(void) const
{
line_bulk_ptr bulk = this->make_line_bulk_ptr();
for (auto& it: this->_m_bulk)
::gpiod_line_bulk_add_line(bulk.get(), it._m_line);
return bulk;
}
GPIOD_CXX_API void line_bulk::line_bulk_deleter::operator()(::gpiod_line_bulk *bulk)
{
::gpiod_line_bulk_free(bulk);
}
} /* namespace gpiod */

20
bindings/cxx/misc.cpp Normal file
View File

@@ -0,0 +1,20 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include "internal.hpp"
namespace gpiod {
GPIOD_CXX_API bool is_gpiochip_device(const ::std::filesystem::path& path)
{
return ::gpiod_is_gpiochip_device(path.c_str());
}
GPIOD_CXX_API const ::std::string& version_string()
{
static const ::std::string version(::gpiod_version_string());
return version;
}
} /* namespace gpiod */

View File

@@ -0,0 +1,134 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <ostream>
#include <utility>
#include "internal.hpp"
namespace gpiod {
struct request_builder::impl
{
impl(chip& parent)
: line_cfg(),
req_cfg(),
parent(parent)
{
}
impl(const impl& other) = delete;
impl(impl&& other) = delete;
impl& operator=(const impl& other) = delete;
impl& operator=(impl&& other) = delete;
line_config line_cfg;
request_config req_cfg;
chip parent;
};
GPIOD_CXX_API request_builder::request_builder(chip& chip)
: _m_priv(new impl(chip))
{
}
GPIOD_CXX_API request_builder::request_builder(request_builder&& other) noexcept
: _m_priv(::std::move(other._m_priv))
{
}
GPIOD_CXX_API request_builder::~request_builder()
{
}
GPIOD_CXX_API request_builder& request_builder::operator=(request_builder&& other) noexcept
{
this->_m_priv = ::std::move(other._m_priv);
return *this;
}
GPIOD_CXX_API request_builder& request_builder::set_request_config(request_config& req_cfg)
{
this->_m_priv->req_cfg = req_cfg;
return *this;
}
GPIOD_CXX_API const request_config& request_builder::get_request_config() const noexcept
{
return this->_m_priv->req_cfg;
}
GPIOD_CXX_API request_builder&
request_builder::set_consumer(const ::std::string& consumer) noexcept
{
this->_m_priv->req_cfg.set_consumer(consumer);
return *this;
}
GPIOD_CXX_API request_builder&
request_builder::set_event_buffer_size(::std::size_t event_buffer_size) noexcept
{
this->_m_priv->req_cfg.set_event_buffer_size(event_buffer_size);
return *this;
}
GPIOD_CXX_API request_builder& request_builder::set_line_config(line_config &line_cfg)
{
this->_m_priv->line_cfg = line_cfg;
return *this;
}
GPIOD_CXX_API const line_config& request_builder::get_line_config() const noexcept
{
return this->_m_priv->line_cfg;
}
GPIOD_CXX_API request_builder&
request_builder::add_line_settings(line::offset offset, const line_settings& settings)
{
return this->add_line_settings(line::offsets({offset}), settings);
}
GPIOD_CXX_API request_builder&
request_builder::add_line_settings(const line::offsets& offsets, const line_settings& settings)
{
this->_m_priv->line_cfg.add_line_settings(offsets, settings);
return *this;
}
GPIOD_CXX_API line_request request_builder::do_request()
{
line_request_ptr request(::gpiod_chip_request_lines(
this->_m_priv->parent._m_priv->chip.get(),
this->_m_priv->req_cfg._m_priv->config.get(),
this->_m_priv->line_cfg._m_priv->config.get()));
if (!request)
throw_from_errno("error requesting GPIO lines");
line_request ret;
ret._m_priv.get()->set_request_ptr(request);
return ret;
}
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const request_builder& builder)
{
out << "gpiod::request_builder(request_config=" << builder._m_priv->req_cfg <<
", line_config=" << builder._m_priv->line_cfg <<
", parent=" << builder._m_priv->parent <<
")";
return out;
}
} /* namespace gpiod */

View File

@@ -0,0 +1,103 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
#include <ostream>
#include <utility>
#include "internal.hpp"
namespace gpiod {
namespace {
request_config_ptr make_request_config()
{
request_config_ptr config(::gpiod_request_config_new());
if (!config)
throw_from_errno("Unable to allocate the request config object");
return config;
}
} /* namespace */
request_config::impl::impl()
: config(make_request_config())
{
}
GPIOD_CXX_API request_config::request_config()
: _m_priv(new impl)
{
}
GPIOD_CXX_API request_config::request_config(request_config&& other) noexcept
: _m_priv(::std::move(other._m_priv))
{
}
GPIOD_CXX_API request_config::~request_config()
{
}
request_config& request_config::operator=(const request_config& other)
{
this->_m_priv = other._m_priv;
return *this;
}
GPIOD_CXX_API request_config& request_config::operator=(request_config&& other) noexcept
{
this->_m_priv = ::std::move(other._m_priv);
return *this;
}
GPIOD_CXX_API request_config&
request_config::set_consumer(const ::std::string& consumer) noexcept
{
::gpiod_request_config_set_consumer(this->_m_priv->config.get(), consumer.c_str());
return *this;
}
GPIOD_CXX_API ::std::string request_config::consumer() const noexcept
{
const char* consumer = ::gpiod_request_config_get_consumer(this->_m_priv->config.get());
return consumer ?: "";
}
GPIOD_CXX_API request_config&
request_config::set_event_buffer_size(::std::size_t event_buffer_size) noexcept
{
::gpiod_request_config_set_event_buffer_size(this->_m_priv->config.get(),
event_buffer_size);
return *this;
}
GPIOD_CXX_API ::std::size_t request_config::event_buffer_size() const noexcept
{
return ::gpiod_request_config_get_event_buffer_size(this->_m_priv->config.get());
}
GPIOD_CXX_API ::std::ostream& operator<<(::std::ostream& out, const request_config& config)
{
::std::string consumer;
consumer = config.consumer().empty() ? "N/A" : ::std::string("'") + config.consumer() + "'";
out << "gpiod::request_config(consumer=" << consumer <<
", event_buffer_size=" << config.event_buffer_size() <<
")";
return out;
}
} /* namespace gpiod */

View File

@@ -1,21 +1,32 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
AM_CPPFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include
AM_CPPFLAGS += -I$(top_srcdir)/tests/mockup/
AM_CPPFLAGS += -Wall -Wextra -g -std=gnu++11 $(CATCH2_CFLAGS)
AM_CXXFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include
AM_CXXFLAGS += -I$(top_srcdir)/tests/gpiosim/
AM_CXXFLAGS += -Wall -Wextra -g -std=gnu++17 $(CATCH2_CFLAGS)
AM_CXXFLAGS += $(PROFILING_CFLAGS)
AM_LDFLAGS = -lgpiodcxx -L$(top_builddir)/bindings/cxx/
AM_LDFLAGS += -lgpiomockup -L$(top_builddir)/tests/mockup/
AM_LDFLAGS += -lgpiosim -L$(top_builddir)/tests/gpiosim/
AM_LDFLAGS += $(PROFILING_LDFLAGS)
AM_LDFLAGS += -pthread
bin_PROGRAMS = gpiod-cxx-test
gpiod_cxx_test_SOURCES = \
check-kernel.cpp \
gpiod-cxx-test-main.cpp \
gpiod-cxx-test.cpp \
gpio-mockup.cpp \
gpio-mockup.hpp \
gpiosim.cpp \
gpiosim.hpp \
helpers.cpp \
helpers.hpp \
tests-chip.cpp \
tests-event.cpp \
tests-iter.cpp \
tests-line.cpp
tests-chip-info.cpp \
tests-edge-event.cpp \
tests-info-event.cpp \
tests-line.cpp \
tests-line-config.cpp \
tests-line-info.cpp \
tests-line-request.cpp \
tests-line-settings.cpp \
tests-misc.cpp \
tests-request-config.cpp

View File

@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <linux/version.h>
#include <sys/utsname.h>
#include <system_error>
#include <sstream>
namespace {
class kernel_checker
{
public:
kernel_checker(int major, int minor, int release)
{
int curr_major, curr_minor, curr_release, curr_ver, req_ver;
::std::string major_str, minor_str, release_str;
::utsname un;
int ret;
ret = ::uname(::std::addressof(un));
if (ret)
throw ::std::system_error(errno, ::std::system_category(),
"unable to read the kernel version");
::std::stringstream ver_stream(::std::string(un.release));
::std::getline(ver_stream, major_str, '.');
::std::getline(ver_stream, minor_str, '.');
::std::getline(ver_stream, release_str, '-');
curr_major = ::std::stoi(major_str, nullptr, 0);
curr_minor = ::std::stoi(minor_str, nullptr, 0);
curr_release = ::std::stoi(release_str, nullptr, 0);
curr_ver = KERNEL_VERSION(curr_major, curr_minor, curr_release);
req_ver = KERNEL_VERSION(major, minor, release);
if (curr_ver < req_ver)
throw ::std::runtime_error("kernel release must be at least: " +
::std::to_string(major) + "." +
::std::to_string(minor) + "." +
::std::to_string(release));
}
};
kernel_checker require_kernel(5, 19, 0);
} /* namespace */

View File

@@ -1,153 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
#include <system_error>
#include "gpio-mockup.hpp"
namespace gpiod {
namespace test {
const ::std::bitset<32> mockup::FLAG_NAMED_LINES("1");
mockup& mockup::instance(void)
{
static mockup mockup;
return mockup;
}
mockup::mockup(void) : _m_mockup(::gpio_mockup_new())
{
if (!this->_m_mockup)
throw ::std::system_error(errno, ::std::system_category(),
"unable to create the gpio-mockup context");
}
mockup::~mockup(void)
{
::gpio_mockup_unref(this->_m_mockup);
}
void mockup::probe(const ::std::vector<unsigned int>& chip_sizes,
const ::std::bitset<32>& flags)
{
int ret, probe_flags = 0;
if (flags.to_ulong() & FLAG_NAMED_LINES.to_ulong())
probe_flags |= GPIO_MOCKUP_FLAG_NAMED_LINES;
ret = ::gpio_mockup_probe(this->_m_mockup, chip_sizes.size(),
chip_sizes.data(), probe_flags);
if (ret)
throw ::std::system_error(errno, ::std::system_category(),
"unable to probe gpio-mockup module");
}
void mockup::remove(void)
{
int ret = ::gpio_mockup_remove(this->_m_mockup);
if (ret)
throw ::std::system_error(errno, ::std::system_category(),
"unable to remove gpio-mockup module");
}
::std::string mockup::chip_name(unsigned int idx) const
{
const char *name = ::gpio_mockup_chip_name(this->_m_mockup, idx);
if (!name)
throw ::std::system_error(errno, ::std::system_category(),
"unable to retrieve the chip name");
return ::std::string(name);
}
::std::string mockup::chip_path(unsigned int idx) const
{
const char *path = ::gpio_mockup_chip_path(this->_m_mockup, idx);
if (!path)
throw ::std::system_error(errno, ::std::system_category(),
"unable to retrieve the chip path");
return ::std::string(path);
}
unsigned int mockup::chip_num(unsigned int idx) const
{
int num = ::gpio_mockup_chip_num(this->_m_mockup, idx);
if (num < 0)
throw ::std::system_error(errno, ::std::system_category(),
"unable to retrieve the chip number");
return num;
}
int mockup::chip_get_value(unsigned int chip_idx, unsigned int line_offset)
{
int val = ::gpio_mockup_get_value(this->_m_mockup, chip_idx, line_offset);
if (val < 0)
throw ::std::system_error(errno, ::std::system_category(),
"error reading the line value");
return val;
}
void mockup::chip_set_pull(unsigned int chip_idx, unsigned int line_offset, int pull)
{
int ret = ::gpio_mockup_set_pull(this->_m_mockup, chip_idx, line_offset, pull);
if (ret)
throw ::std::system_error(errno, ::std::system_category(),
"error setting line pull");
}
mockup::probe_guard::probe_guard(const ::std::vector<unsigned int>& chip_sizes,
const ::std::bitset<32>& flags)
{
mockup::instance().probe(chip_sizes, flags);
}
mockup::probe_guard::~probe_guard(void)
{
mockup::instance().remove();
}
mockup::event_thread::event_thread(unsigned int chip_index,
unsigned int line_offset, unsigned int period_ms)
: _m_chip_index(chip_index),
_m_line_offset(line_offset),
_m_period_ms(period_ms),
_m_stop(false),
_m_mutex(),
_m_cond(),
_m_thread(&event_thread::event_worker, this)
{
}
mockup::event_thread::~event_thread(void)
{
::std::unique_lock<::std::mutex> lock(this->_m_mutex);
this->_m_stop = true;
this->_m_cond.notify_all();
lock.unlock();
this->_m_thread.join();
}
void mockup::event_thread::event_worker(void)
{
for (unsigned int i = 0;; i++) {
::std::unique_lock<::std::mutex> lock(this->_m_mutex);
if (this->_m_stop)
break;
::std::cv_status status = this->_m_cond.wait_for(lock,
std::chrono::milliseconds(this->_m_period_ms));
if (status == ::std::cv_status::timeout)
mockup::instance().chip_set_pull(this->_m_chip_index,
this->_m_line_offset, i % 2);
}
}
} /* namespace test */
} /* namespace gpiod */

View File

@@ -1,94 +0,0 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
#ifndef __GPIOD_CXX_TEST_HPP__
#define __GPIOD_CXX_TEST_HPP__
#include <bitset>
#include <condition_variable>
#include <gpio-mockup.h>
#include <mutex>
#include <string>
#include <vector>
#include <thread>
namespace gpiod {
namespace test {
class mockup
{
public:
static mockup& instance(void);
mockup(const mockup& other) = delete;
mockup(mockup&& other) = delete;
mockup& operator=(const mockup& other) = delete;
mockup& operator=(mockup&& other) = delete;
void probe(const ::std::vector<unsigned int>& chip_sizes,
const ::std::bitset<32>& flags = 0);
void remove(void);
std::string chip_name(unsigned int idx) const;
std::string chip_path(unsigned int idx) const;
unsigned int chip_num(unsigned int idx) const;
int chip_get_value(unsigned int chip_idx, unsigned int line_offset);
void chip_set_pull(unsigned int chip_idx, unsigned int line_offset, int pull);
static const ::std::bitset<32> FLAG_NAMED_LINES;
class probe_guard
{
public:
probe_guard(const ::std::vector<unsigned int>& chip_sizes,
const ::std::bitset<32>& flags = 0);
~probe_guard(void);
probe_guard(const probe_guard& other) = delete;
probe_guard(probe_guard&& other) = delete;
probe_guard& operator=(const probe_guard& other) = delete;
probe_guard& operator=(probe_guard&& other) = delete;
};
class event_thread
{
public:
event_thread(unsigned int chip_index, unsigned int line_offset, unsigned int period_ms);
~event_thread(void);
event_thread(const event_thread& other) = delete;
event_thread(event_thread&& other) = delete;
event_thread& operator=(const event_thread& other) = delete;
event_thread& operator=(event_thread&& other) = delete;
private:
void event_worker(void);
unsigned int _m_chip_index;
unsigned int _m_line_offset;
unsigned int _m_period_ms;
bool _m_stop;
::std::mutex _m_mutex;
::std::condition_variable _m_cond;
::std::thread _m_thread;
};
private:
mockup(void);
~mockup(void);
::gpio_mockup *_m_mockup;
};
} /* namespace test */
} /* namespace gpiod */
#endif /* __GPIOD_CXX_TEST_HPP__ */

View File

@@ -1,55 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
#include <linux/version.h>
#include <sys/utsname.h>
#include <system_error>
#include <sstream>
namespace {
class kernel_checker
{
public:
kernel_checker(unsigned int major, unsigned int minor, unsigned int release)
{
unsigned long curr_major, curr_minor, curr_release, curr_ver, req_ver;
::std::string major_str, minor_str, release_str;
::utsname un;
int ret;
ret = ::uname(::std::addressof(un));
if (ret)
throw ::std::system_error(errno, ::std::system_category(),
"unable to read the kernel version");
::std::stringstream ver_stream(::std::string(un.release));
::std::getline(ver_stream, major_str, '.');
::std::getline(ver_stream, minor_str, '.');
::std::getline(ver_stream, release_str, '.');
curr_major = ::std::stoul(major_str, nullptr, 0);
curr_minor = ::std::stoul(minor_str, nullptr, 0);
curr_release = ::std::stoul(release_str, nullptr, 0);
curr_ver = KERNEL_VERSION(curr_major, curr_minor, curr_release);
req_ver = KERNEL_VERSION(major, minor, release);
if (curr_ver < req_ver)
throw ::std::system_error(EOPNOTSUPP, ::std::system_category(),
"kernel release must be at least: " +
::std::to_string(major) + "." +
::std::to_string(minor) + "." +
::std::to_string(release));
}
kernel_checker(const kernel_checker& other) = delete;
kernel_checker(kernel_checker&& other) = delete;
kernel_checker& operator=(const kernel_checker& other) = delete;
kernel_checker& operator=(kernel_checker&& other) = delete;
};
kernel_checker require_kernel(5, 10, 0);
} /* namespace */

View File

@@ -0,0 +1,277 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
#include <map>
#include <system_error>
#include <utility>
#include "gpiosim.h"
#include "gpiosim.hpp"
namespace gpiosim {
namespace {
const ::std::map<chip::pull, int> pull_mapping = {
{ chip::pull::PULL_UP, GPIOSIM_PULL_UP },
{ chip::pull::PULL_DOWN, GPIOSIM_PULL_DOWN }
};
const ::std::map<chip_builder::hog_direction, int> hog_dir_mapping = {
{ chip_builder::hog_direction::INPUT, GPIOSIM_HOG_DIR_INPUT },
{ chip_builder::hog_direction::OUTPUT_HIGH, GPIOSIM_HOG_DIR_OUTPUT_HIGH },
{ chip_builder::hog_direction::OUTPUT_LOW, GPIOSIM_HOG_DIR_OUTPUT_LOW }
};
const ::std::map<int, chip::value> value_mapping = {
{ GPIOSIM_VALUE_INACTIVE, chip::value::INACTIVE },
{ GPIOSIM_VALUE_ACTIVE, chip::value::ACTIVE }
};
template<class gpiosim_type, void free_func(gpiosim_type*)> struct deleter
{
void operator()(gpiosim_type* ptr)
{
free_func(ptr);
}
};
using ctx_deleter = deleter<::gpiosim_ctx, ::gpiosim_ctx_unref>;
using dev_deleter = deleter<::gpiosim_dev, ::gpiosim_dev_unref>;
using bank_deleter = deleter<::gpiosim_bank, ::gpiosim_bank_unref>;
using ctx_ptr = ::std::unique_ptr<::gpiosim_ctx, ctx_deleter>;
using dev_ptr = ::std::unique_ptr<::gpiosim_dev, dev_deleter>;
using bank_ptr = ::std::unique_ptr<::gpiosim_bank, bank_deleter>;
ctx_ptr sim_ctx;
class sim_ctx_initializer
{
public:
sim_ctx_initializer()
{
sim_ctx.reset(gpiosim_ctx_new());
if (!sim_ctx)
throw ::std::system_error(errno, ::std::system_category(),
"unable to create the GPIO simulator context");
}
};
dev_ptr make_sim_dev()
{
static sim_ctx_initializer ctx_initializer;
dev_ptr dev(::gpiosim_dev_new(sim_ctx.get()));
if (!dev)
throw ::std::system_error(errno, ::std::system_category(),
"failed to create a new GPIO simulator device");
return dev;
}
bank_ptr make_sim_bank(const dev_ptr& dev)
{
bank_ptr bank(::gpiosim_bank_new(dev.get()));
if (!bank)
throw ::std::system_error(errno, ::std::system_category(),
"failed to create a new GPIO simulator bank");
return bank;
}
} /* namespace */
struct chip::impl
{
impl()
: dev(make_sim_dev()),
bank(make_sim_bank(this->dev))
{
}
impl(const impl& other) = delete;
impl(impl&& other) = delete;
~impl() = default;
impl& operator=(const impl& other) = delete;
impl& operator=(impl&& other) = delete;
dev_ptr dev;
bank_ptr bank;
};
chip::chip()
: _m_priv(new impl)
{
}
chip::chip(chip&& other)
: _m_priv(::std::move(other._m_priv))
{
}
chip::~chip()
{
}
chip& chip::operator=(chip&& other)
{
this->_m_priv = ::std::move(other._m_priv);
return *this;
}
::std::filesystem::path chip::dev_path() const
{
return ::gpiosim_bank_get_dev_path(this->_m_priv->bank.get());
}
::std::string chip::name() const
{
return ::gpiosim_bank_get_chip_name(this->_m_priv->bank.get());
}
chip::value chip::get_value(unsigned int offset)
{
int val = ::gpiosim_bank_get_value(this->_m_priv->bank.get(), offset);
if (val < 0)
throw ::std::system_error(errno, ::std::system_category(),
"failed to read the simulated GPIO line value");
return value_mapping.at(val);
}
void chip::set_pull(unsigned int offset, pull pull)
{
int ret = ::gpiosim_bank_set_pull(this->_m_priv->bank.get(),
offset, pull_mapping.at(pull));
if (ret)
throw ::std::system_error(errno, ::std::system_category(),
"failed to set the pull of simulated GPIO line");
}
struct chip_builder::impl
{
impl()
: num_lines(0),
label(),
line_names(),
hogs()
{
}
::std::size_t num_lines;
::std::string label;
::std::map<unsigned int, ::std::string> line_names;
::std::map<unsigned int, ::std::pair<::std::string, hog_direction>> hogs;
};
chip_builder::chip_builder()
: _m_priv(new impl)
{
}
chip_builder::chip_builder(chip_builder&& other)
: _m_priv(::std::move(other._m_priv))
{
}
chip_builder::~chip_builder()
{
}
chip_builder& chip_builder::operator=(chip_builder&& other)
{
this->_m_priv = ::std::move(other._m_priv);
return *this;
}
chip_builder& chip_builder::set_num_lines(::std::size_t num_lines)
{
this->_m_priv->num_lines = num_lines;
return *this;
}
chip_builder& chip_builder::set_label(const ::std::string& label)
{
this->_m_priv->label = label;
return *this;
}
chip_builder& chip_builder::set_line_name(unsigned int offset, const ::std::string& name)
{
this->_m_priv->line_names[offset] = name;
return *this;
}
chip_builder& chip_builder::set_hog(unsigned int offset, const ::std::string& name, hog_direction direction)
{
this->_m_priv->hogs[offset] = { name, direction };
return *this;
}
chip chip_builder::build()
{
chip sim;
int ret;
if (this->_m_priv->num_lines) {
ret = ::gpiosim_bank_set_num_lines(sim._m_priv->bank.get(),
this->_m_priv->num_lines);
if (ret)
throw ::std::system_error(errno, ::std::system_category(),
"failed to set number of lines");
}
if (!this->_m_priv->label.empty()) {
ret = ::gpiosim_bank_set_label(sim._m_priv->bank.get(),
this->_m_priv->label.c_str());
if (ret)
throw ::std::system_error(errno, ::std::system_category(),
"failed to set the chip label");
}
for (const auto& name: this->_m_priv->line_names) {
ret = ::gpiosim_bank_set_line_name(sim._m_priv->bank.get(),
name.first, name.second.c_str());
if (ret)
throw ::std::system_error(errno, ::std::system_category(),
"failed to set the line name");
}
for (const auto& hog: this->_m_priv->hogs) {
ret = ::gpiosim_bank_hog_line(sim._m_priv->bank.get(), hog.first,
hog.second.first.c_str(),
hog_dir_mapping.at(hog.second.second));
if (ret)
throw ::std::system_error(errno, ::std::system_category(),
"failed to hog the line");
}
ret = ::gpiosim_dev_enable(sim._m_priv->dev.get());
if (ret)
throw ::std::system_error(errno, ::std::system_category(),
"failed to enable the simulated GPIO device");
return sim;
}
chip_builder make_sim()
{
return chip_builder();
}
} /* namespace gpiosim */

View File

@@ -0,0 +1,86 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
#ifndef __GPIOD_CXX_GPIOSIM_HPP__
#define __GPIOD_CXX_GPIOSIM_HPP__
#include <filesystem>
#include <memory>
namespace gpiosim {
class chip_builder;
class chip
{
public:
enum class pull {
PULL_UP = 1,
PULL_DOWN
};
enum class value {
INACTIVE = 0,
ACTIVE = 1
};
chip(const chip& other) = delete;
chip(chip&& other);
~chip();
chip& operator=(const chip& other) = delete;
chip& operator=(chip&& other);
::std::filesystem::path dev_path() const;
::std::string name() const;
value get_value(unsigned int offset);
void set_pull(unsigned int offset, pull pull);
private:
chip();
struct impl;
::std::unique_ptr<impl> _m_priv;
friend chip_builder;
};
class chip_builder
{
public:
enum class hog_direction {
INPUT = 1,
OUTPUT_HIGH,
OUTPUT_LOW
};
chip_builder();
chip_builder(const chip_builder& other) = delete;
chip_builder(chip_builder&& other);
~chip_builder();
chip_builder& operator=(const chip_builder& other) = delete;
chip_builder& operator=(chip_builder&& other);
chip_builder& set_num_lines(::std::size_t num_lines);
chip_builder& set_label(const ::std::string& label);
chip_builder& set_line_name(unsigned int offset, const ::std::string& name);
chip_builder& set_hog(unsigned int offset, const ::std::string& name, hog_direction direction);
chip build();
private:
struct impl;
::std::unique_ptr<impl> _m_priv;
};
chip_builder make_sim();
} /* namespace gpiosim */
#endif /* __GPIOD_CXX_GPIOSIM_HPP__ */

View File

@@ -0,0 +1,37 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include "helpers.hpp"
system_error_matcher::system_error_matcher(int expected_errno)
: _m_cond(::std::system_category().default_error_condition(expected_errno))
{
}
::std::string system_error_matcher::describe() const
{
return "matches: errno " + ::std::to_string(this->_m_cond.value());
}
bool system_error_matcher::match(const ::std::system_error& error) const
{
return error.code().value() == this->_m_cond.value();
}
regex_matcher::regex_matcher(const ::std::string& pattern)
: _m_pattern(pattern),
_m_repr("matches: regex \"" + pattern + "\"")
{
}
::std::string regex_matcher::describe() const
{
return this->_m_repr;
}
bool regex_matcher::match(const ::std::string& str) const
{
return ::std::regex_match(str, this->_m_pattern);
}

View File

@@ -0,0 +1,62 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl> */
#ifndef __GPIOD_CXX_TEST_HELPERS_HPP__
#define __GPIOD_CXX_TEST_HELPERS_HPP__
#include <catch2/catch.hpp>
#include <regex>
#include <string>
#include <sstream>
#include <system_error>
class system_error_matcher : public Catch::MatcherBase<::std::system_error>
{
public:
explicit system_error_matcher(int expected_errno);
::std::string describe() const override;
bool match(const ::std::system_error& error) const override;
private:
::std::error_condition _m_cond;
};
class regex_matcher : public Catch::MatcherBase<::std::string>
{
public:
explicit regex_matcher(const ::std::string& pattern);
::std::string describe() const override;
bool match(const ::std::string& str) const override;
private:
::std::regex _m_pattern;
::std::string _m_repr;
};
template<class T> class stringify_matcher : public Catch::MatcherBase<T>
{
public:
explicit stringify_matcher(const ::std::string& expected) : _m_expected(expected)
{
}
::std::string describe() const override
{
return "equals " + this->_m_expected;
}
bool match(const T& obj) const override
{
::std::stringstream buf;
buf << obj;
return buf.str() == this->_m_expected;
}
private:
::std::string _m_expected;
};
#endif /* __GPIOD_CXX_TEST_HELPERS_HPP__ */

View File

@@ -0,0 +1,117 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <gpiod.hpp>
#include <sstream>
#include "gpiosim.hpp"
#include "helpers.hpp"
using ::gpiosim::make_sim;
namespace {
TEST_CASE("chip_info properties can be read", "[chip-info][chip]")
{
auto sim = make_sim()
.set_num_lines(8)
.set_label("foobar")
.build();
::gpiod::chip chip(sim.dev_path());
auto info = chip.get_info();
SECTION("get chip name")
{
REQUIRE_THAT(info.name(), Catch::Equals(sim.name()));
}
SECTION("get chip label")
{
REQUIRE_THAT(info.label(), Catch::Equals("foobar"));
}
SECTION("get num_lines")
{
REQUIRE(info.num_lines() == 8);
}
}
TEST_CASE("chip_info can be copied and moved", "[chip-info]")
{
auto sim = make_sim()
.set_num_lines(4)
.set_label("foobar")
.build();
::gpiod::chip chip(sim.dev_path());
auto info = chip.get_info();
SECTION("copy constructor works")
{
auto copy(info);
REQUIRE_THAT(copy.name(), Catch::Equals(sim.name()));
REQUIRE_THAT(copy.label(), Catch::Equals("foobar"));
REQUIRE(copy.num_lines() == 4);
REQUIRE_THAT(info.name(), Catch::Equals(sim.name()));
REQUIRE_THAT(info.label(), Catch::Equals("foobar"));
REQUIRE(info.num_lines() == 4);
}
SECTION("assignment operator works")
{
auto copy = chip.get_info();
copy = info;
REQUIRE_THAT(copy.name(), Catch::Equals(sim.name()));
REQUIRE_THAT(copy.label(), Catch::Equals("foobar"));
REQUIRE(copy.num_lines() == 4);
REQUIRE_THAT(info.name(), Catch::Equals(sim.name()));
REQUIRE_THAT(info.label(), Catch::Equals("foobar"));
REQUIRE(info.num_lines() == 4);
}
SECTION("move constructor works")
{
auto moved(std::move(info));
REQUIRE_THAT(moved.name(), Catch::Equals(sim.name()));
REQUIRE_THAT(moved.label(), Catch::Equals("foobar"));
REQUIRE(moved.num_lines() == 4);
}
SECTION("move assignment operator works")
{
auto moved = chip.get_info();
moved = ::std::move(info);
REQUIRE_THAT(moved.name(), Catch::Equals(sim.name()));
REQUIRE_THAT(moved.label(), Catch::Equals("foobar"));
REQUIRE(moved.num_lines() == 4);
}
}
TEST_CASE("stream insertion operator works for chip_info", "[chip-info]")
{
auto sim = make_sim()
.set_num_lines(4)
.set_label("foobar")
.build();
::gpiod::chip chip(sim.dev_path());
auto info = chip.get_info();
::std::stringstream expected;
expected << "gpiod::chip_info(name=\"" << sim.name() <<
"\", label=\"foobar\", num_lines=4)";
REQUIRE_THAT(info, stringify_matcher<::gpiod::chip_info>(expected.str()));
}
} /* namespace */

View File

@@ -1,173 +1,182 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <gpiod.hpp>
#include <sstream>
#include <system_error>
#include <utility>
#include "gpio-mockup.hpp"
#include "gpiosim.hpp"
#include "helpers.hpp"
using ::gpiod::test::mockup;
using ::gpiosim::make_sim;
TEST_CASE("GPIO chip device can be verified with is_gpiochip_device()", "[chip]")
namespace {
TEST_CASE("chip constructor works", "[chip]")
{
mockup::probe_guard mockup_chips({ 8 });
SECTION("good chip")
SECTION("open an existing GPIO chip")
{
REQUIRE(::gpiod::is_gpiochip_device(mockup::instance().chip_path(0)));
auto sim = make_sim().build();
REQUIRE_NOTHROW(::gpiod::chip(sim.dev_path()));
}
SECTION("not a chip")
SECTION("opening a nonexistent file fails with ENOENT")
{
REQUIRE_FALSE(::gpiod::is_gpiochip_device("/dev/null"));
REQUIRE_THROWS_MATCHES(::gpiod::chip("/dev/nonexistent"),
::std::system_error, system_error_matcher(ENOENT));
}
SECTION("nonexistent file")
SECTION("opening a file that is not a device fails with ENOTTY")
{
REQUIRE_FALSE(::gpiod::is_gpiochip_device("/dev/nonexistent_device"));
REQUIRE_THROWS_MATCHES(::gpiod::chip("/tmp"),
::std::system_error, system_error_matcher(ENOTTY));
}
SECTION("opening a non-GPIO character device fails with ENODEV")
{
REQUIRE_THROWS_MATCHES(::gpiod::chip("/dev/null"),
::std::system_error, system_error_matcher(ENODEV));
}
SECTION("move constructor")
{
auto sim = make_sim()
.set_label("foobar")
.build();
::gpiod::chip first(sim.dev_path());
REQUIRE_THAT(first.get_info().label(), Catch::Equals("foobar"));
::gpiod::chip second(::std::move(first));
REQUIRE_THAT(second.get_info().label(), Catch::Equals("foobar"));
}
}
TEST_CASE("GPIO chip device can be opened", "[chip]")
TEST_CASE("chip operators work", "[chip]")
{
mockup::probe_guard mockup_chips({ 8, 8, 8 });
auto sim = make_sim()
.set_label("foobar")
.build();
SECTION("open from constructor")
::gpiod::chip chip(sim.dev_path());
SECTION("assignment operator")
{
REQUIRE_NOTHROW(::gpiod::chip(mockup::instance().chip_path(1)));
auto moved_sim = make_sim()
.set_label("moved")
.build();
::gpiod::chip moved_chip(moved_sim.dev_path());
REQUIRE_THAT(chip.get_info().label(), Catch::Equals("foobar"));
chip = ::std::move(moved_chip);
REQUIRE_THAT(chip.get_info().label(), Catch::Equals("moved"));
}
SECTION("open from open() method")
SECTION("boolean operator")
{
::gpiod::chip chip;
REQUIRE_FALSE(!!chip);
REQUIRE_NOTHROW(chip.open(mockup::instance().chip_path(1)));
REQUIRE(chip);
chip.close();
REQUIRE_FALSE(chip);
}
}
TEST_CASE("Uninitialized GPIO chip behaves correctly", "[chip]")
TEST_CASE("chip properties can be read", "[chip]")
{
::gpiod::chip chip;
auto sim = make_sim()
.set_num_lines(8)
.set_label("foobar")
.build();
SECTION("uninitialized chip is 'false'")
::gpiod::chip chip(sim.dev_path());
SECTION("get device path")
{
REQUIRE_FALSE(!!chip);
REQUIRE_THAT(chip.path(), Catch::Equals(sim.dev_path()));
}
SECTION("using uninitialized chip throws logic_error")
SECTION("get file descriptor")
{
REQUIRE_THROWS_AS(chip.name(), ::std::logic_error);
REQUIRE(chip.fd() >= 0);
}
}
TEST_CASE("Trying to open a nonexistent chip throws system_error", "[chip]")
TEST_CASE("line lookup by name works", "[chip]")
{
REQUIRE_THROWS_AS(::gpiod::chip("nonexistent-chip"), ::std::system_error);
}
auto sim = make_sim()
.set_num_lines(8)
.set_line_name(0, "foo")
.set_line_name(2, "bar")
.set_line_name(3, "baz")
.set_line_name(5, "xyz")
.build();
TEST_CASE("Chip object can be reset", "[chip]")
{
mockup::probe_guard mockup_chips({ 8 });
::gpiod::chip chip(sim.dev_path());
::gpiod::chip chip(mockup::instance().chip_path(0));
REQUIRE(chip);
chip.reset();
REQUIRE_FALSE(!!chip);
}
TEST_CASE("Chip info can be correctly retrieved", "[chip]")
{
mockup::probe_guard mockup_chips({ 8, 16, 8 });
::gpiod::chip chip(mockup::instance().chip_path(1));
REQUIRE(chip.name() == mockup::instance().chip_name(1));
REQUIRE(chip.label() == "gpio-mockup-B");
REQUIRE(chip.num_lines() == 16);
}
TEST_CASE("Chip object can be copied and compared", "[chip]")
{
mockup::probe_guard mockup_chips({ 8, 8 });
::gpiod::chip chip1(mockup::instance().chip_path(0));
auto chip2 = chip1;
REQUIRE(chip1 == chip2);
REQUIRE_FALSE(chip1 != chip2);
::gpiod::chip chip3(mockup::instance().chip_path(1));
REQUIRE(chip1 != chip3);
REQUIRE_FALSE(chip2 == chip3);
}
TEST_CASE("Lines can be retrieved from chip objects", "[chip]")
{
mockup::probe_guard mockup_chips({ 8, 8, 8 }, mockup::FLAG_NAMED_LINES);
::gpiod::chip chip(mockup::instance().chip_path(1));
SECTION("get single line by offset")
SECTION("lookup successful")
{
auto line = chip.get_line(3);
REQUIRE(line.name() == "gpio-mockup-B-3");
REQUIRE(chip.get_line_offset_from_name("baz") == 3);
}
SECTION("find single line by name")
SECTION("lookup failed")
{
auto offset = chip.find_line("gpio-mockup-B-3");
REQUIRE(offset == 3);
}
SECTION("get multiple lines by offsets")
{
auto lines = chip.get_lines({ 1, 3, 4, 7});
REQUIRE(lines.size() == 4);
REQUIRE(lines.get(0).name() == "gpio-mockup-B-1");
REQUIRE(lines.get(1).name() == "gpio-mockup-B-3");
REQUIRE(lines.get(2).name() == "gpio-mockup-B-4");
REQUIRE(lines.get(3).name() == "gpio-mockup-B-7");
}
SECTION("get multiple lines by offsets in mixed order")
{
auto lines = chip.get_lines({ 5, 1, 3, 2});
REQUIRE(lines.size() == 4);
REQUIRE(lines.get(0).name() == "gpio-mockup-B-5");
REQUIRE(lines.get(1).name() == "gpio-mockup-B-1");
REQUIRE(lines.get(2).name() == "gpio-mockup-B-3");
REQUIRE(lines.get(3).name() == "gpio-mockup-B-2");
REQUIRE(chip.get_line_offset_from_name("nonexistent") < 0);
}
}
TEST_CASE("All lines can be retrieved from a chip at once", "[chip]")
TEST_CASE("line lookup: behavior for duplicate names", "[chip]")
{
mockup::probe_guard mockup_chips({ 4 });
::gpiod::chip chip(mockup::instance().chip_path(0));
auto sim = make_sim()
.set_num_lines(8)
.set_line_name(0, "foo")
.set_line_name(2, "bar")
.set_line_name(3, "baz")
.set_line_name(5, "bar")
.build();
auto lines = chip.get_all_lines();
REQUIRE(lines.size() == 4);
REQUIRE(lines.get(0).offset() == 0);
REQUIRE(lines.get(1).offset() == 1);
REQUIRE(lines.get(2).offset() == 2);
REQUIRE(lines.get(3).offset() == 3);
::gpiod::chip chip(sim.dev_path());
REQUIRE(chip.get_line_offset_from_name("bar") == 2);
}
TEST_CASE("Errors occurring when retrieving lines are correctly reported", "[chip]")
TEST_CASE("closed chip can no longer be used", "[chip]")
{
mockup::probe_guard mockup_chips({ 8 }, mockup::FLAG_NAMED_LINES);
::gpiod::chip chip(mockup::instance().chip_path(0));
auto sim = make_sim().build();
SECTION("invalid offset (single line)")
::gpiod::chip chip(sim.dev_path());
chip.close();
REQUIRE_THROWS_AS(chip.path(), ::gpiod::chip_closed);
}
TEST_CASE("stream insertion operator works for chip", "[chip]")
{
auto sim = make_sim()
.set_num_lines(4)
.set_label("foobar")
.build();
::gpiod::chip chip(sim.dev_path());
::std::stringstream buf;
SECTION("open chip")
{
REQUIRE_THROWS_AS(chip.get_line(9), ::std::out_of_range);
::std::stringstream expected;
expected << "gpiod::chip(path=" << sim.dev_path() <<
", info=gpiod::chip_info(name=\"" << sim.name() <<
"\", label=\"foobar\", num_lines=4))";
buf << chip;
REQUIRE_THAT(buf.str(), Catch::Equals(expected.str()));
}
SECTION("invalid offset (multiple lines)")
SECTION("closed chip")
{
REQUIRE_THROWS_AS(chip.get_lines({ 1, 19, 4, 7 }), ::std::out_of_range);
}
SECTION("line not found by name")
{
REQUIRE(chip.find_line("nonexistent-line") == -1);
chip.close();
REQUIRE_THAT(chip, stringify_matcher<::gpiod::chip>("gpiod::chip(closed)"));
}
}
} /* namespace */

View File

@@ -0,0 +1,422 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <chrono>
#include <gpiod.hpp>
#include <sstream>
#include <thread>
#include <utility>
#include "gpiosim.hpp"
#include "helpers.hpp"
using ::gpiosim::make_sim;
using direction = ::gpiod::line::direction;
using edge = ::gpiod::line::edge;
using offsets = ::gpiod::line::offsets;
using pull = ::gpiosim::chip::pull;
using event_type = ::gpiod::edge_event::event_type;
namespace {
TEST_CASE("edge_event_buffer capacity settings work", "[edge-event]")
{
SECTION("default capacity")
{
REQUIRE(::gpiod::edge_event_buffer().capacity() == 64);
}
SECTION("user-defined capacity")
{
REQUIRE(::gpiod::edge_event_buffer(123).capacity() == 123);
}
SECTION("max capacity")
{
REQUIRE(::gpiod::edge_event_buffer(16 * 64 * 2).capacity() == 1024);
}
}
TEST_CASE("edge_event wait timeout", "[edge-event]")
{
auto sim = make_sim().build();
::gpiod::chip chip(sim.dev_path());
auto request = chip.prepare_request()
.add_line_settings(
0,
::gpiod::line_settings()
.set_edge_detection(edge::BOTH)
)
.do_request();
REQUIRE_FALSE(request.wait_edge_event(::std::chrono::milliseconds(100)));
}
TEST_CASE("output mode and edge detection don't work together", "[edge-event]")
{
auto sim = make_sim().build();
REQUIRE_THROWS_AS(
::gpiod::chip(sim.dev_path())
.prepare_request()
.add_line_settings(
0,
::gpiod::line_settings()
.set_direction(direction::OUTPUT)
.set_edge_detection(edge::BOTH)
)
.do_request(),
::std::invalid_argument
);
}
void trigger_falling_and_rising_edge(::gpiosim::chip& sim, unsigned int offset)
{
::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
sim.set_pull(offset, pull::PULL_UP);
::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
sim.set_pull(offset, pull::PULL_DOWN);
}
void trigger_rising_edge_events_on_two_offsets(::gpiosim::chip& sim,
unsigned int off0, unsigned int off1)
{
::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
sim.set_pull(off0, pull::PULL_UP);
::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
sim.set_pull(off1, pull::PULL_UP);
}
TEST_CASE("waiting for and reading edge events works", "[edge-event]")
{
auto sim = make_sim()
.set_num_lines(8)
.build();
::gpiod::chip chip(sim.dev_path());
::gpiod::edge_event_buffer buffer;
SECTION("both edge events")
{
auto request = chip
.prepare_request()
.add_line_settings(
2,
::gpiod::line_settings()
.set_edge_detection(edge::BOTH)
)
.do_request();
::std::uint64_t ts_rising, ts_falling;
::std::thread thread(trigger_falling_and_rising_edge, ::std::ref(sim), 2);
REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
REQUIRE(request.read_edge_event(buffer, 1) == 1);
REQUIRE(buffer.num_events() == 1);
auto event = buffer.get_event(0);
REQUIRE(event.type() == event_type::RISING_EDGE);
REQUIRE(event.line_offset() == 2);
ts_rising = event.timestamp_ns();
REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
REQUIRE(request.read_edge_event(buffer, 1) == 1);
REQUIRE(buffer.num_events() == 1);
event = buffer.get_event(0);
REQUIRE(event.type() == event_type::FALLING_EDGE);
REQUIRE(event.line_offset() == 2);
ts_falling = event.timestamp_ns();
REQUIRE_FALSE(request.wait_edge_event(::std::chrono::milliseconds(100)));
thread.join();
REQUIRE(ts_falling > ts_rising);
}
SECTION("rising edge event")
{
auto request = chip
.prepare_request()
.add_line_settings(
6,
::gpiod::line_settings()
.set_edge_detection(edge::RISING)
)
.do_request();
::std::thread thread(trigger_falling_and_rising_edge, ::std::ref(sim), 6);
REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
REQUIRE(request.read_edge_event(buffer, 1) == 1);
REQUIRE(buffer.num_events() == 1);
auto event = buffer.get_event(0);
REQUIRE(event.type() == event_type::RISING_EDGE);
REQUIRE(event.line_offset() == 6);
REQUIRE_FALSE(request.wait_edge_event(::std::chrono::milliseconds(100)));
thread.join();
}
SECTION("falling edge event")
{
auto request = chip
.prepare_request()
.add_line_settings(
7,
::gpiod::line_settings()
.set_edge_detection(edge::FALLING)
)
.do_request();
::std::thread thread(trigger_falling_and_rising_edge, ::std::ref(sim), 7);
REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
REQUIRE(request.read_edge_event(buffer, 1) == 1);
REQUIRE(buffer.num_events() == 1);
auto event = buffer.get_event(0);
REQUIRE(event.type() == event_type::FALLING_EDGE);
REQUIRE(event.line_offset() == 7);
REQUIRE_FALSE(request.wait_edge_event(::std::chrono::milliseconds(100)));
thread.join();
}
SECTION("sequence numbers")
{
auto request = chip
.prepare_request()
.add_line_settings(
{ 0, 1 },
::gpiod::line_settings()
.set_edge_detection(edge::BOTH)
)
.do_request();
::std::thread thread(trigger_rising_edge_events_on_two_offsets, ::std::ref(sim), 0, 1);
REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
REQUIRE(request.read_edge_event(buffer, 1) == 1);
REQUIRE(buffer.num_events() == 1);
auto event = buffer.get_event(0);
REQUIRE(event.type() == event_type::RISING_EDGE);
REQUIRE(event.line_offset() == 0);
REQUIRE(event.global_seqno() == 1);
REQUIRE(event.line_seqno() == 1);
REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
REQUIRE(request.read_edge_event(buffer, 1) == 1);
REQUIRE(buffer.num_events() == 1);
event = buffer.get_event(0);
REQUIRE(event.type() == event_type::RISING_EDGE);
REQUIRE(event.line_offset() == 1);
REQUIRE(event.global_seqno() == 2);
REQUIRE(event.line_seqno() == 1);
thread.join();
}
}
TEST_CASE("reading multiple events", "[edge-event]")
{
auto sim = make_sim()
.set_num_lines(8)
.build();
::gpiod::chip chip(sim.dev_path());
auto request = chip
.prepare_request()
.add_line_settings(
1,
::gpiod::line_settings()
.set_edge_detection(edge::BOTH)
)
.do_request();
unsigned long line_seqno = 1, global_seqno = 1;
sim.set_pull(1, pull::PULL_UP);
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
sim.set_pull(1, pull::PULL_DOWN);
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
sim.set_pull(1, pull::PULL_UP);
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
SECTION("read multiple events")
{
::gpiod::edge_event_buffer buffer;
REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
REQUIRE(request.read_edge_event(buffer) == 3);
REQUIRE(buffer.num_events() == 3);
for (const auto& event: buffer) {
REQUIRE(event.line_offset() == 1);
REQUIRE(event.line_seqno() == line_seqno++);
REQUIRE(event.global_seqno() == global_seqno++);
}
}
SECTION("read over capacity")
{
::gpiod::edge_event_buffer buffer(2);
REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
REQUIRE(request.read_edge_event(buffer) == 2);
REQUIRE(buffer.num_events() == 2);
}
}
TEST_CASE("edge_event_buffer can be moved", "[edge-event]")
{
auto sim = make_sim()
.set_num_lines(2)
.build();
::gpiod::chip chip(sim.dev_path());
::gpiod::edge_event_buffer buffer(13);
/* Get some events into the buffer. */
auto request = chip
.prepare_request()
.add_line_settings(
1,
::gpiod::line_settings()
.set_edge_detection(edge::BOTH)
)
.do_request();
sim.set_pull(1, pull::PULL_UP);
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
sim.set_pull(1, pull::PULL_DOWN);
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
sim.set_pull(1, pull::PULL_UP);
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
::std::this_thread::sleep_for(::std::chrono::milliseconds(500));
REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
REQUIRE(request.read_edge_event(buffer) == 3);
SECTION("move constructor works")
{
auto moved(::std::move(buffer));
REQUIRE(moved.capacity() == 13);
REQUIRE(moved.num_events() == 3);
}
SECTION("move assignment operator works")
{
::gpiod::edge_event_buffer moved;
moved = ::std::move(buffer);
REQUIRE(moved.capacity() == 13);
REQUIRE(moved.num_events() == 3);
}
}
TEST_CASE("edge_event can be copied and moved", "[edge-event]")
{
auto sim = make_sim().build();
::gpiod::chip chip(sim.dev_path());
::gpiod::edge_event_buffer buffer;
auto request = chip
.prepare_request()
.add_line_settings(
0,
::gpiod::line_settings()
.set_edge_detection(edge::BOTH)
)
.do_request();
sim.set_pull(0, pull::PULL_UP);
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
REQUIRE(request.read_edge_event(buffer) == 1);
auto event = buffer.get_event(0);
sim.set_pull(0, pull::PULL_DOWN);
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
REQUIRE(request.read_edge_event(buffer) == 1);
auto copy = buffer.get_event(0);
SECTION("copy constructor works")
{
auto copy(event);
REQUIRE(copy.line_offset() == 0);
REQUIRE(copy.type() == event_type::RISING_EDGE);
REQUIRE(event.line_offset() == 0);
REQUIRE(event.type() == event_type::RISING_EDGE);
}
SECTION("move constructor works")
{
auto copy(::std::move(event));
REQUIRE(copy.line_offset() == 0);
REQUIRE(copy.type() == event_type::RISING_EDGE);
}
SECTION("assignment operator works")
{
copy = event;
REQUIRE(copy.line_offset() == 0);
REQUIRE(copy.type() == event_type::RISING_EDGE);
REQUIRE(event.line_offset() == 0);
REQUIRE(event.type() == event_type::RISING_EDGE);
}
SECTION("move assignment operator works")
{
copy = ::std::move(event);
REQUIRE(copy.line_offset() == 0);
REQUIRE(copy.type() == event_type::RISING_EDGE);
}
}
TEST_CASE("stream insertion operators work for edge_event and edge_event_buffer", "[edge-event]")
{
/*
* This tests the stream insertion operators for both edge_event and
* edge_event_buffer classes.
*/
auto sim = make_sim().build();
::gpiod::chip chip(sim.dev_path());
::gpiod::edge_event_buffer buffer;
::std::stringstream sbuf, expected;
auto request = chip
.prepare_request()
.add_line_settings(
0,
::gpiod::line_settings()
.set_edge_detection(edge::BOTH)
)
.do_request();
sim.set_pull(0, pull::PULL_UP);
::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
sim.set_pull(0, pull::PULL_DOWN);
::std::this_thread::sleep_for(::std::chrono::milliseconds(30));
REQUIRE(request.wait_edge_event(::std::chrono::seconds(1)));
REQUIRE(request.read_edge_event(buffer) == 2);
sbuf << buffer;
expected << "gpiod::edge_event_buffer\\(num_events=2, capacity=64, events=\\[gpiod::edge_event\\" <<
"(type='RISING_EDGE', timestamp=[1-9][0-9]+, line_offset=0, global_seqno=1, " <<
"line_seqno=1\\), gpiod::edge_event\\(type='FALLING_EDGE', timestamp=[1-9][0-9]+, " <<
"line_offset=0, global_seqno=2, line_seqno=2\\)\\]\\)";
REQUIRE_THAT(sbuf.str(), regex_matcher(expected.str()));
}
} /* namespace */

View File

@@ -1,280 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
#include <catch2/catch.hpp>
#include <gpiod.hpp>
#include <poll.h>
#include <thread>
#include "gpio-mockup.hpp"
using ::gpiod::test::mockup;
namespace {
const ::std::string consumer = "event-test";
} /* namespace */
TEST_CASE("Line events can be detected", "[event][line]")
{
mockup::probe_guard mockup_chips({ 8 });
mockup::event_thread events(0, 4, 200);
::gpiod::chip chip(mockup::instance().chip_path(0));
auto line = chip.get_line(4);
::gpiod::line_request config;
config.consumer = consumer.c_str();
SECTION("rising edge")
{
config.request_type = ::gpiod::line_request::EVENT_RISING_EDGE;
line.request(config);
auto got_event = line.event_wait(::std::chrono::seconds(1));
REQUIRE(got_event);
auto event = line.event_read();
REQUIRE(event.source == line);
REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
}
SECTION("falling edge")
{
config.request_type = ::gpiod::line_request::EVENT_FALLING_EDGE;
line.request(config);
auto got_event = line.event_wait(::std::chrono::seconds(1));
REQUIRE(got_event);
auto event = line.event_read();
REQUIRE(event.source == line);
REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE);
}
SECTION("both edges")
{
config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
line.request(config);
auto got_event = line.event_wait(::std::chrono::seconds(1));
REQUIRE(got_event);
auto event = line.event_read();
REQUIRE(event.source == line);
REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
got_event = line.event_wait(::std::chrono::seconds(1));
REQUIRE(got_event);
event = line.event_read();
REQUIRE(event.source == line);
REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE);
}
SECTION("active-low")
{
config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;
line.request(config);
auto got_event = line.event_wait(::std::chrono::seconds(1));
REQUIRE(got_event);
auto event = line.event_read();
REQUIRE(event.source == line);
REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE);
got_event = line.event_wait(::std::chrono::seconds(1));
REQUIRE(got_event);
event = line.event_read();
REQUIRE(event.source == line);
REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
}
}
TEST_CASE("Watching line_bulk objects for events works", "[event][bulk]")
{
mockup::probe_guard mockup_chips({ 8 });
mockup::event_thread events(0, 2, 200);
::gpiod::chip chip(mockup::instance().chip_path(0));
auto lines = chip.get_lines({ 0, 1, 2, 3 });
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
lines.request(config);
auto event_lines = lines.event_wait(::std::chrono::seconds(1));
REQUIRE(event_lines);
REQUIRE(event_lines.size() == 1);
auto event = event_lines.get(0).event_read();
REQUIRE(event.source == event_lines.get(0));
REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
event_lines = lines.event_wait(::std::chrono::seconds(1));
REQUIRE(event_lines);
REQUIRE(event_lines.size() == 1);
event = event_lines.get(0).event_read();
REQUIRE(event.source == event_lines.get(0));
REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE);
}
TEST_CASE("It's possible to retrieve the event file descriptor", "[event][line]")
{
mockup::probe_guard mockup_chips({ 8 });
::gpiod::chip chip(mockup::instance().chip_path(0));
auto line = chip.get_line(4);
::gpiod::line_request config;
config.consumer = consumer.c_str();
SECTION("get the fd")
{
config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
line.request(config);
REQUIRE(line.event_get_fd() >= 0);
}
SECTION("error if not requested")
{
REQUIRE_THROWS_AS(line.event_get_fd(), ::std::system_error);
}
SECTION("error if requested for values")
{
config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
line.request(config);
REQUIRE_THROWS_AS(line.event_get_fd(), ::std::system_error);
}
}
TEST_CASE("Event file descriptors can be used for polling", "[event]")
{
mockup::probe_guard mockup_chips({ 8 });
mockup::event_thread events(0, 3, 200);
::gpiod::chip chip(mockup::instance().chip_path(0));
auto lines = chip.get_lines({ 0, 1, 2, 3, 4, 5 });
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
lines.request(config);
::std::vector<::pollfd> fds(3);
fds[0].fd = lines[1].event_get_fd();
fds[1].fd = lines[3].event_get_fd();
fds[2].fd = lines[5].event_get_fd();
fds[0].events = fds[1].events = fds[2].events = POLLIN | POLLPRI;
int ret = ::poll(fds.data(), 3, 1000);
REQUIRE(ret == 1);
for (int i = 0; i < 3; i++) {
if (fds[i].revents) {
auto event = lines[3].event_read();
REQUIRE(event.source == lines[3]);
REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
}
}
}
TEST_CASE("It's possible to read a value from a line requested for events", "[event][line]")
{
mockup::probe_guard mockup_chips({ 8 });
::gpiod::chip chip(mockup::instance().chip_path(0));
auto line = chip.get_line(4);
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
mockup::instance().chip_set_pull(0, 4, 1);
SECTION("active-high (default)")
{
line.request(config);
REQUIRE(line.get_value() == 1);
}
SECTION("active-low")
{
config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;
line.request(config);
REQUIRE(line.get_value() == 0);
}
}
TEST_CASE("It's possible to read values from lines requested for events", "[event][bulk]")
{
mockup::probe_guard mockup_chips({ 8 });
::gpiod::chip chip(mockup::instance().chip_path(0));
auto lines = chip.get_lines({ 0, 1, 2, 3, 4 });
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
mockup::instance().chip_set_pull(0, 5, 1);
SECTION("active-high (default)")
{
lines.request(config);
REQUIRE(lines.get_values() == ::std::vector<int>({ 0, 0, 0, 0, 0 }));
mockup::instance().chip_set_pull(0, 1, 1);
mockup::instance().chip_set_pull(0, 3, 1);
mockup::instance().chip_set_pull(0, 4, 1);
REQUIRE(lines.get_values() == ::std::vector<int>({ 0, 1, 0, 1, 1 }));
}
SECTION("active-low")
{
config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;
lines.request(config);
REQUIRE(lines.get_values() == ::std::vector<int>({ 1, 1, 1, 1, 1 }));
mockup::instance().chip_set_pull(0, 1, 1);
mockup::instance().chip_set_pull(0, 3, 1);
mockup::instance().chip_set_pull(0, 4, 1);
REQUIRE(lines.get_values() == ::std::vector<int>({ 1, 0, 1, 0, 0 }));
}
}
TEST_CASE("It's possible to read more than one line event", "[event][line]")
{
mockup::probe_guard mockup_chips({ 8 });
::gpiod::chip chip(mockup::instance().chip_path(0));
auto line = chip.get_line(4);
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
line.request(config);
mockup::instance().chip_set_pull(0, 4, 1);
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
mockup::instance().chip_set_pull(0, 4, 0);
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
mockup::instance().chip_set_pull(0, 4, 1);
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
auto events = line.event_read_multiple();
REQUIRE(events.size() == 3);
REQUIRE(events.at(0).event_type == ::gpiod::line_event::RISING_EDGE);
REQUIRE(events.at(1).event_type == ::gpiod::line_event::FALLING_EDGE);
REQUIRE(events.at(2).event_type == ::gpiod::line_event::RISING_EDGE);
REQUIRE(events.at(0).source == line);
REQUIRE(events.at(1).source == line);
REQUIRE(events.at(2).source == line);
}

View File

@@ -0,0 +1,196 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <chrono>
#include <gpiod.hpp>
#include <sstream>
#include <thread>
#include <utility>
#include "gpiosim.hpp"
#include "helpers.hpp"
using ::gpiosim::make_sim;
using direction = ::gpiod::line::direction;
using event_type = ::gpiod::info_event::event_type;
namespace {
void request_reconfigure_release_line(::gpiod::chip& chip)
{
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
auto request = chip
.prepare_request()
.add_line_settings(7, ::gpiod::line_settings())
.do_request();
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
request.reconfigure_lines(
::gpiod::line_config()
.add_line_settings(
7,
::gpiod::line_settings()
.set_direction(direction::OUTPUT)
)
);
::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
request.release();
}
TEST_CASE("Lines can be watched", "[info-event][chip]")
{
auto sim = make_sim()
.set_num_lines(8)
.build();
::gpiod::chip chip(sim.dev_path());
SECTION("watch_line_info() returns line info")
{
auto info = chip.watch_line_info(7);
REQUIRE(info.offset() == 7);
}
SECTION("watch_line_info() fails for offset out of range")
{
REQUIRE_THROWS_AS(chip.watch_line_info(8), ::std::invalid_argument);
}
SECTION("waiting for event timeout")
{
chip.watch_line_info(3);
REQUIRE_FALSE(chip.wait_info_event(::std::chrono::milliseconds(100)));
}
SECTION("request-reconfigure-release events")
{
auto info = chip.watch_line_info(7);
::std::uint64_t ts_req, ts_rec, ts_rel;
REQUIRE(info.direction() == direction::INPUT);
::std::thread thread(request_reconfigure_release_line, ::std::ref(chip));
REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
auto event = chip.read_info_event();
REQUIRE(event.type() == event_type::LINE_REQUESTED);
REQUIRE(event.get_line_info().direction() == direction::INPUT);
ts_req = event.timestamp_ns();
REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
event = chip.read_info_event();
REQUIRE(event.type() == event_type::LINE_CONFIG_CHANGED);
REQUIRE(event.get_line_info().direction() == direction::OUTPUT);
ts_rec = event.timestamp_ns();
REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
event = chip.read_info_event();
REQUIRE(event.type() == event_type::LINE_RELEASED);
ts_rel = event.timestamp_ns();
/* No more events. */
REQUIRE_FALSE(chip.wait_info_event(::std::chrono::milliseconds(100)));
thread.join();
/* Check timestamps are really monotonic. */
REQUIRE(ts_rel > ts_rec);
REQUIRE(ts_rec > ts_req);
}
}
TEST_CASE("info_event can be copied and moved", "[info-event]")
{
auto sim = make_sim().build();
::gpiod::chip chip(sim.dev_path());
::std::stringstream buf, expected;
chip.watch_line_info(0);
auto request = chip
.prepare_request()
.add_line_settings(0, ::gpiod::line_settings())
.do_request();
REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
auto event = chip.read_info_event();
request.release();
REQUIRE(chip.wait_info_event(::std::chrono::seconds(1)));
auto copy = chip.read_info_event();
SECTION("copy constructor works")
{
auto copy(event);
REQUIRE(copy.type() == event_type::LINE_REQUESTED);
REQUIRE(copy.get_line_info().offset() == 0);
REQUIRE(event.type() == event_type::LINE_REQUESTED);
REQUIRE(event.get_line_info().offset() == 0);
}
SECTION("assignment operator works")
{
copy = event;
REQUIRE(copy.type() == event_type::LINE_REQUESTED);
REQUIRE(copy.get_line_info().offset() == 0);
REQUIRE(event.type() == event_type::LINE_REQUESTED);
REQUIRE(event.get_line_info().offset() == 0);
}
SECTION("move constructor works")
{
auto copy(::std::move(event));
REQUIRE(copy.type() == event_type::LINE_REQUESTED);
REQUIRE(copy.get_line_info().offset() == 0);
}
SECTION("move assignment operator works")
{
copy = ::std::move(event);
REQUIRE(copy.type() == event_type::LINE_REQUESTED);
REQUIRE(copy.get_line_info().offset() == 0);
}
}
TEST_CASE("info_event stream insertion operator works", "[info-event][line-info]")
{
/*
* This tests the stream insertion operator for both the info_event
* and line_info classes.
*/
auto sim = make_sim().build();
::gpiod::chip chip(sim.dev_path());
::std::stringstream buf, expected;
chip.watch_line_info(0);
auto request = chip
.prepare_request()
.add_line_settings(0, ::gpiod::line_settings())
.do_request();
auto event = chip.read_info_event();
buf << event;
expected << "gpiod::info_event\\(event_type='LINE_REQUESTED', timestamp=[1-9][0-9]+, " <<
"line_info=gpiod::line_info\\(offset=0, name=unnamed, used=true, consumer='', " <<
"direction=INPUT, active_low=false, bias=UNKNOWN, drive=PUSH_PULL, " <<
"edge_detection=NONE, event_clock=MONOTONIC, debounced=false\\)\\)";
REQUIRE_THAT(buf.str(), regex_matcher(expected.str()));
}
} /* namespace */

View File

@@ -1,21 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
#include <catch2/catch.hpp>
#include <gpiod.hpp>
#include "gpio-mockup.hpp"
using ::gpiod::test::mockup;
TEST_CASE("Line iterator works", "[iter][line]")
{
mockup::probe_guard mockup_chips({ 4 });
::gpiod::chip chip(mockup::instance().chip_path(0));
unsigned int count = 0;
for (auto& it: ::gpiod::line_iter(chip))
REQUIRE(it.offset() == count++);
REQUIRE(count == chip.num_lines());
}

View File

@@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <gpiod.hpp>
#include "helpers.hpp"
using namespace ::std::chrono_literals;
using direction = ::gpiod::line::direction;
using drive = ::gpiod::line::drive;
using edge = ::gpiod::line::edge;
namespace {
TEST_CASE("line_config constructor works", "[line-config]")
{
SECTION("no arguments - default values")
{
::gpiod::line_config cfg;
REQUIRE(cfg.get_line_settings().size() == 0);
}
}
TEST_CASE("adding line_settings to line_config works", "[line-config][line-settings]")
{
::gpiod::line_config cfg;
cfg.add_line_settings(4,
::gpiod::line_settings()
.set_direction(direction::INPUT)
.set_edge_detection(edge::RISING));
cfg.add_line_settings({7, 2},
::gpiod::line_settings()
.set_direction(direction::OUTPUT)
.set_drive(drive::OPEN_DRAIN));
auto settings = cfg.get_line_settings();
REQUIRE(settings.size() == 3);
REQUIRE(settings.at(2).direction() == direction::OUTPUT);
REQUIRE(settings.at(2).drive() == drive::OPEN_DRAIN);
REQUIRE(settings.at(4).direction() == direction::INPUT);
REQUIRE(settings.at(4).edge_detection() == edge::RISING);
REQUIRE(settings.at(7).direction() == direction::OUTPUT);
REQUIRE(settings.at(7).drive() == drive::OPEN_DRAIN);
}
TEST_CASE("line_config can be reset", "[line-config]")
{
::gpiod::line_config cfg;
cfg.add_line_settings({3, 4, 7},
::gpiod::line_settings()
.set_direction(direction::INPUT)
.set_edge_detection(edge::BOTH));
auto settings = cfg.get_line_settings();
REQUIRE(settings.size() == 3);
REQUIRE(settings.at(3).direction() == direction::INPUT);
REQUIRE(settings.at(3).edge_detection() == edge::BOTH);
REQUIRE(settings.at(4).direction() == direction::INPUT);
REQUIRE(settings.at(4).edge_detection() == edge::BOTH);
REQUIRE(settings.at(7).direction() == direction::INPUT);
REQUIRE(settings.at(7).edge_detection() == edge::BOTH);
cfg.reset();
REQUIRE(cfg.get_line_settings().size() == 0);
}
TEST_CASE("line_config stream insertion operator works", "[line-config]")
{
::gpiod::line_config cfg;
SECTION("empty config")
{
REQUIRE_THAT(cfg, stringify_matcher<::gpiod::line_config>(
"gpiod::line_config(num_settings=0)"));
}
SECTION("config with settings")
{
cfg.add_line_settings({0, 2},
::gpiod::line_settings()
.set_direction(direction::OUTPUT)
.set_drive(drive::OPEN_SOURCE)
);
REQUIRE_THAT(cfg, stringify_matcher<::gpiod::line_config>(
"gpiod::line_config(num_settings=2, "
"settings=[0: gpiod::line_settings(direction=OUTPUT, edge_detection=NONE, "
"bias=AS_IS, drive=OPEN_SOURCE, active-high, debounce_period=0, "
"event_clock=MONOTONIC, output_value=INACTIVE), "
"2: gpiod::line_settings(direction=OUTPUT, edge_detection=NONE, bias=AS_IS, "
"drive=OPEN_SOURCE, active-high, debounce_period=0, event_clock=MONOTONIC, "
"output_value=INACTIVE)])"));
}
}
} /* namespace */

View File

@@ -0,0 +1,154 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <gpiod.hpp>
#include <string>
#include "helpers.hpp"
#include "gpiosim.hpp"
using ::gpiosim::make_sim;
using hog_dir = ::gpiosim::chip_builder::hog_direction;
using direction = ::gpiod::line::direction;
using edge = ::gpiod::line::edge;
using bias = ::gpiod::line::bias;
using drive = ::gpiod::line::drive;
using event_clock = ::gpiod::line::clock;
using namespace ::std::chrono_literals;
namespace {
TEST_CASE("get_line_info() works", "[chip][line-info]")
{
auto sim = make_sim()
.set_num_lines(8)
.set_line_name(0, "foobar")
.set_hog(0, "hog", hog_dir::OUTPUT_HIGH)
.build();
::gpiod::chip chip(sim.dev_path());
SECTION("line_info can be retrieved from chip")
{
auto info = chip.get_line_info(0);
REQUIRE(info.offset() == 0);
REQUIRE_THAT(info.name(), Catch::Equals("foobar"));
REQUIRE(info.used());
REQUIRE_THAT(info.consumer(), Catch::Equals("hog"));
REQUIRE(info.direction() == ::gpiod::line::direction::OUTPUT);
REQUIRE_FALSE(info.active_low());
REQUIRE(info.bias() == ::gpiod::line::bias::UNKNOWN);
REQUIRE(info.drive() == ::gpiod::line::drive::PUSH_PULL);
REQUIRE(info.edge_detection() == ::gpiod::line::edge::NONE);
REQUIRE(info.event_clock() == ::gpiod::line::clock::MONOTONIC);
REQUIRE_FALSE(info.debounced());
REQUIRE(info.debounce_period() == 0us);
}
SECTION("offset out of range")
{
REQUIRE_THROWS_AS(chip.get_line_info(8), ::std::invalid_argument);
}
}
TEST_CASE("line properties can be retrieved", "[line-info]")
{
auto sim = make_sim()
.set_num_lines(8)
.set_line_name(1, "foo")
.set_line_name(2, "bar")
.set_line_name(4, "baz")
.set_line_name(5, "xyz")
.set_hog(3, "hog3", hog_dir::OUTPUT_HIGH)
.set_hog(4, "hog4", hog_dir::OUTPUT_LOW)
.build();
::gpiod::chip chip(sim.dev_path());
SECTION("basic properties")
{
auto info4 = chip.get_line_info(4);
auto info6 = chip.get_line_info(6);
REQUIRE(info4.offset() == 4);
REQUIRE_THAT(info4.name(), Catch::Equals("baz"));
REQUIRE(info4.used());
REQUIRE_THAT(info4.consumer(), Catch::Equals("hog4"));
REQUIRE(info4.direction() == direction::OUTPUT);
REQUIRE(info4.edge_detection() == edge::NONE);
REQUIRE_FALSE(info4.active_low());
REQUIRE(info4.bias() == bias::UNKNOWN);
REQUIRE(info4.drive() == drive::PUSH_PULL);
REQUIRE(info4.event_clock() == event_clock::MONOTONIC);
REQUIRE_FALSE(info4.debounced());
REQUIRE(info4.debounce_period() == 0us);
}
}
TEST_CASE("line_info can be copied and moved")
{
auto sim = make_sim()
.set_num_lines(4)
.set_line_name(2, "foobar")
.build();
::gpiod::chip chip(sim.dev_path());
auto info = chip.get_line_info(2);
SECTION("copy constructor works")
{
auto copy(info);
REQUIRE(copy.offset() == 2);
REQUIRE_THAT(copy.name(), Catch::Equals("foobar"));
/* info can still be used */
REQUIRE(info.offset() == 2);
REQUIRE_THAT(info.name(), Catch::Equals("foobar"));
}
SECTION("assignment operator works")
{
auto copy = chip.get_line_info(0);
copy = info;
REQUIRE(copy.offset() == 2);
REQUIRE_THAT(copy.name(), Catch::Equals("foobar"));
/* info can still be used */
REQUIRE(info.offset() == 2);
REQUIRE_THAT(info.name(), Catch::Equals("foobar"));
}
SECTION("move constructor works")
{
auto copy(::std::move(info));
REQUIRE(copy.offset() == 2);
REQUIRE_THAT(copy.name(), Catch::Equals("foobar"));
}
SECTION("move assignment operator works")
{
auto copy = chip.get_line_info(0);
copy = ::std::move(info);
REQUIRE(copy.offset() == 2);
REQUIRE_THAT(copy.name(), Catch::Equals("foobar"));
}
}
TEST_CASE("line_info stream insertion operator works")
{
auto sim = make_sim()
.set_line_name(0, "foo")
.set_hog(0, "hogger", hog_dir::OUTPUT_HIGH)
.build();
::gpiod::chip chip(sim.dev_path());
auto info = chip.get_line_info(0);
REQUIRE_THAT(info, stringify_matcher<::gpiod::line_info>(
"gpiod::line_info(offset=0, name='foo', used=true, consumer='foo', direction=OUTPUT, "
"active_low=false, bias=UNKNOWN, drive=PUSH_PULL, edge_detection=NONE, event_clock=MONOTONIC, debounced=false)"));
}
} /* namespace */

View File

@@ -0,0 +1,498 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <gpiod.hpp>
#include <sstream>
#include <stdexcept>
#include <vector>
#include "gpiosim.hpp"
#include "helpers.hpp"
using ::gpiosim::make_sim;
using offsets = ::gpiod::line::offsets;
using values = ::gpiod::line::values;
using direction = ::gpiod::line::direction;
using value = ::gpiod::line::value;
using simval = ::gpiosim::chip::value;
using pull = ::gpiosim::chip::pull;
namespace {
class value_matcher : public Catch::MatcherBase<value>
{
public:
value_matcher(pull pull, bool active_low = false)
: _m_pull(pull),
_m_active_low(active_low)
{
}
::std::string describe() const override
{
::std::string repr(this->_m_pull == pull::PULL_UP ? "PULL_UP" : "PULL_DOWN");
::std::string active_low = this->_m_active_low ? "(active-low) " : "";
return active_low + "corresponds with " + repr;
}
bool match(const value& val) const override
{
if (this->_m_active_low) {
if ((val == value::ACTIVE && this->_m_pull == pull::PULL_DOWN) ||
(val == value::INACTIVE && this->_m_pull == pull::PULL_UP))
return true;
} else {
if ((val == value::ACTIVE && this->_m_pull == pull::PULL_UP) ||
(val == value::INACTIVE && this->_m_pull == pull::PULL_DOWN))
return true;
}
return false;
}
private:
pull _m_pull;
bool _m_active_low;
};
TEST_CASE("requesting lines behaves correctly with invalid arguments", "[line-request][chip]")
{
auto sim = make_sim()
.set_num_lines(8)
.build();
::gpiod::chip chip(sim.dev_path());
SECTION("no offsets")
{
REQUIRE_THROWS_AS(chip.prepare_request().do_request(), ::std::invalid_argument);
}
SECTION("duplicate offsets")
{
auto request = chip
.prepare_request()
.add_line_settings({ 2, 0, 0, 4 }, ::gpiod::line_settings())
.do_request();
auto offsets = request.offsets();
REQUIRE(offsets.size() == 3);
REQUIRE(offsets[0] == 2);
REQUIRE(offsets[1] == 0);
REQUIRE(offsets[2] == 4);
}
SECTION("offset out of bounds")
{
REQUIRE_THROWS_AS(chip
.prepare_request()
.add_line_settings({ 2, 0, 8, 4 }, ::gpiod::line_settings())
.do_request(),
::std::invalid_argument
);
}
}
TEST_CASE("consumer string is set correctly", "[line-request]")
{
auto sim = make_sim()
.set_num_lines(4)
.build();
::gpiod::chip chip(sim.dev_path());
offsets offs({ 3, 0, 2 });
SECTION("set custom consumer")
{
auto request = chip
.prepare_request()
.add_line_settings(offs, ::gpiod::line_settings())
.set_consumer("foobar")
.do_request();
auto info = chip.get_line_info(2);
REQUIRE(info.used());
REQUIRE_THAT(info.consumer(), Catch::Equals("foobar"));
}
SECTION("empty consumer")
{
auto request = chip
.prepare_request()
.add_line_settings(2, ::gpiod::line_settings())
.do_request();
auto info = chip.get_line_info(2);
REQUIRE(info.used());
REQUIRE_THAT(info.consumer(), Catch::Equals("?"));
}
}
TEST_CASE("values can be read", "[line-request]")
{
auto sim = make_sim()
.set_num_lines(8)
.build();
const offsets offs({ 7, 1, 0, 6, 2 });
const ::std::vector<pull> pulls({
pull::PULL_UP,
pull::PULL_UP,
pull::PULL_DOWN,
pull::PULL_UP,
pull::PULL_DOWN
});
for (unsigned int i = 0; i < offs.size(); i++)
sim.set_pull(offs[i], pulls[i]);
auto request = ::gpiod::chip(sim.dev_path())
.prepare_request()
.add_line_settings(
offs,
::gpiod::line_settings()
.set_direction(direction::INPUT)
)
.do_request();
SECTION("get all values (returning variant)")
{
auto vals = request.get_values();
REQUIRE_THAT(vals[0], value_matcher(pull::PULL_UP));
REQUIRE_THAT(vals[1], value_matcher(pull::PULL_UP));
REQUIRE_THAT(vals[2], value_matcher(pull::PULL_DOWN));
REQUIRE_THAT(vals[3], value_matcher(pull::PULL_UP));
REQUIRE_THAT(vals[4], value_matcher(pull::PULL_DOWN));
}
SECTION("get all values (passed buffer variant)")
{
values vals(5);
request.get_values(vals);
REQUIRE_THAT(vals[0], value_matcher(pull::PULL_UP));
REQUIRE_THAT(vals[1], value_matcher(pull::PULL_UP));
REQUIRE_THAT(vals[2], value_matcher(pull::PULL_DOWN));
REQUIRE_THAT(vals[3], value_matcher(pull::PULL_UP));
REQUIRE_THAT(vals[4], value_matcher(pull::PULL_DOWN));
}
SECTION("get_values(buffer) throws for invalid buffer size")
{
values vals(4);
REQUIRE_THROWS_AS(request.get_values(vals), ::std::invalid_argument);
vals.resize(6);
REQUIRE_THROWS_AS(request.get_values(vals), ::std::invalid_argument);
}
SECTION("get a single value")
{
auto val = request.get_value(7);
REQUIRE_THAT(val, value_matcher(pull::PULL_UP));
}
SECTION("get a single value (active-low)")
{
request.reconfigure_lines(
::gpiod::line_config()
.add_line_settings(
offs,
::gpiod::line_settings()
.set_active_low(true))
);
auto val = request.get_value(7);
REQUIRE_THAT(val, value_matcher(pull::PULL_UP, true));
}
SECTION("get a subset of values (returning variant)")
{
auto vals = request.get_values(offsets({ 2, 0, 6 }));
REQUIRE_THAT(vals[0], value_matcher(pull::PULL_DOWN));
REQUIRE_THAT(vals[1], value_matcher(pull::PULL_DOWN));
REQUIRE_THAT(vals[2], value_matcher(pull::PULL_UP));
}
SECTION("get a subset of values (passed buffer variant)")
{
values vals(3);
request.get_values(offsets({ 2, 0, 6 }), vals);
REQUIRE_THAT(vals[0], value_matcher(pull::PULL_DOWN));
REQUIRE_THAT(vals[1], value_matcher(pull::PULL_DOWN));
REQUIRE_THAT(vals[2], value_matcher(pull::PULL_UP));
}
}
TEST_CASE("output values can be set at request time", "[line-request]")
{
auto sim = make_sim()
.set_num_lines(8)
.build();
::gpiod::chip chip(sim.dev_path());
const offsets offs({ 0, 1, 3, 4 });
::gpiod::line_settings settings;
settings.set_direction(direction::OUTPUT);
settings.set_output_value(value::ACTIVE);
::gpiod::line_config line_cfg;
line_cfg.add_line_settings(offs, settings);
SECTION("default output value")
{
auto request = chip
.prepare_request()
.set_line_config(line_cfg)
.do_request();
for (const auto& off: offs)
REQUIRE(sim.get_value(off) == simval::ACTIVE);
REQUIRE(sim.get_value(2) == simval::INACTIVE);
}
SECTION("overridden output value")
{
settings.set_output_value(value::INACTIVE);
line_cfg.add_line_settings(1, settings);
auto request = chip
.prepare_request()
.set_line_config(line_cfg)
.do_request();
REQUIRE(sim.get_value(0) == simval::ACTIVE);
REQUIRE(sim.get_value(1) == simval::INACTIVE);
REQUIRE(sim.get_value(2) == simval::INACTIVE);
REQUIRE(sim.get_value(3) == simval::ACTIVE);
REQUIRE(sim.get_value(4) == simval::ACTIVE);
}
}
TEST_CASE("values can be set after requesting lines", "[line-request]")
{
auto sim = make_sim()
.set_num_lines(8)
.build();
const offsets offs({ 0, 1, 3, 4 });
auto request = ::gpiod::chip(sim.dev_path())
.prepare_request()
.add_line_settings(
offs,
::gpiod::line_settings()
.set_direction(direction::OUTPUT)
)
.do_request();
SECTION("set single value")
{
request.set_value(1, value::ACTIVE);
REQUIRE(sim.get_value(0) == simval::INACTIVE);
REQUIRE(sim.get_value(1) == simval::ACTIVE);
REQUIRE(sim.get_value(3) == simval::INACTIVE);
REQUIRE(sim.get_value(4) == simval::INACTIVE);
}
SECTION("set all values")
{
request.set_values({
value::ACTIVE,
value::INACTIVE,
value::ACTIVE,
value::INACTIVE
});
REQUIRE(sim.get_value(0) == simval::ACTIVE);
REQUIRE(sim.get_value(1) == simval::INACTIVE);
REQUIRE(sim.get_value(3) == simval::ACTIVE);
REQUIRE(sim.get_value(4) == simval::INACTIVE);
}
SECTION("set a subset of values")
{
request.set_values({ 4, 3 }, { value::ACTIVE, value::INACTIVE });
REQUIRE(sim.get_value(0) == simval::INACTIVE);
REQUIRE(sim.get_value(1) == simval::INACTIVE);
REQUIRE(sim.get_value(3) == simval::INACTIVE);
REQUIRE(sim.get_value(4) == simval::ACTIVE);
}
SECTION("set a subset of values with mappings")
{
request.set_values({
{ 0, value::ACTIVE },
{ 4, value::INACTIVE },
{ 1, value::ACTIVE}
});
REQUIRE(sim.get_value(0) == simval::ACTIVE);
REQUIRE(sim.get_value(1) == simval::ACTIVE);
REQUIRE(sim.get_value(3) == simval::INACTIVE);
REQUIRE(sim.get_value(4) == simval::INACTIVE);
}
}
TEST_CASE("line_request can be moved", "[line-request]")
{
auto sim = make_sim()
.set_num_lines(8)
.build();
::gpiod::chip chip(sim.dev_path());
const offsets offs({ 3, 1, 0, 2 });
auto request = chip
.prepare_request()
.add_line_settings(
offs,
::gpiod::line_settings()
)
.do_request();
auto fd = request.fd();
auto another = chip
.prepare_request()
.add_line_settings(6, ::gpiod::line_settings())
.do_request();
SECTION("move constructor works")
{
auto moved(::std::move(request));
REQUIRE(moved.fd() == fd);
REQUIRE_THAT(moved.offsets(), Catch::Equals(offs));
}
SECTION("move assignment operator works")
{
another = ::std::move(request);
REQUIRE(another.fd() == fd);
REQUIRE_THAT(another.offsets(), Catch::Equals(offs));
}
}
TEST_CASE("released request can no longer be used", "[line-request]")
{
auto sim = make_sim().build();
auto request = ::gpiod::chip(sim.dev_path())
.prepare_request()
.add_line_settings(0, ::gpiod::line_settings())
.do_request();
request.release();
REQUIRE_THROWS_AS(request.offsets(), ::gpiod::request_released);
}
TEST_CASE("line_request survives parent chip", "[line-request][chip]")
{
auto sim = make_sim().build();
sim.set_pull(0, pull::PULL_UP);
SECTION("chip is released")
{
::gpiod::chip chip(sim.dev_path());
auto request = chip
.prepare_request()
.add_line_settings(
0,
::gpiod::line_settings()
.set_direction(direction::INPUT)
)
.do_request();
REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
chip.close();
REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
}
SECTION("chip goes out of scope")
{
/* Need to get the request object somehow. */
::gpiod::chip dummy(sim.dev_path());
::gpiod::line_config cfg;
cfg.add_line_settings(0, ::gpiod::line_settings().set_direction(direction::INPUT));
auto request = dummy
.prepare_request()
.set_line_config(cfg)
.do_request();
request.release();
dummy.close();
{
::gpiod::chip chip(sim.dev_path());
request = chip
.prepare_request()
.set_line_config(cfg)
.do_request();
REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
}
REQUIRE_THAT(request.get_value(0), value_matcher(pull::PULL_UP));
}
}
TEST_CASE("line_request stream insertion operator works", "[line-request]")
{
auto sim = make_sim()
.set_num_lines(4)
.build();
auto request = ::gpiod::chip(sim.dev_path())
.prepare_request()
.add_line_settings({ 3, 1, 0, 2}, ::gpiod::line_settings())
.do_request();
::std::stringstream buf, expected;
expected << "gpiod::line_request(num_lines=4, line_offsets=gpiod::offsets(3, 1, 0, 2), fd=" <<
request.fd() << ")";
SECTION("active request")
{
buf << request;
REQUIRE_THAT(buf.str(), Catch::Equals(expected.str()));
}
SECTION("request released")
{
request.release();
buf << request;
REQUIRE_THAT(buf.str(), Catch::Equals("gpiod::line_request(released)"));
}
}
} /* namespace */

View File

@@ -0,0 +1,145 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <gpiod.hpp>
#include "helpers.hpp"
using value = ::gpiod::line::value;
using direction = ::gpiod::line::direction;
using edge = ::gpiod::line::edge;
using bias = ::gpiod::line::bias;
using drive = ::gpiod::line::drive;
using clock_type = ::gpiod::line::clock;
using value = ::gpiod::line::value;
using namespace ::std::chrono_literals;
namespace {
TEST_CASE("line_settings constructor works", "[line-settings]")
{
::gpiod::line_settings settings;
REQUIRE(settings.direction() == direction::AS_IS);
REQUIRE(settings.edge_detection() == edge::NONE);
REQUIRE(settings.bias() == bias::AS_IS);
REQUIRE(settings.drive() == drive::PUSH_PULL);
REQUIRE_FALSE(settings.active_low());
REQUIRE(settings.debounce_period() == 0us);
REQUIRE(settings.event_clock() == clock_type::MONOTONIC);
REQUIRE(settings.output_value() == value::INACTIVE);
}
TEST_CASE("line_settings mutators work", "[line-settings]")
{
::gpiod::line_settings settings;
SECTION("direction")
{
settings.set_direction(direction::INPUT);
REQUIRE(settings.direction() == direction::INPUT);
settings.set_direction(direction::AS_IS);
REQUIRE(settings.direction() == direction::AS_IS);
settings.set_direction(direction::OUTPUT);
REQUIRE(settings.direction() == direction::OUTPUT);
REQUIRE_THROWS_AS(settings.set_direction(static_cast<direction>(999)),
::std::invalid_argument);
}
SECTION("edge detection")
{
settings.set_edge_detection(edge::BOTH);
REQUIRE(settings.edge_detection() == edge::BOTH);
settings.set_edge_detection(edge::NONE);
REQUIRE(settings.edge_detection() == edge::NONE);
settings.set_edge_detection(edge::FALLING);
REQUIRE(settings.edge_detection() == edge::FALLING);
settings.set_edge_detection(edge::RISING);
REQUIRE(settings.edge_detection() == edge::RISING);
REQUIRE_THROWS_AS(settings.set_edge_detection(static_cast<edge>(999)),
::std::invalid_argument);
}
SECTION("bias")
{
settings.set_bias(bias::DISABLED);
REQUIRE(settings.bias() == bias::DISABLED);
settings.set_bias(bias::AS_IS);
REQUIRE(settings.bias() == bias::AS_IS);
settings.set_bias(bias::PULL_DOWN);
REQUIRE(settings.bias() == bias::PULL_DOWN);
settings.set_bias(bias::PULL_UP);
REQUIRE(settings.bias() == bias::PULL_UP);
REQUIRE_THROWS_AS(settings.set_bias(static_cast<bias>(999)), ::std::invalid_argument);
REQUIRE_THROWS_AS(settings.set_bias(bias::UNKNOWN), ::std::invalid_argument);
}
SECTION("drive")
{
settings.set_drive(drive::OPEN_DRAIN);
REQUIRE(settings.drive() == drive::OPEN_DRAIN);
settings.set_drive(drive::PUSH_PULL);
REQUIRE(settings.drive() == drive::PUSH_PULL);
settings.set_drive(drive::OPEN_SOURCE);
REQUIRE(settings.drive() == drive::OPEN_SOURCE);
REQUIRE_THROWS_AS(settings.set_drive(static_cast<drive>(999)), ::std::invalid_argument);
}
SECTION("active-low")
{
settings.set_active_low(true);
REQUIRE(settings.active_low());
settings.set_active_low(false);
REQUIRE_FALSE(settings.active_low());
}
SECTION("debounce period")
{
settings.set_debounce_period(2000us);
REQUIRE(settings.debounce_period() == 2000us);
}
SECTION("event clock")
{
settings.set_event_clock(clock_type::REALTIME);
REQUIRE(settings.event_clock() == clock_type::REALTIME);
settings.set_event_clock(clock_type::MONOTONIC);
REQUIRE(settings.event_clock() == clock_type::MONOTONIC);
settings.set_event_clock(clock_type::HTE);
REQUIRE(settings.event_clock() == clock_type::HTE);
REQUIRE_THROWS_AS(settings.set_event_clock(static_cast<clock_type>(999)),
::std::invalid_argument);
}
SECTION("output value")
{
settings.set_output_value(value::ACTIVE);
REQUIRE(settings.output_value() == value::ACTIVE);
settings.set_output_value(value::INACTIVE);
REQUIRE(settings.output_value() == value::INACTIVE);
REQUIRE_THROWS_AS(settings.set_output_value(static_cast<value>(999)),
::std::invalid_argument);
}
}
TEST_CASE("line_settings stream insertion operator works", "[line-settings]")
{
::gpiod::line_settings settings;
REQUIRE_THAT(settings
.set_active_low(true)
.set_direction(direction::INPUT)
.set_edge_detection(edge::BOTH)
.set_bias(bias::PULL_DOWN)
.set_event_clock(clock_type::REALTIME),
stringify_matcher<::gpiod::line_settings>(
"gpiod::line_settings(direction=INPUT, edge_detection=BOTH_EDGES, "
"bias=PULL_DOWN, drive=PUSH_PULL, active-low, debounce_period=0, "
"event_clock=REALTIME, output_value=INACTIVE)"
)
);
}
} /* namespace */

View File

@@ -1,467 +1,139 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <gpiod.hpp>
#include "gpio-mockup.hpp"
#include "helpers.hpp"
using ::gpiod::test::mockup;
using offset = ::gpiod::line::offset;
using value = ::gpiod::line::value;
using direction = ::gpiod::line::direction;
using edge = ::gpiod::line::edge;
using bias = ::gpiod::line::bias;
using drive = ::gpiod::line::drive;
using clock_type = ::gpiod::line::clock;
using offsets = ::gpiod::line::offsets;
using values = ::gpiod::line::values;
using value_mapping = ::gpiod::line::value_mapping;
using value_mappings = ::gpiod::line::value_mappings;
namespace {
const ::std::string consumer = "line-test";
TEST_CASE("stream insertion operators for types in gpiod::line work", "[line]")
{
SECTION("offset")
{
offset off = 4;
REQUIRE_THAT(off, stringify_matcher<offset>("4"));
}
SECTION("value")
{
auto active = value::ACTIVE;
auto inactive = value::INACTIVE;
REQUIRE_THAT(active, stringify_matcher<value>("ACTIVE"));
REQUIRE_THAT(inactive, stringify_matcher<value>("INACTIVE"));
}
SECTION("direction")
{
auto input = direction::INPUT;
auto output = direction::OUTPUT;
auto as_is = direction::AS_IS;
REQUIRE_THAT(input, stringify_matcher<direction>("INPUT"));
REQUIRE_THAT(output, stringify_matcher<direction>("OUTPUT"));
REQUIRE_THAT(as_is, stringify_matcher<direction>("AS_IS"));
}
SECTION("edge")
{
auto rising = edge::RISING;
auto falling = edge::FALLING;
auto both = edge::BOTH;
auto none = edge::NONE;
REQUIRE_THAT(rising, stringify_matcher<edge>("RISING_EDGE"));
REQUIRE_THAT(falling, stringify_matcher<edge>("FALLING_EDGE"));
REQUIRE_THAT(both, stringify_matcher<edge>("BOTH_EDGES"));
REQUIRE_THAT(none, stringify_matcher<edge>("NONE"));
}
SECTION("bias")
{
auto pull_up = bias::PULL_UP;
auto pull_down = bias::PULL_DOWN;
auto disabled = bias::DISABLED;
auto as_is = bias::AS_IS;
auto unknown = bias::UNKNOWN;
REQUIRE_THAT(pull_up, stringify_matcher<bias>("PULL_UP"));
REQUIRE_THAT(pull_down, stringify_matcher<bias>("PULL_DOWN"));
REQUIRE_THAT(disabled, stringify_matcher<bias>("DISABLED"));
REQUIRE_THAT(as_is, stringify_matcher<bias>("AS_IS"));
REQUIRE_THAT(unknown, stringify_matcher<bias>("UNKNOWN"));
}
SECTION("drive")
{
auto push_pull = drive::PUSH_PULL;
auto open_drain = drive::OPEN_DRAIN;
auto open_source = drive::OPEN_SOURCE;
REQUIRE_THAT(push_pull, stringify_matcher<drive>("PUSH_PULL"));
REQUIRE_THAT(open_drain, stringify_matcher<drive>("OPEN_DRAIN"));
REQUIRE_THAT(open_source, stringify_matcher<drive>("OPEN_SOURCE"));
}
SECTION("clock")
{
auto monotonic = clock_type::MONOTONIC;
auto realtime = clock_type::REALTIME;
auto hte = clock_type::HTE;
REQUIRE_THAT(monotonic, stringify_matcher<clock_type>("MONOTONIC"));
REQUIRE_THAT(realtime, stringify_matcher<clock_type>("REALTIME"));
REQUIRE_THAT(hte, stringify_matcher<clock_type>("HTE"));
}
SECTION("offsets")
{
offsets offs = { 2, 5, 3, 9, 8, 7 };
REQUIRE_THAT(offs, stringify_matcher<offsets>("gpiod::offsets(2, 5, 3, 9, 8, 7)"));
}
SECTION("values")
{
values vals = {
value::ACTIVE,
value::INACTIVE,
value::ACTIVE,
value::ACTIVE,
value::INACTIVE
};
REQUIRE_THAT(vals,
stringify_matcher<values>("gpiod::values(ACTIVE, INACTIVE, ACTIVE, ACTIVE, INACTIVE)"));
}
SECTION("value_mapping")
{
value_mapping val = { 4, value::ACTIVE };
REQUIRE_THAT(val, stringify_matcher<value_mapping>("gpiod::value_mapping(4: ACTIVE)"));
}
SECTION("value_mappings")
{
value_mappings vals = { { 0, value::ACTIVE }, { 4, value::INACTIVE }, { 8, value::ACTIVE } };
REQUIRE_THAT(vals, stringify_matcher<value_mappings>(
"gpiod::value_mappings(gpiod::value_mapping(0: ACTIVE), gpiod::value_mapping(4: INACTIVE), gpiod::value_mapping(8: ACTIVE))"));
}
}
} /* namespace */
TEST_CASE("Line information can be correctly retrieved", "[line]")
{
mockup::probe_guard mockup_chips({ 8 }, mockup::FLAG_NAMED_LINES);
::gpiod::chip chip(mockup::instance().chip_path(0));
auto line = chip.get_line(4);
SECTION("unexported line")
{
REQUIRE(line.offset() == 4);
REQUIRE(line.name() == "gpio-mockup-A-4");
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_INPUT);
REQUIRE_FALSE(line.is_active_low());
REQUIRE(line.consumer().empty());
REQUIRE_FALSE(line.is_used());
REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
REQUIRE(line.bias() == ::gpiod::line::BIAS_UNKNOWN);
}
SECTION("exported line")
{
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
line.request(config);
REQUIRE(line.offset() == 4);
REQUIRE(line.name() == "gpio-mockup-A-4");
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE_FALSE(line.is_active_low());
REQUIRE(line.is_used());
REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
REQUIRE(line.bias() == ::gpiod::line::BIAS_UNKNOWN);
}
SECTION("exported line with flags")
{
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW |
::gpiod::line_request::FLAG_OPEN_DRAIN;
line.request(config);
REQUIRE(line.offset() == 4);
REQUIRE(line.name() == "gpio-mockup-A-4");
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE(line.is_active_low());
REQUIRE(line.is_used());
REQUIRE(line.drive() == ::gpiod::line::DRIVE_OPEN_DRAIN);
REQUIRE(line.bias() == ::gpiod::line::BIAS_UNKNOWN);
}
SECTION("exported open source line")
{
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
config.flags = ::gpiod::line_request::FLAG_OPEN_SOURCE;
line.request(config);
REQUIRE(line.offset() == 4);
REQUIRE(line.name() == "gpio-mockup-A-4");
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE_FALSE(line.is_active_low());
REQUIRE(line.is_used());
REQUIRE(line.drive() == ::gpiod::line::DRIVE_OPEN_SOURCE);
REQUIRE(line.bias() == ::gpiod::line::BIAS_UNKNOWN);
}
SECTION("exported bias disable line")
{
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
config.flags = ::gpiod::line_request::FLAG_BIAS_DISABLED;
line.request(config);
REQUIRE(line.offset() == 4);
REQUIRE(line.name() == "gpio-mockup-A-4");
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE_FALSE(line.is_active_low());
REQUIRE(line.is_used());
REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
REQUIRE(line.bias() == ::gpiod::line::BIAS_DISABLED);
}
SECTION("exported pull-down line")
{
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
config.flags = ::gpiod::line_request::FLAG_BIAS_PULL_DOWN;
line.request(config);
REQUIRE(line.offset() == 4);
REQUIRE(line.name() == "gpio-mockup-A-4");
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE_FALSE(line.is_active_low());;
REQUIRE(line.is_used());
REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
REQUIRE(line.bias() == ::gpiod::line::BIAS_PULL_DOWN);
}
SECTION("exported pull-up line")
{
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
config.flags = ::gpiod::line_request::FLAG_BIAS_PULL_UP;
line.request(config);
REQUIRE(line.offset() == 4);
REQUIRE(line.name() == "gpio-mockup-A-4");
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE_FALSE(line.is_active_low());
REQUIRE(line.is_used());
REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
REQUIRE(line.bias() == ::gpiod::line::BIAS_PULL_UP);
}
}
TEST_CASE("Line values can be set and read", "[line]")
{
mockup::probe_guard mockup_chips({ 8 });
::gpiod::chip chip(mockup::instance().chip_path(0));
::gpiod::line_request config;
config.consumer = consumer.c_str();
SECTION("get value (single line)")
{
auto line = chip.get_line(3);
config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
line.request(config);
REQUIRE(line.get_value() == 0);
mockup::instance().chip_set_pull(0, 3, 1);
REQUIRE(line.get_value() == 1);
}
SECTION("set value (single line)")
{
auto line = chip.get_line(3);
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
line.request(config);
line.set_value(1);
REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
line.set_value(0);
REQUIRE(mockup::instance().chip_get_value(0, 3) == 0);
}
SECTION("set value with default value parameter")
{
auto line = chip.get_line(3);
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
line.request(config, 1);
REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
}
SECTION("get multiple values at once")
{
auto lines = chip.get_lines({ 0, 1, 2, 3, 4 });
config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
lines.request(config);
REQUIRE(lines.get_values() == ::std::vector<int>({ 0, 0, 0, 0, 0 }));
mockup::instance().chip_set_pull(0, 1, 1);
mockup::instance().chip_set_pull(0, 3, 1);
mockup::instance().chip_set_pull(0, 4, 1);
REQUIRE(lines.get_values() == ::std::vector<int>({ 0, 1, 0, 1, 1 }));
}
SECTION("set multiple values at once")
{
auto lines = chip.get_lines({ 0, 1, 2, 6, 7 });
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
lines.request(config);
lines.set_values({ 1, 1, 0, 1, 0 });
REQUIRE(mockup::instance().chip_get_value(0, 0) == 1);
REQUIRE(mockup::instance().chip_get_value(0, 1) == 1);
REQUIRE(mockup::instance().chip_get_value(0, 2) == 0);
REQUIRE(mockup::instance().chip_get_value(0, 6) == 1);
REQUIRE(mockup::instance().chip_get_value(0, 7) == 0);
}
SECTION("set multiple values with default values parameter")
{
auto lines = chip.get_lines({ 1, 2, 4, 6, 7 });
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
lines.request(config, { 1, 1, 0, 1, 0 });
REQUIRE(mockup::instance().chip_get_value(0, 1) == 1);
REQUIRE(mockup::instance().chip_get_value(0, 2) == 1);
REQUIRE(mockup::instance().chip_get_value(0, 4) == 0);
REQUIRE(mockup::instance().chip_get_value(0, 6) == 1);
REQUIRE(mockup::instance().chip_get_value(0, 7) == 0);
}
SECTION("get value (single line, active-low")
{
auto line = chip.get_line(4);
config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;
line.request(config);
REQUIRE(line.get_value() == 1);
mockup::instance().chip_set_pull(0, 4, 1);
REQUIRE(line.get_value() == 0);
}
SECTION("set value (single line, active-low)")
{
auto line = chip.get_line(3);
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;
line.request(config);
line.set_value(1);
REQUIRE(mockup::instance().chip_get_value(0, 3) == 0);
line.set_value(0);
REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
}
}
TEST_CASE("Line can be reconfigured", "[line]")
{
mockup::probe_guard mockup_chips({ 8 });
::gpiod::chip chip(mockup::instance().chip_path(0));
::gpiod::line_request config;
config.consumer = consumer.c_str();
SECTION("set config (single line, active-state)")
{
auto line = chip.get_line(3);
config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
config.flags = 0;
line.request(config);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_INPUT);
REQUIRE_FALSE(line.is_active_low());
line.set_config(::gpiod::line_request::DIRECTION_OUTPUT,
::gpiod::line_request::FLAG_ACTIVE_LOW,1);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE(line.is_active_low());
REQUIRE(mockup::instance().chip_get_value(0, 3) == 0);
line.set_value(0);
REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
line.set_config(::gpiod::line_request::DIRECTION_OUTPUT, 0);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE_FALSE(line.is_active_low());
REQUIRE(mockup::instance().chip_get_value(0, 3) == 0);
line.set_value(1);
REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
}
SECTION("set flags (single line, active-state)")
{
auto line = chip.get_line(3);
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
config.flags = 0;
line.request(config,1);
REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
line.set_flags(::gpiod::line_request::FLAG_ACTIVE_LOW);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE(line.is_active_low());
REQUIRE(mockup::instance().chip_get_value(0, 3) == 0);
line.set_flags(0);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE_FALSE(line.is_active_low());
REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
}
SECTION("set flags (single line, drive)")
{
auto line = chip.get_line(3);
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
config.flags = 0;
line.request(config);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
line.set_flags(::gpiod::line_request::FLAG_OPEN_DRAIN);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE(line.drive() == ::gpiod::line::DRIVE_OPEN_DRAIN);
line.set_flags(::gpiod::line_request::FLAG_OPEN_SOURCE);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE(line.drive() == ::gpiod::line::DRIVE_OPEN_SOURCE);
line.set_flags(0);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
}
SECTION("set flags (single line, bias)")
{
auto line = chip.get_line(3);
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
config.flags = 0;
line.request(config);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
line.set_flags(::gpiod::line_request::FLAG_OPEN_DRAIN);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE(line.drive() == ::gpiod::line::DRIVE_OPEN_DRAIN);
line.set_flags(::gpiod::line_request::FLAG_OPEN_SOURCE);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE(line.drive() == ::gpiod::line::DRIVE_OPEN_SOURCE);
line.set_flags(0);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE(line.drive() == ::gpiod::line::DRIVE_PUSH_PULL);
}
SECTION("set direction input (single line)")
{
auto line = chip.get_line(3);
config.request_type = ::gpiod::line_request::DIRECTION_OUTPUT;
config.flags = 0;
line.request(config);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
line.set_direction_input();
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_INPUT);
}
SECTION("set direction output (single line)")
{
auto line = chip.get_line(3);
config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
config.flags = 0;
line.request(config);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_INPUT);
line.set_direction_output(1);
REQUIRE(line.direction() == ::gpiod::line::DIRECTION_OUTPUT);
REQUIRE(mockup::instance().chip_get_value(0, 3) == 1);
}
}
TEST_CASE("Exported line can be released", "[line]")
{
mockup::probe_guard mockup_chips({ 8 });
::gpiod::chip chip(mockup::instance().chip_path(0));
auto line = chip.get_line(4);
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
line.request(config);
REQUIRE(line.get_value() == 0);
line.release();
REQUIRE_THROWS_AS(line.get_value(), ::std::system_error);
}
TEST_CASE("Uninitialized GPIO line behaves correctly", "[line]")
{
::gpiod::line line;
SECTION("uninitialized line is 'false'")
{
REQUIRE_FALSE(line);
}
SECTION("using uninitialized line throws logic_error")
{
REQUIRE_THROWS_AS(line.name(), ::std::logic_error);
}
}
TEST_CASE("Uninitialized GPIO line_bulk behaves correctly", "[line][bulk]")
{
::gpiod::line_bulk bulk;
SECTION("uninitialized line_bulk is 'false'")
{
REQUIRE_FALSE(bulk);
}
SECTION("using uninitialized line_bulk throws logic_error")
{
REQUIRE_THROWS_AS(bulk.get(0), ::std::logic_error);
}
}
TEST_CASE("Cannot request the same line twice", "[line]")
{
mockup::probe_guard mockup_chips({ 8 });
::gpiod::chip chip(mockup::instance().chip_path(0));
::gpiod::line_request config;
config.consumer = consumer.c_str();
config.request_type = ::gpiod::line_request::DIRECTION_INPUT;
SECTION("two separate calls to request()")
{
auto line = chip.get_line(3);
REQUIRE_NOTHROW(line.request(config));
REQUIRE_THROWS_AS(line.request(config), ::std::system_error);
}
SECTION("request the same line twice in line_bulk")
{
/*
* While a line_bulk object can hold two or more line objects
* representing the same line - requesting it will fail.
*/
auto lines = chip.get_lines({ 2, 3, 4, 4 });
REQUIRE_THROWS_AS(lines.request(config), ::std::system_error);
}
}
TEST_CASE("Cannot get/set values of unrequested lines", "[line]")
{
mockup::probe_guard mockup_chips({ 8 });
::gpiod::chip chip(mockup::instance().chip_path(0));
auto line = chip.get_line(3);
SECTION("get value")
{
REQUIRE_THROWS_AS(line.get_value(), ::std::system_error);
}
SECTION("set value")
{
REQUIRE_THROWS_AS(line.set_value(1), ::std::system_error);
}
}
TEST_CASE("Line objects can be compared")
{
mockup::probe_guard mockup_chips({ 8 });
::gpiod::chip chip(mockup::instance().chip_path(0));
auto line1 = chip.get_line(3);
auto line2 = chip.get_line(3);
auto line3 = chip.get_line(4);
REQUIRE(line1 == line2);
REQUIRE(line2 != line3);
}

View File

@@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <filesystem>
#include <gpiod.hpp>
#include <string>
#include <regex>
#include <unistd.h>
#include "gpiosim.hpp"
#include "helpers.hpp"
using ::gpiosim::make_sim;
namespace {
class symlink_guard
{
public:
symlink_guard(const ::std::filesystem::path& target,
const ::std::filesystem::path& link)
: _m_link(link)
{
::std::filesystem::create_symlink(target, this->_m_link);
}
~symlink_guard()
{
::std::filesystem::remove(this->_m_link);
}
private:
::std::filesystem::path _m_link;
};
TEST_CASE("is_gpiochip_device() works", "[misc][chip]")
{
SECTION("is_gpiochip_device() returns false for /dev/null")
{
REQUIRE_FALSE(::gpiod::is_gpiochip_device("/dev/null"));
}
SECTION("is_gpiochip_device() returns false for nonexistent file")
{
REQUIRE_FALSE(::gpiod::is_gpiochip_device("/dev/nonexistent"));
}
SECTION("is_gpiochip_device() returns true for a GPIO chip")
{
auto sim = make_sim().build();
REQUIRE(::gpiod::is_gpiochip_device(sim.dev_path()));
}
SECTION("is_gpiochip_device() can resolve a symlink")
{
auto sim = make_sim().build();
::std::string link("/tmp/gpiod-cxx-tmp-link.");
link += ::std::to_string(::getpid());
symlink_guard link_guard(sim.dev_path(), link);
REQUIRE(::gpiod::is_gpiochip_device(link));
}
}
TEST_CASE("version_string() returns a valid API version", "[misc]")
{
SECTION("check version_string() format")
{
REQUIRE_THAT(::gpiod::version_string(),
regex_matcher("^[0-9][1-9]?\\.[0-9][1-9]?([\\.0-9]?|\\-devel)$"));
}
}
} /* namespace */

View File

@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2021-2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <catch2/catch.hpp>
#include <cstddef>
#include <gpiod.hpp>
#include <string>
#include <sstream>
#include "helpers.hpp"
using offsets = ::gpiod::line::offsets;
namespace {
TEST_CASE("request_config constructor works", "[request-config]")
{
SECTION("no arguments")
{
::gpiod::request_config cfg;
REQUIRE(cfg.consumer().empty());
REQUIRE(cfg.event_buffer_size() == 0);
}
}
TEST_CASE("request_config can be moved", "[request-config]")
{
::gpiod::request_config cfg;
cfg.set_consumer("foobar").set_event_buffer_size(64);
SECTION("move constructor works")
{
auto moved(::std::move(cfg));
REQUIRE_THAT(moved.consumer(), Catch::Equals("foobar"));
REQUIRE(moved.event_buffer_size() == 64);
}
SECTION("move assignment operator works")
{
::gpiod::request_config moved;
moved = ::std::move(cfg);
REQUIRE_THAT(moved.consumer(), Catch::Equals("foobar"));
REQUIRE(moved.event_buffer_size() == 64);
}
}
TEST_CASE("request_config mutators work", "[request-config]")
{
::gpiod::request_config cfg;
SECTION("set consumer")
{
cfg.set_consumer("foobar");
REQUIRE_THAT(cfg.consumer(), Catch::Equals("foobar"));
}
SECTION("set event_buffer_size")
{
cfg.set_event_buffer_size(128);
REQUIRE(cfg.event_buffer_size() == 128);
}
}
TEST_CASE("request_config stream insertion operator works", "[request-config]")
{
::gpiod::request_config cfg;
cfg.set_consumer("foobar").set_event_buffer_size(32);
::std::stringstream buf;
buf << cfg;
::std::string expected("gpiod::request_config(consumer='foobar', event_buffer_size=32)");
REQUIRE_THAT(buf.str(), Catch::Equals(expected));
}
} /* namespace */

8
bindings/python/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <brgl@bgdev.pl>
build/
__pycache__/
dist/
gpiod.egg-info/
*.so

View File

@@ -1,16 +1,26 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
pyexec_LTLIBRARIES = gpiod.la
EXTRA_DIST = setup.py
gpiod_la_SOURCES = gpiodmodule.c
if WITH_TESTS
gpiod_la_CFLAGS = -I$(top_srcdir)/include/
gpiod_la_CFLAGS += -Wall -Wextra -g -std=gnu89 $(PYTHON_CPPFLAGS)
gpiod_la_LDFLAGS = -module -avoid-version
gpiod_la_LIBADD = $(top_builddir)/lib/libgpiod.la $(PYTHON_LIBS)
BUILD_TESTS = 1
SUBDIRS = .
endif
all-local:
GPIOD_VERSION_STRING=$(VERSION_STR) \
GPIOD_WITH_TESTS=$(BUILD_TESTS) \
$(PYTHON) setup.py build_ext --inplace \
--include-dirs=$(top_srcdir)/include/:$(top_srcdir)/tests/gpiosim/ \
--library-dirs=$(top_builddir)/lib/.libs/:$(top_srcdir)/tests/gpiosim/.libs/
install-exec-local:
GPIOD_WITH_TESTS= \
$(PYTHON) setup.py install --prefix=$(prefix)
SUBDIRS = gpiod
if WITH_TESTS

View File

@@ -1,10 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
EXTRA_DIST = \
gpiodetect.py \
gpiofind.py \
gpioget.py \
gpioinfo.py \
gpiomon.py \
gpioset.py
EXTRA_DIST = \
gpiodetect.py \
gpiofind.py \
gpioget.py \
gpioinfo.py \
gpiomon.py \
gpioset.py

View File

@@ -1,16 +1,15 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
'''Reimplementation of the gpiodetect tool in Python.'''
"""Reimplementation of the gpiodetect tool in Python."""
import gpiod
import os
if __name__ == '__main__':
for entry in os.scandir('/dev/'):
if gpiod.is_gpiochip_device(entry.path):
with gpiod.Chip(entry.path) as chip:
print('{} [{}] ({} lines)'.format(chip.name(),
chip.label(),
chip.num_lines()))
from helpers import gpio_chips
if __name__ == "__main__":
for chip in gpio_chips():
info = chip.get_info()
print("{} [{}] ({} lines)".format(info.name, info.label, info.num_lines))

View File

@@ -1,20 +1,20 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
'''Reimplementation of the gpiofind tool in Python.'''
"""Reimplementation of the gpiofind tool in Python."""
import gpiod
import os
import sys
if __name__ == '__main__':
for entry in os.scandir('/dev/'):
if __name__ == "__main__":
for entry in os.scandir("/dev/"):
if gpiod.is_gpiochip_device(entry.path):
with gpiod.Chip(entry.path) as chip:
offset = chip.find_line(sys.argv[1], unique=True)
offset = chip.line_offset_from_id(sys.argv[1])
if offset is not None:
print('{} {}'.format(line.owner().name(), offset))
sys.exit(0)
print("{} {}".format(chip.get_info().name, offset))
sys.exit(0)
sys.exit(1)

View File

@@ -1,25 +1,29 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
'''Simplified reimplementation of the gpioget tool in Python.'''
"""Simplified reimplementation of the gpioget tool in Python."""
import gpiod
import sys
if __name__ == '__main__':
from gpiod.line import Direction
if __name__ == "__main__":
if len(sys.argv) < 3:
raise TypeError('usage: gpioget.py <gpiochip> <offset1> <offset2> ...')
raise TypeError("usage: gpioget.py <gpiochip> <offset1> <offset2> ...")
with gpiod.Chip(sys.argv[1]) as chip:
offsets = []
for off in sys.argv[2:]:
offsets.append(int(off))
path = sys.argv[1]
lines = [int(line) if line.isdigit() else line for line in sys.argv[2:]]
lines = chip.get_lines(offsets)
lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_IN)
vals = lines.get_values()
request = gpiod.request_lines(
path,
consumer="gpioget.py",
config={tuple(lines): gpiod.LineSettings(direction=Direction.INPUT)},
)
for val in vals:
print(val, end=' ')
print()
vals = request.get_values()
for val in vals:
print("{} ".format(val.value), end="")
print()

View File

@@ -1,28 +1,28 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
'''Simplified reimplementation of the gpioinfo tool in Python.'''
"""Simplified reimplementation of the gpioinfo tool in Python."""
import gpiod
import os
if __name__ == '__main__':
for entry in os.scandir('/dev/'):
if gpiod.is_gpiochip_device(entry.path):
with gpiod.Chip(entry.path) as chip:
print('{} - {} lines:'.format(chip.name(), chip.num_lines()))
from helpers import gpio_chips
for line in gpiod.LineIter(chip):
offset = line.offset()
name = line.name()
consumer = line.consumer()
direction = line.direction()
active_low = line.is_active_low()
if __name__ == "__main__":
for chip in gpio_chips():
cinfo = chip.get_info()
print("{} - {} lines:".format(cinfo.name, cinfo.num_lines))
print('\tline {:>3}: {:>18} {:>12} {:>8} {:>10}'.format(
offset,
'unnamed' if name is None else name,
'unused' if consumer is None else consumer,
'input' if direction == gpiod.Line.DIRECTION_INPUT else 'output',
'active-low' if active_low else 'active-high'))
for offset in range(0, cinfo.num_lines):
linfo = chip.get_line_info(offset)
is_input = linfo.direction == gpiod.line.Direction.INPUT
print(
"\tline {:>3}: {:>18} {:>12} {:>8} {:>10}".format(
linfo.offset,
linfo.name or "unnamed",
linfo.consumer or "unused",
"input" if is_input else "output",
"active-low" if linfo.active_low else "active-high",
)
)

View File

@@ -1,42 +1,26 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
'''Simplified reimplementation of the gpiomon tool in Python.'''
"""Simplified reimplementation of the gpiomon tool in Python."""
import gpiod
import sys
if __name__ == '__main__':
def print_event(event):
if event.type == gpiod.LineEvent.RISING_EDGE:
evstr = ' RISING EDGE'
elif event.type == gpiod.LineEvent.FALLING_EDGE:
evstr = 'FALLING EDGE'
else:
raise TypeError('Invalid event type')
print('event: {} offset: {} timestamp: [{}.{}]'.format(evstr,
event.source.offset(),
event.sec, event.nsec))
from gpiod.line import Edge
if __name__ == "__main__":
if len(sys.argv) < 3:
raise TypeError('usage: gpiomon.py <gpiochip> <offset1> <offset2> ...')
raise TypeError("usage: gpiomon.py <gpiochip> <offset1> <offset2> ...")
with gpiod.Chip(sys.argv[1]) as chip:
offsets = []
for off in sys.argv[2:]:
offsets.append(int(off))
path = sys.argv[1]
lines = [int(line) if line.isdigit() else line for line in sys.argv[2:]]
lines = chip.get_lines(offsets)
lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_EV_BOTH_EDGES)
try:
while True:
ev_lines = lines.event_wait(sec=1)
if ev_lines:
for line in ev_lines:
event = line.event_read()
print_event(event)
except KeyboardInterrupt:
sys.exit(130)
with gpiod.request_lines(
path,
consumer="gpiomon.py",
config={tuple(lines): gpiod.LineSettings(edge_detection=Edge.BOTH)},
) as request:
while True:
for event in request.read_edge_event():
print(event)

View File

@@ -1,25 +1,36 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
'''Simplified reimplementation of the gpioset tool in Python.'''
"""Simplified reimplementation of the gpioset tool in Python."""
import gpiod
import sys
if __name__ == '__main__':
from gpiod.line import Direction, Value
if __name__ == "__main__":
if len(sys.argv) < 3:
raise TypeError('usage: gpioset.py <gpiochip> <offset1>=<value1> ...')
raise TypeError(
"usage: gpioset.py <gpiochip> <offset1>=<value1> <offset2>=<value2> ..."
)
with gpiod.Chip(sys.argv[1]) as chip:
offsets = []
values = []
for arg in sys.argv[2:]:
arg = arg.split('=')
offsets.append(int(arg[0]))
values.append(int(arg[1]))
path = sys.argv[1]
lines = chip.get_lines(offsets)
lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_OUT)
lines.set_values(values)
input()
def parse_value(arg):
x, y = arg.split("=")
return (x, Value(int(y)))
lvs = [parse_value(arg) for arg in sys.argv[2:]]
lines = [x[0] for x in lvs]
values = dict(lvs)
request = gpiod.request_lines(
path,
consumer="gpioset.py",
config={tuple(lines): gpiod.LineSettings(direction=Direction.OUTPUT)},
)
vals = request.set_values(values)
input()

View File

@@ -0,0 +1,15 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
import gpiod
import os
def gpio_chips():
for path in [
entry.path
for entry in os.scandir("/dev/")
if gpiod.is_gpiochip_device(entry.path)
]:
with gpiod.Chip(path) as chip:
yield chip

View File

@@ -0,0 +1,17 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
SUBDIRS = ext
EXTRA_DIST = \
chip_info.py \
chip.py \
edge_event.py \
exception.py \
info_event.py \
__init__.py \
internal.py \
line_info.py \
line.py \
line_request.py \
line_settings.py

View File

@@ -0,0 +1,53 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
"""
Python bindings for libgpiod.
This module wraps the native C API of libgpiod in a set of python classes.
"""
from . import _ext
from . import line
from .chip import Chip
from .chip_info import ChipInfo
from .edge_event import EdgeEvent
from .exception import ChipClosedError, RequestReleasedError
from .info_event import InfoEvent
from .line_request import LineRequest
from .line_settings import LineSettings
__version__ = _ext.__version__
def is_gpiochip_device(path: str) -> bool:
"""
Check if the file pointed to by path is a GPIO chip character device.
Args:
path
Path to the file that should be checked.
Returns:
Returns true if so, False otherwise.
"""
return _ext.is_gpiochip_device(path)
def request_lines(path: str, *args, **kwargs) -> LineRequest:
"""
Open a GPIO chip pointed to by 'path', request lines according to the
configuration arguments, close the chip and return the request object.
Args:
path
Path to the GPIO character device file.
*args
**kwargs
See Chip.request_lines() for configuration arguments.
Returns:
Returns a new LineRequest object.
"""
with Chip(path) as chip:
return chip.request_lines(*args, **kwargs)

View File

@@ -0,0 +1,329 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
from . import _ext
from .chip_info import ChipInfo
from .exception import ChipClosedError
from .info_event import InfoEvent
from .internal import poll_fd
from .line_info import LineInfo
from .line_settings import LineSettings, _line_settings_to_ext
from .line_request import LineRequest
from collections import Counter
from datetime import timedelta
from errno import ENOENT
from select import select
from typing import Union, Optional
class Chip:
"""
Represents a GPIO chip.
Chip object manages all resources associated with the GPIO chip it represents.
The gpiochip device file is opened during the object's construction. The Chip
object's constructor takes the path to the GPIO chip device file
as the only argument.
Callers must close the chip by calling the close() method when it's no longer
used.
Example:
chip = gpiod.Chip(\"/dev/gpiochip0\")
do_something(chip)
chip.close()
The gpiod.Chip class also supports controlled execution ('with' statement).
Example:
with gpiod.Chip(path="/dev/gpiochip0") as chip:
do_something(chip)
"""
def __init__(self, path: str):
"""
Open a GPIO device.
Args:
path:
Path to the GPIO character device file.
"""
self._chip = _ext.Chip(path)
self._info = None
def __bool__(self) -> bool:
"""
Boolean conversion for GPIO chips.
Returns:
True if the chip is open and False if it's closed.
"""
return True if self._chip else False
def __enter__(self):
"""
Controlled execution enter callback.
"""
self._check_closed()
return self
def __exit__(self, exc_type, exc_value, traceback) -> None:
"""
Controlled execution exit callback.
"""
self.close()
def _check_closed(self) -> None:
if not self._chip:
raise ChipClosedError()
def close(self) -> None:
"""
Close the associated GPIO chip descriptor. The chip object must no
longer be used after this method is called.
"""
self._check_closed()
self._chip.close()
self._chip = None
def get_info(self) -> ChipInfo:
"""
Get the information about the chip.
Returns:
New gpiod.ChipInfo object.
"""
self._check_closed()
if not self._info:
self._info = self._chip.get_info()
return self._info
def line_offset_from_id(self, id: Union[str, int]) -> int:
"""
Map a line's identifier to its offset within the chip.
Args:
id:
Name of the GPIO line, its offset as a string or its offset as an
integer.
Returns:
If id is an integer - it's returned as is (unless it's out of range
for this chip). If it's a string, the method tries to interpret it as
the name of the line first and tries too perform a name lookup within
the chip. If it fails, it tries to convert the string to an integer
and check if it represents a valid offset within the chip and if
so - returns it.
"""
self._check_closed()
if not isinstance(id, int):
try:
return self._chip.line_offset_from_id(id)
except OSError as ex:
if ex.errno == ENOENT:
try:
offset = int(id)
except ValueError:
raise ex
else:
raise ex
else:
offset = id
if offset >= self.get_info().num_lines:
raise ValueError("line offset of out range")
return offset
def _get_line_info(self, line: Union[int, str], watch: bool) -> LineInfo:
self._check_closed()
return self._chip.get_line_info(self.line_offset_from_id(line), watch)
def get_line_info(self, line: Union[int, str]) -> LineInfo:
"""
Get the snapshot of information about the line at given offset.
Args:
line:
Offset or name of the GPIO line to get information for.
Returns:
New LineInfo object.
"""
return self._get_line_info(line, watch=False)
def watch_line_info(self, line: Union[int, str]) -> LineInfo:
"""
Get the snapshot of information about the line at given offset and
start watching it for future changes.
Args:
line:
Offset or name of the GPIO line to get information for.
Returns:
New gpiod.LineInfo object.
"""
return self._get_line_info(line, watch=True)
def unwatch_line_info(self, line: Union[int, str]) -> None:
"""
Stop watching a line for status changes.
Args:
line:
Offset or name of the line to stop watching.
"""
self._check_closed()
return self._chip.unwatch_line_info(self.line_offset_from_id(line))
def wait_info_event(
self, timeout: Optional[Union[timedelta, float]] = None
) -> bool:
"""
Wait for line status change events on any of the watched lines on the
chip.
Args:
timeout:
Wait time limit represented as either a datetime.timedelta object
or the number of seconds stored in a float.
Returns:
True if an info event is ready to be read from the chip, False if the
wait timed out without any events.
"""
self._check_closed()
return poll_fd(self.fd, timeout)
def read_info_event(self) -> InfoEvent:
"""
Read a single line status change event from the chip.
Returns:
New gpiod.InfoEvent object.
Note:
This function may block if there are no available events in the queue.
"""
self._check_closed()
return self._chip.read_info_event()
def request_lines(
self,
config: dict[tuple[Union[int, str]], Optional[LineSettings]],
consumer: Optional[str] = None,
event_buffer_size: Optional[int] = None,
) -> LineRequest:
"""
Request a set of lines for exclusive usage.
Args:
config:
Dictionary mapping offsets or names (or tuples thereof) to
LineSettings. If None is passed as the value of the mapping,
default settings are used.
consumer:
Consumer string to use for this request.
event_buffer_size:
Size of the kernel edge event buffer to configure for this request.
Returns:
New LineRequest object.
"""
self._check_closed()
line_cfg = _ext.LineConfig()
# Sanitize lines - don't allow offset repeatitions or offset-name conflicts.
for offset, count in Counter(
[
self.line_offset_from_id(line)
for line in (
lambda t: [
j for i in (t) for j in (i if isinstance(i, tuple) else (i,))
]
)(tuple(config.keys()))
]
).items():
if count != 1:
raise ValueError(
"line must be configured exactly once - offset {} repeats".format(
offset
)
)
for lines, settings in config.items():
offsets = list()
name_map = dict()
offset_map = dict()
if isinstance(lines, int) or isinstance(lines, str):
lines = (lines,)
for line in lines:
offset = self.line_offset_from_id(line)
offsets.append(offset)
if isinstance(line, str):
name_map[line] = offset
offset_map[offset] = line
line_cfg.add_line_settings(
offsets, _line_settings_to_ext(settings or LineSettings())
)
req_internal = self._chip.request_lines(line_cfg, consumer, event_buffer_size)
request = LineRequest(req_internal)
request._offsets = req_internal.offsets
request._name_map = name_map
request._offset_map = offset_map
request._lines = [
offset_map[off] if off in offset_map else off for off in request.offsets
]
return request
def __repr__(self) -> str:
"""
Return a string that can be used to re-create this chip object.
"""
if not self._chip:
return "<Chip CLOSED>"
return 'Chip("{}")'.format(self.path)
def __str__(self) -> str:
"""
Return a user-friendly, human-readable description of this chip.
"""
if not self._chip:
return "<Chip CLOSED>"
return '<Chip path="{}" fd={} info={}>'.format(
self.path, self.fd, self.get_info()
)
@property
def path(self) -> str:
"""
Filesystem path used to open this chip.
"""
self._check_closed()
return self._chip.path
@property
def fd(self) -> int:
"""
File descriptor associated with this chip.
"""
self._check_closed()
return self._chip.fd

View File

@@ -0,0 +1,21 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
from dataclasses import dataclass
@dataclass(frozen=True, repr=False)
class ChipInfo:
"""
Snapshot of a chip's status.
"""
name: str
label: str
num_lines: int
def __str__(self):
return '<ChipInfo name="{}" label="{}" num_lines={}>'.format(
self.name, self.label, self.num_lines
)

View File

@@ -0,0 +1,46 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
from . import _ext
from dataclasses import dataclass
from enum import Enum
@dataclass(frozen=True, init=False, repr=False)
class EdgeEvent:
"""
Immutable object containing data about a single edge event.
"""
class Type(Enum):
RISING_EDGE = _ext.EDGE_EVENT_TYPE_RISING
FALLING_EDGE = _ext.EDGE_EVENT_TYPE_FALLING
event_type: Type
timestamp_ns: int
line_offset: int
global_seqno: int
line_seqno: int
def __init__(
self,
event_type: int,
timestamp_ns: int,
line_offset: int,
global_seqno: int,
line_seqno: int,
):
object.__setattr__(self, "event_type", EdgeEvent.Type(event_type))
object.__setattr__(self, "timestamp_ns", timestamp_ns)
object.__setattr__(self, "line_offset", line_offset)
object.__setattr__(self, "global_seqno", global_seqno)
object.__setattr__(self, "line_seqno", line_seqno)
def __str__(self):
return "<EdgeEvent type={} timestamp_ns={} line_offset={} global_seqno={} line_seqno={}>".format(
self.event_type,
self.timestamp_ns,
self.line_offset,
self.global_seqno,
self.line_seqno,
)

View File

@@ -0,0 +1,20 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
class ChipClosedError(Exception):
"""
Error raised when an already closed chip is used.
"""
def __init__(self):
super().__init__("I/O operation on closed chip")
class RequestReleasedError(Exception):
"""
Error raised when a released request is used.
"""
def __init__(self):
super().__init__("GPIO lines have been released")

View File

@@ -0,0 +1,11 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
EXTRA_DIST = \
chip.c \
common.c \
internal.h \
line-config.c \
line-settings.c \
module.c \
request.c

View File

@@ -0,0 +1,339 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include "internal.h"
typedef struct {
PyObject_HEAD;
struct gpiod_chip *chip;
} chip_object;
static int
chip_init(chip_object *self, PyObject *args, PyObject *Py_UNUSED(ignored))
{
struct gpiod_chip *chip;
char *path;
int ret;
ret = PyArg_ParseTuple(args, "s", &path);
if (!ret)
return -1;
Py_BEGIN_ALLOW_THREADS;
chip = gpiod_chip_open(path);
Py_END_ALLOW_THREADS;
if (!chip) {
Py_gpiod_SetErrFromErrno();
return -1;
}
self->chip = chip;
return 0;
}
static void chip_finalize(chip_object *self)
{
if (self->chip)
PyObject_CallMethod((PyObject *)self, "close", "");
}
static PyObject *chip_path(chip_object *self, void *Py_UNUSED(ignored))
{
return PyUnicode_FromString(gpiod_chip_get_path(self->chip));
}
static PyObject *chip_fd(chip_object *self, void *Py_UNUSED(ignored))
{
return PyLong_FromLong(gpiod_chip_get_fd(self->chip));
}
static PyGetSetDef chip_getset[] = {
{
.name = "path",
.get = (getter)chip_path,
},
{
.name = "fd",
.get = (getter)chip_fd,
},
{ }
};
static PyObject *chip_close(chip_object *self, PyObject *Py_UNUSED(ignored))
{
Py_BEGIN_ALLOW_THREADS;
gpiod_chip_close(self->chip);
Py_END_ALLOW_THREADS;
self->chip = NULL;
Py_RETURN_NONE;
}
static PyObject *chip_get_info(chip_object *self, PyObject *Py_UNUSED(ignored))
{
struct gpiod_chip_info *info;
PyObject *type, *ret;
type = Py_gpiod_GetGlobalType("ChipInfo");
if (!type)
return NULL;
info = gpiod_chip_get_info(self->chip);
if (!info)
return PyErr_SetFromErrno(PyExc_OSError);
ret = PyObject_CallFunction(type, "ssI",
gpiod_chip_info_get_name(info),
gpiod_chip_info_get_label(info),
gpiod_chip_info_get_num_lines(info));
gpiod_chip_info_free(info);
return ret;
}
static PyObject *make_line_info(struct gpiod_line_info *info)
{
PyObject *type;
type = Py_gpiod_GetGlobalType("LineInfo");
if (!type)
return NULL;
return PyObject_CallFunction(type, "IsOsiOiiiiOk",
gpiod_line_info_get_offset(info),
gpiod_line_info_get_name(info),
gpiod_line_info_is_used(info) ?
Py_True : Py_False,
gpiod_line_info_get_consumer(info),
gpiod_line_info_get_direction(info),
gpiod_line_info_is_active_low(info) ?
Py_True : Py_False,
gpiod_line_info_get_bias(info),
gpiod_line_info_get_drive(info),
gpiod_line_info_get_edge_detection(info),
gpiod_line_info_get_event_clock(info),
gpiod_line_info_is_debounced(info) ?
Py_True : Py_False,
gpiod_line_info_get_debounce_period_us(info));
}
static PyObject *chip_get_line_info(chip_object *self, PyObject *args)
{
struct gpiod_line_info *info;
unsigned int offset;
PyObject *info_obj;
int ret, watch;
ret = PyArg_ParseTuple(args, "Ip", &offset, &watch);
if (!ret)
return NULL;
Py_BEGIN_ALLOW_THREADS;
if (watch)
info = gpiod_chip_watch_line_info(self->chip, offset);
else
info = gpiod_chip_get_line_info(self->chip, offset);
Py_END_ALLOW_THREADS;
if (!info)
return Py_gpiod_SetErrFromErrno();
info_obj = make_line_info(info);
gpiod_line_info_free(info);
return info_obj;
}
static PyObject *
chip_unwatch_line_info(chip_object *self, PyObject *args)
{
unsigned int offset;
int ret;
ret = PyArg_ParseTuple(args, "I", &offset);
if (!ret)
return NULL;
Py_BEGIN_ALLOW_THREADS;
ret = gpiod_chip_unwatch_line_info(self->chip, offset);
Py_END_ALLOW_THREADS;
if (ret)
return Py_gpiod_SetErrFromErrno();
Py_RETURN_NONE;
}
static PyObject *
chip_read_info_event(chip_object *self, PyObject *Py_UNUSED(ignored))
{
PyObject *type, *info_obj, *event_obj;
struct gpiod_info_event *event;
struct gpiod_line_info *info;
type = Py_gpiod_GetGlobalType("InfoEvent");
if (!type)
return NULL;
Py_BEGIN_ALLOW_THREADS;
event = gpiod_chip_read_info_event(self->chip);
Py_END_ALLOW_THREADS;
if (!event)
return Py_gpiod_SetErrFromErrno();
info = gpiod_info_event_get_line_info(event);
info_obj = make_line_info(info);
if (!info_obj) {
gpiod_info_event_free(event);
return NULL;
}
event_obj = PyObject_CallFunction(type, "iKO",
gpiod_info_event_get_event_type(event),
gpiod_info_event_get_timestamp_ns(event),
info_obj);
Py_DECREF(info_obj);
gpiod_info_event_free(event);
return event_obj;
}
static PyObject *chip_line_offset_from_id(chip_object *self, PyObject *args)
{
int ret, offset;
char *name;
ret = PyArg_ParseTuple(args, "s", &name);
if (!ret)
return NULL;
Py_BEGIN_ALLOW_THREADS;
offset = gpiod_chip_get_line_offset_from_name(self->chip, name);
Py_END_ALLOW_THREADS;
if (offset < 0)
return Py_gpiod_SetErrFromErrno();
return PyLong_FromLong(offset);
}
static struct gpiod_request_config *
make_request_config(PyObject *consumer_obj, PyObject *event_buffer_size_obj)
{
struct gpiod_request_config *req_cfg;
size_t event_buffer_size;
const char *consumer;
req_cfg = gpiod_request_config_new();
if (!req_cfg) {
Py_gpiod_SetErrFromErrno();
return NULL;
}
if (consumer_obj != Py_None) {
consumer = PyUnicode_AsUTF8(consumer_obj);
if (!consumer) {
gpiod_request_config_free(req_cfg);
return NULL;
}
gpiod_request_config_set_consumer(req_cfg, consumer);
}
if (event_buffer_size_obj != Py_None) {
event_buffer_size = PyLong_AsSize_t(event_buffer_size_obj);
if (PyErr_Occurred()) {
gpiod_request_config_free(req_cfg);
return NULL;
}
gpiod_request_config_set_event_buffer_size(req_cfg,
event_buffer_size);
}
return req_cfg;
}
static PyObject *chip_request_lines(chip_object *self, PyObject *args)
{
PyObject *line_config, *consumer, *event_buffer_size, *req_obj;
struct gpiod_request_config *req_cfg;
struct gpiod_line_config *line_cfg;
struct gpiod_line_request *request;
int ret;
ret = PyArg_ParseTuple(args, "OOO",
&line_config, &consumer, &event_buffer_size);
if (!ret)
return NULL;
line_cfg = Py_gpiod_LineConfigGetData(line_config);
if (!line_cfg)
return NULL;
req_cfg = make_request_config(consumer, event_buffer_size);
if (!req_cfg)
return NULL;
Py_BEGIN_ALLOW_THREADS;
request = gpiod_chip_request_lines(self->chip, req_cfg, line_cfg);
Py_END_ALLOW_THREADS;
gpiod_request_config_free(req_cfg);
if (!request)
return Py_gpiod_SetErrFromErrno();
req_obj = Py_gpiod_MakeRequestObject(request,
gpiod_request_config_get_event_buffer_size(req_cfg));
if (!req_obj)
gpiod_line_request_release(request);
return req_obj;
}
static PyMethodDef chip_methods[] = {
{
.ml_name = "close",
.ml_meth = (PyCFunction)chip_close,
.ml_flags = METH_NOARGS,
},
{
.ml_name = "get_info",
.ml_meth = (PyCFunction)chip_get_info,
.ml_flags = METH_NOARGS,
},
{
.ml_name = "get_line_info",
.ml_meth = (PyCFunction)chip_get_line_info,
.ml_flags = METH_VARARGS,
},
{
.ml_name = "unwatch_line_info",
.ml_meth = (PyCFunction)chip_unwatch_line_info,
.ml_flags = METH_VARARGS,
},
{
.ml_name = "read_info_event",
.ml_meth = (PyCFunction)chip_read_info_event,
.ml_flags = METH_NOARGS,
},
{
.ml_name = "line_offset_from_id",
.ml_meth = (PyCFunction)chip_line_offset_from_id,
.ml_flags = METH_VARARGS,
},
{
.ml_name = "request_lines",
.ml_meth = (PyCFunction)chip_request_lines,
.ml_flags = METH_VARARGS,
},
{ }
};
PyTypeObject chip_type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "gpiod._ext.Chip",
.tp_basicsize = sizeof(chip_object),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)chip_init,
.tp_finalize = (destructor)chip_finalize,
.tp_dealloc = (destructor)Py_gpiod_dealloc,
.tp_getset = chip_getset,
.tp_methods = chip_methods,
};

View File

@@ -0,0 +1,92 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include "internal.h"
/* Generic dealloc callback for all gpiod objects. */
void Py_gpiod_dealloc(PyObject *self)
{
int ret;
ret = PyObject_CallFinalizerFromDealloc(self);
if (ret < 0)
return;
PyObject_Del(self);
}
PyObject *_Py_gpiod_SetErrFromErrno(const char *filename)
{
PyObject *exc;
if (errno == ENOMEM)
return PyErr_NoMemory();
switch (errno) {
case EINVAL:
exc = PyExc_ValueError;
break;
case EOPNOTSUPP:
exc = PyExc_NotImplementedError;
break;
case EPIPE:
exc = PyExc_BrokenPipeError;
break;
case ECHILD:
exc = PyExc_ChildProcessError;
break;
case EINTR:
exc = PyExc_InterruptedError;
break;
case EEXIST:
exc = PyExc_FileExistsError;
break;
case ENOENT:
exc = PyExc_FileNotFoundError;
break;
case EISDIR:
exc = PyExc_IsADirectoryError;
break;
case ENOTDIR:
exc = PyExc_NotADirectoryError;
break;
case EPERM:
exc = PyExc_PermissionError;
break;
case ETIMEDOUT:
exc = PyExc_TimeoutError;
break;
default:
exc = PyExc_OSError;
break;
}
return PyErr_SetFromErrnoWithFilename(exc, filename);
}
PyObject *Py_gpiod_GetGlobalType(const char *type_name)
{
PyObject *globals;
globals = PyEval_GetGlobals();
if (!globals)
return NULL;
return PyDict_GetItemString(globals, type_name);
}
unsigned int Py_gpiod_PyLongAsUnsignedInt(PyObject *pylong)
{
unsigned long tmp;
tmp = PyLong_AsUnsignedLong(pylong);
if (PyErr_Occurred())
return 0;
if (tmp > UINT_MAX) {
PyErr_SetString(PyExc_ValueError, "value exceeding UINT_MAX");
return 0;
}
return tmp;
}

View File

@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl> */
#ifndef __LIBGPIOD_PYTHON_MODULE_H__
#define __LIBGPIOD_PYTHON_MODULE_H__
#include <gpiod.h>
#include <Python.h>
PyObject *_Py_gpiod_SetErrFromErrno(const char *filename);
#define Py_gpiod_SetErrFromErrno() _Py_gpiod_SetErrFromErrno(__FILE__)
PyObject *Py_gpiod_GetGlobalType(const char *type_name);
unsigned int Py_gpiod_PyLongAsUnsignedInt(PyObject *pylong);
void Py_gpiod_dealloc(PyObject *self);
PyObject *Py_gpiod_MakeRequestObject(struct gpiod_line_request *request,
size_t event_buffer_size);
struct gpiod_line_config *Py_gpiod_LineConfigGetData(PyObject *obj);
struct gpiod_line_settings *Py_gpiod_LineSettingsGetData(PyObject *obj);
#endif /* __LIBGPIOD_PYTHON_MODULE_H__ */

View File

@@ -0,0 +1,133 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include "internal.h"
typedef struct {
PyObject_HEAD;
struct gpiod_line_config *cfg;
} line_config_object;
static int line_config_init(line_config_object *self,
PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(ignored))
{
self->cfg = gpiod_line_config_new();
if (!self->cfg) {
Py_gpiod_SetErrFromErrno();
return -1;
}
return 0;
}
static void line_config_finalize(line_config_object *self)
{
if (self->cfg)
gpiod_line_config_free(self->cfg);
}
static unsigned int *make_offsets(PyObject *obj, Py_ssize_t len)
{
unsigned int *offsets;
PyObject *offset;
Py_ssize_t i;
offsets = PyMem_Calloc(len, sizeof(unsigned int));
if (!offsets)
return (unsigned int *)PyErr_NoMemory();
for (i = 0; i < len; i++) {
offset = PyList_GetItem(obj, i);
if (!offset) {
PyMem_Free(offsets);
return NULL;
}
offsets[i] = Py_gpiod_PyLongAsUnsignedInt(offset);
if (PyErr_Occurred()) {
PyMem_Free(offsets);
return NULL;
}
}
return offsets;
}
static PyObject *
line_config_add_line_settings(line_config_object *self, PyObject *args)
{
PyObject *offsets_obj, *settings_obj;
struct gpiod_line_settings *settings;
unsigned int *offsets;
Py_ssize_t num_offsets;
int ret;
ret = PyArg_ParseTuple(args, "OO", &offsets_obj, &settings_obj);
if (!ret)
return NULL;
num_offsets = PyObject_Size(offsets_obj);
if (num_offsets < 0)
return NULL;
offsets = make_offsets(offsets_obj, num_offsets);
if (!offsets)
return NULL;
settings = Py_gpiod_LineSettingsGetData(settings_obj);
if (!settings) {
PyMem_Free(offsets);
return NULL;
}
ret = gpiod_line_config_add_line_settings(self->cfg, offsets,
num_offsets, settings);
PyMem_Free(offsets);
if (ret)
return Py_gpiod_SetErrFromErrno();
Py_RETURN_NONE;
}
static PyMethodDef line_config_methods[] = {
{
.ml_name = "add_line_settings",
.ml_meth = (PyCFunction)line_config_add_line_settings,
.ml_flags = METH_VARARGS,
},
{ }
};
PyTypeObject line_config_type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "gpiod._ext.LineConfig",
.tp_basicsize = sizeof(line_config_object),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)line_config_init,
.tp_finalize = (destructor)line_config_finalize,
.tp_dealloc = (destructor)Py_gpiod_dealloc,
.tp_methods = line_config_methods,
};
struct gpiod_line_config *Py_gpiod_LineConfigGetData(PyObject *obj)
{
line_config_object *line_cfg;
PyObject *type;
type = PyObject_Type(obj);
if (!type)
return NULL;
if ((PyTypeObject *)type != &line_config_type) {
PyErr_SetString(PyExc_TypeError,
"not a gpiod._ext.LineConfig object");
Py_DECREF(type);
return NULL;
}
Py_DECREF(type);
line_cfg = (line_config_object *)obj;
return line_cfg->cfg;
}

View File

@@ -0,0 +1,130 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include "internal.h"
typedef struct {
PyObject_HEAD;
struct gpiod_line_settings *settings;
} line_settings_object;
static int set_int_prop(struct gpiod_line_settings *settings, int val,
int (*func)(struct gpiod_line_settings *, int))
{
int ret;
ret = func(settings, val);
if (ret) {
Py_gpiod_SetErrFromErrno();
return -1;
}
return 0;
}
static int
line_settings_init(line_settings_object *self, PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = {
"direction",
"edge_detection",
"bias",
"drive",
"active_low",
"debounce_period",
"event_clock",
"output_value",
NULL
};
int direction, edge, bias, drive, active_low, event_clock, output_value,
ret;
unsigned long debounce_period;
ret = PyArg_ParseTupleAndKeywords(args, kwargs, "IIIIpkII", kwlist,
&direction, &edge, &bias, &drive, &active_low,
&debounce_period, &event_clock, &output_value);
if (!ret)
return -1;
self->settings = gpiod_line_settings_new();
if (!self->settings) {
Py_gpiod_SetErrFromErrno();
return -1;
}
ret = set_int_prop(self->settings, direction,
gpiod_line_settings_set_direction);
if (ret)
return -1;
ret = set_int_prop(self->settings, edge,
gpiod_line_settings_set_edge_detection);
if (ret)
return -1;
ret = set_int_prop(self->settings, bias,
gpiod_line_settings_set_bias);
if (ret)
return -1;
ret = set_int_prop(self->settings, drive,
gpiod_line_settings_set_drive);
if (ret)
return -1;
gpiod_line_settings_set_active_low(self->settings, active_low);
gpiod_line_settings_set_debounce_period_us(self->settings,
debounce_period);
ret = set_int_prop(self->settings, edge,
gpiod_line_settings_set_edge_detection);
if (ret)
return -1;
ret = set_int_prop(self->settings, output_value,
gpiod_line_settings_set_output_value);
if (ret)
return -1;
return 0;
}
static void line_settings_finalize(line_settings_object *self)
{
if (self->settings)
gpiod_line_settings_free(self->settings);
}
PyTypeObject line_settings_type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "gpiod._ext.LineSettings",
.tp_basicsize = sizeof(line_settings_object),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)line_settings_init,
.tp_finalize = (destructor)line_settings_finalize,
.tp_dealloc = (destructor)Py_gpiod_dealloc,
};
struct gpiod_line_settings *Py_gpiod_LineSettingsGetData(PyObject *obj)
{
line_settings_object *settings;
PyObject *type;
type = PyObject_Type(obj);
if (!type)
return NULL;
if ((PyTypeObject *)type != &line_settings_type) {
PyErr_SetString(PyExc_TypeError,
"not a gpiod._ext.LineSettings object");
Py_DECREF(type);
return NULL;
}
Py_DECREF(type);
settings = (line_settings_object *)obj;
return settings->settings;
}

View File

@@ -0,0 +1,193 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include <gpiod.h>
#include <Python.h>
struct module_const {
const char *name;
long val;
};
static const struct module_const module_constants[] = {
{
.name = "VALUE_INACTIVE",
.val = GPIOD_LINE_VALUE_INACTIVE,
},
{
.name = "VALUE_ACTIVE",
.val = GPIOD_LINE_VALUE_ACTIVE,
},
{
.name = "DIRECTION_AS_IS",
.val = GPIOD_LINE_DIRECTION_AS_IS,
},
{
.name = "DIRECTION_INPUT",
.val = GPIOD_LINE_DIRECTION_INPUT,
},
{
.name = "DIRECTION_OUTPUT",
.val = GPIOD_LINE_DIRECTION_OUTPUT,
},
{
.name = "BIAS_AS_IS",
.val = GPIOD_LINE_BIAS_AS_IS,
},
{
.name = "BIAS_UNKNOWN",
.val = GPIOD_LINE_BIAS_UNKNOWN,
},
{
.name = "BIAS_DISABLED",
.val = GPIOD_LINE_BIAS_DISABLED,
},
{
.name = "BIAS_PULL_UP",
.val = GPIOD_LINE_BIAS_PULL_UP,
},
{
.name = "BIAS_PULL_DOWN",
.val = GPIOD_LINE_BIAS_PULL_DOWN,
},
{
.name = "DRIVE_PUSH_PULL",
.val = GPIOD_LINE_DRIVE_PUSH_PULL,
},
{
.name = "DRIVE_OPEN_DRAIN",
.val = GPIOD_LINE_DRIVE_OPEN_DRAIN,
},
{
.name = "DRIVE_OPEN_SOURCE",
.val = GPIOD_LINE_DRIVE_OPEN_SOURCE,
},
{
.name = "EDGE_NONE",
.val = GPIOD_LINE_EDGE_NONE,
},
{
.name = "EDGE_FALLING",
.val = GPIOD_LINE_EDGE_FALLING,
},
{
.name = "EDGE_RISING",
.val = GPIOD_LINE_EDGE_RISING,
},
{
.name = "EDGE_BOTH",
.val = GPIOD_LINE_EDGE_BOTH,
},
{
.name = "CLOCK_MONOTONIC",
.val = GPIOD_LINE_EVENT_CLOCK_MONOTONIC,
},
{
.name = "CLOCK_REALTIME",
.val = GPIOD_LINE_EVENT_CLOCK_REALTIME,
},
{
.name = "CLOCK_HTE",
.val = GPIOD_LINE_EVENT_CLOCK_HTE,
},
{
.name = "EDGE_EVENT_TYPE_RISING",
.val = GPIOD_EDGE_EVENT_RISING_EDGE,
},
{
.name = "EDGE_EVENT_TYPE_FALLING",
.val = GPIOD_EDGE_EVENT_FALLING_EDGE,
},
{
.name = "INFO_EVENT_TYPE_LINE_REQUESTED",
.val = GPIOD_INFO_EVENT_LINE_REQUESTED,
},
{
.name = "INFO_EVENT_TYPE_LINE_RELEASED",
.val = GPIOD_INFO_EVENT_LINE_RELEASED,
},
{
.name = "INFO_EVENT_TYPE_LINE_CONFIG_CHANGED",
.val = GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED,
},
{ }
};
static PyObject *
module_is_gpiochip_device(PyObject *Py_UNUSED(self), PyObject *args)
{
const char *path;
int ret;
ret = PyArg_ParseTuple(args, "s", &path);
if (!ret)
return NULL;
return PyBool_FromLong(gpiod_is_gpiochip_device(path));
}
static PyMethodDef module_methods[] = {
{
.ml_name = "is_gpiochip_device",
.ml_meth = (PyCFunction)module_is_gpiochip_device,
.ml_flags = METH_VARARGS,
},
{ }
};
static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "gpiod._ext",
.m_methods = module_methods,
};
extern PyTypeObject chip_type;
extern PyTypeObject line_config_type;
extern PyTypeObject line_settings_type;
extern PyTypeObject request_type;
static PyTypeObject *types[] = {
&chip_type,
&line_config_type,
&line_settings_type,
&request_type,
NULL,
};
PyMODINIT_FUNC PyInit__ext(void)
{
const struct module_const *modconst;
PyTypeObject **type;
PyObject *module;
int ret;
module = PyModule_Create(&module_def);
if (!module)
return NULL;
ret = PyModule_AddStringConstant(module, "__version__",
gpiod_version_string());
if (ret) {
Py_DECREF(module);
return NULL;
}
for (type = types; *type; type++) {
ret = PyModule_AddType(module, *type);
if (ret) {
Py_DECREF(module);
return NULL;
}
}
for (modconst = module_constants; modconst->name; modconst++) {
ret = PyModule_AddIntConstant(module,
modconst->name, modconst->val);
if (ret) {
Py_DECREF(module);
return NULL;
}
}
return module;
}

View File

@@ -0,0 +1,402 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
#include "internal.h"
typedef struct {
PyObject_HEAD;
struct gpiod_line_request *request;
unsigned int *offsets;
int *values;
size_t num_lines;
struct gpiod_edge_event_buffer *buffer;
} request_object;
static int request_init(PyObject *Py_UNUSED(ignored0),
PyObject *Py_UNUSED(ignored1),
PyObject *Py_UNUSED(ignored2))
{
PyErr_SetString(PyExc_NotImplementedError,
"_ext.LineRequest cannot be instantiated");
return -1;
}
static void request_finalize(request_object *self)
{
if (self->request)
PyObject_CallMethod((PyObject *)self, "release", "");
if (self->offsets)
PyMem_Free(self->offsets);
if (self->values)
PyMem_Free(self->values);
if (self->buffer)
gpiod_edge_event_buffer_free(self->buffer);
}
static PyObject *
request_num_lines(request_object *self, void *Py_UNUSED(ignored))
{
return PyLong_FromUnsignedLong(
gpiod_line_request_get_num_lines(self->request));
}
static PyObject *request_offsets(request_object *self, void *Py_UNUSED(ignored))
{
PyObject *lines, *line;
unsigned int *offsets;
size_t num_lines, i;
int ret;
num_lines = gpiod_line_request_get_num_lines(self->request);
offsets = PyMem_Calloc(num_lines, sizeof(unsigned int));
if (!offsets)
return PyErr_NoMemory();
gpiod_line_request_get_offsets(self->request, offsets);
lines = PyList_New(num_lines);
if (!lines) {
PyMem_Free(offsets);
return NULL;
}
for (i = 0; i < num_lines; i++) {
line = PyLong_FromUnsignedLong(offsets[i]);
if (!line) {
Py_DECREF(lines);
PyMem_Free(offsets);
return NULL;
}
ret = PyList_SetItem(lines, i, line);
if (ret) {
Py_DECREF(line);
Py_DECREF(lines);
PyMem_Free(offsets);
return NULL;
}
}
PyMem_Free(offsets);
return lines;
}
static PyObject *request_fd(request_object *self, void *Py_UNUSED(ignored))
{
return PyLong_FromLong(gpiod_line_request_get_fd(self->request));
}
static PyGetSetDef request_getset[] = {
{
.name = "num_lines",
.get = (getter)request_num_lines,
},
{
.name = "offsets",
.get = (getter)request_offsets,
},
{
.name = "fd",
.get = (getter)request_fd,
},
{ }
};
static PyObject *
request_release(request_object *self, PyObject *Py_UNUSED(ignored))
{
Py_BEGIN_ALLOW_THREADS;
gpiod_line_request_release(self->request);
Py_END_ALLOW_THREADS;
self->request = NULL;
Py_RETURN_NONE;
}
static void clear_buffers(request_object *self)
{
memset(self->offsets, 0, self->num_lines * sizeof(unsigned int));
memset(self->values, 0, self->num_lines * sizeof(int));
}
static PyObject *request_get_values(request_object *self, PyObject *args)
{
PyObject *offsets, *values, *val, *type, *iter, *next;
Py_ssize_t num_offsets, pos;
int ret;
ret = PyArg_ParseTuple(args, "OO", &offsets, &values);
if (!ret)
return NULL;
num_offsets = PyObject_Size(offsets);
if (num_offsets < 0)
return NULL;
type = Py_gpiod_GetGlobalType("Value");
if (!type)
return NULL;
iter = PyObject_GetIter(offsets);
if (!iter)
return NULL;
clear_buffers(self);
for (pos = 0;; pos++) {
next = PyIter_Next(iter);
if (!next) {
Py_DECREF(iter);
break;
}
self->offsets[pos] = Py_gpiod_PyLongAsUnsignedInt(next);
Py_DECREF(next);
if (PyErr_Occurred()) {
Py_DECREF(iter);
return NULL;
}
}
Py_BEGIN_ALLOW_THREADS;
ret = gpiod_line_request_get_values_subset(self->request,
num_offsets,
self->offsets,
self->values);
Py_END_ALLOW_THREADS;
if (ret)
return Py_gpiod_SetErrFromErrno();
for (pos = 0; pos < num_offsets; pos++) {
val = PyObject_CallFunction(type, "i", self->values[pos]);
if (!val)
return NULL;
ret = PyList_SetItem(values, pos, val);
if (ret) {
Py_DECREF(val);
return NULL;
}
}
Py_RETURN_NONE;
}
static PyObject *request_set_values(request_object *self, PyObject *args)
{
PyObject *values, *key, *val, *val_stripped;
Py_ssize_t pos = 0;
int ret;
ret = PyArg_ParseTuple(args, "O", &values);
if (!ret)
return NULL;
clear_buffers(self);
while (PyDict_Next(values, &pos, &key, &val)) {
self->offsets[pos - 1] = Py_gpiod_PyLongAsUnsignedInt(key);
if (PyErr_Occurred())
return NULL;
val_stripped = PyObject_GetAttrString(val, "value");
if (!val_stripped)
return NULL;
self->values[pos - 1] = PyLong_AsLong(val_stripped);
Py_DECREF(val_stripped);
if (PyErr_Occurred())
return NULL;
}
Py_BEGIN_ALLOW_THREADS;
ret = gpiod_line_request_set_values_subset(self->request,
pos,
self->offsets,
self->values);
Py_END_ALLOW_THREADS;
if (ret)
return Py_gpiod_SetErrFromErrno();
Py_RETURN_NONE;
}
static PyObject *request_reconfigure_lines(request_object *self, PyObject *args)
{
struct gpiod_line_config *line_cfg;
PyObject *line_cfg_obj;
int ret;
ret = PyArg_ParseTuple(args, "O", &line_cfg_obj);
if (!ret)
return NULL;
line_cfg = Py_gpiod_LineConfigGetData(line_cfg_obj);
if (!line_cfg)
return NULL;
Py_BEGIN_ALLOW_THREADS;
ret = gpiod_line_request_reconfigure_lines(self->request, line_cfg);
Py_END_ALLOW_THREADS;
if (ret)
return Py_gpiod_SetErrFromErrno();
Py_RETURN_NONE;
}
static PyObject *request_read_edge_event(request_object *self, PyObject *args)
{
PyObject *max_events_obj, *event_obj, *events, *type;
size_t max_events, num_events, i;
struct gpiod_edge_event *event;
int ret;
ret = PyArg_ParseTuple(args, "O", &max_events_obj);
if (!ret)
return NULL;
if (max_events_obj != Py_None) {
max_events = PyLong_AsSize_t(max_events_obj);
if (PyErr_Occurred())
return NULL;
} else {
max_events = 64;
}
type = Py_gpiod_GetGlobalType("EdgeEvent");
if (!type)
return NULL;
Py_BEGIN_ALLOW_THREADS;
ret = gpiod_line_request_read_edge_event(self->request,
self->buffer, max_events);
Py_END_ALLOW_THREADS;
if (ret < 0)
return Py_gpiod_SetErrFromErrno();
num_events = ret;
events = PyList_New(num_events);
if (!events)
return NULL;
for (i = 0; i < num_events; i++) {
event = gpiod_edge_event_buffer_get_event(self->buffer, i);
if (!event) {
Py_DECREF(events);
return NULL;
}
event_obj = PyObject_CallFunction(type, "iKiii",
gpiod_edge_event_get_event_type(event),
gpiod_edge_event_get_timestamp_ns(event),
gpiod_edge_event_get_line_offset(event),
gpiod_edge_event_get_global_seqno(event),
gpiod_edge_event_get_line_seqno(event));
if (!event_obj) {
Py_DECREF(events);
return NULL;
}
ret = PyList_SetItem(events, i, event_obj);
if (ret) {
Py_DECREF(event_obj);
Py_DECREF(events);
return NULL;
}
}
return events;
}
static PyMethodDef request_methods[] = {
{
.ml_name = "release",
.ml_meth = (PyCFunction)request_release,
.ml_flags = METH_NOARGS,
},
{
.ml_name = "get_values",
.ml_meth = (PyCFunction)request_get_values,
.ml_flags = METH_VARARGS,
},
{
.ml_name = "set_values",
.ml_meth = (PyCFunction)request_set_values,
.ml_flags = METH_VARARGS,
},
{
.ml_name = "reconfigure_lines",
.ml_meth = (PyCFunction)request_reconfigure_lines,
.ml_flags = METH_VARARGS,
},
{
.ml_name = "read_edge_event",
.ml_meth = (PyCFunction)request_read_edge_event,
.ml_flags = METH_VARARGS,
},
{ }
};
PyTypeObject request_type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "gpiod._ext.Request",
.tp_basicsize = sizeof(request_object),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)request_init,
.tp_finalize = (destructor)request_finalize,
.tp_dealloc = (destructor)Py_gpiod_dealloc,
.tp_getset = request_getset,
.tp_methods = request_methods,
};
PyObject *Py_gpiod_MakeRequestObject(struct gpiod_line_request *request,
size_t event_buffer_size)
{
struct gpiod_edge_event_buffer *buffer;
request_object *req_obj;
unsigned int *offsets;
size_t num_lines;
int *values;
num_lines = gpiod_line_request_get_num_lines(request);
req_obj = PyObject_New(request_object, &request_type);
if (!req_obj)
return NULL;
offsets = PyMem_Calloc(num_lines, sizeof(unsigned int));
if (!offsets) {
Py_DECREF(req_obj);
return NULL;
}
values = PyMem_Calloc(num_lines, sizeof(int));
if (!values) {
PyMem_Free(offsets);
Py_DECREF(req_obj);
return NULL;
}
buffer = gpiod_edge_event_buffer_new(event_buffer_size);
if (!buffer) {
PyMem_Free(values);
PyMem_Free(offsets);
Py_DECREF(req_obj);
return Py_gpiod_SetErrFromErrno();
}
req_obj->request = request;
req_obj->offsets = offsets;
req_obj->values = values;
req_obj->num_lines = num_lines;
req_obj->buffer = buffer;
return (PyObject *)req_obj;
}

View File

@@ -0,0 +1,33 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
from . import _ext
from .line_info import LineInfo
from dataclasses import dataclass
from enum import Enum
@dataclass(frozen=True, init=False, repr=False)
class InfoEvent:
"""
Immutable object containing data about a single line info event.
"""
class Type(Enum):
LINE_REQUESTED = _ext.INFO_EVENT_TYPE_LINE_REQUESTED
LINE_RELEASED = _ext.INFO_EVENT_TYPE_LINE_RELEASED
LINE_CONFIG_CHANGED = _ext.INFO_EVENT_TYPE_LINE_CONFIG_CHANGED
event_type: Type
timestamp_ns: int
line_info: LineInfo
def __init__(self, event_type: int, timestamp_ns: int, line_info: LineInfo):
object.__setattr__(self, "event_type", InfoEvent.Type(event_type))
object.__setattr__(self, "timestamp_ns", timestamp_ns)
object.__setattr__(self, "line_info", line_info)
def __str__(self):
return "<InfoEvent type={} timestamp_ns={} line_info={}>".format(
self.event_type, self.timestamp_ns, self.line_info
)

View File

@@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
from datetime import timedelta
from select import select
from typing import Optional, Union
def poll_fd(fd: int, timeout: Optional[Union[timedelta, float]] = None) -> bool:
if timeout is None:
timeout = 0.0
if isinstance(timeout, timedelta):
sec = timeout.total_seconds()
else:
sec = timeout
readable, _, _ = select([fd], [], [], sec)
return True if fd in readable else False

View File

@@ -0,0 +1,56 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
from . import _ext
from enum import Enum
class Value(Enum):
"""Logical line states."""
INACTIVE = _ext.VALUE_INACTIVE
ACTIVE = _ext.VALUE_ACTIVE
class Direction(Enum):
"""Direction settings."""
AS_IS = _ext.DIRECTION_AS_IS
INPUT = _ext.DIRECTION_INPUT
OUTPUT = _ext.DIRECTION_OUTPUT
class Bias(Enum):
"""Internal bias settings."""
AS_IS = _ext.BIAS_AS_IS
UNKNOWN = _ext.BIAS_UNKNOWN
DISABLED = _ext.BIAS_DISABLED
PULL_UP = _ext.BIAS_PULL_UP
PULL_DOWN = _ext.BIAS_PULL_DOWN
class Drive(Enum):
"""Drive settings."""
PUSH_PULL = _ext.DRIVE_PUSH_PULL
OPEN_DRAIN = _ext.DRIVE_OPEN_DRAIN
OPEN_SOURCE = _ext.DRIVE_OPEN_SOURCE
class Edge(Enum):
"""Edge detection settings."""
NONE = _ext.EDGE_NONE
RISING = _ext.EDGE_RISING
FALLING = _ext.EDGE_FALLING
BOTH = _ext.EDGE_BOTH
class Clock(Enum):
"""Event clock settings."""
MONOTONIC = _ext.CLOCK_MONOTONIC
REALTIME = _ext.CLOCK_REALTIME
HTE = _ext.CLOCK_HTE

View File

@@ -0,0 +1,73 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
from . import _ext
from dataclasses import dataclass
from datetime import timedelta
from gpiod.line import Direction, Bias, Drive, Edge, Clock
@dataclass(frozen=True, init=False, repr=False)
class LineInfo:
"""
Snapshot of a line's status.
"""
offset: int
name: str
used: bool
consumer: str
direction: Direction
active_low: bool
bias: Bias
drive: Drive
edge_detection: Edge
event_clock: Clock
debounced: bool
debounce_period: timedelta
def __init__(
self,
offset: int,
name: str,
used: bool,
consumer: str,
direction: int,
active_low: bool,
bias: int,
drive: int,
edge_detection: int,
event_clock: int,
debounced: bool,
debounce_period_us: int,
):
object.__setattr__(self, "offset", offset)
object.__setattr__(self, "name", name)
object.__setattr__(self, "used", used)
object.__setattr__(self, "consumer", consumer)
object.__setattr__(self, "direction", Direction(direction))
object.__setattr__(self, "active_low", active_low)
object.__setattr__(self, "bias", Bias(bias))
object.__setattr__(self, "drive", Drive(drive))
object.__setattr__(self, "edge_detection", Edge(edge_detection))
object.__setattr__(self, "event_clock", Clock(event_clock))
object.__setattr__(self, "debounced", debounced)
object.__setattr__(
self, "debounce_period", timedelta(microseconds=debounce_period_us)
)
def __str__(self):
return '<LineInfo offset={} name="{}" used={} consumer="{}" direction={} active_low={} bias={} drive={} edge_detection={} event_clock={} debounced={} debounce_period={}>'.format(
self.offset,
self.name,
self.used,
self.consumer,
self.direction,
self.active_low,
self.bias,
self.drive,
self.edge_detection,
self.event_clock,
self.debounced,
self.debounce_period,
)

View File

@@ -0,0 +1,247 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
from . import _ext
from .edge_event import EdgeEvent
from .exception import RequestReleasedError
from .internal import poll_fd
from .line import Value
from .line_settings import LineSettings, _line_settings_to_ext
from collections.abc import Iterable
from datetime import timedelta
from typing import Optional, Union
class LineRequest:
"""
Stores the context of a set of requested GPIO lines.
"""
def __init__(self, req: _ext.Request):
"""
DON'T USE
LineRequest objects can only be instantiated by a Chip parent. This is
not part of stable API.
"""
self._req = req
def __bool__(self) -> bool:
"""
Boolean conversion for GPIO line requests.
Returns:
True if the request is live and False if it's been released.
"""
return True if self._req else False
def __enter__(self):
"""
Controlled execution enter callback.
"""
self._check_released()
return self
def __exit__(self, exc_type, exc_value, traceback):
"""
Controlled execution exit callback.
"""
self.release()
def _check_released(self) -> None:
if not self._req:
raise RequestReleasedError()
def release(self) -> None:
"""
Release this request and free all associated resources. The object must
not be used after a call to this method.
"""
self._check_released()
self._req.release()
self._req = None
def get_value(self, line: Union[int, str]) -> Value:
"""
Get a single GPIO line value.
Args:
line:
Offset or name of the line to get value for.
Returns:
Logical value of the line.
"""
return self.get_values([line])[0]
def _check_line_name(self, line):
if isinstance(line, str):
if line not in self._name_map:
raise ValueError("unknown line name: {}".format(line))
return True
return False
def get_values(
self, lines: Optional[Iterable[Union[int, str]]] = None
) -> list[Value]:
"""
Get values of a set of GPIO lines.
Args:
lines:
List of names or offsets of GPIO lines to get values for. Can be
None in which case all requested lines will be read.
Returns:
List of logical line values.
"""
self._check_released()
lines = lines or self._lines
offsets = [
self._name_map[line] if self._check_line_name(line) else line
for line in lines
]
buf = [None] * len(lines)
self._req.get_values(offsets, buf)
return buf
def set_value(self, line: Union[int, str], value: Value) -> None:
"""
Set the value of a single GPIO line.
Args:
line:
Offset or name of the line to set.
value:
New value.
"""
self.set_values({line: value})
def set_values(self, values: dict[Union[int, str], Value]) -> None:
"""
Set the values of a subset of GPIO lines.
Args:
values:
Dictionary mapping line offsets or names to desired values.
"""
self._check_released()
mapped = {
self._name_map[line] if self._check_line_name(line) else line: values[line]
for line in values
}
self._req.set_values(mapped)
def reconfigure_lines(
self, config: dict[tuple[Union[int, str]], LineSettings]
) -> None:
"""
Reconfigure requested lines.
Args:
config
Dictionary mapping offsets or names (or tuples thereof) to
LineSettings. If None is passed as the value of the mapping,
default settings are used.
"""
self._check_released()
line_cfg = _ext.LineConfig()
for lines, settings in config.items():
if isinstance(lines, int) or isinstance(lines, str):
lines = [lines]
offsets = [
self._name_map[line] if self._check_line_name(line) else line
for line in lines
]
line_cfg.add_line_settings(offsets, _line_settings_to_ext(settings))
self._req.reconfigure_lines(line_cfg)
def wait_edge_event(
self, timeout: Optional[Union[timedelta, float]] = None
) -> bool:
"""
Wait for edge events on any of the requested lines.
Args:
timeout:
Wait time limit expressed as either a datetime.timedelta object
or the number of seconds stored in a float.
Returns:
True if events are ready to be read. False on timeout.
"""
self._check_released()
return poll_fd(self.fd, timeout)
def read_edge_event(self, max_events: Optional[int] = None) -> list[EdgeEvent]:
"""
Read a number of edge events from a line request.
Args:
max_events:
Maximum number of events to read.
Returns:
List of read EdgeEvent objects.
"""
self._check_released()
return self._req.read_edge_event(max_events)
def __str__(self):
"""
Return a user-friendly, human-readable description of this request.
"""
if not self._req:
return "<LineRequest RELEASED>"
return "<LineRequest num_lines={} offsets={} fd={}>".format(
self.num_lines, self.offsets, self.fd
)
@property
def num_lines(self) -> int:
"""
Number of requested lines.
"""
self._check_released()
return len(self._offsets)
@property
def offsets(self) -> list[int]:
"""
List of requested offsets. Lines requested by name are mapped to their
offsets.
"""
self._check_released()
return self._offsets
@property
def lines(self) -> list[Union[int, str]]:
"""
List of requested lines. Lines requested by name are listed as such.
"""
self._check_released()
return self._lines
@property
def fd(self) -> int:
"""
File descriptor associated with this request.
"""
self._check_released()
return self._req.fd

View File

@@ -0,0 +1,62 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
from . import _ext
from dataclasses import dataclass
from datetime import timedelta
from gpiod.line import Direction, Bias, Drive, Edge, Clock, Value
@dataclass(repr=False)
class LineSettings:
"""
Stores a set of line properties.
"""
direction: Direction = Direction.AS_IS
edge_detection: Edge = Edge.NONE
bias: Bias = Bias.AS_IS
drive: Drive = Drive.PUSH_PULL
active_low: bool = False
debounce_period: timedelta = timedelta()
event_clock: Clock = Clock.MONOTONIC
output_value: Value = Value.INACTIVE
# __repr__ generated by @dataclass uses repr for enum members resulting in
# an unusable representation as those are of the form: <NAME: $value>
def __repr__(self):
return "LineSettings(direction={}, edge_detection={} bias={} drive={} active_low={} debounce_period={} event_clock={} output_value={})".format(
str(self.direction),
str(self.edge_detection),
str(self.bias),
str(self.drive),
self.active_low,
repr(self.debounce_period),
str(self.event_clock),
str(self.output_value),
)
def __str__(self):
return "<LineSettings direction={} edge_detection={} bias={} drive={} active_low={} debounce_period={} event_clock={} output_value={}>".format(
self.direction,
self.edge_detection,
self.bias,
self.drive,
self.active_low,
self.debounce_period,
self.event_clock,
self.output_value,
)
def _line_settings_to_ext(settings: LineSettings) -> _ext.LineSettings:
return _ext.LineSettings(
direction=settings.direction.value,
edge_detection=settings.edge_detection.value,
bias=settings.bias.value,
drive=settings.drive.value,
active_low=settings.active_low,
debounce_period=int(settings.debounce_period.total_seconds() * 1000000),
event_clock=settings.event_clock.value,
output_value=settings.output_value.value,
)

File diff suppressed because it is too large Load Diff

47
bindings/python/setup.py Normal file
View File

@@ -0,0 +1,47 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
from os import environ
from setuptools import setup, Extension, find_packages
gpiod_ext = Extension(
"gpiod._ext",
sources=[
"gpiod/ext/chip.c",
"gpiod/ext/common.c",
"gpiod/ext/line-config.c",
"gpiod/ext/line-settings.c",
"gpiod/ext/module.c",
"gpiod/ext/request.c",
],
define_macros=[("_GNU_SOURCE", "1")],
libraries=["gpiod"],
extra_compile_args=["-Wall", "-Wextra"],
)
gpiosim_ext = Extension(
"tests.gpiosim._ext",
sources=["tests/gpiosim/ext.c"],
define_macros=[("_GNU_SOURCE", "1")],
libraries=["gpiosim"],
extra_compile_args=["-Wall", "-Wextra"],
)
extensions = [gpiod_ext]
with_tests = bool(environ["GPIOD_WITH_TESTS"])
if with_tests:
extensions.append(gpiosim_ext)
# FIXME Find a better way to get the version
version = None
try:
version = environ["GPIOD_VERSION_STR"]
except KeyError:
pass
setup(
name="gpiod",
packages=find_packages(include=["gpiod"]),
ext_modules=extensions,
version=version,
)

View File

@@ -1,13 +1,17 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
dist_bin_SCRIPTS = gpiod_py_test.py
SUBDIRS = gpiosim
pyexec_LTLIBRARIES = gpiomockup.la
gpiomockup_la_SOURCES = gpiomockupmodule.c
gpiomockup_la_CFLAGS = -I$(top_srcdir)/tests/mockup/
gpiomockup_la_CFLAGS += -Wall -Wextra -g -std=gnu89 $(PYTHON_CPPFLAGS)
gpiomockup_la_LDFLAGS = -module -avoid-version
gpiomockup_la_LIBADD = $(top_builddir)/tests/mockup/libgpiomockup.la
gpiomockup_la_LIBADD += $(PYTHON_LIBS)
EXTRA_DIST = \
helpers.py \
__init__.py \
__main__.py \
tests_chip_info.py \
tests_chip.py \
tests_edge_event.py \
tests_info_event.py \
tests_line_info.py \
tests_line_request.py \
tests_line_settings.py \
tests_module.py

Some files were not shown because too many files have changed in this diff Show More