Qt QML - QModBus 读取被 QML BusyIndi​​cator/Animation 破坏 - SingleThread

Posted

技术标签:

【中文标题】Qt QML - QModBus 读取被 QML BusyIndi​​cator/Animation 破坏 - SingleThread【英文标题】:Qt QML - QModBus read corrupted by QML BusyIndicator/Animation - SingleThread 【发布时间】:2021-12-12 17:02:55 【问题描述】:

我有一个单线程 QQuick 应用程序,它有一个主窗口和一个处理 Modbus 写/读功能的类。到目前为止一切正常,但是当我在我的 qml 窗口中放置 BusyIndi​​cator 以显示总线繁忙时,我得到 CRC 不匹配和响应超时,例如:

“丢弃带有错误 CRC 的响应,收到:64580,计算出的 CRC:55067” “读取响应错误:响应超时。(代码:0x5)” - qt.modbus:(RTU 客户端)无法将响应与打开的请求匹配,忽略

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "modbusinterface.h"

int main(int argc, char *argv[])

QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

QGuiApplication app(argc, argv);

ModbusInterface modbus;

QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                 &app, [url](QObject *obj, const QUrl &objUrl) 
    if (!obj && url == objUrl)
        QCoreApplication::exit(-1);
, Qt::QueuedConnection);
engine.load(url);

engine.rootContext()->setContextProperty("modbus", &modbus);

return app.exec();

modbusinterface.h

#ifndef MODBUSINTERFACE_H
#define MODBUSINTERFACE_H

#include <QObject>

#include <QSerialPort>
#include <QModbusRtuSerialMaster>
#include <QModbusDevice>
#include <QModbusClient>
#include <QVariant>
#include <QDebug>

class ModbusInterface : public QObject

    Q_OBJECT
    Q_PROPERTY(bool busBusy READ busBusy NOTIFY busBusyChanged)

public:
    explicit ModbusInterface(QObject *parent = nullptr);
    bool busBusy(void) return m_busBusy;
    Q_INVOKABLE bool read(int deviceId, int startAddress, quint16 count);

public slots:
    void readReady();

signals:
    void busBusyChanged();

private:
    bool m_busBusy = false;
    QModbusReply *m_lastRequest = nullptr;
    QModbusClient *m_client = nullptr;
;

#endif // MODBUSINTERFACE_H

modbusinterface.cpp

#include "modbusinterface.h"

ModbusInterface::ModbusInterface(QObject *parent) : QObject(parent)

    m_client = new QModbusRtuSerialMaster();
    m_client->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "ttyUSB0");
    m_client->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud19200);
    m_client->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);
    m_client->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity);
    m_client->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);

    m_client->setTimeout(1000);
    m_client->setNumberOfRetries(1);

    if (!m_client->connectDevice()) 
        qDebug() << "Connect failed: " << m_client->errorString();
     else 
        qDebug() << "Modbus Client is Connected";
    


bool ModbusInterface::read(int deviceId, int startAddress, quint16 count)

    QModbusDataUnit RxData;

    if(startAddress>=40000) RxData.setRegisterType(QModbusDataUnit::HoldingRegisters);
    else RxData.setRegisterType(QModbusDataUnit::InputRegisters);

    RxData.setStartAddress(startAddress);
    RxData.setValueCount(count);

    if (!m_client) 
        qDebug() << "!m_client";
        return false;
    

    if (m_client->state() != QModbusDevice::ConnectedState)
        qDebug() << "Modbus Client is not Connected in read section";
        return false;
    

    if (auto *reply = m_client->sendReadRequest(RxData, deviceId))
    
        if (!reply->isFinished())
            connect(reply, &QModbusReply::finished, this, &ModbusInterface::readReady);
            m_lastRequest = reply;
            m_busBusy = true;
            emit busBusyChanged();
         else 
            delete reply;
        
        return true;
    
    return false;


