2

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?

scopchanov
  • 7,966
  • 10
  • 40
  • 68
  • The question should be updated to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. – scopchanov Aug 05 '20 at 17:20
  • The Question here is: How can I connect to Modbus, read some data and close the connection with one button? And the code above is allready very shortened to show what I am trying to do but big enough to reproduce the problem. I really hope someone is able to help me. After searching and learning more about Modbus and Qt ist, that I could run into a thread issue? – RavenMujitsuNo Aug 06 '20 at 06:27
  • _the code above is allready very shortened_ Do you use `modbusDevice` anywhere? – scopchanov Aug 06 '20 at 07:16
  • You also might want to use [QModbusDevice::stateChanged](https://doc.qt.io/qt-5/qmodbusdevice.html#stateChanged). – scopchanov Aug 06 '20 at 07:23
  • @scopchanov You mean this line: QModbusClient *modbusDevice = nullptr; ?? No, forgot to delete it. Will check the stateChanged Part. I really miss a good documentation with simple examples and declaration for the QModbus TCP stuff... And not just one "huge" Example in the Qt-Welcome Area – RavenMujitsuNo Aug 06 '20 at 08:31
  • _I really miss a good documentation with simple examples_ That is a fair point. So... Many people fail to realise, that __communications take time__. In a GUI application, it is probably the best, when the code reacts to events, instead of trying to execute everything lineary. That's where signals and slots come into play. 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). If you connect to the `QModbusDevice::stateChanged` signal, you would know exactly when to do what. – scopchanov Aug 06 '20 at 12:57
  • Ok, that's a good point. Have to implement the stateChanged Signal into code. But I do with the F10 Single Line Debug style (or doing enough delay time into it), then the communication should have enough time to do the communication. I am beginnen to think, that the Programm is only excepting one Modbus Communication (and it doesn't matter if its open or read some stuff) per Function... or does the signal fires after the function is finished? – RavenMujitsuNo Aug 06 '20 at 14:14
  • Did my answer work for you? – scopchanov Aug 09 '20 at 11:22

1 Answers1

1

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();
}
scopchanov
  • 7,966
  • 10
  • 40
  • 68