ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Qt代码实战:电力行业通信协议Modbus、101/103/104与61850的解析与实现

Qt代码实战:电力行业通信协议Modbus、101/103/104与61850的解析与实现 1. 电力SCADA上位机多协议接入的真实痛点做电力行业上位机的朋友大概率都遇到过这种局面一个变电站监控项目里Modbus RTU 要读电表IEC 60870-5-104 要对接远动网关103 要接保护装置61850 又要跟智能终端打交道。四种协议、四套报文格式、四种链路行为全塞进一个 Qt 工程里代码很快就变成一团乱麻。我自己在做一个配电房集中监控的时候最初的想法很朴素——每个协议写一个类各自开线程收数据收到就丢给 UI 刷新。结果跑起来问题一堆Modbus 的 CRC 校验偶尔对不上104 的启动帧和测试帧时序错乱103 的固定帧长和可变帧长混在一起解析越界61850 的 MMS 报文更是直接卡在 ASN.1 解码上。最要命的是串口和 TCP 的收发节奏完全不同UI 线程被阻塞到界面直接假死。后来我把架构重新拆了一遍核心思路是协议解析层与传输层彻底解耦每个协议一个独立的会话对象统一通过信号槽把解析结果抛给上层。传输层只负责字节流的收发不关心内容解析层只负责把字节流还原成结构化数据不关心从哪来。这样 Modbus RTU 走 QSerialPort、Modbus TCP 走 QTcpSocket、104 走 QTcpSocket、103 走 QSerialPort、61850 走 QTcpSocket UDP 组播底层传输可以自由替换解析逻辑完全复用。这篇文章就按这个思路把 Qt 工程配置、四种协议的帧解析代码、串口/TCP 联调验证步骤完整走一遍。目标很明确你照着做完能在 Qt 里同时跑通 Modbus、101/103/104 和 61850 的数据采集并在界面上看到实时刷新的遥测遥信值。适合谁看有 C 和 Qt 基础、正在做电力 SCADA 或工业网关上位机、被多协议报文解析折磨过的开发者。如果你只是想知道 Modbus 怎么读寄存器那网上教程很多但如果你要在一个工程里同时搞定这四种协议这篇的架构和代码片段应该能帮你少踩不少坑。先说清楚一件事电力协议规范本身是公开标准但部分商业协议栈有版权约束。我的做法是底层用开源协议栈libmodbus、lib60870、libiec61850做二次封装Qt 层只写业务适配和 UI 展示这样既合规又省力。下面所有代码都是基于这个思路的可运行片段。2. TaoToken 前置配置统一管理多协议接入的模型与密钥等等你可能会问——电力协议解析跟 TaoToken 有什么关系关系在于当你的 Qt 上位机需要接入 AI 辅助做报文异常诊断、协议文档问答、或者把采集数据丢给大模型做趋势分析时你需要一个统一的 API 入口来管理模型调用和密钥。TaoToken 在这里扮演的就是这个角色。我试过的场景是这样的Qt 上位机采集到 104 的 ASDU 报文后如果解析失败可以把原始十六进制报文和错误码发给模型让模型帮忙判断是帧结构问题还是数据类型不匹配。这比翻规范快得多。另外61850 的 SCL 文件解析遇到陌生节点时也可以直接问模型。TaoToken 的接入方式很简单它兼容 OpenAI 风格的 API你只需要一个 Base URL、一个 API Key 和一个 Model ID。对于 Qt 工程来说用 QNetworkAccessManager 发 HTTP 请求就行不需要额外依赖。获取 API Key 的步骤打开 https://taotoken.net/api-keys 注册后创建一个新的 Key。注意这个 Key 只在创建时显示一次复制下来存好。如果你只是做协议解析辅助用按量计费的模型就够不需要开长期套餐。Base URL 配置TaoToken 的 API 地址是https://taotoken.net/api注意不要加 UTM 参数直接用作请求前缀。比如对话接口就是https://taotoken.net/api/v1/chat/completions。Model ID 选择在 https://taotoken.net/models 可以看到可用模型列表。做报文诊断建议选推理能力强的模型做文档问答选上下文长的模型。具体 Model ID 直接复制页面上的字符串填到你的配置里。在 Qt 工程中配置我习惯把配置写在一个独立的 JSON 文件里跟工程一起管理但不要提交到公开仓库。文件路径放在config/taotoken.json{ base_url: https://taotoken.net/api, api_key: sk-你的实际Key, model_id: 你选择的模型ID, timeout_ms: 30000 }然后在 Qt 里用 QJsonDocument 读取#include QJsonDocument #include QJsonObject #include QFile struct TaoTokenConfig { QString baseUrl; QString apiKey; QString modelId; int timeoutMs 30000; }; TaoTokenConfig loadTaoTokenConfig(const QString path) { TaoTokenConfig cfg; QFile file(path); if (!file.open(QIODevice::ReadOnly)) { qWarning() 无法打开配置文件: path; return cfg; } QJsonDocument doc QJsonDocument::fromJson(file.readAll()); QJsonObject obj doc.object(); cfg.baseUrl obj.value(base_url).toString(); cfg.apiKey obj.value(api_key).toString(); cfg.modelId obj.value(model_id).toString(); cfg.timeoutMs obj.value(timeout_ms).toInt(30000); return cfg; }为什么要在 Qt 工程里做这一步因为电力协议解析经常需要查规范、对报文、验证时序。你不可能每次都去翻几百页的 PDF。把模型调用封装成一个ProtocolAssistant类在解析失败时自动把报文和错误上下文发过去几秒钟就能拿到排查建议。这个类后面在排障章节会用到。如果你需要长期跑编码和 Agent 任务比如自动生成协议解析代码、批量处理 SCL 文件可以考虑 Coding Plan入口在 https://taotoken.net/coding-plan 。不过对于大多数上位机项目按量调用就足够了。配置好之后先别急着写协议代码用模型对话页面验证一下 Key 是否可用打开 https://taotoken.net/chat 发一条测试消息确认能正常返回。这一步能排除掉大部分密钥配置问题。3. 可复制配置Qt 工程结构与四种协议帧解析代码这一章是核心。我会给出完整的 Qt 工程配置.pro 文件、四种协议的帧解析代码片段以及串口/TCP 的初始化配置。所有代码都可以直接复制到你的工程里编译运行。3.1 Qt 工程配置.pro 文件先看工程文件。你需要用到 serialbus、serialport、network 三个模块QT core gui widgets QT serialbus serialport network CONFIG c17 TARGET PowerProtocolMonitor TEMPLATE app SOURCES \ main.cpp \ mainwindow.cpp \ modbusworker.cpp \ iec104worker.cpp \ iec103worker.cpp \ iec61850worker.cpp \ protocolassistant.cpp HEADERS \ mainwindow.h \ modbusworker.h \ iec104worker.h \ iec103worker.h \ iec61850worker.h \ protocolassistant.h # 如果使用 lib60870 和 libiec61850取消下面注释并配置路径 # LIBS -L$$PWD/third_party/lib60870/lib -llib60870 # LIBS -L$$PWD/third_party/libiec61850/lib -liec61850 # INCLUDEPATH $$PWD/third_party/lib60870/include # INCLUDEPATH $$PWD/third_party/libiec61850/include如果你不想引入第三方库下面给出的解析代码都是纯 Qt 实现的可以直接用。lib60870 和 libiec61850 适合需要完整协议栈的场景但学习成本和集成复杂度更高。3.2 Modbus RTU/TCP 帧解析Modbus 是最简单的。RTU 帧结构是地址(1字节) 功能码(1字节) 数据(N字节) CRC(2字节)。TCP 帧多了 MBAP 头事务ID(2) 协议ID(2) 长度(2) 单元ID(1)。CRC16 校验是 RTU 的核心很多解析失败都是 CRC 算错。下面是标准实现quint16 modbusCRC16(const QByteArray data) { quint16 crc 0xFFFF; for (char byte : data) { crc ^ static_castquint8(byte); for (int i 0; i 8; i) { if (crc 0x0001) { crc 1; crc ^ 0xA001; } else { crc 1; } } } return crc; } bool parseModbusRTU(const QByteArray frame, quint8 addr, quint8 func, QByteArray payload) { if (frame.size() 4) return false; addr static_castquint8(frame[0]); func static_castquint8(frame[1]); int dataLen frame.size() - 4; payload frame.mid(2, dataLen); quint16 recvCrc (static_castquint8(frame[frame.size()-1]) 8) | static_castquint8(frame[frame.size()-2]); quint16 calcCrc modbusCRC16(frame.left(frame.size() - 2)); return recvCrc calcCrc; }Modbus TCP 解析更简单因为 TCP 本身保证完整性不需要 CRCbool parseModbusTCP(const QByteArray frame, quint16 transId, quint8 unitId, quint8 func, QByteArray payload) { if (frame.size() 8) return false; transId (static_castquint8(frame[0]) 8) | static_castquint8(frame[1]); quint16 protoId (static_castquint8(frame[2]) 8) | static_castquint8(frame[3]); if (protoId ! 0) return false; quint16 length (static_castquint8(frame[4]) 8) | static_castquint8(frame[5]); unitId static_castquint8(frame[6]); func static_castquint8(frame[7]); payload frame.mid(8, length - 2); return true; }用 Qt 自带的 QModbusClient 也可以但自己解析能更灵活地处理异常帧。我建议两者结合正常采集用 QModbusClient异常诊断用上面的解析函数。3.3 IEC 60870-5-104 帧解析104 协议跑在 TCP 上帧结构分三种I 帧信息传输、S 帧确认、U 帧控制。起始字节固定 0x68第二字节是长度。enum FrameType { IFrame, SFrame, UFrame, UnknownFrame }; FrameType detect104FrameType(const QByteArray frame) { if (frame.size() 2 || static_castquint8(frame[0]) ! 0x68) return UnknownFrame; quint8 ctrl1 static_castquint8(frame[1]); if ((ctrl1 0x01) 0) return IFrame; if ((ctrl1 0x03) 0x01) return SFrame; return UFrame; } bool parse104IFrame(const QByteArray frame, quint16 sendSeq, quint16 recvSeq, QByteArray asdu) { if (frame.size() 6) return false; quint8 c1 static_castquint8(frame[1]); quint8 c2 static_castquint8(frame[2]); quint8 c3 static_castquint8(frame[3]); quint8 c4 static_castquint8(frame[4]); sendSeq ((c1 0xFE) 1) | (static_castquint16(c2) 7); recvSeq ((c3 0xFE) 1) | (static_castquint16(c4) 7); asdu frame.mid(6); return true; }ASDU 解析是 104 的重点。ASDU 头包含类型标识、可变结构限定词、传送原因、公共地址。遥测类型 9/11/13和遥信类型 1/3的解析方式不同struct ASDUInfo { quint8 typeId; bool isSequence; int count; quint8 cause; quint16 commonAddr; QVectorQPairquint32, double values; // IOA - 值 }; bool parseASDU(const QByteArray asdu, ASDUInfo info) { if (asdu.size() 6) return false; info.typeId static_castquint8(asdu[0]); quint8 vsq static_castquint8(asdu[1]); info.isSequence (vsq 0x80) ! 0; info.count vsq 0x7F; info.cause static_castquint8(asdu[2]); info.commonAddr static_castquint8(asdu[3]) | (static_castquint8(asdu[4]) 8); int offset 6; for (int i 0; i info.count; i) { if (offset 3 asdu.size()) break; quint32 ioa static_castquint8(asdu[offset]) | (static_castquint8(asdu[offset1]) 8) | (static_castquint8(asdu[offset2]) 16); offset 3; double value 0; if (info.typeId 9 || info.typeId 11 || info.typeId 13) { if (offset 2 asdu.size()) break; qint16 raw static_castqint16( (static_castquint8(asdu[offset]) 8) | static_castquint8(asdu[offset1])); value raw; offset 2; if (info.typeId 11 || info.typeId 13) offset 2; } else if (info.typeId 1 || info.typeId 3) { if (offset asdu.size()) break; value static_castquint8(asdu[offset]) 0x01; offset 1; } info.values.append({ioa, value}); } return true; }这段代码覆盖了最常见的遥测和遥信类型。实际项目中你可能还要处理带时标的类型如 30/31/34/35那就在 offset 后面多读 7 个字节的 CP56Time2a。3.4 IEC 60870-5-103 帧解析103 协议用于继电保护帧结构比 104 复杂有固定帧长和可变帧长两种。固定帧长 5 字节可变帧长以 0x68 开头。bool parse103Frame(const QByteArray frame, quint8 control, QByteArray data, bool isVariable) { if (frame.size() 5) return false; quint8 start static_castquint8(frame[0]); if (start 0x68) { isVariable true; if (frame.size() 8) return false; quint8 len static_castquint8(frame[1]); if (frame.size() len 2) return false; control static_castquint8(frame[4]); data frame.mid(7, len - 5); } else if (start 0x10) { isVariable false; control static_castquint8(frame[1]); data.clear(); } else { return false; } return true; }103 的 ASDU 解析跟 104 类似但类型标识和传送原因的定义不同。保护装置常用类型 1带时标的报文、类型 2带相对时间的报文、类型 9测量值。解析时要注意 103 的公共地址通常是 1 字节。3.5 IEC 61850 报文解析61850 是最复杂的。它底层用 MMS制造报文规范做客户端/服务器通信GOOSE 和 SV 走以太网组播。完整实现需要 ASN.1 解码器这里给出一个简化版的 MMS 报文头解析思路。对于 GOOSE 报文它直接封装在以太网帧里以太网类型 0x88B8。如果你用 Qt 的 QUdpSocket 接收组播拿到的是 UDP 载荷需要自己按 GOOSE 结构解析struct GooseHeader { quint16 appId; quint16 length; quint16 reserved1; quint16 reserved2; QByteArray pdu; }; bool parseGoose(const QByteArray data, GooseHeader hdr) { if (data.size() 8) return false; hdr.appId (static_castquint8(data[0]) 8) | static_castquint8(data[1]); hdr.length (static_castquint8(data[2]) 8) | static_castquint8(data[3]); hdr.reserved1 (static_castquint8(data[4]) 8) | static_castquint8(data[5]); hdr.reserved2 (static_castquint8(data[6]) 8) | static_castquint8(data[7]); hdr.pdu data.mid(8); return true; }GOOSE 的 PDU 是 ASN.1 BER 编码解析需要递归下降。如果你不想自己写 ASN.1 解析器建议直接用 libiec61850它提供了MmsConnection和GooseReceiver类集成到 Qt 里只需要把回调数据通过信号槽抛出来。对于 MMS 客户端libiec61850 的IedConnection可以连接 61850 服务器读取数据集。Qt 层封装一个Iec61850Worker在独立线程里跑IedConnection通过QMetaObject::invokeMethod把数据传回 UI。3.6 串口与 TCP 初始化配置Modbus RTU 和 103 走串口配置如下QSerialPort *serial new QSerialPort(this); serial-setPortName(COM3); // Linux 下是 /dev/ttyUSB0 serial-setBaudRate(9600); // 103 常用 9600Modbus 常用 9600/19200 serial-setDataBits(QSerialPort::Data8); serial-setParity(QSerialPort::EvenParity); // 103 常用偶校验 serial-setStopBits(QSerialPort::OneStop); serial-setFlowControl(QSerialPort::NoFlowControl); if (!serial-open(QIODevice::ReadWrite)) { qWarning() 串口打开失败: serial-errorString(); }Modbus TCP 和 104 走 TCPQTcpSocket *tcp new QTcpSocket(this); tcp-connectToHost(192.168.1.100, 2404); // 104 默认端口 2404 if (!tcp-waitForConnected(3000)) { qWarning() TCP 连接失败: tcp-errorString(); }61850 的 MMS 默认端口是 102GOOSE 用组播地址。注意 102 端口在部分系统上需要管理员权限。3.7 统一会话管理每个协议一个 Worker 类继承 QObject在独立 QThread 里运行。Worker 收到数据后解析通过信号发出结构化结果class Iec104Worker : public QObject { Q_OBJECT public: explicit Iec104Worker(QObject *parent nullptr); public slots: void start(const QString host, quint16 port); void stop(); signals: void telemetryReceived(quint32 ioa, double value); void telemetryStatusChanged(quint32 ioa, bool on); void errorOccurred(const QString msg); private slots: void onReadyRead(); private: QTcpSocket *m_socket nullptr; QByteArray m_buffer; };主线程的 MainWindow 把这些信号连到 UI 更新槽用Qt::QueuedConnection保证线程安全。这样四种协议的数据最终都汇聚到同一个数据模型里界面用 QTableView 或自定义仪表盘展示。4. 验证请求与成功结果串口/TCP 联调步骤代码写完了怎么确认真的通了这一章给出具体的联调步骤和预期结果。4.1 Modbus RTU 联调准备一个 Modbus 从站设备或模拟器比如 Modbus Slave 软件。设置从站地址 1波特率 9600偶校验。在 Qt 端发送读保持寄存器请求QByteArray buildModbusReadRequest(quint8 addr, quint16 startAddr, quint16 count) { QByteArray frame; frame.append(static_castchar(addr)); frame.append(static_castchar(0x03)); frame.append(static_castchar(startAddr 8)); frame.append(static_castchar(startAddr 0xFF)); frame.append(static_castchar(count 8)); frame.append(static_castchar(count 0xFF)); quint16 crc modbusCRC16(frame); frame.append(static_castchar(crc 0xFF)); frame.append(static_castchar(crc 8)); return frame; }发送后从站应返回地址 功能码 字节数 数据 CRC。在 Qt 的onReadyRead里累积缓冲区按预期长度截取完整帧调用parseModbusRTU验证 CRC。如果 CRC 通过把寄存器值打印出来。预期结果串口助手或 Qt 日志显示Addr1 Func3 Values[1234, 5678]界面上的数值框实时更新。4.2 Modbus TCP 联调用 Modbus Poll 或类似工具开一个 TCP 从站监听 502 端口。Qt 端连接后发送 MBAP PDU。注意事务 ID 要递增方便匹配响应。预期结果收到响应帧parseModbusTCP返回 truepayload 里的寄存器值与从站设置一致。4.3 IEC 104 联调104 联调需要模拟主站或从站。可以用 lib60870 自带的示例程序或者用开源的 104 测试工具。Qt 作为主站先发启动帧U 帧控制域 0x07从站回复确认0x0B。然后发总召唤I 帧ASDU 类型 100从站回复遥测遥信数据。启动帧构造QByteArray build104StartFrame() { QByteArray frame; frame.append(static_castchar(0x68)); frame.append(static_castchar(0x04)); frame.append(static_castchar(0x07)); frame.append(static_castchar(0x00)); frame.append(static_castchar(0x00)); frame.append(static_castchar(0x00)); return frame; }总召唤 ASDUQByteArray build104Interrogation() { QByteArray asdu; asdu.append(static_castchar(100)); // 类型标识总召唤 asdu.append(static_castchar(0x01)); // 可变结构限定词 asdu.append(static_castchar(0x06)); // 传送原因激活 asdu.append(static_castchar(0x00)); // 公共地址低字节 asdu.append(static_castchar(0x01)); // 公共地址高字节 asdu.append(static_castchar(0x00)); // 信息对象地址 asdu.append(static_castchar(0x00)); asdu.append(static_castchar(0x00)); asdu.append(static_castchar(0x14)); // 召唤限定词站召唤 QByteArray frame; frame.append(static_castchar(0x68)); frame.append(static_castchar(asdu.size() 4)); frame.append(static_castchar(0x00)); // 发送序号低 frame.append(static_castchar(0x00)); // 发送序号高 frame.append(static_castchar(0x00)); // 接收序号低 frame.append(static_castchar(0x00)); // 接收序号高 frame.append(asdu); return frame; }预期结果从站回复 I 帧parse104IFrame提取 ASDUparseASDU解析出遥测值。Qt 日志显示IOA16385 Value220.5界面仪表盘指针转动。4.4 IEC 103 联调103 联调需要保护装置或模拟器。Qt 作为主站先发复位通信单元固定帧 0x10 0x40然后发召唤 1 类数据。注意 103 的链路地址和公共地址。预期结果收到可变帧parse103Frame返回 truedata 里包含 ASDU解析出保护测量值。4.5 IEC 61850 联调61850 联调最复杂。建议先用 libiec61850 的示例服务器或者用 61850 模拟器。Qt 作为客户端用IedConnection_connect连接然后IedConnection_readObject读取数据集。如果你只用 GOOSE可以用 Wireshark 抓包确认组播地址和 APPID然后在 Qt 里用 QUdpSocket 加入组播组QUdpSocket *udp new QUdpSocket(this); udp-bind(QHostAddress::AnyIPv4, 0, QUdpSocket::ShareAddress); udp-joinMulticastGroup(QHostAddress(224.0.1.1)); connect(udp, QUdpSocket::readyRead, this, []() { while (udp-hasPendingDatagrams()) { QByteArray buf; buf.resize(udp-pendingDatagramSize()); udp-readDatagram(buf.data(), buf.size()); GooseHeader hdr; if (parseGoose(buf, hdr)) { qDebug() GOOSE APPID: hdr.appId PDU size: hdr.pdu.size(); } } });预期结果Wireshark 能看到 GOOSE 报文Qt 日志打印 APPID 和 PDU 大小进一步解析 PDU 能得到开关位置。4.6 用 TaoToken 辅助验证联调过程中如果报文解析失败把原始十六进制和错误信息发给模型。比如void ProtocolAssistant::diagnose(const QString protocol, const QByteArray raw) { QJsonObject msg; msg[role] user; msg[content] QString(协议:%1 原始报文:%2 解析失败请分析可能原因) .arg(protocol, raw.toHex( )); // 通过 QNetworkAccessManager 发送到 https://taotoken.net/api/v1/chat/completions }模型返回的建议通常能快速定位问题比如 CRC 字节序反了、长度字段没算对、ASDU 类型标识不匹配等。5. 本篇常见错误排查401、local proxy failed、reading choices、OAuth这一章对照真实报错给出排查路径。这些错误我在实际项目中都遇到过有的是协议问题有的是配置问题。5.1 HTTP 401 Unauthorized如果你在调用 TaoToken API 做报文诊断时收到 401说明 API Key 无效或没带上。检查三点第一请求头里是否有Authorization: Bearer sk-xxx第二Key 是否复制完整有没有多余空格第三Key 是否被删除或过期。在 https://taotoken.net/api-keys 重新生成一个更新到config/taotoken.json里。注意401 跟协议解析无关是模型调用层的问题。如果你没用到模型辅助可以忽略。5.2 local proxy failed这个报错通常出现在 Qt 网络请求走系统代理时。如果你的开发环境配置了代理但代理不可用QNetworkAccessManager 会报local proxy failed。解决办法是在 Qt 里显式设置不使用代理QNetworkProxyFactory::setUseSystemConfiguration(false); QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);或者在请求时指定直连。注意这里说的是本地网络配置问题不涉及任何网络访问方式的选择。5.3 reading choices 相关错误这个报错一般出现在解析模型返回的 JSON 时。如果你用模型做报文诊断返回结构里choices数组为空或格式不对就会报reading choices失败。检查两点第一请求体里model字段是否填了有效的 Model ID第二messages数组是否至少有一条 user 消息。用 https://taotoken.net/chat 先手动验证一次确认返回结构正常。5.4 OAuth 相关错误如果你用 Claude Code 或类似工具接入可能会遇到 OAuth 报错。这类工具通常需要配置 Base URL、API Key 和 Model ID 三件套。以 Claude Code 为例配置文件在~/.claude/settings.json{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: 你选择的模型ID } }如果你用 CC Switch 管理多个配置在 CC Switch 里新增一个配置项Base URL 填https://taotoken.net/apiKey 填你的 API KeyModel ID 填模型列表里的字符串。保存后切换过去重启 Claude Code 即可。Cline MCP 的配置类似在 MCP 设置里填 Base URL、Key 和 Model ID。Codex 的auth.json路径通常在~/.codex/auth.json填入对应的 Base URL 和 Key。三件套缺一不可Base URL 决定请求发到哪Key 决定身份Model ID 决定用哪个模型。任何一个填错都会导致 401 或 404。5.5 协议解析常见错误除了 API 层错误协议解析本身也有高频坑Modbus CRC 校验失败最常见的是 CRC 高低字节顺序反了。Modbus RTU 的 CRC 是低字节在前、高字节在后。上面的parseModbusRTU里recvCrc的拼接顺序就是低字节在前。104 帧长度不对104 的第二字节是长度但它不包括起始字节 0x68 本身。如果你按frame.size() length判断会少一字节。正确判断是frame.size() length 2。103 可变帧越界103 的长度字段含义跟 104 不同它包含控制域和地址域。解析时data frame.mid(7, len - 5)里的偏移量要根据实际帧结构调整。61850 GOOSE 收不到检查网卡是否支持组播、是否加入了正确的组播组、APPID 是否匹配。Windows 防火墙可能拦截组播需要放行。5.6 线程安全问题多协议并发时如果多个 Worker 同时往同一个 UI 模型写数据可能崩溃。确保所有跨线程信号都用Qt::QueuedConnectionUI 更新只在主线程做。共享数据结构用 QMutex 保护或者每个协议独立的数据模型最后在 UI 层合并。5.7 串口占用Modbus RTU 和 103 如果共用同一个串口会冲突。实际项目中通常一个串口接一种协议或者用串口服务器分端口。如果必须共用需要做时分复用但电力现场一般不推荐。排查时建议打开 Qt 的日志输出把原始报文十六进制打印出来对照规范逐字节检查。配合 Wireshark 抓 TCP 包能快速定位是发送问题还是接收问题。6. 语义一致 CTA把多协议采集接入你的 Qt 工程到这里四种协议的解析代码和联调步骤都走完了。你手上应该有一个能同时跑 Modbus、101/103/104 和 61850 的 Qt 工程框架每个协议独立线程、独立解析、统一信号槽输出。接下来最实际的一步是把模型辅助诊断接进去。当某个协议解析失败时自动把原始报文和错误上下文发给模型几秒钟拿到排查建议。这比翻规范快得多尤其是在 61850 的 ASN.1 解码和 104 的 ASDU 类型判断上。接入入口我放在这里获取 API Keyhttps://taotoken.net/api-keys查看可用模型和 Model IDhttps://taotoken.net/models接入文档含请求示例和错误码说明https://taotoken.net/doc模型对话验证https://taotoken.net/chat长期编码和 Agent 任务https://taotoken.net/coding-plan配置三件套记住Base URL 用https://taotoken.net/apiKey 从 api-keys 页面复制Model ID 从模型列表复制。填到你的config/taotoken.json里Qt 工程启动时加载。最后说一个我踩过的坑不要把所有协议解析都塞进主线程。哪怕数据量不大串口和 TCP 的阻塞也会让界面卡顿。每个协议一个 QThreadWorker 对象 moveToThread信号槽用 QueuedConnection这是最稳的做法。另外61850 的 GOOSE 对实时性要求高如果 Qt 事件循环有延迟考虑用独立线程加高精度定时器轮询。代码框架搭好之后剩下的就是按现场设备手册调整参数串口波特率、校验位、104 的公共地址、61850 的数据集引用。这些没有通用值只能对着设备文档一个个填。祝你联调顺利。
返回列表