当前位置: 首页 > news >正文

Qt 通过 NMC-G2 读取 UPS 状态

一、 前期准备

1. 硬件链路

  1. NMC-G2 网卡插入山特 UPS 智能卡槽,网线一头插网卡网口,一头插电脑 / 交换机;
  2. UPS 开机市电正常供电,网卡指示灯绿灯常亮。

2. 获取网卡 IP

  1. 打开官网下载的NMC-utility-tool-1.0.4_Windows.zip工具;(下载过程查看NMC-G2网络管理卡 资料官网下载流程-CSDN博客)
  2. 工具自动扫描局域网,列表里会显示 NMC-G2 的 IP(例:192.168.0.50),记下来。
  3. 设置电脑和网卡处于同一局域网网段。

3. 网卡后台开启 Modbus TCP 服务

  1. 浏览器输入网卡 IP,登录 Web 管理页(默认账号 admin/admin);
  2. 左侧菜单:设置 → 通信协议
  3. 勾选开启Modbus TCP,确认端口 502、从站 ID=1,保存;
  4. 固件低于 2.4.0-r0:上传红色 tar 固件包( (SANTAK_NMCG2_updatePackage-2.4.0-r0.tar下载过程查看NMC-G2网络管理卡 资料官网下载流程-CSDN博客))升级,否则寄存器数据不全。

二、Qt 工程基础配置

1. .pro 文件添加 Modbus 模块

QT += core gui widgets modbus network CONFIG += c++11

2. 核心原理回顾(对应你的协议截图)

NMC-G2 把 UPS 所有数据存在保持寄存器(功能码 03 读取)

  1. 0x0000~0x004F:文本信息(型号、序列号,1 个寄存器存 2 个 ASCII 字符)
  2. 0x0060~0x0065:告警故障位掩码
  3. 0x0070:UPS 运行模式(0 市电 / 1 电池 / 2 旁路 / 3 故障)
  4. 0x0080 起:电压、负载、电池容量(数值 ÷10 等于真实值)

3. 通信逻辑说明

  1. Qt 程序 = Modbus 客户端(主站),NMC-G2=Modbus 服务端(从站 ID=1,端口 502);
  2. 所有 UPS 状态存储在保持寄存器,使用功能码 03 批量读取;
  3. 数据分两类解析规则:
    • 文本(UPS 型号):1 个寄存器 = 2 个 ASCII 字符,高字节在前,读到 0 终止;
    • 数值(电压 / 负载):寄存器数值 ÷10 得到真实值;
    • 运行模式:寄存器 0x0070,0 = 市电、1 = 电池、2 = 旁路、3 = 故障;
    • 告警:寄存器 0x0060,二进制位标记各类故障。

三、完整可运行 Qt 代码(直接复制使用)

头文件 mainwindow.h

#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QModbusTcpClient> #include <QTimer> class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: QModbusTcpClient* m_modbus; QTimer* m_pollTimer; // 解析寄存器ASCII字符串(读取UPS型号专用) QString parseStr(const QVector<quint16>& regs, int startAddr, int wordLen); // 定时读取UPS全部寄存器 void readUpsAllData(); // Modbus数据接收回调,解析打印所有状态 void onDataRecv(QModbusReply* reply); }; #endif

实现文件 mainwindow.cpp

