LINUX.ORG.RU

Ошибка Request timeout, но данные по Modbus получены

 ,


1

4

Добрый день. Настраиваю modbus RTU для обмена данными приложения и PLC, но при отправке данных я получаю ошибку «Request timeout». При этом данные отправлены и получены контроллером (у меня стоит приложение на компьютере, которое может это отследить и показать). Я не могу понять, почему возникает эта ошибка. Сначала я думала увеличить время таймаута, но это ни на что не повлияло (при этом по ощущениям значение таймаута будто и не менялось вовсе). Потом я подумала, что, может, у меня посылается несколько раз пакет данных, когда первый нормально считывается, а второй выдает ошибку выхода времени, но при проверке через дебаггер часть кода, которая отвечает за полную передачу, даже не вызывается. Если у кого-нибудь будут предположения, что может быть не так, я буду рада проверить.

Код класса modbus:

modbus.h:

class QModbusClient;
class QModbusReply;

class Modbus : public QObject
{
    Q_OBJECT
public:
    Modbus(QObject *parent = nullptr);
    ~Modbus();
    QModbusDataUnit readRequest() const;
    QModbusDataUnit writeRequest() const;

private:
    void init();
    void on_writeButton_clickedRegisters(qint32 registers[], qint32 values[]);
    QModbusClient *modbusDevice;

    const int SERVER_ADDRESS = 1;
    const int START_ADDRESS = 0;
    const int AMOUNT = 24;
    const int WRITE_SIZE = 24;

    const int BAUDS = 9600;
    const int STOP_BITS = 1;
    const int DATA_BITS = 8;
    const QString PORT = "ttyUSB0";
    const int PARITY = QSerialPort::NoParity;
    const int RESPONSE_TIME = 250;
};
#endif // MODBUS_H

modbus.cpp:

    Modbus::Modbus(QObject *parent) :
    QObject(parent),
    modbusDevice(nullptr)
{
    init();
}

void Modbus::init() {
    modbusDevice = new QModbusRtuSerialMaster(this);
    connect(modbusDevice, &QModbusClient::errorOccurred, [this](QModbusDevice::Error) {
        emit sendData(modbusDevice->errorString());});
    qDebug() << "Error message: " << modbusDevice->errorString();
    if(modbusDevice) {
        connect(modbusDevice, &QModbusClient::stateChanged,this, &Modbus::onStateChanged);
    }

    if (modbusDevice->state() != QModbusDevice::ConnectedState) {
        modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,PORT);
        modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,PARITY);
        modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,BAUDS);
        modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,DATA_BITS);
        modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,STOP_BITS);
        modbusDevice->setTimeout(RESPONSE_TIME);
        modbusDevice->setNumberOfRetries(1);
        if (!modbusDevice->connectDevice()) {
            emit sendData(modbusDevice->errorString());
        }
    } else {
        modbusDevice->disconnectDevice();
    }
}

void Modbus::on_writeButton_clickedRegisters(qint32 registers[], qint32 values[])
{

    <часть кода>
    if (auto *lastRequest = modbusDevice->sendWriteRequest(writeUnit, SERVER_ADDRESS)) {
        if (!lastRequest->isFinished()) {
            connect(lastRequest, &QModbusReply::finished, this, [ lastRequest]() {
                if (lastRequest->error() == QModbusDevice::ProtocolError) {
                    qDebug()<<"1"<<lastRequest->errorString();
                } else if (lastRequest->error() != QModbusDevice::NoError) {
                    qDebug()<<"2"<<lastRequest->errorString();
                    //ошибка проходит через эту часть кода
                }
                lastRequest->deleteLater();
            });
        } else {
            qDebug()<<"yes send";   //это не вызывается
            lastRequest->deleteLater();
        }
    } else {
        qDebug()<<lastRequest->errorString();
    }
}

П.С. Особенности кода:

  1. У меня приложение, в котором много окон, взаимодействующих друг с другом и, соответственно, много файлов. Чтобы не создавать в каждом файле свой объект modbus, я создала класс, в котором обозначила глобальный объект класса Modbus, к которому обращается каждое окно, если ему это необходимо. Выглядит это так:
#ifndef GLOBALVARIABLE_H
#define GLOBALVARIABLE_H

#include "modbus.h"
inline Modbus connect_modbus = Modbus();

#endif // GLOBALVARIABLE_H
  1. Файлы не посылают одновременно запросы Modbus’у, так как у меня работает всего один поток, то есть запросы пересекаться не будут.

лучше использовать что-то типа QMetaObject::invokeMethod, кмк, клиента сделать синглетоном или инстансом в апликейшин классе, открывать и закрывать коннект

