一、 前期准备
1. 硬件链路
- NMC-G2 网卡插入山特 UPS 智能卡槽,网线一头插网卡网口,一头插电脑 / 交换机;
- UPS 开机市电正常供电,网卡指示灯绿灯常亮。
2. 获取网卡 IP
- 打开官网下载的
NMC-utility-tool-1.0.4_Windows.zip工具;(下载过程查看NMC-G2网络管理卡 资料官网下载流程-CSDN博客) - 工具自动扫描局域网,列表里会显示 NMC-G2 的 IP(例:192.168.0.50),记下来。
- 设置电脑和网卡处于同一局域网网段。
3. 网卡后台开启 Modbus TCP 服务
- 浏览器输入网卡 IP,登录 Web 管理页(默认账号 admin/admin);
- 左侧菜单:设置 → 通信协议;
- 勾选开启
Modbus TCP,确认端口 502、从站 ID=1,保存; - 固件低于 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++112. 核心原理回顾(对应你的协议截图)
NMC-G2 把 UPS 所有数据存在保持寄存器(功能码 03 读取):
- 0x0000~0x004F:文本信息(型号、序列号,1 个寄存器存 2 个 ASCII 字符)
- 0x0060~0x0065:告警故障位掩码
- 0x0070:UPS 运行模式(0 市电 / 1 电池 / 2 旁路 / 3 故障)
- 0x0080 起:电压、负载、电池容量(数值 ÷10 等于真实值)
3. 通信逻辑说明
- Qt 程序 = Modbus 客户端(主站),NMC-G2=Modbus 服务端(从站 ID=1,端口 502);
- 所有 UPS 状态存储在保持寄存器,使用功能码 03 批量读取;
- 数据分两类解析规则:
- 文本(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 个寄存器:读电压发一次请求、读负载再发一次请求,来回频繁通信,卡顿、效率低;
- 批量读取:一次网络请求,告诉网卡「从地址 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