【Qt Modbus通信】QModbus实现modbus的主机功能 源码分享

2023-11-17

前言

modbus在上下位机数据交互时被广泛使用,因此写了这篇笔记和大家一起学习。
【Qt Modbus通信】libmodbus实现modbus的主机功能/从机功能 源码分享
之前使用libmodbus实现了modbus的主从功能,但发现主机查询从机的从机ID不能大于200+,因此参考QT5的modbusDEMO重新写了一份基于QModbus实现的modbus主机功能。

参考文献

在这里插入图片描述

程序执行效果

QT官方DEMO
在这里插入图片描述modbus主机 运行效果在这里插入图片描述

源码下载

https://gitee.com/jiang_bin_yu/QSerialBus-modbus-master/tree/master/

程序源码

下面我将官方DEMO中的关键代码移植出来,实现了modbus主机程序
一、项目配置
QT += core gui serialport serialbus
二、寻找可用串口

void MainWindow::freshSerialPortCombox()
{
    foreach (const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
    {
        QSerialPort tempSer;
        tempSer.setPort(info);
        if(tempSer.open(QIODevice::ReadWrite))
        {
            ui->comboBox_serialName->addItem(tempSer.portName());
            tempSer.close();
        }
    }
}

三、初始modbus

void MainWindow::MainWindow::InitModbus()
{
    //获取modbus raw 数据帧
    connect(SaveLog::Instance(),&SaveLog::sigModbusData,this,&MainWindow::onModbusRawData);
    if (modbusDevice) {
        modbusDevice->disconnectDevice();
        delete modbusDevice;
        modbusDevice = nullptr;
    }

    modbusDevice = new QModbusRtuSerialMaster(this);
    connect(modbusDevice, &QModbusClient::errorOccurred, [this](QModbusDevice::Error) {
        qDebug() << "modbus Error:" << modbusDevice->errorString();
    });
}

四、串口连接

void MainWindow::connectModbus()
{
    disconnnectModbus();
    if (!modbusDevice)
        return;
    modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,ui->comboBox_serialName->currentText());
    modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,parity);
    modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,baud);
    modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,dataBit);
    modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,stopBit);

    modbusDevice->setTimeout(1000);
    modbusDevice->setNumberOfRetries(0);
    //连接失败
    if(modbusDevice->connectDevice())
    {
        ui->pushButton_connect->setText("断开连接");
        qDebug() << "连接成功";
    }
    else
    {
        ui->pushButton_connect->setText("建立连接");
        qDebug() << "连接失败";
        QMessageBox::information(NULL,  "Title",  "串口打开失败");
    }
}

五、写入寄存器
a.writeUnit:写入寄存器 slaveId 写入的从机ID startAddress写入的起始地址 values写入的值

void MainWindow::writeUnit(int slaveId, int startAddress, QList<quint16> values)
{
    if (!modbusDevice)
    {
        QMessageBox::information(NULL,  "Title",  "请先连接设备");
        return;
    }
    connectModbus();
    QModbusDataUnit writeUnit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters,startAddress, values.size());
    for(int i=0; i<values.size(); i++)
    {
        writeUnit.setValue(i, values.at(i));
    }

    //serverEdit 发生给slave的ID
    if (auto *reply = modbusDevice->sendWriteRequest(writeUnit,slaveId)) {
        if (!reply->isFinished()) {
            connect(reply, &QModbusReply::finished, this, [this, reply]() {
                if (reply->error() == QModbusDevice::ProtocolError) {
                    qDebug() << QString("Write response error: %1 (Mobus exception: 0x%2)")
                                .arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16);
                } else if (reply->error() != QModbusDevice::NoError) {
                    qDebug() << QString("Write response error: %1 (code: 0x%2)").
                                arg(reply->errorString()).arg(reply->error(), -1, 16);
                }
                reply->deleteLater();
            });
        } else {
            reply->deleteLater();
        }
    } else {
        qDebug() << QString(("Write error: ") + modbusDevice->errorString());
    }
}

b.writeUnit的调用方法

void MainWindow::on_pushButton_send_clicked()
{
    QList<quint16> values;
    for(quint16 i=0; i<READNUM; i++)
    {
        values.append(i);
    }
    writeUnit(0x01,0,values);	//修改从机0x01,0~50寄存器的值
}

五、读取寄存器
a.readUnit:读取寄存器 slaveId 读取的从机ID startAddress读取的起始地址 readNum读取的数量

