0

I am trying to commicate with a motor controller via modbus with Qt/C++.

I connect to it using this code that I took mainly from Qt SerialBus adueditor example:

    void Stepper::connect_device(){
    if (ui.pb_connect->text() == "Connect"){
    m_device = device;device->setParameters(0, 0x0000, 0x0000, 0x000F, 0x0000);   
    m_device->setConnectionParameter(QModbusDevice::NetworkAddressParameter, ui.tcpAddressEdit->text());
    m_device->setConnectionParameter(QModbusDevice::NetworkPortParameter, ui.tcpPortEdit->text());
    m_device->setTimeout(1000);
    m_device->setNumberOfRetries(3);connect(m_device, &QModbusDevice::errorOccurred, this, [this](QModbusDevice::Error) {
        qDebug().noquote() << QStringLiteral("Error: %1").arg(m_device->errorString());
        reset();
        /*QMessageBox msgBox;
        msgBox.setWindowTitle("Modbus TCP Client");
        msgBox.setText("Connection error !");
        msgBox.exec();
        emit ui.pb_connect->clicked();*/
        return;
    }, Qt::QueuedConnection);

    connect(m_device, &QModbusDevice::stateChanged, [this](QModbusDevice::State state) {
        switch (state) {
        case QModbusDevice::UnconnectedState:
            qDebug().noquote() << QStringLiteral("State: Entered unconnected state.");
            ui.pb_connect->setEnabled(true);
            ui.pb_connect->setText("Connect");
            break;
        case QModbusDevice::ConnectingState:
            qDebug().noquote() << QStringLiteral("State: Entered connecting state.");
            ui.pb_connect->setEnabled(false);
            ui.pb_connect->setText("Trying to connect..");
            break;
        case QModbusDevice::ConnectedState:
            qDebug().noquote() << QStringLiteral("State: Entered connected state.");
            ui.pb_connect->setText("Disconnect");
            ui.pb_connect->setEnabled(true);
            break;
        case QModbusDevice::ClosingState:
            qDebug().noquote() << QStringLiteral("State: Entered closing state.");
            ui.pb_connect->setEnabled(true);
            ui.pb_connect->setText("Connect");
            break;
        case QModbusDevice::TimeoutError:
            qDebug().noquote() << QStringLiteral("State: Time out error.");
            QMessageBox msgBox;
            msgBox.setWindowTitle("Modbus TCP Client");
            msgBox.setText("Time out !");
            msgBox.exec();
        }
    });
    m_device->connectDevice();
}
else
{
    disconnectAndDelete();
}}

Once the connection is established, I commission the drive using this tcp pdu = "00000004084301000000000000", the device is commissionned and an error with code "E047" occurs because the communication was interrupted. The problem is when I try to make a reset (which is a rising edge on the reset bit) I send these two consecutive frames, but it doesn't work, the error still remains.

void Stepper::reset(){ 
QModbusReply *reply = nullptr;
Data = "00000004084301000000000000";
QByteArray pduData = QByteArray::fromHex(Data.toLatin1());
reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(0x0010), pduData), 0x01);  

connect(reply, &QModbusReply::finished, [reply, this]() {
    qDebug() << "Receive: Asynchronous response PDU: " << reply->rawResult() << endl;
    Data = "00000004084B01000000000000";
    QByteArray pduData = QByteArray::fromHex(Data.toLatin1());
    QModbusReply *reply = nullptr;
    while (reply)
        reply = m_device >sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(0x0010), pduData), 0x01);
});}

I took a look at this simulator "Modbus TCP Client V1.0.0.12" https://www.festo.com/net/cs_cz/SupportPortal/default.aspx?q=modbus&tab=4 that festo offers, when I click "Start" button, the frame is sent permanently, then I can click on the reset bit and the error is eliminated. The communication is interrupted and the error occurs only when I click "Stop". When the communication is running I can do other things like clicking stop button or changing function code, is it using multithreading? In this case, how can I provide a continuous communication with the controller like this simulator interface?

click here to see modbus tcp simulator images

Khaled
  • 81
  • 1
  • 14
  • It is not that much code to understand but I do not think multithreading is involved, in my opinion (but it is just poor guessing work) your connection is closed because either your Stepper instance goes out of scope and is destroyed or because you are missing to send some necessary..keep alive message – Marco Nov 21 '17 at 10:21
  • Your second guessing is correct, the "keep alive" message is what is necessary, but I don't know how to do it. In the simulator, the number of packets sent is always incrementing, so I'm guessing the solution to keep it alive (keeping the connection going) is to send the same packet repeatedly. – Khaled Nov 21 '17 at 10:31
  • In your place I would setup a QTimer and keep sending a packet. – Marco Nov 21 '17 at 10:45
  • @Marco good idea, could you give me an example on how to do that ? – Khaled Nov 21 '17 at 10:56
  • QTimer *timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(send_single_packet())); timer->start([milliseconds]); – Marco Nov 21 '17 at 11:02
  • @Marco This code sends one packet when the time is out, it does not keep sending a packet, it's the same problem. – Khaled Nov 21 '17 at 11:25
  • Please have a look at [Qt: QTimer::singleShot](http://doc.qt.io/qt-5/qtimer.html#singleShot-prop). According to doc. the default should be `false` i.e. the timer event should happen periodically. You didn't set it `true`, did you? – Scheff's Cat Nov 21 '17 at 11:35
  • And, of course, a timer event is "fired" from the event loop. This means, you have to return to the event loop (at best ASAP) - no long running code which prevents dispatching of further events... – Scheff's Cat Nov 21 '17 at 11:37
  • 1
    I'm not sure about the `QMessageBox` (in `case QModbusDevice::TimeoutError:`). It might provide its own event loop and thus block the application's event loop (as you call `QMessageBox::exec()`). The better is to use a `QLabel` (which is part of your main window) instead where you put your message text in. – Scheff's Cat Nov 21 '17 at 11:43
  • Thank you @Scheff it works. – Khaled Nov 21 '17 at 15:51

1 Answers1

1

It finally worked, here is the code:

void Stepper::reset(){
QTimer *timer1 = new QTimer(this);
timer1->setSingleShot(false);
timer1->start(100);
connect(timer1, &QTimer::timeout, [this]() {
    send_packet("00000004084301000000000000");
});
QTimer *timer2 = new QTimer(this);
timer2->setSingleShot(false);
timer2->start(100);
connect(timer2, &QTimer::timeout, [this]() {
send_packet("00000004084B01000000000000");
});}

The send_packet function is simply this function :

void Stepper::send_packet(QString Data){
QModbusReply *reply = nullptr;
QByteArray pduData = QByteArray::fromHex(Data.toLatin1());
reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(0x0010), pduData), 0x01);  
if (reply)
    return;}
Khaled
  • 81
  • 1
  • 14