MQTT
本页介绍 MikroTik RouterOS 中的 MQTT 集成,涵盖面向 Kaa、ThingsBoard 等 IoT 平台的发布/订阅工作流,并说明 MQTT 协议基础以及 RouterOS 作为 MQTT 客户端的能力。
MQTT
本节介绍 MQTT 集成示例,用于将 RouterOS 的 MQTT 发布与订阅工作流接入 Kaa IoT、ThingsBoard 等 IoT 平台。
MQTT 是一种开放的 OASIS 和 ISO 标准轻量级发布-订阅网络协议,用于在设备之间传输消息。典型的 MQTT 通信拓扑包括:
- MQTT 发布者:向服务器发送信息的设备。
- MQTT broker:存储数据的服务器。
- MQTT 订阅者:读取/监控服务器上已发布数据的设备。
RouterOS 可以作为 MQTT 发布者和订阅者(从 7.11beta2 起)。您也可以通过容器功能运行 MQTT broker/服务器。Mosquitto MQTT broker 配置请访问此链接。
以下是一些 MQTT 发布场景的应用示例:
请注意,AWS 和 Azure 示例(脚本)展示的是发布蓝牙标签数据。目前只有 KNOT 内置蓝牙芯片。
配置
子菜单: /iot/mqtt
注意:需要安装 iot 软件包。
IoT 软件包自 RouterOS 6.48.3 起提供。可从下载页面的 “Extra packages” 部分获取。
| 属性 | 说明 |
|---|---|
| brokers | 已配置的 MQTT broker 列表。 |
| connect | 指定要连接哪个 broker 的命令。 |
| disconnect | 指定要断开哪个 broker 的命令。 |
| publish | 定义要发布的 MQTT 消息的命令。 |
| subscribe | 定义要订阅的 MQTT 主题的命令。 |
| subscriptions | 已订阅主题和已接收消息的列表。 |
| unsubscribe | 指定要取消订阅哪个主题的命令。 |
Broker
子菜单: /iot/mqtt/brokers
要添加新的 MQTT broker(或 MQTT 服务器),请运行以下命令:
/iot/mqtt/brokers/add
可配置属性如下:
| 属性 | 说明 |
|---|---|
| address(IP|hostname;默认:) | broker 的 IP 地址或主机名。 |
| auto-connect(yes | no;默认:no) | 启用后,与 MQTT broker 的连接断开或中断后,RouterOS 会反复尝试重新建立连接。 |
| certificate(string;默认:) | 用于 SSL 连接的证书库证书。 |
| client-id(string;默认:) | 用于连接的唯一 ID,broker 使用该 ID 识别客户端。 |
| keep-alive(integer:30..64800;默认:60) | 定义时间(秒),超过该时间后客户端应向 MQTT broker “ping” 以表明自己“存活”,从而确保连接持续。该值应根据 MQTT broker 的设置进行配置。 |
| name(string;默认:) | broker 的描述性名称。 |
| parallel-scripts-limit(integer:3..1000;默认:关闭) | 定义此 broker 的 on-message 功能允许同时运行多少个脚本。在持续发布大量消息时,可用于降低 CPU 占用。 |
| password(string;默认:)敏感参数 | broker 密码(如果 broker 要求)。 |
| port(integer:0..4294967295;默认:1883) | broker 使用的网络端口。 |
| ssl(yes | no;默认:no) | Secure Socket Layer 配置。 |
| username(string;默认:) | broker 用户名(如果 broker 要求)。 |
| will-message(string;默认:) | 配置 Last Will and Testament(LWT)消息,在连接期间发送给 broker。该消息会存储在 broker 上。除非 RouterOS 意外断开连接(未发送正常的 “DISCONNECT” 数据包),否则 broker 不会发布该消息。 |
| will-qos(integer;默认:) | 配置 LWT 消息的 QoS 值。 |
| will-retain(yes | no;默认:) | 配置是否保留 LWT 消息。 |
| will-topic(string;默认:) | 配置 LWT 主题,即 will-message 的发布位置。 |
添加 broker 的示例:
/iot/mqtt/brokers/add name="broker" address="192.168.88.33" port=1883 ssl=no client-id="test-client" auto-connect=no keep-alive=60
结果:
/iot/mqtt/brokers/print
0 name="broker" address="192.168.88.33" port=1883 ssl=no client-id="test-client" auto-connect=no keep-alive=60 connected=no
连接
子菜单: /iot/mqtt/connect
要连接到预配置的 MQTT broker,请执行:
/iot/mqtt/connect broker="broker"
如果连接成功,“connected” 参数应变为 “yes”:
/iot/mqtt/brokers/print
0 name="broker" address="192.168.88.33" port=1883 ssl=no client-id="test-client" auto-connect=no keep-alive=60 connected=yes
断开连接
子菜单: /iot/mqtt/disconnect
要断开与 MQTT broker 的连接,请执行:
/iot/mqtt/disconnect broker="broker"
要确认 broker 已断开,请执行以下命令,应显示 “connected=no”:
/iot/mqtt/brokers/print
0 name="broker" address="192.168.88.33" port=1883 ssl=no client-id="test-client" auto-connect=no keep-alive=60 connected=no
发布
子菜单: /iot/mqtt/publish
Publish 菜单用于向 MQTT broker 发送 MQTT 消息。
| 属性 | 说明 |
|---|---|
| broker(string;默认:) | 选择发布消息的 broker。 |
| disconnect-after(yes | no;默认:no) | 确保发布消息发送后自动断开与 broker 的连接。 |
| force(yes | no;默认:yes) | 设为 “yes” 时,如果尚未与 broker 建立连接(“connected=no”)就尝试发布消息,RouterOS 会先尝试与指定 broker 建立 MQTT 连接,然后再发布。设为 “no” 时,除非事先已建立连接(“connected=yes”),否则 RouterOS 无法发送消息。 |
| message(string;默认:) | 要发布到 broker 的消息。 |
| qos(integer:0..2;默认:0) | 服务质量参数,由 broker 定义。qos=0 → 消息最多接收一次(发送后即不管);qos=1 → 消息至少接收一次(发布者收到 broker 的确认数据包 <code>PUBACK</code> 前会持续发送);qos=2 → 消息恰好接收一次(发送并通过四次数据包交换确保只投递一次)。 |
| retain(yes | no;默认:no) | 无人订阅该主题时是保留消息还是丢弃。该参数由 broker 定义。保留消息保存在 broker 上,并由 broker 自动发送给新订阅者。 |
| topic(string;默认:) | 主题,由 broker 定义。 |
发布消息示例:
/iot/mqtt/publish message="test-message" broker="broker" topic="my/test/topic"
订阅
子菜单: /iot/mqtt/subscribe
信息
请记住,如果与 broker 的连接正在进行(连接状态为 “connected=yes”),并且您通过该 broker 订阅主题,则必须重新建立连接! 此菜单用于订阅 broker 上的 MQTT 主题。
| 属性 | 说明 |
|---|---|
| broker(string;默认:) | 选择要订阅的 broker。 |
| force(yes | no;默认:yes) | 设为 “yes” 时,如果尚未与 broker 建立连接(“connected=no”)就尝试订阅,RouterOS 会先尝试与指定 broker 建立 MQTT 连接,然后再订阅主题。设为 “no” 时,除非事先已建立连接(“connected=yes”),否则 RouterOS 无法订阅主题。 |
| qos(integer:0..2;默认:0) | 服务质量参数,由 broker 定义。 |
| topic(string;默认:) | 要订阅的主题,由 broker 定义。 |
订阅示例
/iot/mqtt/subscribe broker="broker" topic="my/test/topic"
也支持通配符订阅(单层 “+” 和多层 “#”)。RouterOS 不允许向通配符主题发布,但允许订阅:
/iot/mqtt/subscribe broker="broker" topic="my/test/#"
/iot/mqtt/subscribe broker="broker" topic="my/test/+"
这意味着订阅 topic="my/test/#" 后,可以接收发布到以通配符 “#” 之前模式开头的任意主题的消息(例如 "my/test/topic"、"my/test/topic/something")。
而订阅 topic="my/test/+" 后,可以接收发布到比该主题多一级的主题的消息(例如 "my/test/topic"、"my/test/something")。
订阅列表
子菜单: /iot/mqtt/subscriptions
此部分用于管理已添加的订阅(此前通过订阅部分添加)。
其属性与订阅部分相同。
信息
从 v7.12beta9 起,此菜单允许为订阅添加 “on-message” 设置。 | 属性 | 说明 | | :-- | :-- | | on-message(string;默认:) | 配置一个脚本,每当订阅主题收到新消息时自动触发/运行。 |
要检查已订阅的主题,请执行:
/iot/mqtt/subscriptions/print
0 broker=broker topic="my/test/topic" qos=0
按上文发布部分所示发布测试消息后:
/iot/mqtt/publish message="test-message" broker="broker" topic="my/test/topic"
应可以在以下位置检查收到的消息:
/iot/mqtt/subscriptions/recv/print
0 broker=broker topic="my/test/topic" data="test-message" time=2023-05-22 16:57:00
信息
收到的消息列表最多保留 1024 条,超出后较旧的条目会被新条目覆盖。 要清除已存储的消息,请执行:
/iot/mqtt/subscriptions/recv/clear
要在订阅主题中出现任何新消息时运行脚本(例如一个简单的 “log” 脚本),可以使用 on-message 功能:
/iot/mqtt/subscriptions/set on-message={:log info "Got data {$msgData} from topic {$msgTopic}"} broker=broker 0
脚本可以使用 $msgData 和 $msgTopic 变量。$msgData 表示已发布的 MQTT 消息,$msgTopic 表示消息发布所在的 MQTT 主题。这两个变量会在出现新消息时自动生成。
说明
- 在 “System>Script” 部分创建的脚本中使用 $msgData 和 $msgTopic 变量不会生效,也就是说,在通过 “/iot/mqtt/subscriptions/set on-message={
/system/script/runx}” 添加的脚本中不会生效。这两个变量仅在 “on-message={}” 直接编写的脚本中有效,例如 “on-message={:log info "Got data {$msgData} from topic {$msgTopic}"}”。- 全局变量的使用也是如此。如果全局变量是由其他脚本“生成”的(即出现在 System>Script>Environment 部分的变量),它们在 “on-message” 脚本中不会生效。
向订阅主题发布新的 MQTT 消息后,应出现新的日志条目:
/log/print
10:19:15 script,info Got data {test-message} from topic {my/test/topic}
第二个示例展示如何在出现特定消息(消息中的关键字)时运行脚本。如果希望仅在 MQTT 消息包含特定内容或关键字时运行脚本,可以使用 if 条件语句:
/iot/mqtt/subscriptions/set 0 on-message={:if ($msgData~"\\{\"test\":\"123\"\\}") do={:log info "Got data {$msgData} from topic {$msgTopic}"}}
或者:
/iot/mqtt/subscriptions/set 0 on-message={:if ($msgData~"test") do={:log info "Got data {$msgData} from topic {$msgTopic}"}}
这样,每收到一条 MQTT 消息,脚本都会检查 if 条件是否成立。如果成立($msgData 包含 JSON 字符串 {"test":"123"},或 $msgData 包含字符串 “test”),就会生成日志条目;否则不做任何操作。
也就是说,只有发布如下消息时脚本才会运行:
/iot/mqtt/publish broker=broker topic="my/test/topic" message="{\"test\":\"123\"}"
当收到的消息所属主题同时匹配多个配置了 on-message 的订阅时,只会运行 1 个 on-message 脚本。RouterOS 按以下逻辑/优先级选择要运行的 on-message 脚本:
- 订阅配置的主题完全匹配 → 第一优先级。
- 主题名称不完全匹配(使用了通配符)→ 第二优先级为单层通配符主题。
- 主题不属于单层通配符类别 → 第三优先级为基于主题层级的多层通配符主题。
示例:
/iot/mqtt/subscriptions/print
0 broker=broker topic="some/sort/of/topic" qos=0 on-message="/system/script/run script1"
1 broker=broker topic="some/#" qos=0 on-message="/system/script/run script2"
2 broker=broker topic="some/sort/of/+" qos=0 on-message="/system/script/run script3"
3 broker=broker topic="some/thing/#" qos=0 on-message="/system/script/run script4"
向 some/sort/of/topic 发布数据时,会执行 script1,因为主题完全匹配。
向 some/sort/of/thing 发布数据时,会执行 script3,因为它属于单层通配符主题名称。
向 some/name 发布数据时,会执行 script2,因为它属于多层通配符主题名称。
向 some/thing/else 发布数据时,会执行 script4,因为它属于多层通配符主题名称(尽管也匹配 some/#,但它与 some/thing/# 条目更接近一级)。
取消订阅
子菜单: /iot/mqtt/unsubscribe
| 属性 | 说明 |
|---|---|
| broker(string;默认:) | 选择要取消订阅的 broker。 |
| topic(string;默认:) | 选择要取消订阅的主题,由 broker 定义。 |
从 broker 和主题取消订阅的示例如下:
/iot/mqtt/unsubscribe broker="broker" topic="my/test/topic"
使用脚本发布 RouterOS 统计信息
也可以使用脚本构造包含 RouterOS 统计信息的 MQTT 消息,然后使用调度器按需运行该脚本。
例如,可以运行类似 mqttpublish.rsc 的脚本(将以下 RouterOS 代码内容复制到新终端并按 <kbd>Enter</kbd>):
/system/script/add dont-require-permissions=no name=mqttpublish owner=admin policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\
\_Required packages: iot\r\
\n\r\
\n################################ Configuration #########################\
#######\r\
\n# Name of an existing MQTT broker that should be used for publishing\r\
\n:local broker \"broker\"\r\
\n\r\
\n# MQTT topic where the message should be published\r\
\n:local topic \"my/test/topic\"\r\
\n\r\
\n#################################### System ############################\
#######\r\
\n:put (\"[*] Gathering system info...\")\r\
\n:local cpuLoad [/system/resource/get cpu-load]\r\
\n:local freeMemory [/system/resource/get free-memory]\r\
\n:local usedMemory ([/system/resource/get total-memory] - \$freeMemory)\r\
\n:local rosVersion [/system/package/get value-name=version \\\r\
\n\A0 \A0 [/system/package/find where name ~ \"^routeros\"]]\r\
\n:local model [/system/routerboard/get value-name=model]\r\
\n:local serialNumber [/system/routerboard/get value-name=serial-number]\r\
\n:local upTime [/system/resource/get uptime]\r\
\n\r\
\n#################################### MQTT ##############################\
#######\r\
\n:local message \\\r\
\n\A0 \A0 \"{\\\"model\\\":\\\"\$model\\\",\\\r\
\n\A0 \A0 \A0 \A0 \A0 \A0 \A0 \A0 \\\"sn\\\":\\\"\$serialNumber\\\",\\\r\
\n\A0 \A0 \A0 \A0 \A0 \A0 \A0 \A0 \\\"ros\\\":\\\"\$rosVersion\\\",\\\r\
\n\A0 \A0 \A0 \A0 \A0 \A0 \A0 \A0 \\\"cpu\\\":\$cpuLoad,\\\r\
\n\A0 \A0 \A0 \A0 \A0 \A0 \A0 \A0 \\\"umem\\\":\$usedMemory,\\\r\
\n\A0 \A0 \A0 \A0 \A0 \A0 \A0 \A0 \\\"fmem\\\":\$freeMemory,\\\r\
\n\A0 \A0 \A0 \A0 \A0 \A0 \A0 \A0 \\\"uptime\\\":\\\"\$upTime\\\"}\"\r\
\n\r\
\n:log info \"\$message\";\r\
\n:put (\"[*] Total message size: \$[:len \$message] bytes\")\r\
\n:put (\"[*] Sending message to MQTT broker...\")\r\
\n/iot/mqtt/publish broker=\$broker topic=\$topic message=\$message\r\
\n:put (\"[*] Done\")"
该脚本从 RouterOS 收集数据(型号名称、序列号、RouterOS 版本、当前 CPU、已用内存、空闲内存和运行时间),并以 JSON 格式将消息(数据)发布到 broker:
/system/script/run mqttpublish
[*] Gathering system info...
[*] Total message size: 125 bytes
[*] Sending message to MQTT broker...
[*] Done
可以订阅该主题以检查结果:
/iot/mqtt/subscriptions/recv/print
0 broker=broker topic="my/test/topic" data="{"model":"RB924i-2nD-BT5&BG77","sn":"E9C80EAEXXXX","ros":"7.9","cpu":13,"umem":47476736,
"fmem":19632128,"uptime":"02:21:18"}"
time=2023-05-22 17:03:52
请记得根据实际设置修改脚本中的 “Configuration” 部分(主题和 broker)。