Warm tip: This article is reproduced from stackoverflow.com, please click
c++ modbus-tcp QT

Why QModbusClient does not read data after open statement?

发布于 2020-12-14 19:42:30

I am trying to get a simple Modbus running and I am getting trouble with the sequense of commands.

I have figured first, that I can't run multiple functions in a function. If I do this, then it looks like a connection was made, but it fails. If I create 2 buttons ("Connect", "Read") and click first connect and then read, then the connection was succesfully and i am able to read the response.

So how can I change the code, so that it will connect to a TCP Modbus, read some data and then close the connection with one function/button?

This is an example of my code:

In file modbusmaster.hpp:

#ifndef MODBUSMASTER_HPP
#define MODBUSMASTER_HPP
#include <QMainWindow>
#include <QModbusTcpClient>
#include <QModbusDevice>
#include <QModbusDataUnit>
#include <QDebug>
#include <QUrl>

class QModbusClient;

class ModbusMaster : public QMainWindow
{
    Q_OBJECT
public:
    explicit ModbusMaster(QWidget *parent = nullptr);

    QModbusClient *_modbus = nullptr;
    QModbusClient *modbusDevice = nullptr;
    bool open(QString host, int port);
    bool read(QModbusDataUnit::RegisterType type, int startAddress, quint16 count);
    void readReady();

signals:

};

#endif // MODBUSMASTER_HPP

In file modbusmaster.cpp:

#include "modbusmaster.hpp"

ModbusMaster::ModbusMaster(QWidget *parent) : QMainWindow(parent)
{
}

bool ModbusMaster::open(QString host, int port)
{
    if (_modbus) {
        _modbus->disconnectDevice();
        delete _modbus;
        _modbus = nullptr;
    }
    _modbus = new QModbusTcpClient(this);

    connect(_modbus, &QModbusClient::errorOccurred, [this](QModbusDevice::Error) {
        qDebug() << _modbus->errorString();
    });

    if(!_modbus) {
        qDebug() << "Could not create Modbus Client.";
    } else {
        qDebug() << "Modbus Client is created.";
    }

    if (_modbus->state() != QModbusDevice::ConnectedState) {
        _modbus->setConnectionParameter(QModbusDevice::NetworkPortParameter, port);
        _modbus->setConnectionParameter(QModbusDevice::NetworkAddressParameter, host);
        _modbus->setTimeout(1000);
        _modbus->setNumberOfRetries(3);

        if (!_modbus->connectDevice()) {
            qDebug() << "Connect failed: " << _modbus->errorString();
        } else {
            qDebug() << "Modbus Client is Connected";
            return true;
        }
    }
    return false;
}

bool ModbusMaster::read(QModbusDataUnit::RegisterType type, int startAddress, quint16 count)
{
    if (!_modbus) {
        qDebug() << "!_modbus";
        return false;
    }

    if (_modbus->state() != QModbusDevice::ConnectedState){
        qDebug() << "Modbus Client is not Connected in read section";
        return false;
    }

    QModbusDataUnit req(type, startAddress, count);
    if (auto *reply = _modbus->sendReadRequest(req, 1))
    {
        qDebug() << "auto *reply = _modbus->sendReadRequest(req, 1)";
        if (!reply->isFinished())
            connect(reply, &QModbusReply::finished, this, &ModbusMaster::readReady);
        else
            delete reply;
        return true;
    }
    return false;
}

void ModbusMaster::readReady()
{
    auto reply = qobject_cast<QModbusReply *>(sender());
    if (!reply) return;
    reply->deleteLater();

    if (reply->error() == QModbusDevice::NoError)
    {
        qDebug() << reply;
    }
    else if (reply->error() == QModbusDevice::ProtocolError)
    {
        qDebug() << QString("Read response error: %1 (Mobus exception: 0x%2)").
                                    arg(reply->errorString()).
                                    arg(reply->rawResult().exceptionCode(), -1, 16);
    } else {
        qDebug() << QString("Read response error: %1 (code: 0x%2)").
                                    arg(reply->errorString()).
                                    arg(reply->error(), -1, 16);
    }
}

In file mainwindow.cpp:

#include "modbusmaster.hpp"
.......
void mainwindow::on_button_clicked()
{
    ModbusMaster test;
    test.open("172.19.1.54", 54);
    test.read(QModbusDataUnit::HoldingRegisters, 0, 10);

}
.......

the "on_button_clicked" doesn't work. It shows only the qDebug() Results:

Modbus Client is created.
Modbus Client is Connected
Modbus Client is not Connected in read section

If I use 2 buttons, one for the test.open and the other with test.read, then it's ok.

So what am I missing here?

Questioner
RavenMujitsuNo
Viewed
0
scopchanov 2020-08-07 06:24

Cause

Many people fail to realise, that communications take time. So, in your case, you open the device and immediatelly initiate a read request, which fails, because the device is not open yet (because it takes time, as everithing else).

Solution

In a GUI application, it is probably the best, when the code reacts to events, instead of trying to execute everything lineary. This is where signals and slots come into play.

Consider the following workflow:

when you create the QModbusDevice, you also connect its signals (e.g. stateChanged and errorOccurred) to slots in your code (e.g. MainWindow::onModbusStateChanged, or MainWindow::onModbusErrorOccurred). Those should be considered as callback functions, which are executed when the corresponding event occurs: the connection state changes, the data is received, the data is sent, an error occurs.

In this manner you would know exactly when to do what.

Example

Here is an example code I have prepared for you to show you how the problem could be approached:

in MainWindow.h

...
private:
    QModbusClient *m_modbus;
...
private slots:
    void onModbusStateChanged(QModbusDevice::State state);
...

in MainWindow.cpp

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    m_modbus(new QModbusClient(this))
{
    ...
    connect(m_modbus, &QModbusClient::stateChanged, this, MainWindow::onModbusStateChanged);
    ...
}

To read the data as soon as the device is connected, the slot might look like this:

void MainWindow::onModbusStateChanged(QModbusDevice::State state)
{
    switch (state) {
        ...
        case QModbusDevice::ConnectedState:
            m_modbus->read(QModbusDataUnit::HoldingRegisters, 0, 10);
        break;
        ...
    }
}

It remains to initiate the connection on a click of a button. In your case:

void MainWindow::on_button_clicked()
{
    m_modbus->connectDevice();
}