From 8078a4a2ad90caf95ef6426c2b5cddeeddc9dc57 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 25 Oct 2017 21:11:37 +0200 Subject: [PATCH] bindings: implement C++ bindings Wrap the C API in a set of C++ classes which allow to use all libgpiod functionalities in an object-oriented manner. Include simplified reimplementations of gpio-tools and a set of tests/examples showing how to use the C++ interface. The C++ API is fully documented in doxygen. Signed-off-by: Bartosz Golaszewski --- .gitignore | 8 + Makefile.am | 6 +- bindings/Makefile.am | 16 + bindings/cxx/Makefile.am | 19 + bindings/cxx/chip.cpp | 192 +++++ bindings/cxx/examples/Makefile.am | 34 + bindings/cxx/examples/gpio_cxx_tests.cpp | 161 ++++ bindings/cxx/examples/gpiodetectcxx.cpp | 33 + bindings/cxx/examples/gpiofindcxx.cpp | 33 + bindings/cxx/examples/gpiogetcxx.cpp | 47 ++ bindings/cxx/examples/gpioinfocxx.cpp | 55 ++ bindings/cxx/examples/gpiomoncxx.cpp | 73 ++ bindings/cxx/examples/gpiosetcxx.cpp | 56 ++ bindings/cxx/gpiod.hpp | 959 +++++++++++++++++++++++ bindings/cxx/iter.cpp | 146 ++++ bindings/cxx/line.cpp | 238 ++++++ bindings/cxx/line_bulk.cpp | 262 +++++++ configure.ac | 27 +- 18 files changed, 2362 insertions(+), 3 deletions(-) create mode 100644 bindings/Makefile.am create mode 100644 bindings/cxx/Makefile.am create mode 100644 bindings/cxx/chip.cpp create mode 100644 bindings/cxx/examples/Makefile.am create mode 100644 bindings/cxx/examples/gpio_cxx_tests.cpp create mode 100644 bindings/cxx/examples/gpiodetectcxx.cpp create mode 100644 bindings/cxx/examples/gpiofindcxx.cpp create mode 100644 bindings/cxx/examples/gpiogetcxx.cpp create mode 100644 bindings/cxx/examples/gpioinfocxx.cpp create mode 100644 bindings/cxx/examples/gpiomoncxx.cpp create mode 100644 bindings/cxx/examples/gpiosetcxx.cpp create mode 100644 bindings/cxx/gpiod.hpp create mode 100644 bindings/cxx/iter.cpp create mode 100644 bindings/cxx/line.cpp create mode 100644 bindings/cxx/line_bulk.cpp diff --git a/.gitignore b/.gitignore index ccd5bbe..49c256f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ !.gitignore libgpiod.la +libgpiodcxx.la libtools-common.la gpiodetect gpioinfo @@ -7,6 +8,13 @@ gpioget gpioset gpiomon gpiofind +gpio_cxx_tests +gpiodetectcxx +gpiofindcxx +gpiogetcxx +gpioinfocxx +gpiomoncxx +gpiosetcxx *.o *.lo doc diff --git a/Makefile.am b/Makefile.am index 9b1d00b..974e10f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -9,7 +9,7 @@ ACLOCAL_AMFLAGS = -I m4 AUTOMAKE_OPTIONS = foreign -SUBDIRS = include src +SUBDIRS = include src bindings pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libgpiod.pc @@ -23,7 +23,9 @@ endif if HAS_DOXYGEN doc: - @(cat Doxyfile; echo PROJECT_NUMBER = $(VERSION_STR)) | doxygen - + @(cat Doxyfile; \ + echo PROJECT_NUMBER = $(VERSION_STR); \ + echo INPUT += bindings/cxx/gpiod.hpp) | doxygen - .PHONY: doc EXTRA_DIST = Doxyfile diff --git a/bindings/Makefile.am b/bindings/Makefile.am new file mode 100644 index 0000000..b398a2d --- /dev/null +++ b/bindings/Makefile.am @@ -0,0 +1,16 @@ +# +# Copyright (C) 2017-2018 Bartosz Golaszewski +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or (at +# your option) any later version. +# + +SUBDIRS = . + +if WITH_BINDINGS_CXX + +SUBDIRS += cxx + +endif diff --git a/bindings/cxx/Makefile.am b/bindings/cxx/Makefile.am new file mode 100644 index 0000000..bbf938f --- /dev/null +++ b/bindings/cxx/Makefile.am @@ -0,0 +1,19 @@ +# +# Copyright (C) 2017-2018 Bartosz Golaszewski +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or (at +# your option) any later version. +# + +lib_LTLIBRARIES = libgpiodcxx.la +libgpiodcxx_la_SOURCES = chip.cpp 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_LDFLAGS = -version-number $(subst .,:,$(PACKAGE_VERSION)) +libgpiodcxx_la_LDFLAGS += -lgpiod -L$(top_builddir)/src/lib + +include_HEADERS = gpiod.hpp + +SUBDIRS = . examples diff --git a/bindings/cxx/chip.cpp b/bindings/cxx/chip.cpp new file mode 100644 index 0000000..5c626a9 --- /dev/null +++ b/bindings/cxx/chip.cpp @@ -0,0 +1,192 @@ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include + +#include +#include +#include +#include + +namespace gpiod { + +namespace { + +::gpiod_chip* open_lookup(const ::std::string& device) +{ + return ::gpiod_chip_open_lookup(device.c_str()); +} + +::gpiod_chip* open_by_path(const ::std::string& device) +{ + return ::gpiod_chip_open(device.c_str()); +} + +::gpiod_chip* open_by_name(const ::std::string& device) +{ + return ::gpiod_chip_open_by_name(device.c_str()); +} + +::gpiod_chip* open_by_label(const ::std::string& device) +{ + return ::gpiod_chip_open_by_label(device.c_str()); +} + +::gpiod_chip* open_by_number(const ::std::string& device) +{ + return ::gpiod_chip_open_by_number(::std::stoul(device)); +} + +using open_func = ::std::function<::gpiod_chip* (const ::std::string&)>; + +const ::std::map open_funcs = { + { chip::OPEN_LOOKUP, open_lookup, }, + { chip::OPEN_BY_PATH, open_by_path, }, + { chip::OPEN_BY_NAME, open_by_name, }, + { chip::OPEN_BY_LABEL, open_by_label, }, + { chip::OPEN_BY_NUMBER, open_by_number, }, +}; + +void chip_deleter(::gpiod_chip* chip) +{ + ::gpiod_chip_close(chip); +} + +} /* namespace */ + +chip::chip(const ::std::string& device, int how) + : _m_chip() +{ + this->open(device, how); +} + +chip::chip(::gpiod_chip* chip) + : _m_chip(chip, chip_deleter) +{ + +} + +void chip::open(const ::std::string& device, int how) +{ + auto func = open_funcs.at(how); + + ::gpiod_chip *chip = func(device); + if (!chip) + throw ::std::system_error(errno, ::std::system_category(), + "cannot open GPIO device " + device); + + this->_m_chip.reset(chip, chip_deleter); +} + +void chip::reset(void) noexcept +{ + this->_m_chip.reset(); +} + +::std::string chip::name(void) const +{ + this->throw_if_noref(); + + const char* name = ::gpiod_chip_name(this->_m_chip.get()); + + return ::std::move(name ? ::std::string(name) : ::std::string()); +} + +::std::string chip::label(void) const +{ + this->throw_if_noref(); + + const char* label = ::gpiod_chip_label(this->_m_chip.get()); + + return ::std::move(label ? ::std::string(label) : ::std::string()); +} + +unsigned int chip::num_lines(void) const +{ + this->throw_if_noref(); + + return ::gpiod_chip_num_lines(this->_m_chip.get()); +} + +line chip::get_line(unsigned int offset) const +{ + this->throw_if_noref(); + + 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 ::std::move(line(line_handle, *this)); +} + +line chip::find_line(const ::std::string& name) const +{ + this->throw_if_noref(); + + ::gpiod_line* handle = ::gpiod_chip_find_line(this->_m_chip.get(), name.c_str()); + if (!handle && errno != ENOENT) + throw ::std::system_error(errno, ::std::system_category(), + "error looking up GPIO line by name"); + + return ::std::move(handle ? line(handle, *this) : line()); +} + +line_bulk chip::get_lines(const ::std::vector& offsets) const +{ + line_bulk lines; + + for (auto& it: offsets) + lines.add(this->get_line(it)); + + return ::std::move(lines); +} + +line_bulk chip::find_lines(const ::std::vector<::std::string>& names) const +{ + line_bulk lines; + + for (auto& it: names) + lines.add(this->find_line(it)); + + return ::std::move(lines); +} + +bool chip::operator==(const chip& rhs) const noexcept +{ + return this->_m_chip.get() == rhs._m_chip.get(); +} + +bool chip::operator!=(const chip& rhs) const noexcept +{ + return this->_m_chip.get() != rhs._m_chip.get(); +} + +chip::operator bool(void) const noexcept +{ + return this->_m_chip.get() != nullptr; +} + +bool chip::operator!(void) const noexcept +{ + return this->_m_chip.get() == nullptr; +} + +void chip::throw_if_noref(void) const +{ + if (!this->_m_chip.get()) + throw ::std::logic_error("object not associated with an open GPIO chip"); +} + +} /* namespace gpiod */ diff --git a/bindings/cxx/examples/Makefile.am b/bindings/cxx/examples/Makefile.am new file mode 100644 index 0000000..4f5ce83 --- /dev/null +++ b/bindings/cxx/examples/Makefile.am @@ -0,0 +1,34 @@ +# +# Copyright (C) 2017-2018 Bartosz Golaszewski +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or (at +# your option) any later version. +# + +AM_CPPFLAGS = -I$(top_srcdir)/bindings/cxx/ -I$(top_srcdir)/include +AM_CPPFLAGS += -Wall -Wextra -g -std=gnu++11 +AM_LDFLAGS = -lgpiodcxx -L$(top_builddir)/bindings/cxx/ + +check_PROGRAMS = gpio_cxx_tests \ + gpiodetectcxx \ + gpiofindcxx \ + gpiogetcxx \ + gpioinfocxx \ + gpiomoncxx \ + gpiosetcxx + +gpio_cxx_tests_SOURCES = gpio_cxx_tests.cpp + +gpiodetectcxx_SOURCES = gpiodetectcxx.cpp + +gpiofindcxx_SOURCES = gpiofindcxx.cpp + +gpiogetcxx_SOURCES = gpiogetcxx.cpp + +gpioinfocxx_SOURCES = gpioinfocxx.cpp + +gpiomoncxx_SOURCES = gpiomoncxx.cpp + +gpiosetcxx_SOURCES = gpiosetcxx.cpp diff --git a/bindings/cxx/examples/gpio_cxx_tests.cpp b/bindings/cxx/examples/gpio_cxx_tests.cpp new file mode 100644 index 0000000..b6de15c --- /dev/null +++ b/bindings/cxx/examples/gpio_cxx_tests.cpp @@ -0,0 +1,161 @@ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +/* Misc tests/examples of the C++ API. */ + +#include + +#include +#include +#include + +namespace { + +using test_func = ::std::function; + +::std::vector<::std::pair<::std::string, test_func>> test_funcs; + +struct test_case +{ + test_case(const ::std::string& name, test_func& func) + { + test_funcs.push_back(::std::make_pair(name, func)); + } +}; + +#define TEST_CASE(_func) \ + test_func _test_func_##_func(_func); \ + test_case _test_case_##_func(#_func, _test_func_##_func) + +void chip_info(void) +{ + ::gpiod::chip chip("gpiochip0"); + + ::std::cout << "chip name: " << chip.name() << ::std::endl; + ::std::cout << "chip label: " << chip.label() << ::std::endl; + ::std::cout << "number of lines " << chip.num_lines() << ::std::endl; +} +TEST_CASE(chip_info); + +void chip_open_different_modes(void) +{ + ::gpiod::chip by_name("gpiochip0", ::gpiod::chip::OPEN_BY_NAME); + ::gpiod::chip by_path("/dev/gpiochip0", ::gpiod::chip::OPEN_BY_PATH); + ::gpiod::chip by_label("gpio-mockup-A", ::gpiod::chip::OPEN_BY_LABEL); + ::gpiod::chip by_number("0", ::gpiod::chip::OPEN_BY_NUMBER); +} +TEST_CASE(chip_open_different_modes); + +void chip_line_ops(void) +{ + ::gpiod::chip chip("gpiochip0"); + + ::gpiod::line line = chip.get_line(3); + ::std::cout << "Got line by offset: " << line.offset() << ::std::endl; + + line = chip.find_line("gpio-mockup-A-4"); + ::std::cout << "Got line by name: " << line.name() << ::std::endl; + + ::gpiod::line_bulk lines = chip.get_lines({ 1, 2, 3, 6, 6 }); + ::std::cout << "Got multiple lines by offset: "; + for (auto& it: lines) + ::std::cout << it.offset() << " "; + ::std::cout << ::std::endl; + + lines = chip.find_lines({ "gpio-mockup-A-1", "gpio-mockup-A-4", "gpio-mockup-A-7" }); + ::std::cout << "Got multiple lines by name: "; + for (auto& it: lines) + ::std::cout << it.name() << " "; + ::std::cout << ::std::endl; +} +TEST_CASE(chip_line_ops); + +void line_info(void) +{ + ::gpiod::chip chip("gpiochip0"); + ::gpiod::line line = chip.get_line(2); + + ::std::cout << "line offset: " << line.offset() << ::std::endl; + ::std::cout << "line name: " << line.name() << ::std::endl; + ::std::cout << "line direction: " + << (line.direction() == ::gpiod::line::DIRECTION_INPUT ? + "input" : "output") << ::std::endl; +} +TEST_CASE(line_info); + +void empty_objects(void) +{ + ::std::cout << "Are initialized line & chip objects 'false'?" << ::std::endl; + + ::gpiod::line line; + if (line) + throw ::std::logic_error("line built with a default constructor should be 'false'"); + + ::gpiod::chip chip; + if (chip) + throw ::std::logic_error("chip built with a default constructor should be 'false'"); + + ::std::cout << "YES" << ::std::endl; +} +TEST_CASE(empty_objects); + +void line_bulk_iterator(void) +{ + ::std::cout << "Checking line_bulk iterators" << ::std::endl; + + ::gpiod::chip chip("gpiochip0"); + ::gpiod::line_bulk bulk = chip.get_lines({ 0, 1, 2, 3, 4 }); + + for (auto& it: bulk) + ::std::cout << it.name() << ::std::endl; + + ::std::cout << "DONE" << ::std::endl; +} +TEST_CASE(line_bulk_iterator); + +void single_line_test(void) +{ + const ::std::string line_name("gpio-mockup-A-4"); + + ::std::cout << "Looking up a GPIO line by name (" << line_name << ")" << ::std::endl; + + ::gpiod::line line = ::gpiod::find_line(line_name); + if (!line) + throw ::std::runtime_error(line_name + " line not found"); + + ::std::cout << "Requesting a single line" << ::std::endl; + + ::gpiod::line_request conf; + conf.consumer = "gpiod_cxx_tests"; + conf.request_type = ::gpiod::line_request::DIRECTION_OUTPUT; + + line.request(conf, 1); + ::std::cout << "Reading value" << ::std::endl; + ::std::cout << line.get_value() << ::std::endl; + ::std::cout << "Changing value to 0" << ::std::endl; + line.set_value(0); + ::std::cout << "Reading value again" << ::std::endl; + ::std::cout << line.get_value() << ::std::endl; +} +TEST_CASE(single_line_test); + +} /* namespace */ + +int main(int, char **) +{ + for (auto& it: test_funcs) { + ::std::cout << "=================================================" << ::std::endl; + ::std::cout << it.first << ":\n" << ::std::endl; + it.second(); + } + + return EXIT_SUCCESS; +} diff --git a/bindings/cxx/examples/gpiodetectcxx.cpp b/bindings/cxx/examples/gpiodetectcxx.cpp new file mode 100644 index 0000000..257a84c --- /dev/null +++ b/bindings/cxx/examples/gpiodetectcxx.cpp @@ -0,0 +1,33 @@ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +/* C++ reimplementation of the gpiodetect tool. */ + +#include + +#include +#include + +int main(int argc, char **argv) +{ + if (argc != 1) { + ::std::cerr << "usage: " << argv[0] << ::std::endl; + return EXIT_FAILURE; + } + + for (auto& it: ::gpiod::make_chip_iterator()) { + ::std::cout << it.name() << " [" + << it.label() << "] (" + << it.num_lines() << " lines)" << ::std::endl; + } + + return EXIT_SUCCESS; +} diff --git a/bindings/cxx/examples/gpiofindcxx.cpp b/bindings/cxx/examples/gpiofindcxx.cpp new file mode 100644 index 0000000..382032b --- /dev/null +++ b/bindings/cxx/examples/gpiofindcxx.cpp @@ -0,0 +1,33 @@ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +/* C++ reimplementation of the gpiofind tool. */ + +#include + +#include +#include + +int main(int argc, char **argv) +{ + if (argc != 2) { + ::std::cerr << "usage: " << argv[0] << " " << ::std::endl; + return EXIT_FAILURE; + } + + ::gpiod::line line = ::gpiod::find_line(argv[1]); + if (!line) + return EXIT_FAILURE; + + ::std::cout << line.get_chip().name() << " " << line.offset() << ::std::endl; + + return EXIT_SUCCESS; +} diff --git a/bindings/cxx/examples/gpiogetcxx.cpp b/bindings/cxx/examples/gpiogetcxx.cpp new file mode 100644 index 0000000..fb372f7 --- /dev/null +++ b/bindings/cxx/examples/gpiogetcxx.cpp @@ -0,0 +1,47 @@ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +/* Simplified C++ reimplementation of the gpioget tool. */ + +#include + +#include +#include + +int main(int argc, char **argv) +{ + if (argc < 3) { + ::std::cerr << "usage: " << argv[0] << " ..." << ::std::endl; + return EXIT_FAILURE; + } + + ::std::vector 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); + + lines.request({ + argv[0], + ::gpiod::line_request::DIRECTION_INPUT, + 0 + }); + + auto vals = lines.get_values(); + + for (auto& it: vals) + ::std::cout << it << ' '; + ::std::cout << ::std::endl; + + return EXIT_SUCCESS; +} diff --git a/bindings/cxx/examples/gpioinfocxx.cpp b/bindings/cxx/examples/gpioinfocxx.cpp new file mode 100644 index 0000000..1559c6b --- /dev/null +++ b/bindings/cxx/examples/gpioinfocxx.cpp @@ -0,0 +1,55 @@ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +/* Simplified C++ reimplementation of the gpioinfo tool. */ + +#include + +#include +#include + +int main(int argc, char **argv) +{ + if (argc != 1) { + ::std::cerr << "usage: " << argv[0] << ::std::endl; + return EXIT_FAILURE; + } + + for (auto& cit: ::gpiod::make_chip_iterator()) { + ::std::cout << cit.name() << " - " << cit.num_lines() << " lines:" << ::std::endl; + + for (auto& lit: ::gpiod::line_iterator(cit)) { + ::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.active_state() == ::gpiod::line::ACTIVE_LOW + ? "active-low" : "active-high"); + + ::std::cout << ::std::endl; + } + } + + return EXIT_SUCCESS; +} diff --git a/bindings/cxx/examples/gpiomoncxx.cpp b/bindings/cxx/examples/gpiomoncxx.cpp new file mode 100644 index 0000000..640aba8 --- /dev/null +++ b/bindings/cxx/examples/gpiomoncxx.cpp @@ -0,0 +1,73 @@ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +/* Simplified C++ reimplementation of the gpiomon tool. */ + +#include + +#include +#include + +namespace { + +void print_event(const ::gpiod::line_event& event) +{ + if (event.event_type == ::gpiod::line_event::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 << " "; + + ::std::cout << ::std::chrono::duration_cast<::std::chrono::seconds>(event.timestamp).count(); + ::std::cout << "."; + ::std::cout << event.timestamp.count() % 1000000000; + + ::std::cout << " line: " << event.source.offset(); + + ::std::cout << ::std::endl; +} + +} /* namespace */ + +int main(int argc, char **argv) +{ + if (argc < 3) { + ::std::cout << "usage: " << argv[0] << " ..." << ::std::endl; + return EXIT_FAILURE; + } + + ::std::vector 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); + + lines.request({ + argv[0], + ::gpiod::line_request::EVENT_BOTH_EDGES, + 0, + }); + + for (;;) { + auto events = lines.event_wait(::std::chrono::nanoseconds(1000000000)); + if (events) { + for (auto& it: events) + print_event(it.event_read()); + } + } + + return EXIT_SUCCESS; +} diff --git a/bindings/cxx/examples/gpiosetcxx.cpp b/bindings/cxx/examples/gpiosetcxx.cpp new file mode 100644 index 0000000..d563629 --- /dev/null +++ b/bindings/cxx/examples/gpiosetcxx.cpp @@ -0,0 +1,56 @@ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +/* Simplified C++ reimplementation of the gpioset tool. */ + +#include + +#include +#include + +int main(int argc, char **argv) +{ + if (argc < 3) { + ::std::cerr << "usage: " << argv[0] << " = ..." << ::std::endl; + return EXIT_FAILURE; + } + + ::std::vector offsets; + ::std::vector values; + + for (int i = 2; i < argc; i++) { + ::std::string arg(argv[i]); + + size_t pos = arg.find('='); + + ::std::string offset(arg.substr(0, pos)); + ::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])); + + offsets.push_back(::std::stoul(offset)); + values.push_back(::std::stoul(value)); + } + + ::gpiod::chip chip(argv[1]); + auto lines = chip.get_lines(offsets); + + lines.request({ + argv[0], + ::gpiod::line_request::DIRECTION_OUTPUT, + 0 + }, values); + + ::std::cin.get(); + + return EXIT_SUCCESS; +} diff --git a/bindings/cxx/gpiod.hpp b/bindings/cxx/gpiod.hpp new file mode 100644 index 0000000..da3747a --- /dev/null +++ b/bindings/cxx/gpiod.hpp @@ -0,0 +1,959 @@ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __LIBGPIOD_GPIOD_CXX_HPP__ +#define __LIBGPIOD_GPIOD_CXX_HPP__ + +#include + +#include +#include +#include +#include +#include + +namespace gpiod { + +class line; +class line_bulk; +class line_event; +class line_iterator; +class chip_iterator; + +/** + * @defgroup __gpiod_cxx__ C++ bindings + * @{ + */ + +/** + * @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. + */ +class chip +{ +public: + + /** + * @brief Default constructor. Creates an empty GPIO chip object. + */ + GPIOD_API chip(void) = default; + + /** + * @brief Constructor. Opens the chip using chip::open. + * @param device String describing the GPIO chip. + * @param how Indicates how the chip should be opened. + */ + GPIOD_API chip(const ::std::string& device, int how = OPEN_LOOKUP); + + /** + * @brief Copy constructor. References the object held by other. + * @param other Other chip object. + */ + GPIOD_API chip(const chip& other) = default; + + /** + * @brief Move constructor. References the object held by other. + * @param other Other chip object. + */ + GPIOD_API chip(chip&& other) = default; + + /** + * @brief Assignment operator. References the object held by other. + * @param other Other chip object. + * @return Reference to this object. + */ + GPIOD_API 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. + */ + GPIOD_API chip& operator=(chip&& other) = default; + + /** + * @brief Destructor. Unreferences the internal chip object. + */ + GPIOD_API ~chip(void) = default; + + /** + * @brief Open a GPIO chip. + * @param device String describing the GPIO chip. + * @param how Indicates how the chip should be opened. + * + * If the object already holds a reference to an open chip, it will be + * closed and the reference reset. + */ + GPIOD_API void open(const ::std::string &device, int how = OPEN_LOOKUP); + + /** + * @brief Reset the internal smart pointer owned by this object. + */ + GPIOD_API void reset(void) noexcept; + + /** + * @brief Return the name of the chip held by this object. + * @return Name of the GPIO chip. + */ + GPIOD_API ::std::string name(void) const; + + /** + * @brief Return the label of the chip held by this object. + * @return Label of the GPIO chip. + */ + GPIOD_API ::std::string label(void) const; + + /** + * @brief Return the number of lines exposed by this chip. + * @return Number of lines. + */ + GPIOD_API 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. + */ + GPIOD_API line get_line(unsigned int offset) const; + + /** + * @brief Get the line exposed by this chip by name. + * @param name Line name. + * @return Line object. + */ + GPIOD_API line 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. + */ + GPIOD_API line_bulk get_lines(const ::std::vector& offsets) const; + + /** + * @brief Get a set of lines exposed by this chip by their names. + * @param names Vector of line names. + * @return Set of lines held by a line_bulk object. + */ + GPIOD_API line_bulk find_lines(const ::std::vector<::std::string>& names) const; + + /** + * @brief Equality operator. + * @param rhs Right-hand side of the equation. + * @return True if rhs references the same chip. False otherwise. + */ + GPIOD_API 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. + */ + GPIOD_API 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. + */ + GPIOD_API 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. + */ + GPIOD_API bool operator!(void) const noexcept; + + /** + * @brief Affect the way in which chip::chip and chip::open will try + * to open a GPIO chip character device. + */ + enum : int { + OPEN_LOOKUP = 1, + /**< Open based on the best guess what the supplied string is. */ + OPEN_BY_PATH, + /**< Assume the string is a path to the GPIO chardev. */ + OPEN_BY_NAME, + /**< Assume the string is the name of the chip */ + OPEN_BY_LABEL, + /**< Assume the string is the label of the GPIO chip. */ + OPEN_BY_NUMBER, + /**< Assume the string is the number of the GPIO chip. */ + }; + +private: + + chip(::gpiod_chip* chip); + + void throw_if_noref(void) const; + + ::std::shared_ptr<::gpiod_chip> _m_chip; + + friend chip_iterator; + friend line_iterator; +}; + +/** + * @brief Stores the configuration for line requests. + */ +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. */ + + ::std::string consumer; + /**< Consumer name to pass to the request. */ + int request_type; + /**< Type of the request. */ + ::std::bitset<32> flags; + /**< Additional request flags. */ +}; + +/** + * @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. + */ +class line +{ +public: + + /** + * @brief Default constructor. Creates an empty line object. + */ + GPIOD_API line(void); + + /** + * @brief Copy constructor. + * @param other Other line object. + */ + GPIOD_API line(const line& other) = default; + + /** + * @brief Move constructor. + * @param other Other line object. + */ + GPIOD_API line(line&& other) = default; + + /** + * @brief Assignment operator. + * @param other Other line object. + * @return Reference to this object. + */ + GPIOD_API line& operator=(const line& other) = default; + + /** + * @brief Move assignment operator. + * @param other Other line object. + * @return Reference to this object. + */ + GPIOD_API line& operator=(line&& other) = default; + + /** + * @brief Destructor. + */ + GPIOD_API ~line(void) = default; + + /** + * @brief Get the offset of this line. + * @return Offet of this line. + */ + GPIOD_API 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. + */ + GPIOD_API ::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. + */ + GPIOD_API ::std::string consumer(void) const; + + /** + * @brief Get current direction of this line. + * @return Current direction setting. + */ + GPIOD_API int direction(void) const noexcept; + + /** + * @brief Get current active state of this line. + * @return Current active state setting. + */ + GPIOD_API int active_state(void) const noexcept; + + /** + * @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. + */ + GPIOD_API bool is_used(void) const; + + /** + * @brief Check if this line represents an open-drain GPIO. + * @return True if the line is an open-drain GPIO, false otherwise. + */ + GPIOD_API bool is_open_drain(void) const; + + /** + * @brief Check if this line represents an open-source GPIO. + * @return True if the line is an open-source GPIO, false otherwise. + */ + GPIOD_API bool is_open_source(void) const; + + /** + * @brief Request this line. + * @param config Request config (see gpiod::line_request). + * @param default_val Default value - only matters for OUTPUT direction. + */ + GPIOD_API void request(const line_request& config, int default_val = 0) const; + + /** + * @brief Check if this user has ownership of this line. + * @return True if the user has ownership of this line, false otherwise. + */ + GPIOD_API bool is_requested(void) const; + + /** + * @brief Read the line value. + * @return Current value (0 or 1). + */ + GPIOD_API int get_value(void) const; + + /** + * @brief Set the value of this line. + * @param val New value (0 or 1). + */ + GPIOD_API void set_value(int val) 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. + */ + GPIOD_API bool event_wait(const ::std::chrono::nanoseconds& timeout) const; + + /** + * @brief Read a line event. + * @return Line event object. + */ + GPIOD_API line_event event_read(void) const; + + /** + * @brief Get the event file descriptor associated with this line. + * @return File descriptor number. + */ + GPIOD_API int event_get_fd(void) const; + + /** + * @brief Get the reference to the parent chip. + * @return Reference to the parent chip object. + */ + GPIOD_API 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. + */ + GPIOD_API 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. + */ + GPIOD_API 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. + */ + GPIOD_API 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. + */ + GPIOD_API 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. + */ + GPIOD_API 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 active state settings. + */ + enum : int { + ACTIVE_LOW = 1, + /**< Line's active state is low. */ + ACTIVE_HIGH, + /**< Line's active state is high. */ + }; + +private: + + line(::gpiod_line* line, const chip& owner); + + void throw_if_null(void) const; + + ::gpiod_line* _m_line; + chip _m_chip; + + friend chip; + friend line_bulk; + friend line_iterator; +}; + +/** + * @brief Find a GPIO line by name. Search all GPIO chips present on the system. + * @param name Name of the line. + * @return Returns a line object - empty if the line was not found. + */ +GPIOD_API line find_line(const ::std::string& name); + +/** + * @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. + */ + GPIOD_API 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. + */ + GPIOD_API line_bulk(const ::std::vector& lines); + + /** + * @brief Copy constructor. + * @param other Other line_bulk object. + */ + GPIOD_API line_bulk(const line_bulk& other) = default; + + /** + * @brief Move constructor. + * @param other Other line_bulk object. + */ + GPIOD_API line_bulk(line_bulk&& other) = default; + + /** + * @brief Assignment operator. + * @param other Other line_bulk object. + * @return Reference to this object. + */ + GPIOD_API line_bulk& operator=(const line_bulk& other) = default; + + /** + * @brief Move assignment operator. + * @param other Other line_bulk object. + * @return Reference to this object. + */ + GPIOD_API line_bulk& operator=(line_bulk&& other) = default; + + /** + * @brief Destructor. + */ + GPIOD_API ~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. + */ + GPIOD_API void add(const line& new_line); + + /** + * @brief Get the line at given offset. + * @param offset Offset of the line to get. + * @return Reference to the line object. + */ + GPIOD_API line& get(unsigned int offset); + + /** + * @brief Get the line at given offset without bounds checking. + * @param offset Offset of the line to get. + * @return Reference to the line object. + * @note No bounds checking is performed. + */ + GPIOD_API line& operator[](unsigned int offset); + + /** + * @brief Get the number of lines currently held by this object. + * @return Number of elements in this line_bulk. + */ + GPIOD_API 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. + */ + GPIOD_API bool empty(void) const noexcept; + + /** + * @brief Remove all lines from this object. + */ + GPIOD_API 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. + */ + GPIOD_API void request(const line_request& config, + std::vector default_vals = std::vector()) 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. + */ + GPIOD_API ::std::vector 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. + */ + GPIOD_API void set_values(const ::std::vector& 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. + */ + GPIOD_API 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. + */ + GPIOD_API 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. + */ + GPIOD_API bool operator!(void) const noexcept; + + /** + * @brief Max number of lines that this object can hold. + */ + GPIOD_API 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. + */ + GPIOD_API iterator(void) = default; + + /** + * @brief Copy constructor. + * @param other Other line_bulk iterator. + */ + GPIOD_API iterator(const iterator& other) = default; + + /** + * @brief Move constructor. + * @param other Other line_bulk iterator. + */ + GPIOD_API iterator(iterator&& other) = default; + + /** + * @brief Assignment operator. + * @param other Other line_bulk iterator. + * @return Reference to this iterator. + */ + GPIOD_API iterator& operator=(const iterator& other) = default; + + /** + * @brief Move assignment operator. + * @param other Other line_bulk iterator. + * @return Reference to this iterator. + */ + GPIOD_API iterator& operator=(iterator&& other) = default; + + /** + * @brief Destructor. + */ + GPIOD_API ~iterator(void) = default; + + /** + * @brief Advance the iterator by one element. + * @return Reference to this iterator. + */ + GPIOD_API iterator& operator++(void); + + /** + * @brief Dereference current element. + * @return Current GPIO line by reference. + */ + GPIOD_API const line& operator*(void) const; + + /** + * @brief Member access operator. + * @return Current GPIO line by pointer. + */ + GPIOD_API 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. + */ + GPIOD_API 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. + */ + GPIOD_API bool operator!=(const iterator& rhs) const noexcept; + + private: + + iterator(const ::std::vector::iterator& it); + + ::std::vector::iterator _m_iter; + + friend line_bulk; + }; + + /** + * @brief Returns an iterator to the first line. + * @return A line_bulk iterator. + */ + GPIOD_API iterator begin(void) noexcept; + + /** + * @brief Returns an iterator to the element following the last line. + * @return A line_bulk iterator. + */ + GPIOD_API iterator end(void) noexcept; + +private: + + void throw_if_empty(void) const; + void to_line_bulk(::gpiod_line_bulk* bulk) const; + + ::std::vector _m_bulk; +}; + +/** + * @brief Create a new chip_iterator. + * @return New chip iterator object pointing to the first GPIO chip on the system. + * @note This function is needed as we already use the default constructor of + * gpiod::chip_iterator as the return value of gpiod::end. + */ +GPIOD_API chip_iterator make_chip_iterator(void); + +/** + * @brief Support for range-based loops for chip iterators. + * @param iter A chip iterator. + * @return Iterator unchanged. + */ +GPIOD_API chip_iterator begin(chip_iterator iter) noexcept; + +/** + * @brief Support for range-based loops for chip iterators. + * @param iter A chip iterator. + * @return New end iterator. + */ +GPIOD_API chip_iterator end(const chip_iterator& iter) noexcept; + +/** + * @brief Allows to iterate over all GPIO chips present on the system. + */ +class chip_iterator +{ +public: + + /** + * @brief Default constructor. Creates the end iterator. + */ + GPIOD_API chip_iterator(void) = default; + + /** + * @brief Copy constructor. + * @param other Other chip_iterator. + */ + GPIOD_API chip_iterator(const chip_iterator& other) = default; + + /** + * @brief Move constructor. + * @param other Other chip_iterator. + */ + GPIOD_API chip_iterator(chip_iterator&& other) = default; + + /** + * @brief Assignment operator. + * @param other Other chip_iterator. + * @return Reference to this iterator. + */ + GPIOD_API chip_iterator& operator=(const chip_iterator& other) = default; + + /** + * @brief Move assignment operator. + * @param other Other chip_iterator. + * @return Reference to this iterator. + */ + GPIOD_API chip_iterator& operator=(chip_iterator&& other) = default; + + /** + * @brief Destructor. + */ + GPIOD_API ~chip_iterator(void) = default; + + /** + * @brief Advance the iterator by one element. + * @return Reference to this iterator. + */ + GPIOD_API chip_iterator& operator++(void); + + /** + * @brief Dereference current element. + * @return Current GPIO chip by reference. + */ + GPIOD_API const chip& operator*(void) const; + + /** + * @brief Member access operator. + * @return Current GPIO chip by pointer. + */ + GPIOD_API const chip* 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 chip_iterator, + * false otherwise. + */ + GPIOD_API bool operator==(const chip_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 chip_iterator, + * false otherwise. + */ + GPIOD_API bool operator!=(const chip_iterator& rhs) const noexcept; + +private: + + chip_iterator(::gpiod_chip_iter* iter); + + ::std::shared_ptr<::gpiod_chip_iter> _m_iter; + chip _m_current; + + friend chip_iterator make_chip_iterator(void); +}; + +/** + * @brief Support for range-based loops for line iterators. + * @param iter A line iterator. + * @return Iterator unchanged. + */ +GPIOD_API line_iterator begin(line_iterator iter) noexcept; + +/** + * @brief Support for range-based loops for line iterators. + * @param iter A line iterator. + * @return New end iterator. + */ +GPIOD_API line_iterator end(const line_iterator& iter) noexcept; + +/** + * @brief Allows to iterate over all lines owned by a GPIO chip. + */ +class line_iterator +{ +public: + + /** + * @brief Default constructor. Creates the end iterator. + */ + GPIOD_API line_iterator(void) = default; + + /** + * @brief Constructor. Creates the begin iterator. + * @param owner Chip owning the GPIO lines over which we want to iterate. + */ + GPIOD_API line_iterator(const chip& owner); + + /** + * @brief Copy constructor. + * @param other Other line iterator. + */ + GPIOD_API line_iterator(const line_iterator& other) = default; + + /** + * @brief Move constructor. + * @param other Other line iterator. + */ + GPIOD_API line_iterator(line_iterator&& other) = default; + + /** + * @brief Assignment operator. + * @param other Other line iterator. + * @return Reference to this line_iterator. + */ + GPIOD_API line_iterator& operator=(const line_iterator& other) = default; + + /** + * @brief Move assignment operator. + * @param other Other line iterator. + * @return Reference to this line_iterator. + */ + GPIOD_API line_iterator& operator=(line_iterator&& other) = default; + + /** + * @brief Destructor. + */ + GPIOD_API ~line_iterator(void) = default; + + /** + * @brief Advance the iterator by one element. + * @return Reference to this iterator. + */ + GPIOD_API line_iterator& operator++(void); + + /** + * @brief Dereference current element. + * @return Current GPIO line by reference. + */ + GPIOD_API const line& operator*(void) const; + + /** + * @brief Member access operator. + * @return Current GPIO line by pointer. + */ + GPIOD_API 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_iterator, + * false otherwise. + */ + GPIOD_API bool operator==(const line_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 line_iterator, + * false otherwise. + */ + GPIOD_API bool operator!=(const line_iterator& rhs) const noexcept; + +private: + + ::std::shared_ptr<::gpiod_line_iter> _m_iter; + line _m_current; +}; + +/** + * @} + */ + +} /* namespace gpiod */ + +#endif /* __LIBGPIOD_GPIOD_CXX_HPP__ */ diff --git a/bindings/cxx/iter.cpp b/bindings/cxx/iter.cpp new file mode 100644 index 0000000..14b4bdb --- /dev/null +++ b/bindings/cxx/iter.cpp @@ -0,0 +1,146 @@ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include + +#include + +namespace gpiod { + +namespace { + +void chip_iter_deleter(::gpiod_chip_iter* iter) +{ + ::gpiod_chip_iter_free_noclose(iter); +} + +void line_iter_deleter(::gpiod_line_iter* iter) +{ + ::gpiod_line_iter_free(iter); +} + +::gpiod_line_iter* make_line_iterator(::gpiod_chip* chip) +{ + ::gpiod_line_iter* iter; + + iter = ::gpiod_line_iter_new(chip); + if (!iter) + throw ::std::system_error(errno, ::std::system_category(), + "error creating GPIO line iterator"); + + return iter; +} + +} /* namespace */ + +chip_iterator make_chip_iterator(void) +{ + ::gpiod_chip_iter* iter = ::gpiod_chip_iter_new(); + if (!iter) + throw ::std::system_error(errno, ::std::system_category(), + "error creating GPIO chip iterator"); + + return ::std::move(chip_iterator(iter)); +} + +bool chip_iterator::operator==(const chip_iterator& rhs) const noexcept +{ + return this->_m_current == rhs._m_current; +} + +bool chip_iterator::operator!=(const chip_iterator& rhs) const noexcept +{ + return this->_m_current != rhs._m_current; +} + +chip_iterator::chip_iterator(::gpiod_chip_iter *iter) + : _m_iter(iter, chip_iter_deleter), + _m_current(chip(::gpiod_chip_iter_next_noclose(this->_m_iter.get()))) +{ + +} + +chip_iterator& chip_iterator::operator++(void) +{ + ::gpiod_chip* next = ::gpiod_chip_iter_next_noclose(this->_m_iter.get()); + + this->_m_current = next ? chip(next) : chip(); + + return *this; +} + +const chip& chip_iterator::operator*(void) const +{ + return this->_m_current; +} + +const chip* chip_iterator::operator->(void) const +{ + return ::std::addressof(this->_m_current); +} + +chip_iterator begin(chip_iterator iter) noexcept +{ + return iter; +} + +chip_iterator end(const chip_iterator&) noexcept +{ + return ::std::move(chip_iterator()); +} + +line_iterator begin(line_iterator iter) noexcept +{ + return iter; +} + +line_iterator end(const line_iterator&) noexcept +{ + return ::std::move(line_iterator()); +} + +line_iterator::line_iterator(const chip& owner) + : _m_iter(make_line_iterator(owner._m_chip.get()), line_iter_deleter), + _m_current(line(::gpiod_line_iter_next(this->_m_iter.get()), owner)) +{ + +} + +line_iterator& line_iterator::operator++(void) +{ + ::gpiod_line* next = ::gpiod_line_iter_next(this->_m_iter.get()); + + this->_m_current = next ? line(next, this->_m_current._m_chip) : line(); + + return *this; +} + +const line& line_iterator::operator*(void) const +{ + return this->_m_current; +} + +const line* line_iterator::operator->(void) const +{ + return ::std::addressof(this->_m_current); +} + +bool line_iterator::operator==(const line_iterator& rhs) const noexcept +{ + return this->_m_current._m_line == rhs._m_current._m_line; +} + +bool line_iterator::operator!=(const line_iterator& rhs) const noexcept +{ + return this->_m_current._m_line != rhs._m_current._m_line; +} + +} /* namespace gpiod */ diff --git a/bindings/cxx/line.cpp b/bindings/cxx/line.cpp new file mode 100644 index 0000000..6603ef7 --- /dev/null +++ b/bindings/cxx/line.cpp @@ -0,0 +1,238 @@ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include + +#include + +namespace gpiod { + +line::line(void) + : _m_line(nullptr), + _m_chip() +{ + +} + +line::line(::gpiod_line* line, const chip& owner) + : _m_line(line), + _m_chip(owner) +{ + +} + +unsigned int line::offset(void) const +{ + this->throw_if_null(); + + return ::gpiod_line_offset(this->_m_line); +} + +::std::string line::name(void) const +{ + this->throw_if_null(); + + const char* name = ::gpiod_line_name(this->_m_line); + + return ::std::move(name ? ::std::string(name) : ::std::string()); +} + +::std::string line::consumer(void) const +{ + this->throw_if_null(); + + const char* consumer = ::gpiod_line_consumer(this->_m_line); + + return ::std::move(consumer ? ::std::string(consumer) : ::std::string()); +} + +int line::direction(void) const noexcept +{ + this->throw_if_null(); + + int dir = ::gpiod_line_direction(this->_m_line); + + return dir == GPIOD_LINE_DIRECTION_INPUT ? DIRECTION_INPUT : DIRECTION_OUTPUT; +} + +int line::active_state(void) const noexcept +{ + this->throw_if_null(); + + int active = ::gpiod_line_active_state(this->_m_line); + + return active == GPIOD_LINE_ACTIVE_STATE_HIGH ? ACTIVE_HIGH : ACTIVE_LOW; +} + +bool line::is_used(void) const +{ + this->throw_if_null(); + + return ::gpiod_line_is_used(this->_m_line); +} + +bool line::is_open_drain(void) const +{ + this->throw_if_null(); + + return ::gpiod_line_is_open_drain(this->_m_line); +} + +bool line::is_open_source(void) const +{ + this->throw_if_null(); + + return ::gpiod_line_is_open_source(this->_m_line); +} + +void line::request(const line_request& config, int default_val) const +{ + this->throw_if_null(); + + line_bulk bulk({ *this }); + + bulk.request(config, { default_val }); +} + +bool line::is_requested(void) const +{ + this->throw_if_null(); + + return ::gpiod_line_is_requested(this->_m_line); +} + +/* + * 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. + */ + +int line::get_value(void) const +{ + this->throw_if_null(); + + line_bulk bulk({ *this }); + + return bulk.get_values()[0]; +} + +void line::set_value(int val) const +{ + this->throw_if_null(); + + line_bulk bulk({ *this }); + + bulk.set_values({ val }); +} + +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 ::std::move(event_bulk); +} + +line_event line::event_read(void) const +{ + this->throw_if_null(); + + ::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"); + + if (event_buf.event_type == GPIOD_LINE_EVENT_RISING_EDGE) + event.event_type = line_event::RISING_EDGE; + else if (event_buf.event_type == GPIOD_LINE_EVENT_FALLING_EDGE) + event.event_type = line_event::FALLING_EDGE; + + event.timestamp = ::std::chrono::nanoseconds( + event_buf.ts.tv_nsec + (event_buf.ts.tv_sec * 1000000000)); + + event.source = *this; + + return ::std::move(event); +} + +int line::event_get_fd(void) const +{ + this->throw_if_null(); + + int ret = ::gpiod_line_event_get_fd(this->_m_line); + + if (ret < 0) + ::std::system_error(errno, ::std::system_category(), + "unable to get the line event file descriptor"); + + return ret; +} + +const chip& line::get_chip(void) const +{ + return this->_m_chip; +} + +void line::reset(void) +{ + this->_m_line = nullptr; + this->_m_chip.reset(); +} + +bool line::operator==(const line& rhs) const noexcept +{ + return this->_m_line == rhs._m_line; +} + +bool line::operator!=(const line& rhs) const noexcept +{ + return this->_m_line != rhs._m_line; +} + +line::operator bool(void) const noexcept +{ + return this->_m_line != nullptr; +} + +bool line::operator!(void) const noexcept +{ + return this->_m_line == nullptr; +} + +void line::throw_if_null(void) const +{ + if (!this->_m_line) + throw ::std::logic_error("object not holding a GPIO line handle"); +} + +line find_line(const ::std::string& name) +{ + line ret; + + for (auto& it: make_chip_iterator()) { + ret = it.find_line(name); + if (ret) + break; + } + + return ::std::move(ret); +} + +} /* namespace gpiod */ diff --git a/bindings/cxx/line_bulk.cpp b/bindings/cxx/line_bulk.cpp new file mode 100644 index 0000000..72dd7d4 --- /dev/null +++ b/bindings/cxx/line_bulk.cpp @@ -0,0 +1,262 @@ +/* + * This file is part of libgpiod. + * + * Copyright (C) 2017-2018 Bartosz Golaszewski + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include + +#include +#include + +namespace gpiod { + +namespace { + +const ::std::map 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) + { + 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, }, +}; + +} /* namespace */ + +const ::std::bitset<32> line_request::FLAG_ACTIVE_LOW("001"); +const ::std::bitset<32> line_request::FLAG_OPEN_SOURCE("010"); +const ::std::bitset<32> line_request::FLAG_OPEN_DRAIN("100"); + +const unsigned int line_bulk::MAX_LINES = GPIOD_LINE_BULK_MAX_LINES; + +line_bulk::line_bulk(const ::std::vector& lines) + : _m_bulk() +{ + this->_m_bulk.reserve(lines.size()); + + for (auto& it: lines) + this->add(it); +} + +void line_bulk::add(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); +} + +line& line_bulk::get(unsigned int offset) +{ + return this->_m_bulk.at(offset); +} + +line& line_bulk::operator[](unsigned int offset) +{ + return this->_m_bulk[offset]; +} + +unsigned int line_bulk::size(void) const noexcept +{ + return this->_m_bulk.size(); +} + +bool line_bulk::empty(void) const noexcept +{ + return this->_m_bulk.empty(); +} + +void line_bulk::clear(void) +{ + this->_m_bulk.clear(); +} + +void line_bulk::request(const line_request& config, std::vector default_vals) const +{ + this->throw_if_empty(); + + 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; + ::gpiod_line_bulk bulk; + int rv; + + this->to_line_bulk(::std::addressof(bulk)); + + 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(::std::addressof(bulk), + ::std::addressof(conf), default_vals.data()); + if (rv) + throw ::std::system_error(errno, ::std::system_category(), + "error requesting GPIO lines"); +} + +::std::vector line_bulk::get_values(void) const +{ + this->throw_if_empty(); + + ::std::vector values; + ::gpiod_line_bulk bulk; + int rv; + + this->to_line_bulk(::std::addressof(bulk)); + values.resize(this->_m_bulk.size()); + + rv = ::gpiod_line_get_value_bulk(::std::addressof(bulk), values.data()); + if (rv) + throw ::std::system_error(errno, ::std::system_category(), + "error reading GPIO line values"); + + return ::std::move(values); +} + +void line_bulk::set_values(const ::std::vector& values) const +{ + this->throw_if_empty(); + + if (values.size() != this->_m_bulk.size()) + throw ::std::invalid_argument("the size of values array must correspond with the number of lines"); + + ::gpiod_line_bulk bulk; + int rv; + + this->to_line_bulk(::std::addressof(bulk)); + + rv = ::gpiod_line_set_value_bulk(::std::addressof(bulk), values.data()); + if (rv) + throw ::std::system_error(errno, ::std::system_category(), + "error setting GPIO line values"); +} + +line_bulk line_bulk::event_wait(const ::std::chrono::nanoseconds& timeout) const +{ + this->throw_if_empty(); + + ::gpiod_line_bulk bulk, event_bulk; + ::timespec ts; + line_bulk ret; + int rv; + + this->to_line_bulk(::std::addressof(bulk)); + + ::gpiod_line_bulk_init(::std::addressof(event_bulk)); + + ts.tv_sec = timeout.count() / 1000000000ULL; + ts.tv_nsec = timeout.count() % 1000000000ULL; + + rv = ::gpiod_line_event_wait_bulk(::std::addressof(bulk), + ::std::addressof(ts), + ::std::addressof(event_bulk)); + if (rv < 0) { + throw ::std::system_error(errno, ::std::system_category(), + "error polling for events"); + } else if (rv > 0) { + for (unsigned int i = 0; i < event_bulk.num_lines; i++) + ret.add(line(event_bulk.lines[i], this->_m_bulk[i].get_chip())); + } + + return ::std::move(ret); +} + +line_bulk::operator bool(void) const noexcept +{ + return !this->_m_bulk.empty(); +} + +bool line_bulk::operator!(void) const noexcept +{ + return this->_m_bulk.empty(); +} + +line_bulk::iterator::iterator(const ::std::vector::iterator& it) + : _m_iter(it) +{ + +} + +line_bulk::iterator& line_bulk::iterator::operator++(void) +{ + this->_m_iter++; + + return *this; +} + +const line& line_bulk::iterator::operator*(void) const +{ + return *this->_m_iter; +} + +const line* line_bulk::iterator::operator->(void) const +{ + return this->_m_iter.operator->(); +} + +bool line_bulk::iterator::operator==(const iterator& rhs) const noexcept +{ + return this->_m_iter == rhs._m_iter; +} + +bool line_bulk::iterator::operator!=(const iterator& rhs) const noexcept +{ + return this->_m_iter != rhs._m_iter; +} + +line_bulk::iterator line_bulk::begin(void) noexcept +{ + return ::std::move(line_bulk::iterator(this->_m_bulk.begin())); +} + +line_bulk::iterator line_bulk::end(void) noexcept +{ + return ::std::move(line_bulk::iterator(this->_m_bulk.end())); +} + +void line_bulk::throw_if_empty(void) const +{ + if (this->_m_bulk.empty()) + throw std::logic_error("line_bulk not holding any GPIO lines"); +} + +void line_bulk::to_line_bulk(::gpiod_line_bulk *bulk) const +{ + ::gpiod_line_bulk_init(bulk); + for (auto& it: this->_m_bulk) + ::gpiod_line_bulk_add(bulk, it._m_line); +} + +} /* namespace gpiod */ diff --git a/configure.ac b/configure.ac index 7aaad08..8ee988c 100644 --- a/configure.ac +++ b/configure.ac @@ -33,6 +33,7 @@ AC_SUBST(AR_FLAGS, [cr]) AM_PROG_AR AC_PROG_CC +AC_PROG_CXX AC_PROG_LIBTOOL AC_PROG_INSTALL @@ -113,6 +114,27 @@ then PKG_CHECK_MODULES([UDEV], [libudev >= 215]) fi +AC_ARG_ENABLE([bindings-cxx], + [AC_HELP_STRING([--enable-bindings-cxx], + [enable C++ bindings [default=no]])], + [ + if test "x$enableval" = xyes + then + with_bindings_cxx=true + else + with_bindings_cxx=false + fi + ], + [with_bindings_cxx=false]) +AM_CONDITIONAL([WITH_BINDINGS_CXX], [test "x$with_bindings_cxx" = xtrue]) + +if test "x$with_bindings_cxx" = xtrue +then + AC_LIBTOOL_CXX + # This needs autoconf-archive + AX_CXX_COMPILE_STDCXX_11([ext], [mandatory]) +fi + AC_CHECK_PROG([has_doxygen], [doxygen], [true], [false]) AM_CONDITIONAL([HAS_DOXYGEN], [test "x$has_doxygen" = xtrue]) if test "x$has_doxygen" = xfalse @@ -126,6 +148,9 @@ AC_CONFIG_FILES([libgpiod.pc src/Makefile src/lib/Makefile src/tools/Makefile - tests/Makefile]) + tests/Makefile + bindings/Makefile + bindings/cxx/Makefile + bindings/cxx/examples/Makefile]) AC_OUTPUT -- 2.30.2