[Asio] Need help making a custom service
Hello everyone, First off, I would like to say that I have been using Boost for the past few years, and have found it very useful. Thanks for all your hard work! Second, I have a question about making a custom service. I would like to make a service that triggers a callback when a GPIO pin changes state and triggers an interrupt (I'm using a Raspberry Pi.) I have (or can obtain) a file descriptor that can be poll()'d, select()'d, or epoll'd and which omits high-priority events (POLLPRI, EPOLLPRI, etc.). This entire use case is pretty Linux-specific, so it doesn't have to be very portable. What I would like to be able to do is something like this: // CODE STARTS HERE #include <iostream> #include <boost/asio.hpp> class gpio_interrupt { public: gpio_interrupt(boost::asio::io_service &io_service, unsigned int number) { // Asio magic goes here } template <typename Callback> void async_wait(Callback c) { // Asio magic goes here } }; static void my_callback(const boost::system::error_code &ec) { if (ec) return; std::cout << "Pin changed state" << std::endl; } int main(int argc, char **argv) { boost::asio::io_service io_service; gpio_interrupt interrupt(io_service, 21); interrupt.async_wait(my_callback); io_service.run(); return 0; } // CODE ENDS HERE I've looked a little bit at the Boost.Asio code (in particular the socket code) to get an idea of how I would do this, but there are so many different classes and inner classes that I'm not sure where to get started. Can someone help me? Thanks in advance! -- Kyle Edwards Software Developer 20 Corporate Circle | Albany, NY 12203 office 888-4BULLEX | 518.689.2023 | fax 518.689.2034 | web: www.bullex.com
On 03/09/2015 06:49 PM, Kyle Edwards wrote:
Second, I have a question about making a custom service. I would like to make a service that triggers a callback when a GPIO pin changes state and triggers an interrupt (I'm using a Raspberry Pi.) I have (or can obtain) a file descriptor that can be poll()'d, select()'d, or epoll'd and which omits high-priority events (POLLPRI, EPOLLPRI, etc.). This
Then you can wrap your file descriptor in a posix::stream_descriptor [1] and wait on events using async_read_some() with null_buffers() [2]. An example can be found in [3]. The example assumes that the wrapper does not take ownership of the file descriptor (if it does, then you can omit the non_closing_service.) [1] http://www.boost.org/doc/html/boost_asio/overview/posix/stream_descriptor.ht... [2] http://www.boost.org/doc/html/boost_asio/overview/core/reactor.html [3] https://github.com/breese/aware/blob/master/include/aware/detail/native_sock...
Hi Bjorn, Thanks for the quick reply. On Mon, 2015-03-09 at 21:21 +0100, Bjorn Reese wrote:
Then you can wrap your file descriptor in a posix::stream_descriptor [1] and wait on events using async_read_some() with null_buffers() [2].
An example can be found in [3]. The example assumes that the wrapper does not take ownership of the file descriptor (if it does, then you can omit the non_closing_service.)
Thanks for the suggestion. Unfortunately, though, I don't think async_read_some() is going to work for me. Here is an excerpt from boost/asio/detail/reactive_descriptor_service.hpp: template <typename Handler> void async_read_some(implementation_type& impl, const null_buffers&, Handler handler) { // ... start_op(impl, reactor::read_op, p.p, false, false); // ... } Notice the use of reactor::read_op. I've done a little more digging through the Boost.Asio code, and it looks like, for this application, I need to use reactor::except_op instead. Is there a simple class/function that does this? Or will I have to implement it myself? Kyle
On Mon, 2015-03-09 at 22:25 +0100, Bjorn Reese wrote:
On 03/09/2015 09:34 PM, Kyle Edwards wrote:
Thanks for the suggestion. Unfortunately, though, I don't think async_read_some() is going to work for me. Here is an excerpt from
How do you obtain the file descriptor?
It's just a native POSIX file descriptor created with open(), like this: int open_pin(unsigned int number) { std::ostringstream filename; filename << "/sys/class/gpio/gpio" << number << "/value"; return open(filename.str().c_str(), O_RDONLY | O_NONBLOCK); }
On 03/09/2015 09:34 PM, Kyle Edwards wrote:
Notice the use of reactor::read_op. I've done a little more digging through the Boost.Asio code, and it looks like, for this application, I need to use reactor::except_op instead. Is there a simple class/function that does this? Or will I have to implement it myself?
A possibility could be to wrap the file descriptor in a udp::socket (or possibly your own derived from basic_datagram_socket) and use async_receive(null_buffers) with the message_out_of_band flag set. Never tried it myself though.
On Tue, 2015-03-10 at 14:53 +0100, Bjorn Reese wrote:
On 03/09/2015 09:34 PM, Kyle Edwards wrote:
Notice the use of reactor::read_op. I've done a little more digging through the Boost.Asio code, and it looks like, for this application, I need to use reactor::except_op instead. Is there a simple class/function that does this? Or will I have to implement it myself?
A possibility could be to wrap the file descriptor in a udp::socket (or possibly your own derived from basic_datagram_socket) and use async_receive(null_buffers) with the message_out_of_band flag set. Never tried it myself though.
Alright, I've written a custom service that just wraps the relevant UDP classes (service, implementation) and does async_receive() with message_out_of_band. It's hairy, but it seems to work. Thank you!
If you got it working by wrapping the file descriptor - post it and I'll give it a shot on tidying it up. 2015-03-10 20:05 GMT+01:00 Kyle Edwards <kedwards@bullex.com>:
On Tue, 2015-03-10 at 14:53 +0100, Bjorn Reese wrote:
On 03/09/2015 09:34 PM, Kyle Edwards wrote:
Notice the use of reactor::read_op. I've done a little more digging through the Boost.Asio code, and it looks like, for this application, I need to use reactor::except_op instead. Is there a simple class/function that does this? Or will I have to implement it myself?
A possibility could be to wrap the file descriptor in a udp::socket (or possibly your own derived from basic_datagram_socket) and use async_receive(null_buffers) with the message_out_of_band flag set. Never tried it myself though.
Alright, I've written a custom service that just wraps the relevant UDP classes (service, implementation) and does async_receive() with message_out_of_band. It's hairy, but it seems to work. Thank you! _______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
On Tue, 2015-03-10 at 20:35 +0100, svante karlsson wrote:
If you got it working by wrapping the file descriptor - post it and I'll give it a shot on tidying it up.
Here's what I've got. It works pretty well so far. ///////////////////////// // gpio_interrupt.hpp: // ///////////////////////// #ifndef GPIO_INTERRUPT_HPP #define GPIO_INTERRUPT_HPP #include <boost/asio/basic_io_object.hpp> #include <boost/asio/ip/udp.hpp> class gpio_interrupt_service : public boost::asio::io_service::service { private: typedef boost::asio::ip::udp::socket socket_type; typedef socket_type::service_type socket_service_type; typedef socket_type::implementation_type socket_implementation_type; public: class implementation_type { private: friend class gpio_interrupt_service; socket_implementation_type underlying_impl_; }; static boost::asio::io_service::id id; gpio_interrupt_service(boost::asio::io_service &io_service); void construct(implementation_type &impl); void destroy(implementation_type &impl); boost::system::error_code open(implementation_type &impl, unsigned int number, boost::system::error_code &ec); boost::system::error_code close(implementation_type &impl, boost::system::error_code &ec); template<typename Handler> void async_wait(implementation_type &impl, Handler handler) { bound_handler<Handler> bound(*this, impl, handler); socket_service_.async_receive(impl.underlying_impl_, boost::asio::null_buffers(), socket_type::message_out_of_band, bound); } boost::system::error_code cancel(implementation_type &impl, boost::system::error_code &ec); private: template<typename Handler> class bound_handler { public: bound_handler(gpio_interrupt_service &service, implementation_type &impl, Handler handler) : service_(service), impl_(impl), handler_(std::move(handler)) {} bound_handler(const bound_handler &other) : service_(other.service_), impl_(other.impl_), handler_(other.handler_) {} bound_handler(bound_handler &&other) : service_(other.service_), impl_(other.impl_), handler_(std::move(other.handler_)) {} void operator()(const boost::system::error_code &ec, std::size_t bytes_transferred) { service_.reset_descriptor(impl_, ec); handler_(ec); } private: gpio_interrupt_service &service_; implementation_type &impl_; Handler handler_; }; socket_service_type &socket_service_; virtual void shutdown_service(); void reset_descriptor(implementation_type &impl, const boost::system::error_code &ec); }; class gpio_interrupt : public boost::asio::basic_io_object<gpio_interrupt_service> { public: gpio_interrupt(boost::asio::io_service &io_service); void open(unsigned int number); boost::system::error_code open(unsigned int number, boost::system::error_code &ec); void close(); boost::system::error_code close(boost::system::error_code &ec); template<typename Handler> void async_wait(Handler &&handler) { this->get_service().async_wait(this->get_implementation(), std::forward<Handler>(handler)); } void cancel(); boost::system::error_code cancel(boost::system::error_code &ec); }; #endif // GPIO_INTERRUPT_HPP ///////////////////////// // gpio_interrupt.cpp: // ///////////////////////// #include "gpio_interrupt.hpp" #include <boost/asio/detail/throw_error.hpp> #include <boost/asio/error.hpp> #include <fcntl.h> boost::asio::io_service::id gpio_interrupt_service::id; gpio_interrupt_service::gpio_interrupt_service(boost::asio::io_service &io_service) : service(io_service), socket_service_(boost::asio::use_service<socket_service_type>(io_service)) { } void gpio_interrupt_service::construct(implementation_type &impl) { socket_service_.construct(impl.underlying_impl_); } void gpio_interrupt_service::destroy(implementation_type &impl) { socket_service_.destroy(impl.underlying_impl_); } boost::system::error_code gpio_interrupt_service::open(implementation_type &impl, unsigned int number, boost::system::error_code &ec) { std::ostringstream filename; filename << "/sys/class/gpio/gpio" << number << "/value"; int fd = ::open(filename.str().c_str(), O_RDONLY | O_NONBLOCK); if (fd < 0) { ec = boost::system::error_code(errno, boost::asio::error::get_system_category()); return ec; } reset_descriptor(impl, ec); return socket_service_.assign(impl.underlying_impl_, boost::asio::ip::udp::v4(), fd, ec); } boost::system::error_code gpio_interrupt_service::close(implementation_type &impl, boost::system::error_code &ec) { return socket_service_.close(impl.underlying_impl_, ec); } boost::system::error_code gpio_interrupt_service::cancel(implementation_type &impl, boost::system::error_code &ec) { return socket_service_.cancel(impl.underlying_impl_, ec); } void gpio_interrupt_service::shutdown_service() { } void gpio_interrupt_service::reset_descriptor(implementation_type &impl, const boost::system::error_code &ec) { if (ec) return; int fd = socket_service_.native_handle(impl.underlying_impl_); lseek(fd, 0, SEEK_SET); char buf[2]; read(fd, buf, 2); } gpio_interrupt::gpio_interrupt(boost::asio::io_service &io_service) : basic_io_object(io_service) { } void gpio_interrupt::open(unsigned int number) { boost::system::error_code ec; this->get_service().open(this->get_implementation(), number, ec); boost::asio::detail::throw_error(ec, "open"); } boost::system::error_code gpio_interrupt::open(unsigned int number, boost::system::error_code &ec) { return this->get_service().open(this->get_implementation(), number, ec); } void gpio_interrupt::close() { boost::system::error_code ec; this->get_service().close(this->get_implementation(), ec); boost::asio::detail::throw_error(ec, "close"); } boost::system::error_code gpio_interrupt::close(boost::system::error_code &ec) { return this->get_service().close(this->get_implementation(), ec); } void gpio_interrupt::cancel() { boost::system::error_code ec; this->get_service().cancel(this->get_implementation(), ec); boost::asio::detail::throw_error(ec, "cancel"); } boost::system::error_code gpio_interrupt::cancel(boost::system::error_code &ec) { return this->get_service().cancel(this->get_implementation(), ec); } ///////////////////////////////// OT: Bjorn, I took a look at your Aware project, and it actually looks like something I might be able to use. Thank you!
* svante karlsson <saka@csi.se> [2015-03-10 20:35+0100]
If you got it working by wrapping the file descriptor - post it and I'll give it a shot on tidying it up.
I *think* I have a vested interest in this as well. An age or two ago, I started an inotify interface on a query engine (when some data file changes on disk, it re-reads). I didn't know what I was doing and ended up with something that tightloops. https://github.com/ericprud/SWObjects/blob/sparql11/lib/SimpleServer.hpp#L11... Since inotify works on fds, what you're doing might teach me my next steps. (I'd of course love to see inotify/fsevent/FileSystemWatcher widgets in Asio and am happy to donate whatever I have of value).
2015-03-10 20:05 GMT+01:00 Kyle Edwards <kedwards@bullex.com>:
On Tue, 2015-03-10 at 14:53 +0100, Bjorn Reese wrote:
On 03/09/2015 09:34 PM, Kyle Edwards wrote:
Notice the use of reactor::read_op. I've done a little more digging through the Boost.Asio code, and it looks like, for this application, I need to use reactor::except_op instead. Is there a simple class/function that does this? Or will I have to implement it myself?
A possibility could be to wrap the file descriptor in a udp::socket (or possibly your own derived from basic_datagram_socket) and use async_receive(null_buffers) with the message_out_of_band flag set. Never tried it myself though.
Alright, I've written a custom service that just wraps the relevant UDP classes (service, implementation) and does async_receive() with message_out_of_band. It's hairy, but it seems to work. Thank you! _______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
-- -ericP office: +1.617.599.3509 mobile: +33.6.80.80.35.59 (eric@w3.org) Feel free to forward this message to any list for any purpose other than email address distribution. There are subtle nuances encoded in font variation and clever layout which can only be seen by printing this message on high-clay paper.
On 11 Mar 2015 at 7:25, Eric Prud'hommeaux wrote:
* svante karlsson <saka@csi.se> [2015-03-10 20:35+0100]
If you got it working by wrapping the file descriptor - post it and I'll give it a shot on tidying it up.
I *think* I have a vested interest in this as well. An age or two ago, I started an inotify interface on a query engine (when some data file changes on disk, it re-reads). I didn't know what I was doing and ended up with something that tightloops. https://github.com/ericprud/SWObjects/blob/sparql11/lib/SimpleServer.hpp#L11...
Since inotify works on fds, what you're doing might teach me my next steps. (I'd of course love to see inotify/fsevent/FileSystemWatcher widgets in Asio and am happy to donate whatever I have of value).
The v1.4 release of AFIO should have some very limited support for file watching, mainly for portably avoiding polling of advisory lock state changes. It should land before the end of 2015. A proper file system monitor is probably a >= 2016 item. They are surprisingly hard to write correctly. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
Sorry for not replying earlier but I have not yet gotten any time to actually try it out on a raspberry. Hopefully tonight.... anyway - if the original code worked then the following "could" there is probably mistakes since I compiled this on windows and the ::open call in not working there... #include <fcntl.h>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/asio.hpp>
#include <boost/function.hpp>
class gpio_interrupt
{
public:
gpio_interrupt(boost::asio::io_service &ios, unsigned int number) :
_socket(ios)
{
open(number);
}
~gpio_interrupt()
{
close();
}
void open(unsigned int number)
{
boost::system::error_code ec;
open(number, ec);
boost::asio::detail::throw_error(ec, "open");
}
void open(unsigned int number, boost::system::error_code& ec)
{
std::ostringstream filename;
filename << "/sys/class/gpio/gpio" << number << "/value";
int fd = ::open(filename.str().c_str(), O_RDONLY | O_NONBLOCK);
if (fd < 0)
{
ec = boost::system::error_code(errno,
boost::asio::error::get_system_category());
return;
}
_socket.assign(boost::asio::ip::udp::v4(), fd, ec);
}
void close()
{
boost::system::error_code ec;
cancel(ec);
boost::asio::detail::throw_error(ec, "close");
}
void close(boost::system::error_code& ec)
{
cancel(ec);
}
void cancel()
{
boost::system::error_code ec;
_socket.cancel(ec);
boost::asio::detail::throw_error(ec, "close");
}
void cancel(boost::system::error_code& ec)
{
_socket.cancel(ec);
}
void async_wait(boost::function<void(const boost::system::error_code& ec)> cb)
{
_socket.async_receive(boost::asio::null_buffers(), [cb](const
boost::system::error_code& ec, std::size_t bytes_transferred)
{
cb(ec);
});
}
private:
boost::asio::ip::udp::socket _socket;
};
int main(int argc, char **argv)
{
boost::asio::io_service ios;
boost::asio::io_service::work work(ios);
boost::thread thread(boost::bind(&boost::asio::io_service::run, &ios));
gpio_interrupt interrupt(ios, 21);
interrupt.async_wait([](const boost::system::error_code& ec)
{
if (ec)
{
std::cout << ec.message() << std::endl;
return;
}
std::cout << "Pin changed state" << std::endl;
});
while (true)
{
boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
// just dummy - do something else here...
}
ios.stop();
thread.join();
return 0;
}
Haven't tested it, but at first glance it looks correct. So you don't think there's a need to implement service and all that, like I did in my code? Kyle On Mon, 2015-03-16 at 12:27 +0100, svante karlsson wrote:
Sorry for not replying earlier but I have not yet gotten any time to actually try it out on a raspberry.
Hopefully tonight.... anyway - if the original code worked then the following "could" there is probably mistakes since I compiled this on windows and the ::open call in not working there...
#include <fcntl.h> #include <iostream> #include <boost/bind.hpp> #include <boost/thread.hpp> #include <boost/asio.hpp> #include <boost/function.hpp>
class gpio_interrupt { public: gpio_interrupt(boost::asio::io_service &ios, unsigned int number) : _socket(ios) { open(number); }
~gpio_interrupt() { close(); }
void open(unsigned int number) { boost::system::error_code ec; open(number, ec); boost::asio::detail::throw_error(ec, "open"); }
void open(unsigned int number, boost::system::error_code& ec) { std::ostringstream filename; filename << "/sys/class/gpio/gpio" << number << "/value";
int fd = ::open(filename.str().c_str(), O_RDONLY | O_NONBLOCK); if (fd < 0) { ec = boost::system::error_code(errno, boost::asio::error::get_system_category()); return; } _socket.assign(boost::asio::ip::udp::v4(), fd, ec); }
void close() { boost::system::error_code ec; cancel(ec); boost::asio::detail::throw_error(ec, "close"); }
void close(boost::system::error_code& ec) { cancel(ec); }
void cancel() { boost::system::error_code ec; _socket.cancel(ec); boost::asio::detail::throw_error(ec, "close"); }
void cancel(boost::system::error_code& ec) { _socket.cancel(ec); }
void async_wait(boost::function<void(const boost::system::error_code& ec)> cb) {
_socket.async_receive(boost::asio::null_buffers(), [cb](const boost::system::error_code& ec, std::size_t bytes_transferred) { cb(ec); }); }
private: boost::asio::ip::udp::socket _socket; };
int main(int argc, char **argv) { boost::asio::io_service ios; boost::asio::io_service::work work(ios); boost::thread thread(boost::bind(&boost::asio::io_service::run, &ios));
gpio_interrupt interrupt(ios, 21); interrupt.async_wait([](const boost::system::error_code& ec) { if (ec) { std::cout << ec.message() << std::endl; return; }
std::cout << "Pin changed state" << std::endl; });
while (true) {
boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); // just dummy - do something else here... }
ios.stop(); thread.join(); return 0; }
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
I think it should be similar to networking code - and there is no need to implement that there. I'm still curious as to why we would get an interrupt because state changes on IO pins (but that has nothing to do with boost). But as I said I have been to busy the last week to try it out on a raspberry. Also note that I have not thought the destruction faze through. So you might crash there.. 2015-03-16 14:18 GMT+01:00 Kyle Edwards <kedwards@bullex.com>:
Haven't tested it, but at first glance it looks correct.
So you don't think there's a need to implement service and all that, like I did in my code?
Kyle
On Mon, 2015-03-16 at 12:27 +0100, svante karlsson wrote:
Sorry for not replying earlier but I have not yet gotten any time to actually try it out on a raspberry.
Hopefully tonight.... anyway - if the original code worked then the following "could" there is probably mistakes since I compiled this on windows and the ::open call in not working there...
#include <fcntl.h> #include <iostream> #include <boost/bind.hpp> #include <boost/thread.hpp> #include <boost/asio.hpp> #include <boost/function.hpp>
class gpio_interrupt { public: gpio_interrupt(boost::asio::io_service &ios, unsigned int number) : _socket(ios) { open(number); }
~gpio_interrupt() { close(); }
void open(unsigned int number) { boost::system::error_code ec; open(number, ec); boost::asio::detail::throw_error(ec, "open"); }
void open(unsigned int number, boost::system::error_code& ec) { std::ostringstream filename; filename << "/sys/class/gpio/gpio" << number << "/value";
int fd = ::open(filename.str().c_str(), O_RDONLY | O_NONBLOCK); if (fd < 0) { ec = boost::system::error_code(errno, boost::asio::error::get_system_category()); return; } _socket.assign(boost::asio::ip::udp::v4(), fd, ec); }
void close() { boost::system::error_code ec; cancel(ec); boost::asio::detail::throw_error(ec, "close"); }
void close(boost::system::error_code& ec) { cancel(ec); }
void cancel() { boost::system::error_code ec; _socket.cancel(ec); boost::asio::detail::throw_error(ec, "close"); }
void cancel(boost::system::error_code& ec) { _socket.cancel(ec); }
void async_wait(boost::function<void(const boost::system::error_code& ec)> cb) {
_socket.async_receive(boost::asio::null_buffers(), [cb](const boost::system::error_code& ec, std::size_t bytes_transferred) { cb(ec); }); }
private: boost::asio::ip::udp::socket _socket; };
int main(int argc, char **argv) { boost::asio::io_service ios; boost::asio::io_service::work work(ios); boost::thread thread(boost::bind(&boost::asio::io_service::run, &ios));
gpio_interrupt interrupt(ios, 21); interrupt.async_wait([](const boost::system::error_code& ec) { if (ec) { std::cout << ec.message() << std::endl; return; }
std::cout << "Pin changed state" << std::endl; });
while (true) {
boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); // just dummy - do something else here...
}
ios.stop(); thread.join(); return 0; }
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
On Mon, 2015-03-16 at 14:32 +0100, svante karlsson wrote:
I'm still curious as to why we would get an interrupt because state changes on IO pins (but that has nothing to do with boost). But as I said I have been to busy the last week to try it out on a raspberry.
It's hardware-specific. Some (not all) processors have GPIO pins that double as interrupt pins. If the processor supports it (Raspberry Pi's BCM2835/2836 does) and it's implemented in the kernel, then you can wait on interrupts in userspace, like we're doing here. Kyle
participants (5)
-
Bjorn Reese
-
Eric Prud'hommeaux
-
Kyle Edwards
-
Niall Douglas
-
svante karlsson