
Modbus is essentially the accepted standard in automation systems for interacting with sensors, actuators, I / O modules, and programmable logic controllers.
In areas where an event model is required, newer standards such as IEC 60870-5-101 / 103/104, CANopen, DNP3 and the like are gradually replacing it, however, due to simplicity, the request-response model and the ability to work in half duplex , Modbus remains an excellent solution for telemetry systems when working through radio modems.
With an increase in the number of objects polled from the base station and an increase in data volumes (values ​​from sensors, I / O status, archives, etc.), the question arises of the polling rate. The main disadvantage when working through the radio modem is the time of "warming up" of the radio module at each initiation of the transfer of the request (it can reach 200-500 ms), which is multiplied by the number of relay nodes. With a large length of the relay chain, the probability that the request and response packet does not reach the addressee due to interference also increases. This is all the laws of physics, and they can not be circumvented.
')
About obvious techniques, such as grouping data in the address space so that they can be counted as one request, storing all the values ​​in the Holding-zone (and not separately in Holding and Inputs) for the same reason, we will not analyze, but think about How can Modbus be improved to optimize radio traffic. The proposed solutions can be easily implemented if you are both a top (OPC server) developer and a middle (controller) level developer, or if you have a close contact between the developers.
An important issue will be maintaining compatibility, so that, for example, your controller can also be polled by any other standard OPC server, and not just by your development. In the case of Modbus, this is easy, because there are about 24 standard functions (of which half is used at best in practice), and the rest are either “user-defined” or reserved. At a minimum, functions from 65 to 72 and from 100 to 110 can be freely redefined for their needs, which we will do.
Large packages
If the quality of communication with individual objects is very good (almost without loss), it makes sense to try to interrogate them in large packets. By standard, the maximum amount of data in a packet is 253 bytes. However, no one forbids us to transfer the client-server and more within our system, and for this you don’t even need to redo much.
Let's take a look at the usual Modbus function request 0x3 (Read holding registers):
01 03 FF 01 00 07 4B 44
Device address, 3rd function, start register address, number of registers requested, CRC-16. For the number of registers requested in the ADU, 2 bytes are reserved, that is, nothing prevents you from requesting values ​​greater than 255 - the main thing is that both the polled device and the polling server support this feature.
The answer is a little more complicated
01 03 FF 01 00 07 14 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E F2 56
as we see, one byte is added there (it keeps the length of the data in bytes), we no longer get into it, and therefore we have to implement a custom modbus function, in which 2 bytes will be allocated for this field.
Data compression
Everything is simple here. The less data we transmit in one packet, the more likely it is to correctly reach the destination address and the CRC will match. Or if we do not slightly fit into one package, it would be wise to try to compress the data so as not to send another request.
The compression algorithm is required to be simple (because it will have to be implemented not only on the server, but also on the controller), and works well with small portions of data (from 16 to 1024 bytes).
In our case, the RLE algorithm, which is ridiculously simple, copes with such conditions, but it turned out to be very effective on data sets typical in PLC registers (order data from ADC channels, configuration settings, etc.). True, the classic implementation, which can be found on almost all sites with algorithms, is still not perfect, because it encodes only the number of repetitions, and if there are few repetitions, the output buffer may turn out even more than the input one. Therefore, I use my own implementation, which encodes not only the number of repetitions, but also the number of non-repetitions :)
A simplified code like this:
int compress(char *in_buf, char *out_buf, int len, int maxclen) { char *c_start = in_buf; char *c_curr = in_buf; int i; int result_size = 0; char curr_type; char run_len = 1; if (*c_curr == *(c_curr+1)) curr_type = 0; else curr_type = 1; while (((c_curr - in_buf) <= len) && (result_size < maxclen)) { if ( ( (*c_curr != *(c_curr+1)) && (curr_type == 1) ) || ( (*c_curr == *(c_curr+1)) && (curr_type == 0) ) && (run_len < 127) && ((c_curr - in_buf) <= len) ) { c_curr++; run_len++; } else { if (curr_type == 0) { *out_buf = run_len; out_buf++; *out_buf = *c_curr; out_buf++; c_curr++; result_size = result_size + 2; } else { run_len--; if (run_len > 0) { *out_buf = 0x80 | run_len; out_buf++; for (i = 0; i <= run_len; i++) *(out_buf + i) = *(c_start + i); out_buf = out_buf + run_len; result_size = result_size + run_len + 1; } } c_start = c_curr; curr_type = curr_type ^ 1; run_len = 1; } } if (result_size >= maxclen) return -1; return result_size; } int decompress(char *in_buf, char *out_buf, int len) { char* c_curr = in_buf; char count; char size = 0; int i; while ((c_curr - in_buf) < len) { count = *c_curr & 0x7F; size = size + count; if (*c_curr & 0x80) { c_curr++; for (i = 0; i < count; i++) { *out_buf = *c_curr; c_curr++; out_buf++; } } else { c_curr++; for (i = 0; i < count; i++) { *out_buf = *c_curr; out_buf++; } c_curr++; } } return size; }
In compressed form, the data will look something like this:
81 45 32 41 81 42 02 41
First there is a byte that determines whether it is a block of repeating or non-repeating bytes (the high bit 0x80 is active for non-repeating blocks, and vice versa), and then directly or the repeating byte itself, or an array of non-repeating, and so on.
The above code is simplified, “fast and dirty”, there is always room to be better. It should be noted that this example compresses data using bytes, and in the case of Modbus, it will sometimes be wiser to compress using words (2 bytes each). It is easy to guess that the changes in the code will be minimal, and which of the options will suit you better depends on the characteristics of the data that you will compress.
If you use large packages, and a full-fledged operating system runs on the PLC (for example, Linux on LinPac controllers), you can even try using zlib.
Incremental read
If polling occurs very often, or vice versa, the data in the controller is updated very rarely, then sometimes it makes sense to read only the changed blocks of the address space.
The algorithm may be something like this:
1. The server sends a request similar to the 0x3 or 0x4 modbus function, but also indicating the last read number (just an incremental counter)
2. The controller checks if the number of the last reading matches the same number in the controller (that is, the previous answer was correctly delivered and processed), then the XOR goes through the prepared register buffer for sending, comparing it with the same buffer saved from the previous one request, after which it encodes only the changed data in the following way.
< >< >< >
As you can see, the algorithm will be in many ways similar to the RLE described above (we kind of believe that all the unchanged data are the same for us) and there is no magic here.
When using this method, more or less frequently changing data (timers, signals from the ADC, etc.) should be placed next to each other in the register map, and only then can the infrequently changing data go in a grouped way (bitmasks of the task’s status, discrete signals). inputs, etc.).
Little about security
Modbus does not offer any encryption or authentication technology. And from this it follows that if security is not an empty sound for you, then it is better to use a more suitable protocol for this.
It is clear that no one will manage the critical systems through an open radio channel, but I have probably seen a dozen systems in my life that worked through FSK radio modems, and the developers and users didn’t even think that any owner of a similar radio kit and laptop, knowing the register card can send any commands to the objects on behalf of the server. The same situation applies to RS-485 cable lines, they are also not protected.
The situation is somewhat complicated by the fact that often PLCs and embedded systems are limited in resources, and therefore any more or less complex and reliable symmetric algorithm will not be easy to put in there, let alone asymmetric ones.
Therefore, when writing to registers (sending an executive command or changing the configuration), besides the register values, you can additionally send, for example, MD5-hash (the algorithm is simple, fast, and despite the fact that it contains flaws in collisions, in our case they do not play a serious role) from the data itself, some “salt” (a key known only to the server and controller, and it would be nice to also provide for the rotation of keys with a small periodicity) and, for example, the current timestamp with an accuracy of 10 seconds ( Zevivimos te on the quality of time synchronization between the server and the PLC and the relay chain). Protection is far from perfect, but due to a combination of factors it can significantly complicate the implementation of evil plans.
If the controller carries on board a full-fledged OS, such as Linux or WinCE and a powerful processor, you can swing a little wider, and take, for example, libCryptoPP, and encrypt blocks, for example, with the 3DES algorithm, and not only for recording functions, but even normal reading.
Address expansion
This solution is not so simple, and not in technical terms (since everything is just the same with this), but rather administratively, because compatibility, alas, is already breaking.
If there is a need to poll more than 247 devices on the same communication line or in the same frequency range, it is worth considering the option of switching to double-byte addresses. Some well-known controllers (for example, ScadaPack) support this solution out of the box.
To be able to interrogate a device locally with a standard modbus scanner, it is possible to provide for a device to be reset to single-byte mode, for example, when a certain jumper is closed, or to reserve some address (for example, 247) that will never be found in the high byte of extended addresses, and when receiving a package with which the controller will always treat it as a standard package.
If the relay algorithm is processed by modems, then there should be no problems with this if devices with addresses containing the same high-order byte of the address are in the same relay group — in this way, the modems will accept the message as a normal Modbus packet, retransmit it to of the final sector, there all the controllers will receive it, in which the high byte of the address matches, but only one will process and respond to it, in which not only the high byte, but also the low byte will match.
It sounds a bit confusing, but this scheme is really tested in practice and works.