Modbus-RTU scripts


Here we describe a method for implementing the Modbus-RTU protocol using a shell script and binding in the form of js code. The discussed method can be used to implement other streaming protocols, where it is necessary to operate with byte arrays in a limited environment (router).

Three line idea

For impatient I show the main idea:

printf "\x00\x03\x00\x00\x00\x01\x85\xDB" > $tty
( dd if=$tty of=$ans count=256 2> /dev/null ) & /usr/bin/sleep $timeout; kill $!
echo "[`hexdump -ve '1/1 "%d,"' $ans | sed 's/\(.*\),/\1/'`]"


To begin with, we will determine the goals. Suppose we have a router with OpenWrt firmware such as TL-MR3020 and we need to use it to control a third-party device using the modbus-rtu protocol. We will not consider the options for connecting such a device to the router (there are several of them), but consider the possible ways of writing control software for such a bundle.

The first thing that comes to mind is the use of libmodbus, but for this you need to write a C program, compile it. Any solution with compilation requires advanced working skills, the availability of appropriate software and even an OS. In general, this is not an option, as a method, for widespread use.

The second thing you can try is the scripting engines available in OpenWrt. For example, lua. There are others there, but again problems. You need to study them, if you do not know, but this is not so bad. There is very little free space on the TL-MR3020 router, literally up to 1 MB. If you install script packages with dependencies, then there may simply not be enough space for something else.

Empirically, sorting through the various options, I noticed here: Some black magic: bash, cgi and file uploads . This short article provides an example of downloading a file using a shell script with the same limitations as mine. In short, we see the use of the dd command to dump a binary stream from a request to a file directly without using temporary files. This code is just the perfect candidate to solve our problem.


Now we will analyze those three lines that I cited above.

Step 1 . To implement the modbus-rtu protocol, we need to form a request and receive a response. This request should be issued as an array of bytes. For this purpose, we use printf and output redirection:

printf "\x00\x03\x00\x00\x00\x01\x85\xDB" > $tty

Step 2 . Ok, we sent a request, but how to get a response? We will not be able to use read for these purposes, as with zero bytes this command is not friendly. We use the technique with the dd command above and save the received data to a file. But there is one thing but, because you need to specify the exact number of bytes received. We won’t be able to parse the package in the script by byte in the loop (the size can be found from the received data), because just do not have time most likely. You can get out of the situation by specifying the maximum size of the package (256 bytes), but dd will freeze and will wait for reception if less has arrived. And here we make the last trick: Timeout a command in bash without unnecessary delay

( dd if=$tty of=$ans count=256 2> /dev/null ) & /usr/bin/sleep $timeout; kill $!

or so:

timeout $timeout dd if=$tty of=$ans count=256 2> /dev/null

The second option requires about 60 Kb to use timeout and we will not use it when there is a “free” solution. As a result of the work of such a command, we get a file with the received data.

Step 3 . We print the received byte array in some convenient format:

echo "[`hexdump -ve '1/1 "%d,"' $ans | sed 's/\(.*\),/\1/'`]"

This code represents each byte in decimal, inserts commas between them, removing the last comma, and wraps them in square brackets. This is an array in json and can be easily converted to a js array (JSON.parse () or generally automatically for $ .post () with the parameter 'json').

If you have the specified router and access to the terminal, then you can check these steps by connecting the router via usb-com adapters and a null modem to the PC. As a modbus device, you can use an emulator, for example this: Modbus Slave .

What does JavaScript have to do with it?

The observant reader may ask: “But how to read crc for the data to be sent in the shell script?” I think that in no way (I found the calculation only for strings and then on bash, but we have a truncated version of the interpreter). We will deal with this “upper” level, namely, the html page that calls the script using the post request. This is not difficult, here is a piece of code from the example, which I will discuss below, which is responsible for executing the query (using jQuery):

Post: function( slaveid, func, bytes ) {
    var self = this;
    // Добавляем CRC к запросу.
    var crc = this.crc16( bytes );
    bytes.push( crc & 0xFF );
    bytes.push( crc >> 8 );
    // Преобразуем массив в строку.
    var adu = '';
    for ( var b in bytes ) adu += '\\x' + dec2hex( bytes[b] );
    // Выводим application data unit (ADU).
    $('#console').val( adu );
    return $.post( this.Url, { action: 'query', serial: this.Serial, data: adu },
        function( data ) { self.OnReceive( slaveid, func, data ); }, 'json' );
Function: function( slaveid, func, address, value ) {
    var bytes = [];
    try {
        bytes.push( slaveid );
        bytes.push( func );
        bytes.push( address >> 8 );
        bytes.push( address & 0xFF );
        bytes.push( value >> 8 );
        bytes.push( value & 0xFF );
        return this.Post( slaveid, func, bytes );
    } catch ( ex ) {
        console.error( ex );

The checksum itself is considered a tabular method. I will not give tables, they are both on the network and in the example, and the code itself is standard:

crc16: function( data ) {
    var hi = 0xFF;
    var lo = 0xFF;
    var i;
    for (var j = 0, l = data.length; j < l; ++j) {
        i = lo ^ data[j];
        lo = hi ^ CRC_HI[i];
        hi = CRC_LO[i];
    return hi << 8 | lo;


It remains only to show a concrete example. This is clearly not easy to do, so I refer to my module for alternative CyberWrt firmware: the CyberWrt Modbus module . There you can download the latest archive with the source code of the module, as well as other related documentation.

An example looks like this:

1. Error while receiving.

2. Read 10 registers.


For example, the archive will contain the source modbus.js, which implements all the functionality of the protocol. The received data is still located in the Modbus.Register [] property. I did this option by analogy with the ActiveX component of the MBAXP Modbus RTU / ASCII ActiveX Control . If you read the help for it, you will understand the organization of the code.

The example is still being finalized, so the current description may be outdated.

Supplement [06/11/2014]

Added support for tasks and their periodic execution. There was a problem with their overlay.



1. Modbus Application Protocol V1.1b3 (pdf)
2. Description of the Modbus protocol in Russian (doc)
3. CyberWrt module “Modbus” (example)

Also popular now: