Asynchronous operation for libmodbus

557 views
Skip to first unread message

Chris Jones

unread,
Dec 16, 2015, 8:53:04 AM12/16/15
to libmodbus
Hi all,

I've been working with libmodbus for the last couple of weeks and would like to thank those involved in its creation.

My application is centred around a select() run loop because it has to service data from various different places, not just Modbus. The existing synchronous API works well but can introduce arbitrary delays while network operations happen. Even though the timeout is quite short at half a second, that's still too long to occupy an application which is busy with other things.

I therefore decided to try implementing an asynchronous API to libmodbus, so that instead of simply calling modbus_connect(), modbus_read_registers() and so on, the application code looks like this:

main() {
  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)
runLoop();
}

void connect_callback(modbus_t *mb, int failure) {
if(failure) {
printf("connect_callback(): Connection failed\n");
} else {
printf("connect_callback(): Connection OK\n");
modbus_read_registers_async(mb, 0, 5, tab_reg);
}
}

void read_callback(modbus_t *mb, int failure) {
int n;

if(failure) {
printf("read_callback(): read failed\n");
} else {
printf("read_callback(): read OK\n");
/* Read 5 registers from the address 0 */
for(n=0;n<5;n++) {
printf("Register %d = 0x%04x\n",n,tab_reg[n]);
}
}
}

The runLoop() function and add_watch_callback() and remove_watch_callback() functions are provided by my application code.

The asynchronous API model I've used is based on the one from D-Bus, which I've used successfully before. I have more details of my thought processes in my blog here:

I have a working demo, both server and client.

My questions: is anyone else interested? Could I contribute my modifications to the code base? They're far from fully developed, but they do work well enough to demonstrate and I am fully intending to ship them in an application in the next few months, so they will be tested!

Regards
Chris Jones
Martin-Jones Technology Ltd
Cambridge, UK and Warsaw, Poland

Adam Parker

unread,
Jan 16, 2016, 3:32:15 AM1/16/16
to libmodbus
Interesting.  What are your speeds compared to the reference implementation?

Async operation is a much needed feature IMO.  But I don't think radical changes need to be made to the API to achieve it.  Allow copying contexts so that each connection may have its own.  I do not mean to hijack your thread but maybe the creator can weigh in on why context copy is not a thing.  I can't see any variables that looks inherently dangerous to copy.  Also functionize the first 400~ lines of modbus_reply so that it can be called without the send_msg and returns the rsp/rsp_len.  That way send_msg can be threaded using the newly copied context and the response generated by the very thorough code in modbus_reply.

This more closely aligns with how a grew up programming multi-threaded servers.  Where when a client would connect, you were given an object, you handed that object to a thread and that thread would loop on it until the connection was closed.  I know in this case there is that pesky modbus array which can be unsafe in a multi-threaded application but I'm sure talented programmers such as ourselves can find a way to manage an array in a thread safe manner.

Chris Jones

unread,
Jan 18, 2016, 12:00:11 PM1/18/16
to libmodbus


On Saturday, January 16, 2016 at 9:32:15 AM UTC+1, Adam Parker wrote:
Interesting.  What are your speeds compared to the reference implementation?

I don't know yet. I haven't tested for speed. My intent was to make a modification to suit the particular programming paradigm I need at the moment, rather than optimise for performance (yet!).
 
Async operation is a much needed feature IMO.  But I don't think radical changes need to be made to the API to achieve it.  Allow copying contexts so that each connection may have its own.  I do not mean to hijack your thread but maybe the creator can weigh in on why context copy is not a thing.  I can't see any variables that looks inherently dangerous to copy.  Also functionize the first 400~ lines of modbus_reply so that it can be called without the send_msg and returns the rsp/rsp_len.  That way send_msg can be threaded using the newly copied context and the response generated by the very thorough code in modbus_reply.


My changes to the API are very much intended to be single-threaded, with the application's run loop centred around a single switch() statement. In this case it's because the application is running on a fairly limited single-core CPU so there's no benefit to threading. I agree that if you want maximum performance from a modern multi-core CPU then multi-threading is the way forward, though I suspect that CPU power isn't the limiting factor on Modbus performance - the communications and the device at the other end are more likely to be the bottlenecks.

Maybe there are two different problems looking for two different solutions here: a thread-safe library for those who need multithreading, and an asynchronous API for those who need to play nicely with a single run loop handling communications and events from multiple sources. I believe GLib works this way.

Chris

Adam Parker

unread,
Jan 18, 2016, 3:56:42 PM1/18/16
to libmodbus
>My changes to the API are very much intended to be single-threaded, with the application's run loop centred around a single switch() statement. In this case it's because the application is running on a fairly limited single-core CPU so there's no benefit to threading. I agree that if you want maximum performance from a modern multi-core CPU then multi-threading is the way forward, though I suspect that CPU power isn't the limiting factor on Modbus performance - the communications and the device at the other end are more likely to be the bottlenecks.

This is true, my build is more for multi-core and implies threading in the codebase it's built upon but you are right, I'm not CPU bound on most machines.  I can CPU bind on a beaglebone but only over loopback and in transfer ranges well exceeding normal throughput.  It's hard to make my idea work without taking some performance hit for everybody and not just people who need concurrency so I'm not comfortable submitting it.  In practicality the numbers are pretty much the same but it fails the "TEST SLAVE REPLY 3/4" unit test with a  timeout (3.0.X).  So that's not workable.  I think there should be a discussion about what could be gained from making certain tasks asynchronous or concurrent and what that looks like.  I go back and forth about how much I'd really gain with concurrency since I use select in the main loop.

Adam

Chris Jones

unread,
Feb 11, 2016, 10:42:38 AM2/11/16
to libmodbus


On Wednesday, December 16, 2015 at 2:53:04 PM UTC+1, Chris Jones wrote:

I therefore decided to try implementing an asynchronous API to libmodbus, so that instead of simply calling modbus_connect(), modbus_read_registers() and so on, the application code looks like this:

...
The asynchronous API model I've used is based on the one from D-Bus, which I've used successfully before. I have more details of my thought processes in my blog here:

I've had a bit of interest expressed in the asynchronous API, so the code is now available on Github at


It does build and work, but there's no documentation yet. Let me know if you want to know more.

Chris 

Robert Middleton

unread,
May 14, 2016, 8:18:53 PM5/14/16
to libmodbus
Has anybody else tried this out?  I want to use asynchronous calls on a project that I'm working on at the moment.  It would be nice if something like this could be merged into the mainline version of libmodbus.

-Robert Middleton
Reply all
Reply to author
Forward
0 new messages