I'm using Qt5 (with QSerialPort) to write a modbus master. The program works, but sometimes (chance of 20%) the receive message is lost, even if the slave device has responded (the receive light on the DB9 connector has flashed). I want to figure out whether the data lost is caused by bugs in my program.
I use a state machine to design the program:
Since modbus protocol is used, the expected head and length of response data is known for each message. So the head and length of the receive data is checked to identify one receive message. On receive data timeout, what is left in the receive buffer is printed.
Here is the code:
struct CommMaster{
typedef enum{
Idle,
WaitForReceive
}MainState;
typedef enum{
WaitForHead,
WaitForComplete
}SubState;
MainState mainState;
SubState subState;
CommMaster(){
mainState=Idle;
}
};
class MainWindow : public QMainWindow{
//...
CommMaster comm;
QSerialPort *serial;
QQueue<QByteArray> sendQueue;
QByteArray expectReceiveHead;
int expectReceiveLength;
QTime lastSendTime;
QByteArray receive;//receive buffer
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
//...
connect(serial,&QSerialPort::readyRead,this,&MainWindow::serial_read);
}
void MainWindow::timer_timeout()//5ms period timer
{
if(serial->isOpen()){
QTime current=QTime::currentTime();
int passedTime=lastSendTime.msecsTo(current);
//state machine
switch(comm.mainState){
case CommMaster::Idle:
if(passedTime>=sendPeriod){
if(sendQueue.length()>0){
SendOneMessageAndSetExpectReceive(sendQueue,expectReceiveHead,expectReceiveLength);//send one message from sendQueue, and get expected receive message head and length
lastSendTime=current;
comm.mainState=CommMaster::WaitForReceive;
comm.subState=CommMaster::WaitForHead;
}
}
break;
case CommMaster::WaitForReceive:
if(passedTime>=kReceiveTimeout){
QByteArray readAll=serial->readAll();
qDebug()<<"Receive timeout, receive="<<receive<<" serial->readAll()="<<readAll;
comm.mainState=CommMaster::Idle;
receive.clear();
}
break;
default:
qDebug()<<"Wrong main state"<<comm.mainState;
}
}else{
sendQueue.clear();
}
}
void MainWindow::serial_read()
{
if(comm.mainState==CommMaster::WaitForReceive){
QByteArray read=serial->readAll();
switch(comm.subState){
case CommMaster::WaitForHead:{
int headIdx=read.indexOf(expectReceiveHead);
if(headIdx>=0){//head found
int length=read.length()-headIdx;
if(length>=expectReceiveLength){
handleReceive((read.data(),expectReceiveLength));
receive.clear();
comm.mainState=CommMaster::Idle;
}else{
receive=read.mid(headIdx);
}
}
break;
}
case CommMaster::WaitForComplete:{
int length=receive.length()+read.length();
receive.append(read);
if(length>=expectReceiveLength){
handleReceive(receive.data(),expectReceiveLength);
receive.clear();
comm.mainState=CommMaster::Idle;
}
break;
}
default:
break;
}
}
void MainWindow::SendOneMessageAndSetExpectReceive(const QQueue<QByteArray> &sendQueue,QByteArray &expectHead,int &expectLength)
{
QByteArray send=sendQueue.dequeue();
expectHead=QByteArray::fromHex("0210");
expectLength=8;
serial->write(send);
}
Here is the debug output:
Receive timeout, receive= "" serial->readAll()= ""
Receive timeout, receive= "" serial->readAll()= ""
Receive timeout, receive= "" serial->readAll()= ""
Receive timeout, receive= "" serial->readAll()= ""
Related
I'm trying to send some data from QLocalSocket to QLocalSever in a loop. Sever only gets the first data and not receiving subsequent data, but if I introduce 1 mec delay between each call from the client then the server starts to receive everything. Please check out the below Client & Server code.
client.cpp
#include "client.h"
#include "QDataStream"
#include <QTest>
TestClient::TestClient() : m_socket{new QLocalSocket(this)}{
m_socket->connectToServer("TestServer");
if (m_socket->waitForConnected(1000)) {
qDebug("socket Connected!");
}
connect(m_socket, &QLocalSocket::readyRead, this, &TestClient::onNewData);
}
void TestClient::onNewData() {
qCritical() << "data received from server";
}
void TestClient::sendDataToServer() {
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_10);
QString testString = "test data";
out << quint32(testString.size());
out << testString;
m_socket->write(block);
m_socket->flush();
}
void TestClient::startClient() {
for(int i = 0; i < 5; i++) {
//QTest::qWait(1); //works if I uncomment this line
sendDataToServer();
}
}
server.cpp
#include "server.h"
TestServer::TestServer() : m_server{new QLocalServer(this)} {
QLocalServer::removeServer("TestServer");
if (!m_server->listen("TestServer")) {
qCritical() << "couldn't connect to server";
}
connect(m_server, &QLocalServer::newConnection, this, &TestServer::onNewConnection);
}
void TestServer::onNewConnection() {
m_socket = m_server->nextPendingConnection();
connect(m_socket, &QLocalSocket::readyRead, this,
&TestServer::onNewData);
connect(m_socket, &QLocalSocket::disconnected, m_socket,
&QLocalSocket::deleteLater);
}
void TestServer::onNewData() {
QLocalSocket* client = qobject_cast<QLocalSocket*>(sender());
client->readAll();
qCritical() << "data read by server";
}
from the qt doc, it's stated that
readyRead() is not emitted recursively; if you reenter the event loop
or call waitForReadyRead() inside a slot connected to the readyRead()
signal, the signal will not be reemitted (although waitForReadyRead()
may still return true).
so is this my problem? adding timer is the only solution here?
You can compile this test project -> http://www.filedropper.com/testsocket
I'm reading data from serial port with QT, in mainwindow I've write this code:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QTextStream>
#include<QSerialPort>
#include<QSerialPortInfo>
#include<QtDebug>
#include<QThread>
QSerialPort *serial;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent),
ui(new Ui::MainWindow),
m_standardOutput(stdout)
{
ui->setupUi(this);
serial= new QSerialPort(this);
serial->setPortName("COM3");
serial->setBaudRate(QSerialPort::Baud115200);
serial->setDataBits(QSerialPort::Data8);
serial->setParity(QSerialPort::NoParity);
serial->setStopBits(QSerialPort::OneStop);
serial->setFlowControl(QSerialPort::NoFlowControl);
serial->open(QIODevice::ReadOnly);
connect(serial, &QSerialPort::readyRead, this, &MainWindow::ReaderH);
float HUM;
HUM=H;
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::ReaderH()
{
quint64 X=20;
serial->waitForReadyRead();
m_readData=serial->QSerialPort::read(X);
//if (!m_timer.isActive())
// m_timer.start(5000);
inter2=QString(m_readData);
QStringList firstlist2= inter2.split(";");
m_readData3=firstlist2.takeFirst();
H=m_readData3.toFloat();
qDebug() <<"Data:"<<m_readData<< " \r\n";
//QThread::sleep(11);
}
the program reads the data correctly for a few seconds, after a while the reading starts to be out of phase, like this:
Data: "60.904655;25.779804$"
Data: "60.970406;25.816269$"
Data: "60.988335;25.798037$"
Data: "60."
Data: "883736;25.7"
Data: "61570$"
Data: "60."
Data: "91063"
Data: "7;25.779804$"
Data: "60."
Data: "934544;25."
Data: "798037$"
Data: "60"
Data: ".871784;25.798037$"
I can't understand how to solve the problem.
Thank you for your time.
When reading from a serial port, you need to cut the "stream" into "messages" yourself. You should not make any assumption about the timing of your data in order to be robust.
When data are received, you should input them to a "decoder", i.e. typically a class that reads byte per byte and finds the "delimiters" of the packets.
The decoder class usually have an internal buffer and may look like that:
class Decoder : public QObject
{
Q_OBJECT
signals:
void messsageReceived(QByteArray message);
public:
void decode(const QByteArray& bytes)
{
for (char c : bytes)
{
if (c == '$')
{
emit messsageReceived(m_buffer);
m_buffer.clear();
}
else
{
m_buffer.append(c);
}
}
}
private:
QByteArray m_buffer;
};
Also there is usually no guarantee that you will start reading at the beginning of a packet. The first read might occur at the middle of a packet, that's why most protocol use a "Start" and a "Stop" sequence, or you should drop the first packet if you are not able to validate its consistency. In the snippet above, the first packet is not handled.
Also, serial ports do not guarantee to transmit bytes error free over the line, so it is mandatory to have a validation mechanism if the data are sensible, such as a checksum (i.e. CRC). If some data is corrupted, the packet should be dropped, and maybe a mechanism has to be put in place at a higher level so that the packet is re-transmitted (if needed).
I have two QT apps. One app can be considered to hold a big data and it sends about 10 KB of data chunk every second to second application.
Earlier I tried using QUdpSocket to transmit the data but due to MTU limitation of about 2-5K and need to divide and reunite the data myself, I switched to QTcpSocket.
Sometimes data is sent correctly with QTcpSocket (especially if I write data very frequently ~every 100 ms) but sometimes data is not sent at all. Not even after 5 sec. And sometimes several data chunks are internally buffered for a long period (several seconds) and then sent together.
m_socket = new QTcpSocket(this);
m_socket->connectToHost(QHostAddress::LocalHost, 45454);
sendDataEverySec()
{
QByteArray datagram(10000, 'a');
qint64 len = m_socket->write(datagram);
if(len != datagram.size())
qDebug() << "Error"; //this NEVER occurs in MY case.
m_socket->flush();
}
On receiver side, I use readyRead signal to know when data has arrived.
How can I ensure that data is sent immediately? Are there any better alternatives for what I am trying to do?
Edit:: When I am writing after long gaps of 1 second, I receive "QAbstractSocket::SocketTimeoutError" on receiver side everytime sender sends data. This error is not received if sender writes data frequently.
Is it OKAY to use QTcpSocket to stream data the way I am doing????
Edit 2: On receiver side, when readyRead signal is emitted, I was again checking while(m_socket->waitForReadyRead(500)) and I was getting "QAbstractSocket::SocketTimeoutError" due to this. Also, this check was preventing delivering of single chunks.
After going through docs more, it seems readyRead will be continuously emitted when new data is available, so no need for waitForReadyRead.
I am receiving all data sent but still data does not come immediately. Sometimes two-three chunks are merged. This may be because of delay on receiver side in reading data etc.
On receiver side, when readyRead signal is emitted, I was again checking while(m_socket->waitForReadyRead(500)) and I was getting "QAbstractSocket::SocketTimeoutError" due to this. Also, this check was preventing delivering of single chunks.
After going through docs more, it seems readyRead will be continuously emitted when new data is available, so there is no need for waitForReadyRead.
It had solved my issue.
my tipical solution for client server app.
on server side :
class Server: public QTcpServer {
public:
Server(QObject *parent = 0);
~Server();
private slots:
void readyRead();
void disconnected();
protected:
void incomingConnection(int);
};
on cpp:
void Server::incomingConnection(int socketfd) {
QTcpSocket *client = new QTcpSocket(this);
client->setSocketDescriptor(socketfd);
connect(client, SIGNAL(readyRead()), this, SLOT(readyRead()));
connect(client, SIGNAL(disconnected()), this, SLOT(disconnected()));
}
void Server::disconnected() {
QTcpSocket *client = (QTcpSocket*) sender();
qDebug() << " INFO : " << QDateTime::currentDateTime()
<< " : CLIENT DISCONNECTED " << client->peerAddress().toString();
}
void Server::readyRead() {
QTcpSocket *client = (QTcpSocket*) sender();
while (client->canReadLine()) {
//here i needed a string..
QString line = QString::fromUtf8(client->readLine()).trimmed();
}
}
on client:
class Client: public QTcpSocket {
public:
Client(const QHostAddress&, int, QObject* = 0);
~Client();
void Client::sendMessage(const QString& );
private slots:
void readyRead();
void connected();
public slots:
void doConnect();
};
on cpp:
void Client::readyRead() {
// if you need to read the answer of server..
while (this->canReadLine()) {
}
}
void Client::doConnect() {
this->connectToHost(ip_, port_);
qDebug() << " INFO : " << QDateTime::currentDateTime()
<< " : CONNESSIONE...";
}
void Client::connected() {
qDebug() << " INFO : " << QDateTime::currentDateTime() << " : CONNESSO a "
<< ip_ << " e PORTA " << port_;
//do stuff if you need
}
void Client::sendMessage(const QString& message) {
this->write(message.toUtf8());
this->write("\n"); //every message ends with a new line
}
I am trying to write a small UDP server application.
I have a client transmitting to this applications socket and I have verified this is sending ok using a small UDP echo program (which echoes the data received on a port to the screen) and also, I can see the packets received in wireshark.
I am using QUdpSocket and it would appear that this Binds ok on setup - but the readyRead() signal doesn't ever seem to get triggered.
I have included some of my code below - at the minute I am simply trying to emulate the little echo program.
Just to give some context to the below code - a button press on the UI calls 'setupNewSocket' on a port that is typed in on UI.
#include "sockethandler.h"
SocketHandler::SocketHandler(QObject *parent) :
QObject(parent)
{
udpSocket = new QUdpSocket(this);
connect( &w, SIGNAL(openNewUDPSocket(quint16)), this, SLOT(setupNewSocket(quint16)) );
connect( this, SIGNAL(printOnUI(QString,QString,QString)), &w, SLOT(updateUI(QString,QString,QString)) );
w.show();
}
void SocketHandler::readPendingDatagrams()
{
while (udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
QString data = QString( datagram.data() );
QString sender_address = sender.toString();
QString sender_port = QString("%1").arg(senderPort);
emit printOnUI(data, sender_address, sender_port);
}
}
void SocketHandler::setupNewSocket(quint16 port)
{
if( udpSocket->bind(QHostAddress::LocalHost, port) )
{
connect(udpSocket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams()));
}
else
{
// bind has failed
}
}
QHostAddress::LocalHost binds to 127.0.0.1 .
Probably you need to use QHostAddress::Any which binds to 0.0.0.0.
I'm having trouble collecting the response from a web request i do. (Because i'm new to Qt).
Why do i have trouble?
I have a request class which send a request and receive a response. But i can't get the response to the parent of the request object, because i have to wait for a "finished" signal from the NetworkAccessMaanager which handles the response.
So i handle the response in a "finished" slot, but i can't return the info to the parent main window holding the request object. How can i do this?
Here's the code:
Main window.cpp:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_buttonLogin_clicked()
{
request->sendRequest("www.request.com");
}
Request.cpp:
Request::Request()
{
oNetworkAccessManager = new QNetworkAccessManager(this);
QObject::connect(oNetworkAccessManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(finishedSlot(QNetworkReply*)));
}
/*
* Sends a request
*/
QNetworkReply* Request::sendRequest(QString url)
{
QUrl httpRequest(url);
QNetworkRequest request;
request.setSslConfiguration(QSslConfiguration::defaultConfiguration()); // Set default ssl config
request.setUrl(httpRequest); // Set the url
QNetworkReply *reply = oNetworkAccessManager->get(QNetworkRequest(httpRequest));
return reply;
}
/*
* Runs when the request is finished and has received a response
*/
void Request::finishedSlot(QNetworkReply *reply)
{
// Reading attributes of the reply
// e.g. the HTTP status code
QVariant statusCodeV = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
// Or the target URL if it was a redirect:
QVariant redirectionTargetUrl =
reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
// see CS001432 on how to handle this
// no error received?
if (reply->error() == QNetworkReply::NoError)
{
// Reading the data from the response
QByteArray bytes = reply->readAll();
QString jsonString(bytes); // string
bool ok;
QVariantMap jsonResult = Json::parse(jsonString,ok).toMap();
if(!ok)
{
qFatal("An error occured during parsing");
exit(1);
}
// Set the jsonResult
setJsonResult(jsonResult);
}
// Some http error received
else
{
// handle errors here
}
// We receive ownership of the reply object
// and therefore need to handle deletion.
delete reply;
}
/*
* Set the json result so that other functions can get it
*/
void Request::setJsonResult(QVariantMap jsonResult)
{
m_jsonResult = jsonResult;
}
/*
* Get the json result
* Return null if there is no result
*/
QVariantMap Request::getJsonResult()
{
return m_jsonResult;
}
Any ideas of how i can do this?
Thanks in advance!
Each QNetworkReply emits finished() signal, so you should connect signal from QNetworkReply* returned by request->sendRequest("www.request.com"); to slot of MainWindow.
EXAMPLE:
void MainWindow::on_buttonLogin_clicked()
{
QNetworkReply *reply = request->sendRequest("www.request.com");
connect(reply, SIGNAL(finished()), this, SLOT(newslot()));
}
void MainWindow::newslot()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
// there you can handle reply as you wish
}