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 发布场景的应用示例:

a) 使用 AWS 云平台的 MQTT/HTTPS 示例

b) 使用 Azure 云平台的 MQTT 示例

c) MQTT 与 ThingsBoard 配置

请注意,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/run x}” 添加的脚本中不会生效。这两个变量仅在 “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 脚本:

  1. 订阅配置的主题完全匹配 → 第一优先级。
  2. 主题名称不完全匹配(使用了通配符)→ 第二优先级为单层通配符主题。
  3. 主题不属于单层通配符类别 → 第三优先级为基于主题层级的多层通配符主题。

示例:

/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)。