Serial Studio MQTT 驱动实战:订阅框架、Sparkplug 集成与 TLS 安全配置
2026/9/18 21:43:26 网站建设 项目流程

Serial Studio MQTT 驱动实战:订阅框架、Sparkplug 集成与 TLS 安全配置

【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio

MQTT 驱动(Subscriber,Pro 功能)让 Serial Studio 项目可以订阅一个或多个 Broker 的主题,并把收到的每条消息像串口或 TCP 套接字收到的字节一样注入常规帧处理管线,是"数据已在 MQTT Broker 上"或"多实例共享同一遥测源"场景下的首选传输方式。本文将带你掌握从连接配置、载荷解析约定,到 Sparkplug B 订阅/发布、多源混合与 TLS 加固的完整用法,并结合仓库源码说明每个配置项背后的真实实现逻辑。

MQTT 与 UART、BLE、CAN Bus 的关键差异在于:它不向 Serial Studio 呈现物理总线,而是通过 TCP 经由 Broker 中转。但驱动依然嵌入与其他传输方式完全一致的"每源(per-source)"架构,因此单个项目可以同时混用 MQTT 订阅源与串口、网络源。

如果你还没接触过 MQTT 协议词汇,建议先阅读 MQTT Topics & Semantics(主题、通配符、QoS、保留消息、会话),本页默认你已具备协议基础。

MQTT 订阅者眼中的数据流

Broker 维护一张路由表:每当发布者向某个主题发布消息,Broker 会把载荷副本转发给所有主题过滤器(topic filter)匹配的客户端。驱动为每个项目源打开一条连接,在 QoS 0 下注册主题过滤器,此后每个匹配载荷都会触发messageReceived回调,字节随后交给 FrameReader,处理方式与 UART 收到的字节完全相同。空载荷与不匹配过滤器的消息会在到达 FrameReader 之前被丢弃。

从源码看,驱动在构造函数中即连接QMqttClient::messageReceived信号到自身的onMessageReceived槽(见 core/Devices/IO/Drivers/MQTT.cpp 第 86 行),再经routeReceivedMessage分流处理,这正是"订阅即回调、回调即入管线"的实现依据。

由此衍生出两条影响配置的原则:

  • 一条 MQTT 消息 = 一帧字节。Broker 保留载荷边界:一次 200 字节的发布到达时就是一次 200 字节的读取。帧检测规则(起始/结束分隔符、固定长度、无分隔符)依然适用,但实践中每条 MQTT 消息通常恰好包含一帧,因此No Delimiters(无分隔符)是常见选择。
  • 通配符会混合多个发布者。sensors/+/temp这样的过滤器会在单个源上接收来自多个发布者的载荷,但 Serial Studio 在字节层面无法区分它们。如果仪表盘需要区分发布者身份,请把身份编码进载荷内部(CSV 加一列 ID、JSON 加一个device字段),或者为每个发布者各建一个源。

Serial Studio 如何使用它

驱动封装 Qt 的QMqttClient,运行在主线程上。每源的 Broker 连接、SSL 配置、主题订阅状态都保存在驱动实例自身,因此项目中的每个 MQTT 源都拥有独立的 Broker 会话;向同一项目添加第二个 MQTT 订阅者时,它不会与第一个共享任何状态。

当你在源的Bus Type中选择MQTT Subscriber时,项目编辑器会在Connection Settings下暴露以下字段:

字段说明
HostnameBroker 地址(IP 或主机名)。默认127.0.0.1
PortBroker TCP 端口。默认1883。TLS Broker 请自行设为8883;开启 TLS 不会自动改端口。
Topic Filter要订阅的主题,支持+(单层)和#(剩余层)通配符。必填,缺少过滤器源将无法打开。
Client IDCONNECT 时发送的客户端标识。为空时自动生成(随机 16 字符)。
Username / Password可选的 Broker 认证。
MQTT VersionMQTT 3.1、3.1.1 或 5.0。默认 MQTT 5.0。
Clean SessionCONNECT 时丢弃任何持久化的会话状态。默认开启。
Keep Alive (s)空闲时 PING 包之间的秒数。默认 60;0禁用该机制。
Auto Keep Alive让客户端自动发送保活 ping。默认开启。
SSL/TLS EnabledTLS 总开关。默认关闭;开启后出现下方三个字段。
SSL ProtocolTLS 协议族:Any Protocol、DTLS 1.2 or Later、Secure Protocols Only、TLS 1.2、TLS 1.3、TLS 1.3 or Later。默认 Secure Protocols Only。
Peer Verify ModeAuto Verify Peer、None、Query Peer、Verify Peer 之一。默认 Auto Verify Peer。
Peer Verify Depth接受的最大证书链长度。默认100= 不限。
Client CertificatePEM 客户端证书路径,用于双向 TLS。可选;普通仅服务端认证(CA-only)的 TLS 可留空。
Private Key客户端证书对应的私钥路径。留空时默认取证书文件本身。
Key Passphrase加密私钥的密码短语(若密钥需要)。
ALPN (MQTT over port 443)在 TLS 握手期间提供 ALPN 协议,这是 Broker 将 MQTT 复用到 443 端口的方式。默认关闭。
ALPN Protocol要提供的协议名,仅在 ALPN 开启时显示。默认x-amzn-mqtt-ca,即 AWS IoT Core 在 443 端口期望的名称;其他 Broker 有自己的文档。

