【Qt】Qt + Modbus 服务端学习笔记

news2025/3/24 23:30:02

《Qt + Modbus 服务端学习笔记》

1.因为项目的需要,要写一个modbus通信,csdn上感觉有些回答,代码是人工智能生成的,有些细节不对。我这个经过实测,是可以直接用的。

首先要包含Qt 的相关模块

在这里插入图片描述

Qt Modbus 模块主要包含以下几类关键组件:

  • 设备类:如 QModbusTcpServerQModbusTcpClient,分别用于创建 Modbus TCP 服务器和客户端,提供了建立连接、断开连接等基础操作接口。

  • 数据单元类:例如 QModbusDataUnit,用于表示 Modbus 协议中的数据单元,可方便地操作和管理 Modbus 寄存器中的数据。

  • 协议数据单元类:像 QModbusPdu,它表示 Modbus 协议数据单元,用于处理 Modbus 请求和响应的协议层数据。

  • 初始化:在 ModeBusServer 类的构造函数里,初始化 QModbusTcpServer 和定时器,同时建立信号与槽的连接,以处理定时器超时、数据写入、状态改变等事件。

  • 启动与停止

    • startServer 函数会对输入参数进行检查,设定 Modbus 数据单元映射和连接参数,尝试连接设备,若成功就启动数据更新定时器。
    • stopServer 函数则停止定时器,断开服务器连接。
  • 数据更新:借助定时器,定时调用 updateData 函数。此函数从 SystemInfoCollector 获取系统信息,将其写入 Modbus 数据单元,再设置到服务器里。

  • 数据处理

    • printUpdateData 函数用于打印更新后的数据和解析出的系统信息。
    • handleRequest 函数处理客户端请求,记录请求信息。
    • onDataWritten 函数处理数据写入事件。

运行结果

在这里插入图片描述

#ifndef MODEBUSSERVER_H
#define MODEBUSSERVER_H

#include <QObject>
#include <QModbusTcpServer>
#include <QModbusDataUnit>
#include <QTimer>
#include <QMap>
#include <memory> // 包含 std::unique_ptr 所在的头文件
#include <iostream>
#include <QDateTime>

#include "SystemInfoCollector.h"
//解决中文乱码
#pragma execution_character_set("utf-8")



class ModeBusServer : public QObject
{
    Q_OBJECT
public:
    explicit ModeBusServer(QObject* parent = nullptr);
    ~ModeBusServer();

    void startServer(quint16 deviceID, const QString& ipAddress, quint16 port);
    void stopServer();

    void printUpdateData(QModbusDataUnit& unit);

signals:
    void statusUpdated(const QString& message);
    void dataUpdated(const QModbusDataUnit& data);
    void requestReceived(const QModbusPdu& request, QModbusDataUnit::RegisterType table, int address, int size);


private slots:
    void updateData();
    void writeSystemInfoToModbus(QModbusDataUnit& unit, SystemInfo& info);
    void handleRequest(const QModbusPdu& request, QModbusDataUnit::RegisterType table, int address, int size);
    void sendDataPeriodically();

    void onDataWritten(QModbusDataUnit::RegisterType table, int address, int size);
private:
    std::unique_ptr<QModbusTcpServer> m_modbusServer;
    std::unique_ptr<QTimer> m_dataUpdateTimer;
    std::unique_ptr<QTimer> m_sendDataTimer;
    QMap<int, quint16> m_dataCache; // 数据缓存
    quint16 m_holdingRegistersSize = 100;
    int m_deviceID;
    int m_valueIndex;
    SystemInfoCollector m_infoCollector;
};

#endif // MODEBUSSERVER_H