https://habr.com/ru/articles/712328/

Syncro ★★★★★
()
Последнее исправление: Syncro (всего исправлений: 1)
Ответ на: комментарий от zurg

Знаете, я попробовала, и все работает, спасибо. Я поменяла код следующим образом:

void Modbus::on_writeButton_clickedRegisters(qint32 registers[], qint32 values[])
{
    <часть кода> 
    if (auto *lastRequest = modbusDevice->sendWriteRequest(writeUnit, SERVER_ADDRESS)) {
        connect(lastRequest, &QModbusReply::finished, this, [ lastRequest](){
        if (lastRequest->error() != QModbusDevice::NoError){
            qDebug()<<"yes send";
            lastRequest->deleteLater();
        }
        });
     }else {
        qDebug()<<lastRequest->errorString();
    }
}

И у меня «yes send» вызывается, то есть и ошибок нет, получается… А в чем тогда была проблема в данном случае? Я что-то дублировала?

alexandra
() автор топика
Ответ на: комментарий от alexandra

похоже сигнал QModbusReply::finished теряется т.е. приходит до вызова connect(), но после проверки isFinished(), сейчас вероятность потерять этот сигнал ниже, но всё равно есть, так-что проверять isFinished() всё-таки надо (и прочие ошибки) после конекта, и в такой последовательности событие гарантированно не потеряется. Странноватый, конечно, интерфейс логичнее было бы иметь возможность сделать коннект до запроса

zurg
()
Ответ на: комментарий от seiken

Давно уж, с какой-то 5-й версии. Я даже баг репортил (в первоначальной реализации криво считались номера регистров с ненулевым начальным адресом). Но мне тогда показалось, что кутэшная реализация для скоростных приложений (со временем ответа порядка 1 мс) подходит слабо, остался на libmodbus.

anonymous
()
Ответ на: комментарий от uralbash

Какая-то кривая реализация. По официальному мануалу единственный метод определения окончания ответа в последовательной линии — таймаут. Если слейв прислал больше или меньше, чем заказывали — это ошибка, но мастер её обязан корректно обработать как frame NOK.

anonymous
()
Ответ на: комментарий от anonymous

И, кстати, не помню, как там точно сказано в рекомендации Модбаса, но получение бо́льшего пакета может даже не быть ошибкой. Скажем, тупой слейв шлёт фиксированный набор регистров независимо от того, сколько запрошено. Если запросить один, а придёт десять, включая запрошенный — ну, вроде как ответ некорректный, но, с другой стороны, запрошенная-то инфа получена, браковать ответ может быть излишне.

anonymous
()
Ответ на: комментарий от alexandra
if (auto *lastRequest = modbusDevice->sendWriteRequest(writeUnit, SERVER_ADDRESS)) {
        connect(lastRequest, &QModbusReply::finished, [ lastRequest](){
        if (lastRequest->error() = QModbusDevice::NoError){ 
            qDebug()<<"yes send connect";
            lastRequest->deleteLater();
        } // и остальные ошибки тоже и лучше через switch такое делать - нагляднее       
        });
       if (lastRequest->isFinished()) {
            qDebug()<<"yes send finished";
       }
     }else {
        qDebug()<<lastRequest->errorString();
    }

вот так оно что выводит? должно гарантированно отловить, ну или ошибка в другом месте

zurg
()
Последнее исправление: zurg (всего исправлений: 2)
Ответ на: комментарий от zurg

Оно все также выводит ошибку таймаута. Я думала, что проблема в том, что что-то перебивается во время работы программы, что посылка приходит неправильно, поэтому попыталась отправить его (весь модбас) в другой поток, изучила немного этот вопрос, но как он выдавал эту ошибку, так дальше и выдает, увеличение времени ожидания ответа и время частоты работы таймера ни к чему не приводит. Я в замешательстве

alexandra
() автор топика
Ответ на: комментарий от alexandra

Оно отличается от тех данных, что были изначально, потому что я решила вывести в отдельное приложение принцип работы, чтобы с ним работать отдельно. Отличие основное состоит только в том, что в исходном вопросе у меня модбас - это глобальный объект (у меня много файлов), а в уменьшенном приложении в этом нет необходимости, поэтому сделала его знакомым основому окну. Также добавила потоки…, а так много всего на окне, чтобы проверить зрительно, как работают потоки и не зависает ли приложение. Основной вопрос именно в модбасе, в котором чтение происходит постоянно, я запись по требованию. А управление происходит посредством клавиатуры нумпада. Это основные моменты, если, вдруг, кто-то решит залезть в сам код… но заранее буду очень благодарна

alexandra
() автор топика