📜 ⬆️ ⬇️

Modbus-RTU on scripts

annotation


Here is described a method for implementing the Modbus-RTU protocol using a shell script and binding in the form of a js code. The method discussed can be used to implement other streaming protocols where you need to operate arrays of bytes in a restricted environment (router).



The idea in three lines


For impatient showing 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/'`]" 

Task


To begin with, we will define our goals. Suppose that we have a router with OpenWrt firmware of type TL-MR3020 and need to use it to manage a third-party device using the modbus-rtu protocol. We will not consider options for connecting such a device to a router (there are several), but consider 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 a compilation requires advanced skills, the availability of appropriate software and even the 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. They need to be studied if you do not know, but this is half the trouble. There is very little free space on the TL-MR3020 router, literally up to 1 MB. If you install script packages with dependencies, you may simply not have enough space for something else.

Experienced way, going through different options, I noticed here: Some black magic: bash, cgi and file uploads . This small article gives an example of loading a file using a shell script with the same restrictions as mine. In short, we see the use of the dd command to reset a binary stream from a request to a file directly without using temporary files. This code is just the perfect candidate for solving our problem.

Decision


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 must 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 . Well, we sent the request, and how to get an answer? We will not be able to use read for this purpose, since with zero bytes, this command is not friendly. We use the trick with the dd command specified above and save the received data to a file. But there is one thing here, because you need to specify the exact number of bytes received. We cannot parse the package in a script by-byte in the script (you can find out the size from the received data), since 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 wait for reception if a smaller amount has arrived. And here we do 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 this command, we get a file with the received data.

Step 3 . We output 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 form, inserts commas between them, removing the last comma, and wrapping it in square brackets. This is an array in json and it is easy to translate it into a js-array (JSON.parse () or in general automatically for $ .post () with the 'json' parameter).

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, like this: Modbus Slave .

And here is JavaScript?


The observant reader may ask: “How do you count crc for the sent data in a shell script?” I think that in any way (I found the calculation only for strings and then on bash, but we have a truncated version of the interpreter). This task will be dealt with by the “upper” level, namely, the html-page causing the script using the post-request. This is done easily, here is a piece of code from the example, which I will discuss below, which is responsible for the execution of 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 ); } }, 

We consider the checksum itself as a tabular method. I will not give the tables, they are in 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; } 

Example


It remains only to show a specific example. Obviously, this is not easy to do, so I refer to my module for an 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.

The example looks like this:

1. Error while receiving.



2. Read 10 registers.



Conclusion


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

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

Addition [11.06.2014]

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

image

Links


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

Source: https://habr.com/ru/post/225833/


All Articles