#include "modeBusServer.h"
#include <QDebug>
ModeBusServer::ModeBusServer(QObject* parent)
	: QObject(parent),
	m_modbusServer(std::make_unique<QModbusTcpServer>(this)),
	m_dataUpdateTimer(std::make_unique<QTimer>(this)),
	m_sendDataTimer(std::make_unique<QTimer>(this))
{

	connect(m_dataUpdateTimer.get(), &QTimer::timeout, this, &ModeBusServer::updateData);
	connect(this, &ModeBusServer::statusUpdated, this, [&](const QString& statusUpdateStr) {
		qDebug() << statusUpdateStr;
		});
	// 修改连接,使用新的槽函数
	connect(m_modbusServer.get(), &QModbusTcpServer::dataWritten, this, &ModeBusServer::onDataWritten);

	connect(m_modbusServer.get(), &QModbusTcpServer::stateChanged, this, [this](int state) {
		if (state == QModbusDevice::ConnectedState) {
			qDebug() << "Server connected";
		}
		else if (state == QModbusDevice::UnconnectedState) {
			qDebug() << "Server disconnected";
		}
		});
}
ModeBusServer::~ModeBusServer()
{
	stopServer();
}
void ModeBusServer::startServer(quint16 deviceID, const QString& ipAddress, quint16 port)
{
	if (port == 0 || deviceID == 0 || ipAddress.isEmpty()) {
		emit statusUpdated("Invalid parameters.");
		return;
	}
	m_deviceID = deviceID;
	QModbusDataUnitMap reg;
	reg.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, m_holdingRegistersSize });
	m_modbusServer->setMap(reg);

	m_modbusServer->setConnectionParameter(QModbusDevice::NetworkAddressParameter, ipAddress);
	m_modbusServer->setConnectionParameter(QModbusDevice::NetworkPortParameter, port);
	m_modbusServer->setServerAddress(deviceID);

	if (m_modbusServer->connectDevice()) {
		emit statusUpdated("Server started on " + ipAddress + ":" + QString::number(port));
		m_dataUpdateTimer->start(500);

	}
	else {
		emit statusUpdated("Server start failed: " + m_modbusServer->errorString());
	}
}
void ModeBusServer::stopServer()
{
	m_dataUpdateTimer->stop();
	m_modbusServer->disconnectDevice();
	emit statusUpdated("Server stopped.");
}
void ModeBusServer::printUpdateData(QModbusDataUnit& unit)
{

		for (int i = 0; i < unit.valueCount(); ++i) {

			m_dataCache[i] = unit.value(i); // 更新缓存
		}

		qDebug() << QString("%1号设备").arg(m_deviceID) << "寄存器数据:" << unit.values();
		SystemInfo info1 = m_infoCollector.parseSystemInfo(unit.values());
		qDebug() << "Memory Usage:" << info1.memoryUsage << "%";
		qDebug() << "CPU Usage:" << info1.cpuUsage << "%";
		qDebug() << "Boot Time:" << info1.bootTime.toString(Qt::ISODate);
		qDebug() << "Up Time:" << info1.upTime << "seconds";

}
// 更新数据
void ModeBusServer::updateData()
{
	m_valueIndex = 0;
	QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, 0, 10);

	SystemInfo info;
	// 将系统信息写入 Modbus 数据单元
	writeSystemInfoToModbus(unit, info);
	bool  isSetDataSuccess = m_modbusServer->setData(unit);


	//设置数据
	if (isSetDataSuccess)
	{
		m_dataCache.clear(); // 清空缓存
		printUpdateData(unit);
		emit dataUpdated(unit);
	}
	else
	{
		qDebug() << "Failed to update data: " << m_modbusServer->errorString();
	}


}
// 将 SystemInfo 数据写入 QModbusDataUnit
void ModeBusServer::writeSystemInfoToModbus(QModbusDataUnit& unit, SystemInfo& info) {

	info = m_infoCollector.getSystemInfo();

	// 写入内存占用率
	quint16 memoryUsageInt = static_cast<quint16>(info.memoryUsage * 100); // 转换为整数
	unit.setValue(m_valueIndex++, memoryUsageInt & 0xFFFF);

	// 写入 CPU 占用率
	quint16 cpuUsageInt = static_cast<quint16>(info.cpuUsage * 100); // 转换为整数
	unit.setValue(m_valueIndex++, cpuUsageInt & 0xFFFF);

	// 写入开机时间(时间戳)
	qint64 bootTimestamp = info.bootTime.toSecsSinceEpoch();
	unit.setValue(m_valueIndex++, static_cast<quint16>(bootTimestamp & 0xFFFF));
	unit.setValue(m_valueIndex++, static_cast<quint16>((bootTimestamp >> 16) & 0xFFFF));

	// 写入运行时间
	unit.setValue(m_valueIndex++, static_cast<quint16>(info.upTime & 0xFFFF));
	unit.setValue(m_valueIndex++, static_cast<quint16>((info.upTime >> 16) & 0xFFFF));

}
void ModeBusServer::handleRequest(const QModbusPdu& request, QModbusDataUnit::RegisterType table, int address, int size)
{
	emit requestReceived(request, table, address, size);
	qDebug() << "Request received: Function Code" << request.functionCode() << ", Type" << table << ", Address" << address << ", Size" << size;
}
void ModeBusServer::sendDataPeriodically()
{
	qDebug() << "Periodic Data Send:";
	for (auto it = m_dataCache.begin(); it != m_dataCache.end(); ++it) {
		qDebug() << "Address" << it.key() << "@" << it.value();
	}
}
// 实现新增的槽函数
void ModeBusServer::onDataWritten(QModbusDataUnit::RegisterType table, int address, int size)
{
	// 处理数据写入事件,可根据需要扩展
	qDebug() << "写入数据类型" << table << "从地址" << address << "开始。" << "数据长度:" << size;

}
	}
}
// 实现新增的槽函数
void ModeBusServer::onDataWritten(QModbusDataUnit::RegisterType table, int address, int size)
{
	// 处理数据写入事件,可根据需要扩展
	qDebug() << "写入数据类型" << table << "从地址" << address << "开始。" << "数据长度:" << size;

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2319081.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【详细解决】pycharm 终端出现报错:“Failed : 无法将“Failed”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。

昨天在终端一顿操作后突然打开pycharm时就开始报错&#xff1a; 无法将“Failed”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后再试一次。 所在位置 行:1 字符: 1 Failed to act…

CXL协议之FM(Fabric Management)解释

CXL协议中的FM功能详解 1. FM的核心作用 FM是CXL&#xff08;Compute Express Link&#xff09;架构中的核心管理实体&#xff0c;负责协调和管理CXL设备之间的通信、资源分配及拓扑结构。其核心功能包括&#xff1a; 设备发现与枚举&#xff1a;识别CXL拓扑中的设备&#x…

Unity URP 实现场景和UI添加后处理

在更新到URP之后&#xff0c;之前内置的渲染管线的那一套后处理已经无法使用&#xff0c;接下来&#xff0c;我们使用URP的内置后处理实现对场景和UI的后处理。 设置UI 如果UI需要使用后处理&#xff0c;在Canvas里&#xff0c;我们要选择Screen Space - Camera&#xff0c;然…

搭建ISCSI传输的配置与管理

前提是&#xff1a; windows server2019设置成桥接模式&#xff0c;因为要让虚拟机和主机设置成一个网段&#xff0c;才能通过网络进行新建虚拟磁盘。 1.添加ISCSI角色 安装位置 选择文件和存储服务----------文件和iscsl 服务------------iscsl目标服务器 2.右上角点击任务&a…

Java 设计模式之享元模式(Flyweight Pattern)

享元模式&#xff08;Flyweight Pattern&#xff09; 是一种 结构型设计模式&#xff0c;旨在通过共享对象来有效支持大量细粒度对象的复用&#xff0c;从而减少内存占用和提高性能。其核心是 分离内部状态&#xff08;可共享&#xff09;与外部状态&#xff08;不可共享&#…

C#入门学习记录(三)C#中的隐式和显示转换

C#类型转换&#xff1a;隐式与显式转换的机制与应用 在C#的强类型体系中&#xff0c;数据类型转换是实现数据交互和算法逻辑的基础操作。当数值类型范围存在包含关系&#xff0c;或对象类型存在继承层次时&#xff0c;系统通过预定义的转换规则实现类型兼容处理。隐式转换&…

Rk3568驱动开发_设备树_9

什么是设备树&#xff1f; 以我目前的理解&#xff0c;设备树更像日常生活中用的地图&#xff0c;用户能根据地图去寻找到相应位置 设备树也是如此它描述了硬件设备的连接关系和配置信息&#xff0c;供 CPU&#xff08;或者更准确地说&#xff0c;是操作系统内核&#xff09;…

深度学习:从零开始的DeepSeek-R1-Distill有监督微调训练实战(SFT)

原文链接&#xff1a;从零开始的DeepSeek微调训练实战&#xff08;SFT&#xff09; 微调参考示例&#xff1a;由unsloth官方提供https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen2.5_(7B)-Alpaca.ipynbhttps://colab.research.google.com/git…

【AI News | 20250320】每日AI进展

AI Repos 1、servers 该仓库提供详细入门指南&#xff0c;用户可通过简单步骤连接Claude客户端&#xff0c;快速使用所有服务器功能。此项目由Anthropic管理&#xff0c;展示MCP的多样性与扩展性&#xff0c;助力开发者为大语言模型提供安全、可控的工具与数据访问。 2、awe…

从零开始实现 C++ TinyWebServer 阻塞队列 BlockQueue类详解

文章目录 阻塞队列是什么&#xff1f;为什么需要阻塞队列&#xff1f;BlockQueue 成员变量实现 push() 函数实现 pop() 函数实现 close() 函数BlockQueue 代码BlockQueue 测试 从零开始实现 C TinyWebServer 项目总览 项目源码 阻塞队列是什么&#xff1f; 阻塞队列是一种线程…

Linux驱动开发基础(can)

目录 1.can的介绍 2.can的硬件连接 2.1 CPU自带can控制器 2.2 CPU没有can控制器 3.电气属性 4.can的特点 5.can协议 5.1 can的种类 5.2 数据帧 5.2.1 标准数据帧格式 5.3.1 扩展数据帧格式 5.3 遥控帧 5.4 错误帧 5.5 过载帧 5.6 帧间隔 5.7 位填充 5.8 位时…

leetcode热题100道——字母异位词分组

给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 示例 1: 输入: strs ["eat", "tea", "tan", "ate", "nat", &…

MCU-芯片时钟与总线和定时器关系,举例QSPI

时钟源&#xff1a; 时钟源为系统时钟提供原始频率信号&#xff0c;系统时钟则通过&#xff08;分频、倍频、选择器&#xff09;成为整个芯片的“主时钟”&#xff0c;驱动 CPU 内核、总线&#xff08;AHB、APB&#xff09;及外设的运行。 内部时钟源&#xff1a; HSI&#x…

技术分享 | MySQL内存使用率高问题排查

本文为墨天轮数据库管理服务团队第51期技术分享&#xff0c;内容原创&#xff0c;如需转载请联系小墨&#xff08;VX&#xff1a;modb666&#xff09;并注明来源。 一、问题现象 问题实例mysql进程实际内存使用率过高 二、问题排查 2.1 参数检查 mysql版本 &#xff1a;8.0.…

分享一个精灵图生成和拆分的实现

概述 精灵图&#xff08;Sprite&#xff09;是一种将多个小图像合并到单个图像文件中的技术&#xff0c;广泛应用于网页开发、游戏开发和UI设计中。在MapboxGL中&#xff0c;跟之配套的还有一个json文件用来记录图标的大小和位置。本文分享基于Node和sharp库实现精灵图的合并与…

函数:形参和实参

在函数的使用过程中分为实参和形参&#xff0c;实参是主函数实际调用的值而形参则是给实参调用的值&#xff0c;如果函数没被调用则函式不会向内存申请空间&#xff0c;先用一段代码演示 形参&#xff1a; int test(int x ,int y ) {int z 0;z x y;return z; } 为何会叫做…

【C#知识点详解】ExcelDataReader介绍

今天来给大家介绍一下ExcelDataReader&#xff0c;ExcelDataReader是一个轻量级的可快速读取Excel文件中数据的工具。话不多说直接开始。 ExcelDataReader简介 ExcelDataReader支持.xlsx、.xlsb、.xls、.csv格式文件的读取&#xff0c;版本基本在2007及以上版本&#xff0c;支…

《视觉SLAM十四讲》ch13 设计SLAM系统 相机轨迹实现

前言 相信大家在slam学习中&#xff0c;一定会遇到slam系统的性能评估问题。虽然有EVO这样的开源评估工具&#xff0c;我们也需要自己了解系统生成的trajectory.txt的含义&#xff0c;方便我们更好的理解相机的运行跟踪过程。 项目配置如下&#xff1a; 数据解读&#xff1a; …

在类Unix终端中如何实现快速进入新建目录

&#x1f6aa; 前言 相信喜欢使用终端工作的小伙伴或多或少会被一个小地方给膈应&#xff0c;那就是每次想要新建一个文件夹并且进入之&#xff0c;那么就需要两条指令&#xff1a;mkdir DIR和cd DIR&#xff0c;有些人可能要杠了&#xff0c;我一条指令也能&#xff0c;mkdir…

TG电报群管理机器人定制开发的重要性

在Telegram&#xff08;电报&#xff09;用户突破20亿、中文社群规模持续扩张的背景下&#xff0c;定制化群管理机器人的开发已成为社群运营的战略刚需。这种技术工具不仅解决了海量用户管理的效率难题&#xff0c;更通过智能化功能重构了数字社群的治理范式。本文从管理效能、…