[SOLVED] Receiving all data of a modbus RTU message
-
Hi,
I'm working on communication between my application and a board, with the modbus RTU protocol. At the moment I can write and read data from the board, but I have problems in reading the whole data unit from the device. I'm using QSerialPort class to control the serial port.I've tried two different solutions, suggested in this forum, but both have similar problems:
- read the input data with a blocked thread;
- connect a slot that manage the data with the readyRead() signal of QSerialPort.
In the first solution I have problems with the waitForData(), that lock the thread forever. It seems to never read data. If I use a loop, without blocking conditions (or with a timeout), I read data:
@
forever{
buffer = port->readAll();
if (buffer.length()>0){
QString str;
printPDU(buffer, buffer.length(), str); //print the read message byte a byte in hex format.
qDebug() << "read: " << str;
}
if(!port->waitForReadyRead(100))
break;
}
@this works. I've read that someone discourage this solution, so I tried to connect a slot with the signal of QSerialPort:
@
void RS485_handler::read(){
buffR.truncate(0);
while(!port->atEnd()){
buffR.append(port->readAll());
}
QString str;
printPDU(buffR, buffR.length(), str); //print the read message byte a byte in hex format.
qDebug() << "read: " << str;
}
@
and it works.The problem is this:
- if I use waitForReadyRead(-1) the thread wait forever;
- if I use waitForReadyRead(100) or signals and slots, the message is read bit a bit. I can't read the whole message, but the application read it in three or four iterations.
For example, this is an output of the last code:
read: "01_"
read: "03_0a_c0_01_"
read: "1a_c3_08_fc_"
read: "00_00_00_02_"
read: "8e_f8_"
but the actual message shoul be 01_03_0a_c0_01_1a_c3_08_fc_00_00_00_02_8e_f8_.I've tried to use atEnd(), but nothing appened.
Are there any solutions to receive the whole message? I'm trying to bufferize the message, but there isn't a finish mark, so I have problem in finding the real end of the message.
-
The modbus protocol has a specific header size that should be in every return packet and an expected return byte size with respect to the function byte used. Also, you know the identifier short within the header that you sent.
What I have done is with sending multiple modbus devices on a start network so I can use chained sends and receives asynchronously is keep a map of 'identifiers' and their expected return size. So when you go to send a packet out the port, append to the map the identifier and the expected return size (header + data size). That way when you receive on the port, you verify the bytes read is > than the header size and parse out the identifier and look in the map for the expected return size. From there you can keep reading until you get the size you expect or store the bytes in a member variable until you get another ready read. Once all of the data is read, remove the entry in the map. You will have to have some flags though basically saying your in the middle of a command recv. if you are getting split up packets.
If you only plan on doing a single exchange between the hardware and your software, then you should know the return size..so you can use synchronous calls (waitForBytesWritten(), waitForReadyRead()) and work in a timeout.
There is also a third party library you can use libmodbus. I think its GPL though which is why I wrote my own.
-
Dear dvez,
thanks for your advice, but I have some question: what happen if there are broken or lost byte? In this case, if I have a read queue where I store the length, consecutive requests could read wrong data. In modbus RTU I've seen that there isn't a unique start or stop byte to identify the data unit. For example, the slave address or the function code are byte that could be repeated in the middle of the message. In these cases, if I expect to read 6 byte of data, but one of them is lost, I read also 1 byte of the following reply. From that point, all the pending readings will be wrong.I have had the same problem programming in java and I solved similarly to the last code I wrote in my last post, using a function corresponding to atEnd() to know the end of the packet, but now it doesn't work.
In your solution you managed the possibile loss of data?
-
2 elfracca,
To you provided the general principle (idea) as it can be implemented. Really implementation you should to finish thinking himself.
Except the package size, you can use also a function code. And to parse a input stream of responses, using both a length of a package, and a function code (also and to check crc). Also you can use QTimer to detect a response timeout (But this interval needs to be done big, ~100 msec. Because on smaller intervals the timer will be lies).
Anybody here doesn't limit you in a choice of your imagination.
-
Dear kuzulis,
I really appreciate your reply, because it is an incouragement for me.
I will write only a few words to explain the solution I have though to solve the problem, I hope this would be helpful for someone in my situation. I connected a read() function to readyRead() signal of QSerialPort. When a "sender" write data on serial port, it extimates the length of the reply and notify it to the receiver. The latter read until the end of the packet or the end of a timer. If the end of the timer has arrived without reading the whole packet, a timeout error will be signaled.If this solution will not be enough for my application, in the future I will try to implement queues.