#include "mainwindow.h" #include <QModbusDataUnit> #include <QModbusReply> #include <QDebug> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), m_modbus(new QModbusTcpClient(this)), m_pollTimer(new QTimer(this)) { // ========== 这里改成你工具扫描到的NMC-G2网卡IP ========== QString cardIp = "192.168.0.50"; m_modbus->setConnectionParameter(QModbusDevice::NetworkAddressParameter, cardIp); m_modbus->setConnectionParameter(QModbusDevice::NetworkPortParameter, 502); // 绑定信号 connect(m_pollTimer, &QTimer::timeout, this, &MainWindow::readUpsAllData); connect(m_modbus, &QModbusTcpClient::errorOccurred, this, [=](){ qDebug() << "Modbus通信错误:" << m_modbus->errorString(); }); // 连接网卡 if(m_modbus->connectDevice()) { qDebug() << "成功连接NMC-G2网卡,开始轮询UPS状态"; m_pollTimer->start(2000); // 2秒读取一次实时状态 } else { qDebug() << "网卡连接失败,请检查IP和网线"; } } MainWindow::~MainWindow() { m_pollTimer->stop(); if(m_modbus->isConnected()) m_modbus->disconnectDevice(); } // 按照协议文档规则,解析寄存器里的型号、序列号字符串 QString MainWindow::parseStr(const QVector<quint16>& regs, int startAddr, int wordLen) { QString result; for(int i=0; i<wordLen; i++) { int regIndex = startAddr + i; if(regIndex >= regs.size()) break; quint16 word = regs[regIndex]; // 拆分高低字节,文档规定高字节在前 char high = (word >> 8) & 0xFF; char low = word & 0xFF; if(high == 0) break; result.append(QChar(high)); if(low == 0) break; result.append(QChar(low)); } return result; } // 发起读取请求:一次性读取0 ~ 0x90所有寄存器,覆盖全部UPS状态 void MainWindow::readUpsAllData() { // 读取保持寄存器:起始地址0,读取144个Word QModbusDataUnit readCmd(QModbusDataUnit::HoldingRegisters, 0, 0x90); QModbusReply* reply = m_modbus->sendReadRequest(readCmd, 1); // 从站ID固定1 if(!reply) return; if(!reply->isFinished()) connect(reply, &QModbusReply::finished, this, &MainWindow::onDataRecv); else reply->deleteLater(); } // 收到网卡返回数据,解析UPS各类状态 void MainWindow::onDataRecv(QModbusReply *reply) { if(reply->error() != QModbusDevice::NoError) { qDebug() << "读取寄存器失败:" << reply->errorString(); reply->deleteLater(); return; } QVector<quint16> regList = reply->result().values(); // 1. 读取UPS型号(0x0010起始,32个Word) QString upsModel = parseStr(regList, 0x0010, 32); qDebug() << "【UPS型号】" << upsModel; // 2. 读取运行模式 地址0x0070 quint16 modeCode = regList[0x0070]; QString runMode; switch(modeCode) { case 0: runMode = "市电正常供电"; break; case 1: runMode = "电池放电(已停电)"; break; case 2: runMode = "旁路模式"; break; case 3: runMode = "UPS故障"; break; default: runMode = "未知状态"; } qDebug() << "【运行模式】" << runMode; // 3. 输入市电电压 地址0x0082,数值/10 float inputVol = regList[0x0082] / 10.0f; qDebug() << "【输入电压】" << inputVol << " V"; // 4. 输出负载百分比 地址0x0088,数值/10 float loadRate = regList[0x0088] / 10.0f; qDebug() << "【负载占比】" << loadRate << " %"; // 5. 电池剩余容量 地址0x0090 quint16 batCap = regList[0x0090]; qDebug() << "【电池容量】" << batCap << " %"; // 6. 告警掩码 地址0x0060 quint16 alarm = regList[0x0060]; if(alarm != 0) qDebug() << "【存在告警】告警掩码二进制:" << QString::number(alarm,2); else qDebug() << "【告警状态】无故障"; qDebug() << "---------------------------"; reply->deleteLater(); }

1. 什么是「功能码 03」

Modbus TCP 规定:客户端(你的 Qt 程序)发给网卡(NMC-G2)的指令里,用数字区分你想做什么操作,这个数字就叫功能码

  • 03= 读保持寄存器(你现在读 UPS 电压、型号、负载全部用这个)
  • 其他功能码你暂时用不上:01 读开关、06 写参数、16 批量写参数

简单理解:03= 固定暗号,告诉网卡:我要读取 UPS 存在寄存器里的数据

2. 什么是「批量读取」

