How to Use QModbus Class to get the data on a DAQ card?



  • Recently I am using Qt to do some job on getting digital signals on an DAQ card.The DAQ card supports Modbus agreement and I have already known the ip and port of the DAQ card,and the register address which holds the signal that I want to read.

    So, anyone can provide a example to initialize the QModbus variables and tell me how to open and read the data from the device??(The documentary has provided some examples about serial port,but it doesn't provide too much about Modbus.)

    Thx



  • @MartinChan

    Hi,
    is your DAQ card provide a modbus server or client?
    Normally they're modbus server, so you have to send request to it from a client.

    With the TCP protocol you can use the QModbusTcpClient (see http://doc.qt.io/qt-5/qmodbustcpclient.html).
    To connect your client to the server call:

    QModbusTcpClient *client = new QModbusTcpClient();
    client->setConnectionParameter(QModbusDevice::NetworkAddressParameter, "192.168.0.1");
    client->setConnectionParameter(QModbusDevice::NetworkPortParameter, 502);
    client->connectDevice();
    

    Now you can call read and/or write requests to the server (see http://doc.qt.io/qt-5/qmodbusdataunit.html)
    Write request:

    QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters, 40003, 1); // write 1 value in address 40003
    writeUnit.setValue(0, 0x253); 
    
    if (auto *reply = client->sendWriteRequest(writeUnit, 1))
    {
    	if (!reply->isFinished())
    	{
    		connect(reply, &QModbusReply::finished, this, [this, reply]() 
    		{
    			if (reply->error() != QModbusDevice::NoError) 				
    					// error in reply
    											
    				reply->deleteLater();
    			});
    	}
    	else
    	{
    		if (reply->error() != QModbusDevice::NoError)			
    			// error in reply
    			
    		// broadcast replies return immediately
    		reply->deleteLater();
    	}
    }
    else
    {
    	// error in request
    }
    

    Read request:

    QModbusDataUnit readUnit(QModbusDataUnit::InputRegisters, 40006, 1); // just read input register 40006 
    
    if (auto *reply = client->sendReadRequest(readUnit, 255)) // client id 255
    {
    	if (!reply->isFinished())
    	{
            // connect the finished signal of the request to your read slot
    		connect(reply, &QModbusReply::finished, this, &YourClass::readReady);
    	}
    	else
    	{
    		delete reply; // broadcast replies return immediately
    	}
    }
    else
    {
    	// request error
    }
    
    YourClass::readReady
    {
    	QModbusReply *reply = qobject_cast<QModbusReply *>(sender());
    	if (!reply)
    		return;
    
    	if (reply->error() == QModbusDevice::NoError)
    	{
    		const QModbusDataUnit unit = reply->result();
    		int startAddress = unit.startAddress(); // the start address, here 40006
    		int value = unit.value(0); // value of the start address + 0
    		... 
    
    	}
    	else
    	{
    		// reply error
    	}
    
    	reply->deleteLater(); // delete the reply 
    }
    

    Note that the API of the QModbusTcpClient class is asynchronous.

    In the source of Qt5.6 there are also some examples of modbus master/clients and slaves/server.

    Hope this helps!



  • Thx first and I'm sorry to bother you because I have some question.

    The sendWrtieRequest func didn't work correctly.May I ask what is the second parameter--server address??I have known the device ip address and its port,but I have no more parameter like server address or something.



  • @MartinChan

    The server address is what used to be the slave ID on the serial bus. Usually on TCP those can be 0 or 255 by default, but can be any other in between. That way, multiple "devices" can be emulated by Modbus TCP which is used in e.g. some VFDs and power control units.

    You need to check your documentation of your device to know which value to set this to.



  • @MartinChan

    What's your error message?



  • Thx @nolden and @beecksche

    About error message,my device supports two connection ways:first is use Modbus TCP ways and second is to use "Virtual Serial Port" to make connection with the virtual port.

    When I use the first way,the code is like

    QModbusTcpClient *client=new QModbusTcpClient();
    client>setConnectionParameter(QModbusDevice::NetworkAddressParameter,"192.168.0.201");
    client->setConnectionParameter(QModbusDevice::NetworkPortParameter,502);
     client->connectDevice();
    
    QModbusDataUnit readUnit(QModbusDataUnit::Coils,00000,4);
     if (auto *reply=client->sendReadRequest(readUnit,255)){
    

    it will show "Cannot conncet the device"on cmd when you execute the last line

    however,it worked when you tried the second way like:

    QModbusRtuSerialMaster *client=new QModbusRtuSerialMaster();
            client->setConnectionParameter(QModbusDevice::SerialPortNameParameter,"COM5");
            client->setConnectionParameter(QModbusDevice::SerialParityParameter,QSerialPort::NoParity);
            client->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,QSerialPort::Baud115200);
            client->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,QSerialPort::Data8);
            client->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,QSerialPort::OneStop);
           client->connectDevice();
    
        QModbusDataUnit readUnit(QModbusDataUnit::Coils,00000,4);
        if (auto *reply=client->sendReadRequest(readUnit,255)){
    

    It can return the value,but it seems I never get the value i want (I use some modbus software like< Modbus Pull> to get the every register value of my device).



  • @MartinChan

    Do you call the code above in one function? Like

    YourClass::connect()
    {
    QModbusTcpClient *client=new QModbusTcpClient();
    client->setConnectionParameter(QModbusDevice::NetworkAddressParameter,"192.168.0.201");
    client->setConnectionParameter(QModbusDevice::NetworkPortParameter,502);
    client->connectDevice();
    
    QModbusDataUnit readUnit(QModbusDataUnit::Coils,00000,4);
    if (auto *reply=client->sendReadRequest(readUnit,255)){
    
    ...
    }
    

    The API of the QModbusDevice classes are asynchronous. That means when you call
    client->connectDevice() your client is not "really" connected to your server. The function just send an event to the event loop. When the application enters the event loop the event will be executed.

    The QModbusDevice class has some signals to control your connection state and error message. I would built my class something like that:

    MyClass::connectToModbusServer()
    {
       QModbusTcpClient *client=new QModbusTcpClient();
       client->setConnectionParameter(QModbusDevice::NetworkAddressParameter,"192.168.0.201");
       client->setConnectionParameter(QModbusDevice::NetworkPortParameter,502);
    
       if (client->connectDevice())
       {
          connect(client, &QModbusTcpClient::stateChanged, this, &MyClass::onStateChanged); 
          connect(client, &QModbusTcpClient::errorOccurred, this, &MyClass::onErrorOccurred); 
       }
       else
          qDebug() << client->errorString();
    }
    
    MyClass::readCoil()
    {
       QModbusDataUnit readUnit(QModbusDataUnit::Coils,00000,4);
    
       if (auto *reply = client->sendReadRequest(readUnit, 255) 
       {
           if (!reply->isFinished())
           {
               // connect the finished signal of the request to your read slot
               connect(reply, &QModbusReply::finished, this, &MyClass::readReady);
           }
           else
           {
               delete reply; // broadcast replies return immediately
           }
       }
       else
       {
        // request error
       }
    }
    

    Check the client state with yout slot. Before reading registers ensure that the client is in connected state.

    Haven't tested the above code.

    Finally i found the Qt Modbus Master example: https://doc-snapshots.qt.io/qt5-5.6/qtserialbus-modbus-master-example.html

    It is a good example of how the API works!



  • @beecksche

    Thx!~~ I will try it soon~~



  • @beecksche

    Finally I succeed and get the right value of the device.The core question of the connection is what u said:The connectionDevice progress is asynchronous and you can't just use the read order after you use the connectionDevice order,its relationship should be more like a trigger and a bullet(Sorry I don't know too much about QEvent/QThread,I used to think it insignificant but now I didn't) ,you should use the connect function to make sure the right process of connection and read function.

    I will put my total code here,although it has a lot of unsteady sentences and problems ,but it is simple and I hope it will help those who want to use it in QT5.

    My DAQ card uses TCP agreement and the Coils(Digital Input)starts from Address 00000 and there're 4 coils,the third is 1 the other should be 0.

    modclient.h:

    #ifndef MODCLIENT_H
    #define MODCLIENT_H
    #include <QModbusTcpClient>
    #include <QModbusDevice>
    #include <QObject>
    class modclient:public QObject
    {
        Q_OBJECT
    public:
        modclient();
        void connecttoModbusServer();
    private:
        QModbusTcpClient *client;
    
    private slots:
        void readready() const;
        void query();
    };
    
    #endif // MODCLIENT_H
    

    modclient.cpp:

    #include "modclient.h"
    #include <QVariant>
    #include <QModbusDataUnit>
    #include <QDebug>
    modclient::modclient()
    {
        client=new QModbusTcpClient();
        client->setConnectionParameter(QModbusDevice::NetworkAddressParameter,"192.168.0.201");
        client->setConnectionParameter(QModbusDevice::NetworkPortParameter,502);
        client->setTimeout(200);
        client->setNumberOfRetries(3);
        
        connecttoModbusServer();
    }
    
    void modclient::connecttoModbusServer(){
        if(!client->connectDevice()){
            qDebug()<<QString("There's something wrong with the device");
        }else {
            qDebug()<<QString("Right connection");
            connect(client,&QModbusTcpClient::stateChanged,this,&modclient::query);
        }
        
        
    }
    void modclient::readready() const{
        QModbusReply *reply=qobject_cast<QModbusReply *>(sender());
        const QModbusDataUnit result=reply->result();
        for (int j=0;j<4;j++)
            qDebug()<<QString("The value of %1 is %2").arg(j).arg(result.value(j));
    }
    
    void modclient::query(){
        QModbusDataUnit readUnit(QModbusDataUnit::Coils,00000,4);
        
        if (auto *reply = client->sendReadRequest(readUnit,1))
        {
            if (!reply->isFinished())
            {
                // connect the finished signal of the request to your read slot
                connect(reply, &QModbusReply::finished,this,&modclient::readready);
            }
            else
            {
                delete reply; // broadcast replies return immediately
            }
        }
        else
        {
            // request error
        }
    }
    
    

    main.cpp

    #include <QCoreApplication>
    #include "modclient.h"
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        modclient d;
        return a.exec();
    }
    

    Thx for u all again~



  • @MartinChan
    i don't understand your connection (it also should shown an error while compiling because the arguments of the both functions missmatch)

    connect(client,&QModbusTcpClient::stateChanged,this,&modclient::query);
    

    The doc says:

    void QModbusDevice::stateChanged(QModbusDevice::State state):

    This signal is emitted every time the state of the device changes. The new state is represented by state.

    In your code every time the state changed the query slot is called, which send a read request to the server.

    I connect the stateChanged signal to something like that:

    onStateChanged(const QModbusDevice::State & newState)
    {
       switch(newState)
       {
       case QModbusDevice::UnconnectedState:
          ...
          break;
       case QModbusDevice::ConnectingState:
          ...
          break;
       case QModbusDevice::ConnectedState:
          ...
          break;
       case QModbusDevice::ClosingState: 
          ...
          break;
       }
    }
    


  • @beecksche
    Hi,I have to say you're right,however,it didn't mean my code couldn't run--because in my code. once you use connectDevice command,it did change the state from "Unconnected"to "Connected"(Your code discuss the problem it could trigger during the connectDevice() progress,it's necessary in practice,in my case I have checked the parameters like IP,Port Address etc,so it didn't show error when I compile the file).


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.