[Boost-users] [Asio] Need help making a custom service

469 views
Skip to first unread message

Kyle Edwards

unread,
Mar 9, 2015, 3:36:27 PM3/9/15
to boost...@lists.boost.org
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




_______________________________________________
Boost-users mailing list
Boost...@lists.boost.org
http://lists.boost.org/mailman/listinfo.cgi/boost-users

Bjorn Reese

unread,
Mar 9, 2015, 4:22:04 PM3/9/15
to boost...@lists.boost.org
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.html
[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_socket.hpp

Kyle Edwards

unread,
Mar 9, 2015, 4:35:03 PM3/9/15
to boost...@lists.boost.org
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

Bjorn Reese

unread,
Mar 9, 2015, 5:25:15 PM3/9/15
to boost...@lists.boost.org
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?

Kyle Edwards

unread,
Mar 10, 2015, 8:28:09 AM3/10/15
to boost...@lists.boost.org
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);

Bjorn Reese

unread,
Mar 10, 2015, 9:54:00 AM3/10/15
to boost...@lists.boost.org
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.

Kyle Edwards

unread,
Mar 10, 2015, 3:05:39 PM3/10/15
to boost...@lists.boost.org
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!

svante karlsson

unread,
Mar 10, 2015, 4:42:36 PM3/10/15
to boost...@lists.boost.org
If you got it working by wrapping the file descriptor - post it and I'll give it a shot on tidying it up.


Kyle Edwards

unread,
Mar 10, 2015, 5:15:08 PM3/10/15
to boost...@lists.boost.org
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!

Eric Prud'hommeaux

unread,
Mar 11, 2015, 7:25:45 AM3/11/15
to boost...@lists.boost.org
* svante karlsson <sa...@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#L1157

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).
--
-ericP

office: +1.617.599.3509
mobile: +33.6.80.80.35.59

(er...@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.

Niall Douglas

unread,
Mar 11, 2015, 10:19:37 AM3/11/15
to boost...@lists.boost.org
On 11 Mar 2015 at 7:25, Eric Prud'hommeaux wrote:

> * svante karlsson <sa...@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#L1157
>
> 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/


svante karlsson

unread,
Mar 16, 2015, 9:07:39 AM3/16/15
to boost...@lists.boost.org
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;
}

Kyle Edwards

unread,
Mar 16, 2015, 9:18:15 AM3/16/15
to boost...@lists.boost.org
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

svante karlsson

unread,
Mar 16, 2015, 9:44:55 AM3/16/15
to boost...@lists.boost.org
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..





Kyle Edwards

unread,
Mar 16, 2015, 9:53:55 AM3/16/15
to boost...@lists.boost.org

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
Reply all
Reply to author
Forward
0 new messages