网卡里存放 UPS 数据的寄存器有上百个(型号、电压、负载、告警分占不同地址)。 有两种读取方式:

  1. 单次只读 1 个寄存器:读电压发一次请求、读负载再发一次请求,来回频繁通信,卡顿、效率低;
  2. 批量读取:一次网络请求,告诉网卡「从地址 0 开始,连续读 144 个寄存器」,网卡一次性把所有数据打包返回给 Qt。 只收发 1 次报文,就能拿到全部 UPS 状态,这就是批量读取。

main.cpp

#include <QApplication> #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }

四、运行后输出示例

【UPS型号】SANTAK C10K 【运行模式】市电正常供电 【输入电压】225.4 V 【负载占比】32.0 % 【电池容量】98 % 【告警状态】无故障 --------------------------- 【UPS型号】SANTAK C10K 【运行模式】电池放电(已停电) 【输入电压】0.0 V 【负载占比】32.0 % 【电池容量】96 % 【存在告警】告警掩码二进制:1 ---------------------------

五,编译问题

报错含义

Qt 找不到modbus模块,工程无法识别 Modbus 相关类(QModbusTcpClient等),编译直接告警失败。

1. 进入目录,运行MaintenanceTool.exe

打开文件夹:C:\Qt\Qt5.12.12目录里直接存在MaintenanceTool.exe,双击即可启动。

2,选择「添加或移除组件」,进入对应版本Qt 5.12.12

3,展开MSVC 2017 64-bit,勾选Qt Modbus模块

4,点击下一步完成安装,重启 Qt Creator

http://www.cnnetsun.cn/news/3457526.html

相关文章:

  • 3分钟掌握Forza Mods AIO:极限竞速地平线游戏修改的终极指南
  • cann/asc-devkit TQue分配Tensor函数
  • AudioShare音频共享解决方案:打破设备壁垒,实现跨平台音频同步传输
  • Akamai Cloud Manager完全指南:从入门到精通Linode服务管理
  • 终极指南:如何用Linkinator快速检测和修复网站死链
  • CANN/Ascend C SIMT-API FP8转换函数
  • 贾子的相变:当历史的筛选机制遇见认知的临界点
  • BetterDB Monitor Webhook配置:实时通知与自动化工作流集成
  • Material Combiner:Blender材质合并与UV修复的技术解决方案
  • Marge-bot 高级功能探索:Commit Trailers、审核者模拟和合并限制
  • 用 AI 藏 AI 痕迹:Academic Humanizer 掀学界论战,AI 智能落地有了新解法
  • 暂存区是不是叫stage? 工作区叫啥?
  • Go-Queryset错误处理与调试:常见问题排查与解决方案
  • Nintendo Switch帧率解锁终极指南:FPSLocker完全掌握手册
  • Smart 265视频编码技术解析与安防监控应用
  • 高效音频转字幕终极指南:AsrTools智能语音识别方案
  • JODConverter终极指南:5个技巧实现Java文档格式互转高效集成
  • 如何用node-persist实现服务器端localStorage:5分钟快速上手教程
  • Servest JSX服务端渲染:如何在Deno中实现现代化Web应用
  • Scala Scraper DSL高级特性:Option、Either与Functor的优雅运用
  • AI 生成组件的自动化回归测试:快照对比与交互回放的工程方案
  • TBAnnotationClustering社区与支持:如何贡献与获取帮助的终极指南
  • 17个Obsidian核心插件模板:快速搭建个人知识管理系统
  • WaveTools鸣潮性能调校完整解决方案:从诊断到优化的全流程指南
  • 收藏!2026年普通人也能抓住的AI就业5条赛道,速看!
  • 如何快速解决音频标签问题:Tag Editor完整使用指南与实用技巧
  • CANN/asc-devkit TQueBind分配Tensor
  • Cursor+FastAPI+Docker后端搭建全栈方案(企业级CI/CD预配置模板首次公开)
  • B站广告跳过插件终极指南:小电视空降助手完全解析
  • UniHacker:Unity开发者的终极跨平台许可证解决方案完整指南