I’m working on a project at the moment which has to connect to some industrial control equipment. The communications protocol in use is Modbus, or to be more precise its Modbus TCP variant. Working with this protocol is made much easier by the convenient libmodbus, a free and open-source software library which handles the communications and data formatting. The library is included with Debian Linux, the platform on which I’m writing the software.
Convenient as it is, libmodbus is written with the assumption that communications are synchronous: that it’s OK to request some data and wait for the response. For example, fetching some data from a Modbus device looks like this (in abbreviated C):
uint16_t registers[5]; modbus_t *mb = modbus_new_tcp("192.168.1.20", 1502); modbus_connect(mb); modbus_read_registers(mb, 0, 5, registers); modbus_close(mb);
The code above fetches the contents of registers 0-5 from the Modbus device at IP address 192.168.1.20, port 1502. It’s delightfully simple. My problem is that each of the network operations: modbus_connect(), modbus_read_registers() and modbus_close() could take some time, if the network is congested or unreliable or if the device is busy doing something else.
My software needs to handle various types of communication from different sources on the network, so hanging around while any of them completes isn’t acceptable. It’s OK to wait for data – that’s just life – but being unresponsive to other things while that data is arriving just won’t do.
Another project I worked on last year used D-Bus communications which faces exactly the same problem. It’s intended for relatively complex software systems where many things could be going on at the same time. The authors of D-Bus have thought of this, and made it easy to use asynchronously. Rather than asking for some data and simply being unable to do anything else until it arrives, asynchronous operation allows the program to request some data, get on with something else, and be informed when the data is ready. The same applies to other operations which may take some time.
At the core of asynchronous operation is the run loop. Rather than the program being a step-by-step series of synchronous operations like the example above, it has a loop which sits waiting for any new activity, and then triggers any actions which need to deal with that activity. For example, in pseudo-C again:
initialise_everything(); while(1) run_loop(); run_loop() { if(nothing_happening()) sleep_for_a_moment(); switch(what_happened()) { case network_connection_succeeded: start_sending_data(); case data_sent: start_receiving_data(); case data_received: notify_application(); case error: /* handle error */ } }
This structure means that lots of operations can be outstanding, and whichever needs attention first can get that attention without waiting for any of the others. It’s more complex but much more powerful.
My application is interested in network data from various places, and Linux (as well as many other operating systems) provides some handy operating system services that make asynchronous operation straightforward, with a little thought. The most important is select().
The select() system call allows a program to wait until something happens to any of a list of file descriptors, each of which can represent a hardware device, a network connection, or various other things. It also allows a timeout, so if nothing happens for a moment, the program can do other things, then call select() again without missing anything.
The D-Bus library has two important interfaces which make it possible to base the applications run loop around select():
- D-Bus will tell the application each time it is interested in a new file descriptor, or it is no longer interested in a file descriptor. This is known as adding or removing a watch.
- D-Bus provides a function which can be called whenever one of the file descriptors is indicated by select().
That’s basically it. It means that D-Bus can get on with whatever networking complexity it likes without occupying my application any more than it has to. Sauce for the goose is sauce for the gander, so this model should fit my Modbus application too.
Since libmodbus is open source, I was able to modify it to support this method of operation. Most of the required code was already in there, but I had to create new functions which called it in particular ways, and add new data to the modbus_t structure to keep track of what operations were outstanding. The new asynchronous way of working looks like this, in rather abbreviated form:
modbus_t *mb = modbus_new_tcp("192.168.1.20",1502); modbus_set_connected_cb(mb, &connect_callback); modbus_set_read_cb(mb, &read_callback); modbus_set_add_watch_cb(mb, &add_watch_callback); modbus_set_remove_watch_cb(mb, &remove_watch_callback); modbus_connect_async(mb); while(1) run_loop(); void connect_callback(int failure) { if(!failure) modbus_read_registers_async(mb, 0, 5, registers); } void read_callback(int failure) { if(!failure) /* we got the data we asked for */ } void add_watch_callback(int fd, int flags) { /* add fd to our list of file descriptors */ } void remove_watch_callback(int fd, int flags) { /* remove fd from our list of file descriptors */ } void run_loop() { if(select(list_of_file_descriptors)) modbus_selected(fd, flags); }
I’ve left lots of detail out here, but the sequence of operations looks like this:
- the application informs libmodbus about the various functions which should be called when things happen: a connection succeeds, data is received, a new watch is to be added, a watch is to be removed.
- the application asks libmodbus to start a connection, but asynchronously using libmodbus_connect_async().
- the application then just sits in the run loop.
- libmodbus call the application back through add_watch_callback(), adding a watch on the socket it will use for the connection. It then asks the operating system to make the connection.
- when the connection completes, select() will return its file descriptor, and the run loop will call libmodbus via modbus_selected()
- libmodbus now checks that the connection was successful, and calls the application through connect_callback().
- The application can now request data using modbus_read_registers_async().
- While requesting the data, libmodbus will almost certainly use add_watch_callback() to inform the run loop that it should keep an eye out for the data.
- When data arrives, which may happen in several small chunks, the run loop will call modbus_selected().
- libmodbus can assemble and check the received data. When it has arrived succesfully, or failed terminally, it will call the application back through read_callback().
- The application can now work with the received data.
While all this is going on, the application can be doing other things: handling other network connections, processing data, or even handling other Modbus connections.
The modifications, after some development and debugging time, work very nicely. After more testing, they’ll almost certainly make it into the final application, and I’d like to contribute them back to the open source community so that other developers can use Modbus asynchronously too.
Hi, do you have your modifications to libmodbus available somewhere? Thanks.
Hi Jorge, yes, the modifications are available because the original code is released under the GPL so I will make my modifications available. I don’t know what the best way to do it is – I could just email you the files, but then we could end up with a version control nightmare! Perhaps we should talk to the original author, Stephane Raimbault, and see if there’s a way of creating a branch for the modifications that everyone can share and experiment with.
Jorge, I’ve been in touch with Stephane Raimbault, the original libmodbus author, and agreed with him to fork libmodbus on github, so my changes are now visible at https://github.com/cmjones01/libmodbus. There’s no documentation or examples there which cover the asynchronous interface at the moment, but the code does build and run in my development application. Let me know if you want to know more.
Thanks for publishing the code. I’ll test it as soon as possible.
Martin, Thanks for publishing your async library modifications to LibModbus. Interestingly enough your application to use the DBUS and LibModbus is almost identical to mine. I have been testing out the error handling (requesting more registers than available) and the underlying code reports this as Illegal data value, however by the time i get the async read callback it seems that the errno has been reset, I just get the failure -1 in the callback.
Have you observed this error ?
Richard, thank you for your reply and I’m glad you’ve found it useful. I can quite believe there are some oversights in the error handling. My application was concerned mostly with networking errors rather than Modbus errors, so I probably haven’t looked at that aspect well enough. The use of errno is pretty fragile in an asynchronous environment, though it should be possible for the library to set it correctly before making the callback. I’ll have to look at the code.
Hey, love the idea and implementation of the asynchronous libmodbus. Thank you very much for providing the code. I tried to setup a server which works, but it is kinda leaking fds, meaning accepted sockets do not get closed if the connection is closed by the peer. Do you possibly have an example where the lib is used as server, because I don´t quite know if I am using it correctly.
I’m glad you like the implementation, and thank you for your feedback. It’s a long time since I’ve used this code and I have only deployed it in to production as a client (it’s still out there doing its job). I think I did implement a test server and vaguely remember some problems with leaking fds on failed connections. I’ll try to find the code when I get chance (may be a while).