Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Modbus TCP handle asynchronous communication



  • Hi,

    I am currently designing an Modbus TCP client application and I'm struggeling with the asynchronous communication. Let's say I have a class "mb" which handles all the modbus related stuff. I need to periodically retrieve values from the server. While my code works so far, my solution doesn't quite feel right.

    mb has the member function "initModbus" which sets up a connection to a server. If that was successful I send a signal "modbusReady" to the mainwindow class to start the periodic timer.
    I request new values with "requestModbusData". This member function will then request all necessary modbus registers. As soon as the new values arrive they get decomposed and the corresponding variables in the values struct are updated.
    I use flags to keep track of new incoming values. This allows me to pull the new data all at once (only if all requested values have been updated).
    This is where things get ugly. In the "pollUpdatedValueFlags" function I have to "and" all flags together. While this seems fine with only two registers this gets hard to maintain if you have to keep track of a lot of registers.
    I had the idea to just use one flag for the last requested value. But this looks a bit unsafe to me.
    There is probably a much better solution.
    It would be nice if you could push me onto the right track.

    mb.h:

    #ifndef mb_H
    #define mb_H
    
    #include <QObject>
    #include <QModbusTcpClient>
    #include <QUrl>
    #include <QModbusDataUnit>
    #include <cstring>
    
    
    
    class mb : public QObject
    {
        Q_OBJECT
    public:
        mb();
        ~mb();
    
        struct values_t{
            QString Register1;
            quint8 Register2;
        };
    
        QModbusDevice::Error initModbus(const QUrl& credentials);
        const values_t& getValues();
        void requestModbusData();
    
       
        struct updatedValueFlags_t{
            bool f_Register1;
            bool f_Register2;
        }updatedValueFlags;
    
        bool pollUpdatedValueFlags();
    
    
    private:
        QModbusTcpClient *mb_handle = nullptr;
        
        enum modbusRegister{
            r_Register1 = 30042,
            r_Register2 = 30053
        };
    
        values_t values;
        void getRegister1();
        void getRegister2();
        void retrieveModbusRegister(const QModbusDataUnit::RegisterType& regType, modbusRegister reg, quint8 unitID);
        void modbusReceiveFinished();
        void resolveReply(const QModbusDataUnit& unit);
    
    signals:
        void modbusReady();
    
    private slots:
        void modbusStateChanged(QModbusDevice::State state);
    
    };
    
    
    #endif // mb_H
    
    
    

    mb.cpp:

    #include "mb.h"
    
    
    mb::mb()
    {
        //initialize the updatedValueFlags with 0
        memset(&this->updatedValueFlags, 0, sizeof(this->updatedValueFlags));
    }
    
    mb::~mb()
    {
    
    }
    
    
    QModbusDevice::Error mb::initModbus(const QUrl &credentials)
    {
        mb_handle = new QModbusTcpClient;
        mb_handle->setConnectionParameter(QModbusDevice::NetworkAddressParameter, credentials.host());
        mb_handle->setConnectionParameter(QModbusDevice::NetworkPortParameter, credentials.port());
        mb_handle->setTimeout(1000);
        mb_handle->setNumberOfRetries(5);
        connect(mb_handle, &QModbusClient::stateChanged, this, &mb::modbusStateChanged);
    
        mb_handle->connectDevice();
    
        return mb_handle->error();
    
    }
    
    void mb::modbusStateChanged(QModbusDevice::State state)
    {
        if (state == QModbusDevice::State::ConnectedState){
            emit modbusReady();
        }
    }
    
    void mb::getRegister1()
    {
        retrieveModbusRegister(QModbusDataUnit::InputRegisters, r_Register1, 3);
    }
    
    void mb::getRegister2()
    {
        retrieveModbusRegister(QModbusDataUnit::InputRegisters, r_Register2, 3);
    }
    
    
    void mb::retrieveModbusRegister(const QModbusDataUnit::RegisterType &regType, mb::modbusRegister reg, quint8 unitID)
    {
        QModbusDataUnit unit(regType, reg, 10);
        if (QModbusReply *reply = mb_handle->sendReadRequest(unit, unitID)){
            if (!reply->isFinished()){
                connect(reply, &QModbusReply::finished, this, &mb::modbusReceiveFinished);
            } else {
                delete reply;
            }
        }
    }
    
    void mb::modbusReceiveFinished()
    {
        auto reply = qobject_cast<QModbusReply*>(sender());
        if (!reply){
            return;
        }
        if (reply->error() == QModbusDevice::NoError){
            QModbusDataUnit unit = reply->result();
            resolveReply(unit);
        }
        reply->deleteLater();
    }
    
    void mb::resolveReply(const QModbusDataUnit &unit)
    {
        switch (unit.startAddress()){
        
        case r_Register1:{
            switch (unit.value(0)){
            case 1:
                this->values.Register1 = "value 1";
                break;
            case 2:
                this->values.Register1 = "value 2";
                break;
            case 3:
                this->values.Register1 = "value 3";
                break;
            default:
                this->values.Register1 = "invalid";
                break;
            }
            updatedValueFlags.f_Register1 = true;
            break;
        }
    
        case r_Register2:{
            switch (unit.value(0)){
            case 1:
                this->values.Register2 = 42;
                break;
            case 2:
                this->values.Register2 = 24;
                break;
            default:
                this->values.Register2 =-1;
                break;
            }
            updatedValueFlags.Register2 = true;
            break;
        }
    
        default:
            break;
        }
    }
    
    void mb::requestModbusData()
    {
        getRegister1();
        getRegister2();
    }
    
    bool mb::pollUpdatedValueFlags()
    {
        return updatedValueFlags.f_Register1 && updatedValueFlags.f_Register2; // && updatedValueFlags.so_on && updatedValueFlags.so_forth && ...;
    }
    
    const mb::values_t &mb::getValues()
    {
        memset(&this->updatedValueFlags, 0, sizeof(this->updatedValueFlags));
        return values;
    }
    
    

    mainwindow.h:

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QTimer>
    #include <QUrl>
    #include "mb.h"
    
    QT_BEGIN_NAMESPACE
    namespace Ui { class MainWindow; }
    QT_END_NAMESPACE
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    private:
        Ui::MainWindow *ui;
        mb *modbusDevice;
        QTimer *periodicWorker;
    
    private slots:
        void on_btnConnect_clicked();
        void worker();
        void modbusReady();
    };
    #endif // MAINWINDOW_H
    
    

    mainwindow.cpp:

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        modbusDevice = new mb;
        periodicWorker = new QTimer;
        periodicWorker->setInterval(500);
        periodicWorker->connect(periodicWorker, SIGNAL(timeout()), this, SLOT(worker()));
    
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    
    void MainWindow::on_btnConnect_clicked()
    {
    	QUrl modbus_credentials;
    	modbus_credentials.setHost(ui->lemodbusDeviceIP->text());
    	modbus_credentials.setPort(ui->cbmodbusDevicePort->currentText().toInt());
    	modbusDevice->connect(modbusDevice, SIGNAL(modbusReady()), this, SLOT(modbusReady()));
    	modbusDevice->initModbus(modbus_credentials);
    }
    
    void MainWindow::worker()
    {
        static bool newDataRequested = false;
    
        if (modbusDevice->pollUpdatedValueFlags() == false){ //no new values
            if (newDataRequested == false){ //no new data requested
                modbusDevice->requestModbusData();
                newDataRequested = true;
            }
        } else{ //new data available
            mb::values_t newData = modbusDevice->getValues();
            qDebug() << "Register1: " << newData.Register1;
            qDebug() << "Register2: " << newData.Register2;
            newDataRequested = false;
        }
    
    
    }
    
    void MainWindow::modbusReady()
    {
        periodicWorker->start();
    }