void MainWindow::readUnit(int slaveId, int startAddress, int readNum)
{
    if (!modbusDevice)
    {
        QMessageBox::information(NULL,  "Title",  "请先连接设备");
        return;
    }
    connectModbus();
    QMutexLocker lock(&m_modbusMutex);
    if (auto *reply = modbusDevice->sendReadRequest(QModbusDataUnit(QModbusDataUnit::HoldingRegisters, startAddress, readNum), slaveId)) {
        if (!reply->isFinished())
            connect(reply, &QModbusReply::finished, this, &MainWindow::onReadReady);
        else
            delete reply;
    } else {
        qDebug() << "Read error: " << modbusDevice->errorString();
    }
}

b.接受modbus返回数据

void MainWindow::onReadReady()
{
    auto reply = qobject_cast<QModbusReply *>(sender());
    if (!reply)
        return;

    if (reply->error() == QModbusDevice::NoError) {
        const QModbusDataUnit unit = reply->result();
        if(unit.valueCount() == READNUM)
                for (uint i = 0; i < unit.valueCount(); i++) {
                const QString entry = tr("Address: %1, Value: %2").arg(unit.startAddress() + i)
                        .arg(QString::number(unit.value(i),
                                             unit.registerType() <= QModbusDataUnit::Coils ? 10 : 16));
                ui->textBrowser->append(entry);
            }
    } 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);
    }
    reply->deleteLater();
}

六、如果你想获得你发送和接受的modbus数据帧,可以参考下我下面这个愚蠢的办法
a.设置日志过滤器,开启关于qt.modbus*的日志打印

    QLoggingCategory::setFilterRules(QStringLiteral("qt.modbus* = true"));

开启后你的调试栏讲会打印modbus相关的内容在这里插入图片描述
b.如果你想获取这些内容并发送给你的界面,你需要开启日志重定向,即装载新的日志钩子(详细请参考上面码云提供的源码 代码在:QModbusDemo/savelog.cpp)

//日志重定向
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
void Log(QtMsgType type, const char *msg)
#else
void Log(QtMsgType type, const QMessageLogContext &, const QString &msg)
#endif
{
    //加锁,防止多线程中qdebug太频繁导致崩溃
    static QMutex mutex;
    QMutexLocker locker(&mutex);
    QString content;

    //这里可以根据不同的类型加上不同的头部用于区分
    switch (type) {
    case QtDebugMsg:
        content = QString("%1").arg(msg);
        break;

    case QtWarningMsg:
        content = QString("QtWarningMsg: %1").arg(msg);
        break;

    case QtCriticalMsg:
        content = QString("QtCriticalMsg: %1").arg(msg);
        break;

    case QtFatalMsg:
        content = QString("QtFatalMsg: %1").arg(msg);
        break;
    default: break;
    }

    SaveLog::Instance()->save(content);
}
//安装日志钩子,输出调试信息到文件,便于调试
void SaveLog::start()
{
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
    qInstallMsgHandler(Log);
#else
    qInstallMessageHandler(Log);
#endif
}

c.在日志重定向中,拦截(RTU client) Sent Serial ADU:和(RTU client) Received ADU:关键字的日志,并将其内容截取发送给mainwindow

void SaveLog::save(const QString &content)
{
    if(1)
    {
        QString msg = static_cast<QString>(content);
        std::cout << msg.toLocal8Bit().data() << std::endl;
        //方法改进:之前每次输出日志都打开文件,改成只有当日期改变时才新建和打开文件
        if(content.contains("(RTU client) Sent Serial ADU:"))
        {
            //(RTU client) Sent Serial ADU: 0xfe0300000029901b
            //std::cout << "SendMsg" << msg.toUtf8().data() << std::endl;
            msg = msg.remove(msg.indexOf("(RTU client) Sent Serial ADU:"),32);

            msg = msg.left(msg.size());
            msg = msg.toUpper();
            int n = msg.length();
            while(n-2 > 0)
            {
                n = n - 2;
                msg.insert(n," ");
            }
            emit sigModbusData(msg,0);
            //std::cout << "SendMsg:  " << msg.toLocal8Bit().data() << std::endl;
        }
        else if(content.contains("(RTU client) Received ADU:"))
        {
            //std::cout << "RecvMsg" << msg.toLocal8Bit().data() << std::endl;
            msg = msg.remove(msg.indexOf("(RTU client) Received ADU:"),28);
            msg = msg.left(msg.size()-1);
            msg = msg.toUpper();
            int n = msg.length();
            while(n-2 > 0)
            {
                n = n - 2;
                msg.insert(n," ");
            }
            emit sigModbusData(msg,1);
            //std::cout << "Received:  " << msg.toLocal8Bit().data() << std::endl;
        }
        else if(!content.contains("(RTU client)"))
        {
            if(this->fileName.isEmpty())
            {
                QString fileName = QString("%1/Log/%2_log_%3.txt").arg(path).arg(name).arg(QDATETIME);
                if (this->fileName != fileName) {
                    this->fileName = fileName;
                    if (file->isOpen()) {
                        file->close();
                    }

                    file->setFileName(fileName);
                    file->open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text);
                }
            }
            QTextStream logStream(file);
            logStream << QString("%1--Debug     %2").arg(QDATETIME_zzz).arg(msg) << "\n";
        }
    }
}

