在Qt中使用MQTT(二)
在上一篇中,我们简单使用了MQTT,现在我们做一个更复杂一点的程序。
一、配置项目
1.创建项目DeviceMonitor,并且配置CMakeList.txt,如下图:


项目中使用了Charts模块和Mqtt模块,需要加载进来。
二、使用MQTT
项目代码如下:
MainWindow.h
c++
#ifndef DEVICEMONITOR_H
#define DEVICEMONITOR_H
#include <QMainWindow>
#include <QMqttClient>
#include <QMap>
#include <QDateTime>
#include <QMqttSubscription>
#include <QMqttTopicFilter>
#include <QValueAxis>
QT_BEGIN_NAMESPACE
class QTableWidget;
class QPushButton;
class QTextEdit;
class QLineEdit;
class QLabel;
class QProgressBar;
class QTimer;
class QChart;
class QChartView;
class QLineSeries;
QT_END_NAMESPACE
// 设备数据结构
struct DeviceInfo {
QString id; // 设备ID
QString name; // 设备名称
QString status; // 在线/离线/运行中/故障
double temperature; // 温度
double humidity; // 湿度
double voltage; // 电压
QDateTime lastSeen; // 最后通信时间
int alertCount; // 告警次数
};
class DeviceMonitor : public QMainWindow {
Q_OBJECT
public:
DeviceMonitor(QWidget *parent = nullptr);
~DeviceMonitor();
private slots:
void onConnectClicked();
void onDisconnectClicked();
void onMqttConnected();
void onMqttDisconnected();
void onMessageReceived(const QByteArray &message, const QMqttTopicName &topic);
void onCheckDevicesTimeout(); // 定时检查设备离线
void onClearAlertsClicked(); // 清除告警
void onExportLogClicked(); // 导出日志
private:
void setupUI();
void setupStyle();
void parseDeviceData(const QString &deviceId, const QJsonObject &json);
void checkThresholds(const QString &deviceId, const DeviceInfo &device);
void addAlert(const QString &deviceId, const QString &alertType, const QString &message);
void updateDeviceTable();
void updateStatusBar();
void updateChart(double temperature, double humidity);
void autoScaleYAxis();
QString getStatusColor(const QString &status);
// MQTT
QMqttClient *m_client;
bool m_connected = false;
// 设备数据
QMap<QString, DeviceInfo> m_devices;
QStringList m_alertHistory;
// UI 控件
QLineEdit *m_hostEdit;
QLineEdit *m_portEdit;
QPushButton *m_connectBtn;
QPushButton *m_disconnectBtn;
QLabel *m_statusLabel;
// 设备表格
QTableWidget *m_deviceTable;
// 实时曲线
QChart *m_chart;
QChartView *m_chartView;
QLineSeries *m_tempSeries;
QLineSeries *m_humiditySeries;
// 告警区域
QTextEdit *m_alertLog;
QPushButton *m_clearAlertBtn;
QPushButton *m_exportBtn;
QLabel *m_alertCountLabel;
// 统计标签
QLabel *m_totalDeviceLabel;
QLabel *m_onlineLabel;
QLabel *m_offlineLabel;
QLabel *m_faultLabel;
// 定时器
QTimer *m_checkTimer;
// 曲线数据管理
static int m_timeCounter; // 时间计数器(静态,跨消息保持)
static const int MAX_POINTS = 100; // 最大保留点数
QValueAxis *m_axisX = nullptr;
QValueAxis *m_axisY = nullptr;
};
#endif
MainWindow.cpp
C++
#include "mainwindow.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QPushButton>
#include <QLineEdit>
#include <QLabel>
#include <QProgressBar>
#include <QTextEdit>
#include <QGroupBox>
#include <QHeaderView>
#include <QMessageBox>
#include <QFileDialog>
#include <QFile>
#include <QTextStream>
#include <QTimer>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QDateTime>
#include <QGraphicsDropShadowEffect>
#include <QtCharts/QChart>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
#include <QApplication>
int DeviceMonitor::m_timeCounter = 0;
const int DeviceMonitor::MAX_POINTS;
DeviceMonitor::DeviceMonitor(QWidget *parent)
: QMainWindow(parent)
, m_client(new QMqttClient(this))
{
setupUI();
setupStyle();
// MQTT 信号连接
connect(m_client, &QMqttClient::connected, this, &DeviceMonitor::onMqttConnected);
connect(m_client, &QMqttClient::disconnected, this, &DeviceMonitor::onMqttDisconnected);
connect(m_client, &QMqttClient::messageReceived, this, &DeviceMonitor::onMessageReceived);
// 定时检查设备离线(每30秒)
m_checkTimer = new QTimer(this);
connect(m_checkTimer, &QTimer::timeout, this, &DeviceMonitor::onCheckDevicesTimeout);
}
DeviceMonitor::~DeviceMonitor() = default;
void DeviceMonitor::setupUI() {
auto *central = new QWidget(this);
auto *mainLayout = new QVBoxLayout(central);
mainLayout->setSpacing(16);
mainLayout->setContentsMargins(20, 20, 20, 20);
// ==================== 顶部连接栏 ====================
auto *connGroup = new QGroupBox("MQTT 服务器连接", this);
auto *connLayout = new QHBoxLayout(connGroup);
m_hostEdit = new QLineEdit("broker.hivemq.com", this);
m_portEdit = new QLineEdit("1883", this);
m_portEdit->setMaximumWidth(80);
m_connectBtn = new QPushButton("🔌 连接", this);
m_disconnectBtn = new QPushButton("⏹ 断开", this);
m_disconnectBtn->setEnabled(false);
m_statusLabel = new QLabel("● 未连接", this);
m_statusLabel->setStyleSheet("color: #b2bec3; font-weight: bold;");
connLayout->addWidget(new QLabel("服务器:"));
connLayout->addWidget(m_hostEdit);
connLayout->addWidget(new QLabel("端口:"));
connLayout->addWidget(m_portEdit);
connLayout->addWidget(m_connectBtn);
connLayout->addWidget(m_disconnectBtn);
connLayout->addStretch();
connLayout->addWidget(m_statusLabel);
// ==================== 统计卡片 ====================
auto *statsLayout = new QHBoxLayout();
auto createStatCard = [&](const QString &title, const QString &color) -> QLabel* {
auto *card = new QWidget(this);
card->setStyleSheet(QString(R"(
QWidget {
background-color: #ffffff;
border-radius: 10px;
border-left: 4px solid %1;
}
)").arg(color));
auto *effect = new QGraphicsDropShadowEffect(card);
effect->setBlurRadius(15);
effect->setColor(QColor(0,0,0,25));
effect->setOffset(0, 3);
card->setGraphicsEffect(effect);
auto *layout = new QVBoxLayout(card);
auto *titleLabel = new QLabel(title, this);
titleLabel->setStyleSheet("color: #636e72; font-size: 11px;");
auto *valueLabel = new QLabel("0", this);
valueLabel->setStyleSheet("color: #2d3436; font-size: 24px; font-weight: bold;");
layout->addWidget(titleLabel);
layout->addWidget(valueLabel);
layout->setContentsMargins(16, 12, 16, 12);
statsLayout->addWidget(card);
return valueLabel;
};
m_totalDeviceLabel = createStatCard("设备总数", "#0984e3");
m_onlineLabel = createStatCard("在线", "#00b894");
m_offlineLabel = createStatCard("离线", "#fdcb6e");
m_faultLabel = createStatCard("故障", "#d63031");
// ==================== 中部:设备表格 + 实时曲线 ====================
auto *midLayout = new QHBoxLayout();
// 左侧设备表格
auto *tableGroup = new QGroupBox("设备状态监控", this);
auto *tableLayout = new QVBoxLayout(tableGroup);
m_deviceTable = new QTableWidget(this);
m_deviceTable->setColumnCount(7);
m_deviceTable->setHorizontalHeaderLabels(
QStringList() << "设备ID" << "名称" << "状态" << "温度(°C)" << "湿度(%)" << "电压(V)" << "最后通信");
m_deviceTable->horizontalHeader()->setStretchLastSection(true);
m_deviceTable->setSelectionBehavior(QAbstractItemView::SelectRows);
m_deviceTable->setAlternatingRowColors(true);
m_deviceTable->setMinimumWidth(600);
tableLayout->addWidget(m_deviceTable);
// 右侧实时曲线
auto *chartGroup = new QGroupBox("实时数据曲线", this);
auto *chartLayout = new QVBoxLayout(chartGroup);
m_chart = new QChart();
m_chart->setTitle("温度 / 湿度 趋势");
m_chart->legend()->setVisible(true);
m_tempSeries = new QLineSeries();
m_tempSeries->setName("温度 °C");
m_humiditySeries = new QLineSeries();
m_humiditySeries->setName("湿度 %");
m_chart->addSeries(m_tempSeries);
m_chart->addSeries(m_humiditySeries);
m_axisX = new QValueAxis();
m_axisX->setTitleText("时间");
m_axisX->setLabelFormat("%d");
m_axisX->setRange(0, MAX_POINTS);
m_axisX->setTickCount(11);
m_axisY = new QValueAxis();
m_axisY->setTitleText("数值");
m_axisY->setRange(0, 100);
m_axisY->setTickCount(11);
m_chart->addAxis(m_axisX, Qt::AlignBottom);
m_chart->addAxis(m_axisY, Qt::AlignLeft);
m_tempSeries->attachAxis(m_axisX);
m_tempSeries->attachAxis(m_axisY);
m_humiditySeries->attachAxis(m_axisX);
m_humiditySeries->attachAxis(m_axisY);
m_chartView = new QChartView(m_chart);
m_chartView->setRenderHint(QPainter::Antialiasing);
m_chartView->setMinimumWidth(400);
chartLayout->addWidget(m_chartView);
midLayout->addWidget(tableGroup, 2);
midLayout->addWidget(chartGroup, 1);
// ==================== 底部告警区域 ====================
auto *alertGroup = new QGroupBox("故障预警中心", this);
auto *alertLayout = new QVBoxLayout(alertGroup);
auto *alertBtnLayout = new QHBoxLayout();
m_alertCountLabel = new QLabel("当前告警: 0", this);
m_alertCountLabel->setStyleSheet("color: #d63031; font-weight: bold;");
m_clearAlertBtn = new QPushButton("🗑 清除告警", this);
m_exportBtn = new QPushButton("📄 导出日志", this);
alertBtnLayout->addWidget(m_alertCountLabel);
alertBtnLayout->addStretch();
alertBtnLayout->addWidget(m_clearAlertBtn);
alertBtnLayout->addWidget(m_exportBtn);
m_alertLog = new QTextEdit(this);
m_alertLog->setReadOnly(true);
m_alertLog->setMaximumHeight(150);
alertLayout->addLayout(alertBtnLayout);
alertLayout->addWidget(m_alertLog);
// 组装主布局
mainLayout->addWidget(connGroup);
mainLayout->addLayout(statsLayout);
mainLayout->addLayout(midLayout, 1);
mainLayout->addWidget(alertGroup);
setCentralWidget(central);
setWindowTitle("🔧 工业设备监控与故障预警系统");
resize(1200, 800);
// 信号连接
connect(m_connectBtn, &QPushButton::clicked, this, &DeviceMonitor::onConnectClicked);
connect(m_disconnectBtn, &QPushButton::clicked, this, &DeviceMonitor::onDisconnectClicked);
connect(m_clearAlertBtn, &QPushButton::clicked, this, &DeviceMonitor::onClearAlertsClicked);
connect(m_exportBtn, &QPushButton::clicked, this, &DeviceMonitor::onExportLogClicked);
}
void DeviceMonitor::setupStyle()
{
QString style = R"(
QWidget {
font-family: "Microsoft YaHei UI";
font-size: 9pt;
}
QMainWindow {
background-color: #f5f6fa;
}
QGroupBox {
font-weight: bold;
border: 1px solid #dcdde1;
border-radius: 8px;
margin-top: 10px;
padding-top: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px;
color: #2d3436;
}
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #667eea, stop:1 #764ba2);
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-weight: bold;
min-width: 80px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #5a6fd6, stop:1 #6a4190);
}
QPushButton:pressed {
padding: 9px 16px 7px 16px;
}
QPushButton:disabled {
background: #b2bec3;
}
QLineEdit {
background: #ffffff;
border: 1px solid #dcdde1;
border-radius: 6px;
padding: 6px 10px;
}
QLineEdit:focus {
border: 2px solid #667eea;
}
QTableWidget {
background: #ffffff;
border: 1px solid #dcdde1;
border-radius: 8px;
gridline-color: #f1f2f6;
}
QTableWidget::item:selected {
background: #74b9ff;
color: white;
}
QHeaderView::section {
background: #f1f2f6;
padding: 8px;
border: none;
font-weight: bold;
color: #2d3436;
}
QTextEdit {
background: #ffffff;
border: 1px solid #dcdde1;
border-radius: 8px;
padding: 8px;
}
QLabel {
color: #2d3436;
}
)";
qApp->setStyleSheet(style);
}
// ==================== MQTT 连接 ====================
void DeviceMonitor::onConnectClicked() {
m_client->setHostname(m_hostEdit->text());
m_client->setPort(m_portEdit->text().toInt());
m_client->connectToHost();
m_statusLabel->setText("● 连接中...");
m_statusLabel->setStyleSheet("color: #fdcb6e; font-weight: bold;");
}
void DeviceMonitor::onDisconnectClicked() {
m_client->disconnectFromHost();
}
void DeviceMonitor::onMqttConnected() {
m_connected = true;
m_statusLabel->setText("● 已连接");
m_statusLabel->setStyleSheet("color: #00b894; font-weight: bold;");
m_connectBtn->setEnabled(false);
m_disconnectBtn->setEnabled(true);
auto sub1 = m_client->subscribe(QMqttTopicFilter("factory/device/+/data"));
auto sub2 = m_client->subscribe(QMqttTopicFilter("factory/device/+/status"));
qDebug() << "订阅 data 结果:" << (sub1 ? "成功" : "失败");
qDebug() << "订阅 status 结果:" << (sub2 ? "成功" : "失败");
m_alertLog->append(QString("[%1] ✅ MQTT 连接成功,开始监听设备...")
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")));
m_checkTimer->start(30000);
}
void DeviceMonitor::onMqttDisconnected() {
m_connected = false;
m_statusLabel->setText("● 未连接");
m_statusLabel->setStyleSheet("color: #d63031; font-weight: bold;");
m_connectBtn->setEnabled(true);
m_disconnectBtn->setEnabled(false);
m_checkTimer->stop();
}
void DeviceMonitor::onMessageReceived(const QByteArray &message, const QMqttTopicName &topic)
{
QString topicStr = topic.name();
QString payload = QString::fromUtf8(message);
// 显示到日志
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
m_alertLog->append(QString("[%1] 📩 [%2]").arg(timestamp, topicStr));
// 解析 JSON
QJsonDocument doc = QJsonDocument::fromJson(message);
if (!doc.isObject()) {
m_alertLog->append("❌ JSON 解析失败");
return;
}
QJsonObject json = doc.object();
// 提取设备ID(兼容多种 topic 格式)
QString deviceId = "unknown";
QStringList parts = topicStr.split('/', Qt::SkipEmptyParts);
if (parts.size() >= 3) {
deviceId = parts[2]; // factory/device/D001/data → D001
}
// 创建设备信息(统一处理,不区分 status/data)
DeviceInfo device;
device.id = deviceId;
device.name = json.value("name").toString(deviceId);
device.status = json.value("status").toString("unknown");
device.temperature = json.value("temperature").toDouble(0);
device.humidity = json.value("humidity").toDouble(0);
device.voltage = json.value("voltage").toDouble(0);
device.lastSeen = QDateTime::currentDateTime();
device.alertCount = m_devices.contains(deviceId) ? m_devices[deviceId].alertCount : 0;
// 保存到设备列表
m_devices[deviceId] = device;
// 更新界面
updateChart(device.temperature, device.humidity);
updateDeviceTable();
updateStatusBar();
checkThresholds(deviceId, device);
// 更新曲线
static int timeCounter = 0;
timeCounter++;
m_tempSeries->append(timeCounter, device.temperature);
m_humiditySeries->append(timeCounter, device.humidity);
if (m_tempSeries->count() > 50) {
m_tempSeries->remove(0);
m_humiditySeries->remove(0);
}
if (m_chart->axisX()) {
m_chart->axisX()->setRange(qMax(0, timeCounter - 50), timeCounter);
}
}
// ==================== 阈值检查(故障预警) ====================
void DeviceMonitor::checkThresholds(const QString &deviceId, const DeviceInfo &device) {
QStringList alerts;
// 温度阈值: > 80°C 高温告警, > 100°C 严重故障
if (device.temperature > 100) {
alerts << QString("🔥 【严重】设备 %1 温度异常: %2°C(超过100°C)")
.arg(device.name).arg(device.temperature);
} else if (device.temperature > 80) {
alerts << QString("⚠️ 【警告】设备 %1 温度过高: %2°C")
.arg(device.name).arg(device.temperature);
}
// 湿度阈值: > 90% 高湿告警
if (device.humidity > 90) {
alerts << QString("💧 【警告】设备 %1 湿度过高: %2%")
.arg(device.name).arg(device.humidity);
}
// 电压阈值: < 200V 欠压, > 250V 过压
if (device.voltage < 200 && device.voltage > 0) {
alerts << QString("🔋 【警告】设备 %1 电压过低: %2V")
.arg(device.name).arg(device.voltage);
} else if (device.voltage > 250) {
alerts << QString("⚡ 【警告】设备 %1 电压过高: %2V")
.arg(device.name).arg(device.voltage);
}
// 状态故障
if (device.status == "fault" || device.status == "error") {
alerts << QString("❌ 【故障】设备 %1 状态异常: %2")
.arg(device.name).arg(device.status);
}
for (const QString &alert : alerts) {
addAlert(deviceId, "threshold", alert);
}
}
void DeviceMonitor::addAlert(const QString &deviceId, const QString &alertType, const QString &message) {
QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
QString fullMsg = QString("[%1] %2").arg(timestamp, message);
// 避免重复告警(同一设备同一类型5分钟内只报一次)
static QMap<QString, QDateTime> lastAlertTime;
QString key = deviceId + "_" + alertType;
if (lastAlertTime.contains(key) &&
lastAlertTime[key].secsTo(QDateTime::currentDateTime()) < 300) {
return;
}
lastAlertTime[key] = QDateTime::currentDateTime();
m_alertHistory.append(fullMsg);
// 红色显示告警
m_alertLog->append(QString("<span style='color:#d63031; font-weight:bold;'>%1</span>").arg(fullMsg));
// 更新告警计数
if (m_devices.contains(deviceId)) {
m_devices[deviceId].alertCount++;
}
m_alertCountLabel->setText(QString("当前告警: %1").arg(m_alertHistory.size()));
// 严重告警弹窗
if (alertType == "threshold" && message.contains("【严重】")) {
QMessageBox::critical(this, "🚨 严重故障告警", message);
}
}
// ==================== 设备离线检查 ====================
void DeviceMonitor::onCheckDevicesTimeout() {
QDateTime now = QDateTime::currentDateTime();
for (auto it = m_devices.begin(); it != m_devices.end(); ++it) {
if (it.value().lastSeen.secsTo(now) > 60) { // 60秒无数据视为离线
if (it.value().status != "offline") {
it.value().status = "offline";
addAlert(it.key(), "offline",
QString("📡 设备 %1 已离线(超过60秒无通信)").arg(it.value().name));
}
}
}
updateDeviceTable();
updateStatusBar();
}
// ==================== UI 更新 ====================
void DeviceMonitor::updateDeviceTable() {
m_deviceTable->setRowCount(m_devices.size());
int row = 0;
for (const auto &device : m_devices) {
m_deviceTable->setItem(row, 0, new QTableWidgetItem(device.id));
m_deviceTable->setItem(row, 1, new QTableWidgetItem(device.name));
auto *statusItem = new QTableWidgetItem(device.status.toUpper());
statusItem->setForeground(QColor(getStatusColor(device.status)));
statusItem->setFont(QFont("Microsoft YaHei UI", 9, QFont::Bold));
m_deviceTable->setItem(row, 2, statusItem);
m_deviceTable->setItem(row, 3, new QTableWidgetItem(QString::number(device.temperature, 'f', 1)));
m_deviceTable->setItem(row, 4, new QTableWidgetItem(QString::number(device.humidity, 'f', 1)));
m_deviceTable->setItem(row, 5, new QTableWidgetItem(QString::number(device.voltage, 'f', 1)));
m_deviceTable->setItem(row, 6, new QTableWidgetItem(device.lastSeen.toString("hh:mm:ss")));
row++;
}
}
void DeviceMonitor::updateStatusBar() {
int total = m_devices.size();
int online = 0, offline = 0, fault = 0;
for (const auto &device : m_devices) {
if (device.status == "online" || device.status == "running") online++;
else if (device.status == "offline") offline++;
else if (device.status == "fault" || device.status == "error") fault++;
}
m_totalDeviceLabel->setText(QString::number(total));
m_onlineLabel->setText(QString::number(online));
m_offlineLabel->setText(QString::number(offline));
m_faultLabel->setText(QString::number(fault));
}
QString DeviceMonitor::getStatusColor(const QString &status) {
if (status == "online" || status == "running") return "#00b894";
if (status == "offline") return "#fdcb6e";
if (status == "fault" || status == "error") return "#d63031";
return "#636e72";
}
// ==================== 按钮功能 ====================
void DeviceMonitor::onClearAlertsClicked() {
m_alertHistory.clear();
m_alertLog->clear();
m_alertCountLabel->setText("当前告警: 0");
for (auto &device : m_devices) {
device.alertCount = 0;
}
}
void DeviceMonitor::onExportLogClicked() {
QString fileName = QFileDialog::getSaveFileName(this, "导出告警日志",
QString("alert_log_%1.txt").arg(QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss")),
"文本文件 (*.txt)");
if (fileName.isEmpty()) return;
QFile file(fileName);
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream stream(&file);
stream << "===== 设备监控告警日志 =====\n";
stream << "导出时间: " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "\n";
stream << "设备总数: " << m_devices.size() << "\n\n";
for (const QString &log : m_alertHistory) {
stream << log << "\n";
}
file.close();
QMessageBox::information(this, "导出成功", "告警日志已保存到:\n" + fileName);
}
}
void DeviceMonitor::updateChart(double temperature, double humidity) {
m_timeCounter++;
// 添加新数据点
m_tempSeries->append(m_timeCounter, temperature);
m_humiditySeries->append(m_timeCounter, humidity);
// 限制最大点数,移除旧数据(滑动窗口效果)
if (m_tempSeries->count() > MAX_POINTS) {
m_tempSeries->remove(0);
m_humiditySeries->remove(0);
// 移除后需要重新设置 X 轴范围,保持滑动效果
int startX = m_timeCounter - MAX_POINTS + 1;
m_axisX->setRange(startX, m_timeCounter);
} else {
// 数据点未满时,X 轴跟随数据增长
m_axisX->setRange(0, qMax(MAX_POINTS, m_timeCounter));
}
// 动态调整 Y 轴范围(自适应)
autoScaleYAxis();
}
void DeviceMonitor::autoScaleYAxis() {
if (m_tempSeries->count() == 0) return;
// 找出所有数据点的最小/最大值
double minY = 9999, maxY = -9999;
for (int i = 0; i < m_tempSeries->count(); i++) {
double temp = m_tempSeries->at(i).y();
double hum = m_humiditySeries->at(i).y();
minY = qMin(minY, qMin(temp, hum));
maxY = qMax(maxY, qMax(temp, hum));
}
// 增加 10% 的边距,避免数据贴边
double margin = (maxY - minY) * 0.1;
if (margin < 5) margin = 5; // 最小边距 5
double lower = qMax(0.0, minY - margin);
double upper = maxY + margin;
// 平滑过渡动画(可选)
m_axisY->setRange(lower, upper);
}
三、项目代码说明
我们使用免费公共 MQTT 测试服务器:broker.hivemq.com ,端口号是1883,相似的网站如下:
常用公共 MQTT Broker 汇总
| 服务商 | 地址 | TCP 端口 | TLS 端口 | WebSocket | 特点 | 推荐度 |
|---|---|---|---|---|---|---|
| EMQX (全球) | broker.emqx.io |
1883 | 8883 | 8083/8084 | 国内访问较快,支持 MQTT 5.0/3.1.1,多区域集群 | ⭐⭐⭐⭐⭐ |
| EMQX (国内) | broker-cn.emqx.io |
1883 | 8883 | 8083/8084 | 腾讯云上海节点,国内延迟最低 (~164ms) | ⭐⭐⭐⭐⭐ |
| Eclipse Mosquitto | mqtt.eclipseprojects.io |
1883 | 8883 | 80/443 | 老牌 Eclipse 项目,稳定性好 | ⭐⭐⭐⭐ |
| Mosquitto 社区 | test.mosquitto.org |
1883 | 8883/8884 | 80/443 | 端口选择多,但延迟较高 (~378ms) | ⭐⭐⭐ |
| HiveMQ | broker.hivemq.com |
1883 | --- | 8000 | 你正在用的,德国节点,延迟中等 (~252ms) | ⭐⭐⭐⭐ |
| Bevywise | broker.bevywise.com |
1883 | --- | 10443 | 支持 MQTT 5.0/3.1.1 | ⭐⭐⭐ |
| Coreflux | iot.coreflux.cloud |
1883 | 8883 | 5000/443 | 无需注册,快速测试 | ⭐⭐⭐ |
| MaQiaTTo | maqiatto.com |
1883 | --- | --- | 完全免费 |
然后我们使用MQTTX软件来测试,MQTTX下载地址如下:
项目是由AI生成的,涉及比较多的模块,后面我也会慢慢学习。
| 端口选择多,但延迟较高 (~378ms) | ⭐⭐⭐ |
| HiveMQ | broker.hivemq.com | 1883 | --- | 8000 | 你正在用的,德国节点,延迟中等 (~252ms) | ⭐⭐⭐⭐ |
| Bevywise | broker.bevywise.com | 1883 | --- | 10443 | 支持 MQTT 5.0/3.1.1 | ⭐⭐⭐ |
| Coreflux | iot.coreflux.cloud | 1883 | 8883 | 5000/443 | 无需注册,快速测试 | ⭐⭐⭐ |
| MaQiaTTo | maqiatto.com | 1883 | --- | --- | 完全免费 | |
然后我们使用MQTTX软件来测试,MQTTX下载地址如下:
具体测试效果如下:
外链图片转存中...(img-L4LTvXQr-1782564681823)
项目是由AI生成的,涉及比较多的模块,后面我也会慢慢学习。