这些默认值在源码中有明确对应:构造函数将m_port初始化为 1883、m_keepAlive为 60、m_hostname"127.0.0.1"、协议版本为QMqttClient::MQTT_5_0、ALPN 协议为"x-amzn-mqtt-ca",SSL 配置则默认使用QSsl::SecureProtocols+AutoVerifyPeer+ 深度 10(见 core/Devices/IO/Drivers/MQTT.cpp)。setKeepAlivesetCleanSessionsetMqttVersion等 setter 在修改后会调用scheduleReconnectIfActive()并在驱动已连接时调度重连,这解释了"改配置即重连"的行为。

ALPN 只作用于握手:它不改变 MQTT 会话本身,忽略该扩展的 Broker 会照常连接。提供错误的协议名会在握手阶段被拒绝,因此此处的失败表现为 TLS 错误,而非 MQTT 错误。

主窗口的Setup面板展示相同配置并附带几个额外项:Client ID旁的Regenerate按钮、CA Certificates一行的Load From Folder…按钮(用于为自签名 Broker 导入 PEM 证书)、以及Client CertificatePrivate Key旁的Browse…按钮(两者仅在SSL/TLS Enabled开启时显示)。Setup 面板省略了Auto Keep Alive,并缩短了部分标签(VersionUse SSL/TLSPeer VerifyVerify Depth),其余字段完全一致。

凭据存储。Broker 用户名/密码和私钥密码短语使用 SimpleCrypt 在应用设置存储(QSettings)内做混淆处理,而非保存在系统钥匙串中——请据此保护好设置文件。源码中的CredentialVault(见 core/Devices/IO/Drivers/MQTT.cpp 第 741 行起的applyCredentials系列调用)在写回前会按(hostname, port)为键一次性落盘凭据对,私钥密码短语同样存入 vault。

同样的字段可通过 API 命令project.mqtt.subscriber.getConfigproject.mqtt.subscriber.setConfigproject.mqtt.subscriber.getStatus脚本化。setConfig只修补你传入的键,并在驱动已连接时调度重连(详见 doc/help/API-Reference.md:getConfig永不返回密码;setConfig支持 hostname/port/clientId/username/password/topicFilter/cleanSession/keepAlive/autoKeepAlive 及 mqttVersion、sslEnabled、sslProtocol、peerVerifyMode、peerVerifyDepth 等字段)。

分步操作指引参见 Protocol Setup Guides(MQTT 一节)。

载荷预期

驱动仅负责传输,不解码载荷;它把字节原样交给项目的帧解析器:

  • Quick Plot 模式:期望逗号分隔的数值(如23.5,48.2,1013.25\n)。每条 MQTT 消息应是一个完整的行。
  • Project File 模式:期望项目的 JavaScript 或 Lua 解析器能接受的内容。JSON、CSV、定长字节结构体、二进制协议都与在其他驱动上表现一致。
  • Console Only 模式:在终端中原样显示载荷。

源的帧检测规则依然生效:如果发布者在载荷内嵌入了起始/结束分隔符,请配置它们;否则保持No Delimiters选中,使每条 MQTT 消息成为一帧。

Sparkplug:叠加在 MQTT 上的约定

Sparkplug 是叠加在 MQTT 之上的一种约定:固定以spBv1.0为根的主题命名空间、protobuf 载荷、声明节点发布内容的出生证书(birth certificate)、以及节点掉线时由 Broker 送达的死亡证书(death certificate)。Serial Studio 同时扮演两方:作为订阅者,MQTT 驱动充当 host application,把命名空间转成数据集;作为生产者,MQTT Publisher 充当 edge node。两端的载荷均遵循 Sparkplug B v1.0 规范,采用 Eclipse Tahu schema。

命名空间常量与容量上限在 core/Protocols/Sparkplug/SparkplugLimits.h 中定义:kNamespace = "spBv1.0"、每个边缘节点的合成在线指标名为"Online"、主题最多 5 个元素、序列号模 256。

订阅方配置

在 Setup 面板的 MQTT 区块勾选Sparkplug。该复选框会替换Topic Filter:驱动自行订阅spBv1.0命名空间,因此无需过滤器,且为普通订阅配置的过滤器在 Sparkplug 开启时不生效。复选框下方会出现两行:

字段说明
Sparkplug Group ID将订阅限制到某一个组。留空表示接收 Broker 上所有组。通配符与/会被拒绝,因为组 ID 是单个主题元素。
Create Project from Births根据目前已发现的指标生成项目。源连接后即可用。

解码是**出生驱动(birth-driven)**的:节点的出生证书命名其指标并为后续数据消息分发数字别名;驱动为每个具名指标分配一个线槽(wire slot)、锁存出生值,并通过该表解析后续所有别名。对于尚无证书的作用域内到达的数据消息、或携带证书未声明别名的消息,驱动不会猜测:它会将该消息暂存直至出生证书到达;若最终无法解析则计数丢弃。每个边缘节点还会获得一个合成的Online指标,携带其出生/死亡状态,因此掉线的节点在仪表盘上显示为离线而非仅仅是数据过期。

序列号按节点逐条检查,出现间隔按计数处理而非插值填补。

会话状态是有上限而非按需增长:2048 个指标槽、256 个节点、每节点 64 个设备、出生前最多暂存 256 条消息。超出上限的流量被拒绝并计数。这些上限正是 core/Protocols/Sparkplug/SparkplugLimits.h 中的kMaxSlots(取自 OPC UA 编码器的kMaxTags = 2048,见 core/Protocols/OpcUa/OpcUaWire.h)、kMaxNodes = 256kMaxDevicesPerNode = 64kMaxPreBirthMessages = 256

Create Project from Births

先连接、让证书到达,然后再生成。该按钮构建一个项目:一个 MQTT Subscriber 类型源(携带 Broker 设置与组 ID)、每个发布作用域(边缘节点;节点发布设备时则是每个设备)一个组、每个发现的指标一个数据集、以及使用Sparkplug模板的内置帧解析器。模板的 schema 参数由机器管理:再次连接发现更多指标后重新生成即可,不要手工编辑。在任一证书到达前生成,会报告"未发现任何内容"而非写出空项目。生成的项目会在项目编辑器中打开供自定义。

生成请求由驱动内部GeneratedProjectRequest承载(见 core/Devices/IO/Drivers/MQTT.h 第 304 行与buildSparkplugProject()方法)。

作为边缘节点发布

出站方向在项目编辑器的 MQTT publisher 的Sparkplug区块配置:

字段说明
Publish as Edge Node把仪表盘的数据集发布进 Sparkplug 命名空间,取代上面选定的载荷模式。
Group ID本节点所属的逻辑组。
Edge Node ID组内标识本节点的 ID。
Device ID可选。设置后,数据集作为该节点下的一个设备发布,走其出生与数据主题而非节点自身的。

发布者拥有生命周期管理。死亡证书在 CONNECT 之前就注册为连接的遗嘱(will)——因为 CONNECT 之后才设置的遗嘱永远不会生效,非正常退出的节点将永远显示为在线。出生证书在连接时发布,将每个数据集声明为带别名的指标;此后每个发布周期发送一条按别名寻址的数据消息,只携带发生变化的指标。后出现的数据集会扩展注册表,节点会在下一条数据消息前重新发布其证书,确保任何 host 都不会收到无法解析的别名。节点还订阅自己的命令主题,收到重生(rebirth)请求时重新发布证书。

源码印证了这一"遗嘱先于 CONNECT"的细节:core/Storage/MQTT/PublisherWorker.cpp 的configureSparkplugWill()注释明确写道"CONNECT 之后设置的遗嘱永远不会生效(a will set after CONNECT never arms)",该函数在连接建立前调用,负责设置 NDEATH 遗嘱并打开连接的 bdSeq;连接转入 Connected 时则发布出生证书(NBIRTH,配置了设备时还有 DBIRTH),订阅 NCMD 主题以响应重生请求(R40/R42/R43 规范要求)。

边缘节点只发布自己的命名空间,不发布其他内容。开启期间,原始字节与脚本载荷模式被抑制,其队列被排空而非留在一个不运行的模式后面继续增长。

监控指标

project.mqtt.subscriber.getStatus在连接状态旁返回一个sparkplug块,报告模式是否开启、组 ID、已发现指标数以及拉取式计数器:序列间隔、容量丢弃、解码错误、忽略消息、出生前暂存/丢弃的消息、重生请求、以及解码器不支持的指标数据类型。

这些计数器在 core/Devices/IO/Drivers/MQTT/SparkplugSession.h 的Counters结构中原样定义(seqGapscapDropsdecodeErrorsignoredMessagespreBirthBufferedpreBirthDroppedrebirthRequestsunsupportedMetrics),按规范 0033 采用"拉取式(pulled)"设计:普通整数就地累加、按调用方自己的节奏读取,每条消息不触发信号、不分配、不加锁。单元测试 app/tests/tst_sparkplug_session.cpp 覆盖了Online合成指标、出生前缓冲溢出、序列间隔计数与重生请求等行为。