void ModbusInterface::readReady()

    auto reply = qobject_cast<QModbusReply *>(sender());
    if (!reply) return;

    if( reply == m_lastRequest)
        m_busBusy = false;
        emit busBusyChanged();
    
    reply->deleteLater();

    if (reply->error() == QModbusDevice::NoError)
    
        qDebug() << reply;
    
    else if (reply->error() == QModbusDevice::ProtocolError)
    
        qDebug() << QString("Read response error: %1 (Mobus exception: 0x%2)").
                    arg(reply->errorString()).
                    arg(reply->rawResult().exceptionCode(), -1, 16);
     else 
        qDebug() << QString("Read response error: %1 (code: 0x%2)").
                    arg(reply->errorString()).
                    arg(reply->error(), -1, 16);
    

main.qml

import QtQuick 2.12
import QtQuick.Window 2.12

import QtQuick.Controls 2.5

Window 
    width: 640
    height: 480
    visible: true
    title: qsTr("ModbusTrial")

    Button
        id: button
        anchors.centerIn: parent
        text: "read Modbus"
        onClicked: 
            for(var i=1; i<=10; i++)
                modbus.read(i, 30001, 1)
            
        
    

    BusyIndicator
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.top: button.bottom
        visible: modbus.busBusy //false
    

在这个最小的工作示例中,我通过我的 modbusinterface 类的 qml/c++ 接口触发/排队 10 个读取请求。当我访问不需要知道哪个服务器地址的设备时,我需要扫描多个 ID 以获取有效响应,并希望在此期间显示一个 busyIndi​​cator。这会导致上述超时/CRC 错误。 如果我的窗口中没有运行动画,则接收到的数据没有错误。

能否通过使用单独的线程运行 modbus 读/写方法来解决此问题,我将如何实现?还是我只会通过将串行函数放在单独的线程中来增加误读?

据我所知,由于我的应用程序在单线程中运行,GUI 的持续更新在某种程度上干扰了串行数据的接收。

我使用了 linux 命令行工具“stress”来查看在高 cpu 负载下我是否也会丢失数据,但事实并非如此。

有趣的是,我得到的第一个响应总是有效的。那么像我这样排队请求会不会有问题呢?在文档中它说:

注意:QModbusClient 将它收到的请求排队。并行执行的请求数取决于协议。例如,桌面平台上的 HTTP 协议为一个主机/端口组合发出 6 个并行请求。

亲切的问候

【问题讨论】:

没有minimal reproducible example,我们无法直接回答您的问题。但我肯定会尝试一个单独的线程,看看是否能解决它。 请提供足够的代码,以便其他人更好地理解或重现问题。 “公交车很忙”是什么意思? 我更新了我的代码示例。总线很忙,我的意思是我发送了多个“readRequests”,在处理它们的时间里——有时直到超时发生——我认为总线很忙 如果你正在读取连续寄存器,更改count会比读取10个单个寄存器更好。 【参考方案1】:

我发现了问题:

我认为 qml 渲染引擎、场景图或任何你称之为的东西都会导致 modbus 接收帧丢失。我想不知何故线程可能对此有所帮助,但我无法通过在单独的线程中运行 modbusInterface 来修复它。

最终解决方案是启用场景图的线程渲染循环,如下所述:https://doc.qt.io/qt-5/qtquick-visualcanvas-scenegraph.html#threaded-render-loop-threaded

即通过放

qputenv("QSG_RENDER_LOOP","threaded");

在我的 main() 中。

【讨论】:

以上是关于Qt QML - QModBus 读取被 QML BusyIndi​​cator/Animation 破坏 - SingleThread的主要内容,如果未能解决你的问题,请参考以下文章

是否有 Qt/QML 函数来检查形状是否已被单击?

IOS上的Qt QML应用程序运行缓慢,JIT被禁用

如何完全禁用 Qt Creator 中的 QML 调试器?

Qt:Qml:创建对话框时关注文本字段

在 Qml 和 javascript 中动态读取配置文件(javascript 文件或 json 文件)

qt qml中PropertyAnimation的几种使用方法