How to Use QModbus Class to get the data on a DAQ card?
-
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.
-
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.
-
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).
-
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!
-
Thx!~~ I will try it soon~~
-
Finally I succeed and get the right value of the device.The core question of the connection is what u said:The
connectionDeviceprogress 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).