一个项目中的多个 MQTT 订阅者

项目编辑器把 MQTT 订阅者视同其他总线类型,因此"两个 ESP32 机群分别挂在本地与云端两个 Broker"就是一个普通的多源项目:

  1. 添加源。在项目编辑器中添加新源,把Bus Type设为MQTT Subscriber
  2. 指向 Broker。填写第一个机群的 hostname、端口、凭据与主题过滤器。
  3. 重复。再添加第二个源,再次把Bus Type设为MQTT Subscriber,为第二个 Broker 配置(或同一 Broker 的不同主题)。
  4. 按源映射数据集。每个源有自己的帧解析器;Frame builder 把解析出的帧按源 ID 保留路由到仪表盘,与串口+网络混用完全一致。

两个 MQTT 源指向同一 Broker 但过滤不同主题也没问题:驱动会打开两条独立的 CONNECT 会话,各注册一个订阅。请为每个源指定自己的Client ID——当两个客户端共享同一 ID 时,Broker 会踢掉较老的连接。没有专门的"共享 Broker"优化,也无需有——QMqttClient实例化成本很低。

TLS / SSL 安全配置

任何可从本地网络外部访问的 Broker 都请使用 TLS:

  • 把端口设为8883(标准的 MQTT-over-TLS 端口)。
  • 开启SSL/TLS
  • 生产环境保持Peer VerifyVerify Peer(或默认的Auto Verify Peer)。仅在针对自签名 Broker 证书测试时才降到None,且绝不要对公共 Broker 使用。
  • 若 Broker 使用私有 CA,在 Setup 面板的CA Certificates下点击Load From Folder…,选择包含 PEM 证书链的目录。

源码中addCaCertificates()(core/Devices/IO/Drivers/MQTT.cpp)通过文件夹选择器把整个目录的 PEM 证书追加进QSslConfiguration

TLS 配置是每源独立的,因此同一项目中的两个 MQTT 订阅者可以使用信任根不同的 Broker,互不干扰。

常见坑

  • 已订阅但无数据。主题区分大小写:Sensors/Tempsensors/temp不是一回事。对 Broker 运行mosquitto_sub -t '#' -v观察实际发布内容。若发布者的层级比预期更深或更浅,过滤器会静默漏掉一切。
  • 已连接却出现陈旧数据。活数据流之上的主题若有保留消息(retained message),可能掩盖新订阅者的新发布。订阅your/topic/#观察 Broker 在连接时投递了什么。
  • Client ID 冲突。Broker 强制 Client ID 唯一。两个 Serial Studio 实例(或同一实例内误配的两个源)共享一个 Client ID 时,Broker 会踢掉较老的连接。在 Setup 面板点击Regenerate为每个源分配全新 ID。regenerateClientId()的实现是从 26 个小写字母加 10 个数字的字符集中随机抽取 16 位(见 core/Devices/IO/Drivers/MQTT.cpp)。
  • TLS 握手失败。要求 TLS 的 Broker 会在证书链不受信任时拒绝连接。自签名 Broker 需要通过CA Certificates字段的Load From Folder…按钮显式导入 CA。
  • 公共 Broker 延迟。test.mosquitto.org这类免费公共 Broker 要经过公网往返,抖动可达数十至数百毫秒。低延迟遥测请在局域网内自行运行 Mosquitto。
  • 高发布速率拖垮仪表盘。MQTT 不是流式协议。在每秒数千条消息时,Broker 队列会积压,仪表盘出现突发与停顿。当不需要逐读数粒度时,把多个读数批量放进单条 MQTT 消息,在项目的帧解析器内逐帧解析。
  • 一个过滤器、多个发布者、混合载荷格式。一个同时捕获room1(CSV)与room2(JSON)的sensors/+/temp过滤器,难以被单个帧解析器干净解析。要么统一载荷格式,要么拆成两个源、各用一个过滤器。

延伸阅读与关联文档

  • MQTT Topics & Semantics:协议词汇——主题、通配符、QoS、保留消息、会话。
  • MQTT Publisher:项目级出站侧,Serial Studio 作为生产者时使用,也是 Sparkplug 边缘节点字段的配置处。
  • Protocol Setup Guides:项目编辑器中的分步 MQTT 配置。
  • Drivers: Network:不需要 Broker 时的裸 TCP/UDP。
  • Data Sources:全部传输方式的驱动能力汇总。
  • Communication Protocols:所有受支持传输协议的概览。
  • Pro vs Free Features:MQTT 属于 Pro 功能。
  • Troubleshooting:通用故障排查指南。

【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询