d.绑定信号与槽,将接受到的modbus数据帧显示在textBrowser上

//获取modbus raw 数据帧
    connect(SaveLog::Instance(),&SaveLog::sigModbusData,this,&MainWindow::onModbusRawData);
//获取modbus数据帧 0发送的数据 1接受的数据
void MainWindow::onModbusRawData(QString data, int type)
{
    QString color;
    QString text;
    if(type == 0)   //发送
    {
        text = "发送数据帧:";
        color = "#F37257";
    }
    else if(type == 1)  //接受
    {
        text = "接受数据帧:";
        color = "#1777D7";
    }
    // 设置文字(样式+内容)
    QString  str = QString("<font color=\"%1\">" +QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ")+ text + data + "</font>").arg(color);
    ui->textBrowser->append(str);
}

程序效果如下图所示

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Qt Modbus通信】QModbus实现modbus的主机功能 源码分享 的相关文章

  • Qt5和QML:如何使用WebEngine Quick Nano浏览器自动输入用户名和密码

    我正在使用编写一个小应用程序Qt and QML使用 Qt 文档中的示例WebEngine Quick Nano 浏览器 https doc snapshots qt io qt5 5 9 qtwebengine webengine qui
  • Qml 和模糊图像

    我想使用 QML 实现模糊效果 我找到了有关 效果 模糊 的参考资料 例子 http qt gitorious org lscunha qt components lscunha qt components blobs d78feec567
  • 仅在内部/外部抚摸路径?

    Given a QPainterPath http qt project org doc qt 4 8 qpainterpath html如何仅在路径的内侧或外侧边缘 或非闭合路径的左侧或右侧 描边路径 QPainter strokePat
  • 在 QtCreator 中使用 .pro 和 .pri 正确(树)显示 Qt 项目

    是否可以使用项目包含文件 pri 显示不止一个额外级别 例如如果 pro文件包括 pri文件包含两个 pri files pro pri pri pri 在 QtCreator 项目 中显示具有误导性 pro pri pri pri 它根本
  • new 运算符(以及 malloc)无法分配约 450 MB 的内存 [重复]

    这个问题在这里已经有答案了 我正在开发一个程序 该程序在内存中存储大约 2 2 亿个短值的数组 该数据块的分配方式如下 short arrayName new short SIZE OF ARRAY 然后将文件的内容读入内存 在团队中的另一
  • QT:删除QGridLayout中QLabel之间的空格

    我将一些具有不同颜色的 QLabels 添加到 QGridLayout 作为 QWidget 的布局 现在我在每个 Qlabel 与下一个 Qlabel 之间有一个间距 我想将其删除 我尝试将标签的边距设置为 0 将 GridLayout
  • 清除pyqt中布局中的所有小部件

    有没有办法清除 删除 布局中的所有小部件 self plot layout QtGui QGridLayout self plot layout setGeometry QtCore QRect 200 200 200 200 self r
  • Retina 显示屏中具有 QOpenGLWIdget 的 Qt MainWindow 显示错误大小

    我有一个 Qt 应用程序MainWindow 我嵌入一个QOpenGLWidget在里面 一切正常 直到我开始使用 Apple Retina 显示屏并在高 DPI 模式下运行我的应用程序 我的QOpenGLWidget只是它应该具有的大小的
  • 使用 OpenGL 渲染 QImage

    与我相关的其他问题 https stackoverflow com questions 20126354 render qimage from sooffscreenrenderer in qglwidget 我认为更核心的问题是 如何渲染
  • 在 Qt 中使用多个不同的流读取同一文件

    使用 Qt 是否可以使用多个流读取文件以同时访问其中的不同数据部分 请注意 Qt 中的流 QTextStream QDataStream 不处理底层设备中的位置 流类只是一个包装器 用于更轻松地解析设备 QFile 实例 内的二进制数据 因
  • 如何在 Qt 应用程序中嵌入 Python 解释器?

    有没有一种简单的方法可以将 Python 解释器嵌入到 Qt 应用程序中 如果可能的话 我希望有一个跨平台的解决方案 这就是目的PythonQt http pythonqt sourceforge net 它支持 Windows Linux
  • 使用 QGraphicsScene 实现流畅的动画

    我希望我的问题并不总是同样的问题 我有一个 QGraphicsScene 它的项目是一些 QGraphicsPixmap 我用一个计时器来移动它们 每秒 SetX 10 我设置 10是因为窗口大100 使用这个解决方案我的动画不流畅 我想我
  • Qt(在 Windows 上)将权限级别设置为“requireAdministrator”

    我正在使用 Qt Creator 并努力制作 exe文件默认以管理员身份运行 在线阅读所有解决方案我试图将这一行放入我的 pro file QMAKE LFLAGS MANIFESTUAC level requireAdministrato
  • QTabWidget 选项卡在垂直方向,但文本在水平方向

    我正在尝试用 C Qt 制作一个带有这样的侧边栏的应用程序 但是当将 QTabWidget 方向设置为西时 它会使文本垂直 如何让文本位于左侧 但水平对齐 PS 我不需要图标 提前致谢 您可以使用QListWidget http doc q
  • QML 列表视图拖放

    我想创建两个 qml 列表视图 可以执行两个功能 拖放一个列表中的项目以更改项目的顺序 跨列表拖放项目 项目将从一个列表中删除并添加到另一个列表中 根据 Qt 文档中的拖放示例 我决定创建两个访问同一列表模型的列表视图 列表模型中的每个项目
  • 在 Qt 中,许多插槽连接到同一信号,它们在发出信号时是否按顺序调用?

    In the Qt文件说 如果多个插槽连接到一个信号 则这些插槽将 按照它们连接的顺序一个接一个地执行 当信号发出时 但在connect 功能 设置Qt ConnectionType输入为Qt QueuedConnection意思是 当控制
  • Qt:更改 Mac OS X 上的应用程序 QMenuBar 内容

    我的应用程序对多个 页面 使用 QTabWidget 其中顶级菜单根据用户所在的页面而变化 我的问题是 尝试重新创建菜单栏的内容会导致严重的显示问题 它在除 Mac OS X 之外的所有平台上按预期使用第一种和第三种样式 尚未测试第二种 但
  • QT:模块“QtWebView”未安装

    我的操作系统是win10 QT版本是Qt5 7 mingw53 32 目标操作系统是win10 当我使用qmlscene执行qml文件时 发生了一些错误 qrc qml 3 模块 QtWebView 未安装 我的 qml 文件如下 impo
  • QThread - 使用槽 quit() 退出线程

    我想在线程完成运行时通知对象 但是 我无法让线程正确退出 我有以下代码 处理器 cpp thread new QThread tw new ThreadWorker connect tw SIGNAL updateStatus QStrin
  • 使用 qbs 构建 qt 应用程序

    我想知道在 Linux 上使用 qbs 编译 构建和创建 Android Qt 应用程序的步骤 我拥有所有必要的工具 目前我可以使用 qmake 创建 apk Qbs 目前不支持构建 Qt Android 应用程序 Qbs v1 4 中引入

随机推荐

  • 第一章 webpack与构建发展简史

    官方loader和插件 Loaders webpack Plugins webpack 为什么需要构建工具 初识webpack webpack默认配置文件 webpack config js 可以通过webpack config
  • 数据结构-图的创建(邻接矩阵,邻接表)C语言实现

    图的定义 图 Graph G由两个集合V和E组成 记为 G V E 其中V是顶点的有穷非空集合 其实就是顶点 E是V中顶点偶对的有穷集合 就是边 V G 和E G 通常分别表示图G的顶点集合以及边集合 E G 可以为空集合 但是此时的图只有
  • 502 Bad Gateway The proxy server received an invalid response from an upstream server

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 打开网站一直报错 查看了一下nginx错误日志 发现很多的报错 2018 12 24 11 02 51 alert 20026 20026 33113943 socket
  • 【渗透测试笔记】之【内网渗透——Windows系统散列值获取与防范】

    拓扑图 Windows系统散列值获取 1 通过CS模块获取用户凭证信息 在获取到目标主机权限后 我们可以抓取hash和dump明文密码 这两项功能都需要管理员权限 如果权限不足 先要进行提权操作 抓取密码哈希 右键被控主机 gt Acces
  • 【OpenCV学习笔记】【教程翻译】五 (车牌识别之OCR分割)

    车牌识别 车牌识别的第二步主要是提取出车牌中的字符 对于每个被检测出的车牌 我们对车牌进行分割获取每个字符 然后用神经网络机器学习算法实现字符的识别 在这个过程中 我们也可以学习到如何评估一个分类算法 OCR分割 首先 我们将车牌图像作为具
  • sqli-labs 21——40关攻略

    Less 21 基于错误的复杂的字符型Cookie注入 base64编码 单引号 报错型 cookie型注入 本关和less 20相似 只是cookie的uname值经过base64编码了 登录后页面 圈出来的地方显然是base64加密过的
  • 浅谈Linux的文件系统, 新增XFS.Ext3.GFS.ReiserFS.JFS相关知识

    如果您是一位新手 也许 您还不知道如何把文件从Windows拷贝到 Linux上吧 下面 我们将说明Unix文件系统以及mount的工作过程 然后再比较详细地讨论 mount的使用和有关选项 如果您已经了解Unix文件系统是如何工作的 那么
  • debug调试神器pysnooper

    异常bug定位 print 函数也可以 但效率上还是慢 后来发现了一个叫PySnooper的装饰器 一般debug调试 都是在我们可能觉得会有问题的地方 去打印输出 看下实际输出了什么 然后思考问题所在 下载库 pip install py
  • python3 练习题100例 (十二)

    题目十二 打印出所有的 水仙花数 所谓 水仙花数 是指一个三位数 其各位数字立方和等于该数本身 例如 153是一个 水仙花数 因为153 1的三次方 5的三次方 3的三次方 usr bin env python3 coding utf 8
  • “ModuleNotFoundError: No module named sklearn”解决办法

    最近在跑实验的时候 需要导入sklearn 但是运行代码一直提示 ModuleNotFoundError No module named sklearn 实验中导入sklearn的代码 from sklearn import metrics
  • Linux CentOS7 中 完美解决VMTools失效,windows 与 Liunx间完美复制文件,无报错的解决方案

    问题 我也是才刚使用CentOS7没多久 搭建好环境后出现比较头疼的问题就是 Windows 和 Linux 之间无法复制粘贴文本和文件 这个问题只要在虚拟机中安装 VMTools 就能解决 但是不知道什么原因导致 我在CentOS 6 8
  • Linux 狂神说学习笔记

    狂神说linux Linux 基本目录 目录相关命令 文件属性 查看文件 硬链接和软链接 vim 账号管理 用户组管理 磁盘管理 进程管理 环境安装 基本目录 目录相关命令 ls al 列出目录 a所有文件包括隐藏文件 l列出所有文件包括文
  • MyBatis ognl.NoSuchPropertyException 或者 Invalid bound statement (not found)

    描述 SpringBoot Mybatis plus 项目 运行时出现如下错误 ognl NoSuchPropertyException 没有对应属性异常 Invalid bound statement not found 绑定语句无效 未
  • 问题小结(3)-dialog标题居中

    dialog标题居中问题 用系统的AlertDialog Builder创建dialog时 如果需要将dialog的title居中显示 需要调用 setCustomTitle View view 方法 对需要设置的view设置居中的相关属性
  • zookeeper 分布式共享锁的流程图

    1分布式共享锁的流程图 原理 package cn itcast bigdata zklock import java util Collections import java util List import java util Rand
  • 水球图 及各种参数设置

    水球图 Liquid Fill Chart 是Echarts的一个插件 在官方文档中没有 可以用来优雅的展示百分比数据 水球图 gif 安装 HTML中引入水球图
  • docker基础1——架构组成、安装配置

    文章目录 一 发展起源 1 1 传统虚拟化与容器虚拟化 1 2 docker底层核心技术 1 2 1 命名空间 1 2 2 控制组 1 3 docker工作方式 1 4 docker容器编排 1 5 docker优劣势 1 6 docker
  • iframe的替代品

    面试题 使用过iframe框架 那你对于iframe框架的优缺点知道多少 并且由于iframe的一些缺点 国内外针对这个框架的替代品你知道有哪些呢 知识点1 iframe框架的优缺点 优点 1 可以跨域请求其他网站 并将网站完整展示出来 2
  • [课程复习] 数据结构之线性表、树、图、查找、排序经典算法复习

    作者最近在复习考博 乘此机会分享一些计算机科学与技术 软件工程等相关专业课程考题 一方面分享给考研 考博 找工作的博友 另一方面也是自己今后完成这些课程的复习资料 同时也是在线笔记 基础知识 希望对您有所帮助 不喜勿喷 无知 乐观 低调 谦
  • 【Qt Modbus通信】QModbus实现modbus的主机功能 源码分享

    前言 modbus在上下位机数据交互时被广泛使用 因此写了这篇笔记和大家一起学习 Qt Modbus通信 libmodbus实现modbus的主机功能 从机功能 源码分享 之前使用libmodbus实现了modbus的主从功能 但发现主机查