MQTT/HTTPS and AWS configuration
使用基于 HTTPS 的 MQTT 将 MikroTik Bluetooth 标签数据转发到 Amazon AWS,涵盖 AWS 账户设置、证书和 broker 配置。
简介
可用于监控 Bluetooth 标签广播的信息的选项之一是云系统,例如 AWS(Amazon WEB Services)。在本文中,我们将展示如何配置 AWS 和 RouterOS 以使用 MQTT 和 HTTPS 协议发布数据。在此场景中,RouterOS 将充当网关,并将 BLEtag 广播的数据发布到云(AWS)。在此场景中,AWS 将充当 MQTT broker 和 MQTT 订阅者(数据将被发布/显示的服务器)。
在继续设置之前,你需要在 AWS 系统中创建一个账户。你可以通过此链接找到有关如何操作的更多信息。
创建账户后,请确保支付卡在 Your_Username>My account 下通过验证,否则账户将不会激活。
转到 Services>Internet of Things>IoT Core。
在 Test 部分中,你可以使用内置的 MQTT 测试客户端来测试发布和订阅 topic。你有 Subscribe to a topic 和 Publish to a topic 选项卡,可以检查这两个过程。MQTT 测试客户端部分还可以显示 HTTPS 提交。
AWS IoT 配置
信息
本节中显示的配置适用于 MQTT 发布/订阅场景,但你也可以使用相同的 AWS 设置来测试 HTTPS 提交。
Things
步骤 1:导航到 Manage>Things。这是配置开始的菜单(Things 菜单是设备或逻辑实体的表示形式)。
**

**
按 Create>Create a single thing>选择一个名称>Next>Create certificate。
步骤 2:下载 "AmazonRootCA1.pem" 证书(AWS 的 Root CA)、"xxxx.cert.pem" 证书("thing" 的证书)和 "xxxx.private.key" 密钥(私钥)- "xxxx" 部分对每个新创建的证书都是唯一的。

步骤 3:你可以选择完成设置(通过选择 Done)或设置策略(通过选择 Attach a policy)。你需要为创建的 "thing" 添加策略,但如果你使用新账户进行操作,可能没有任何可供选择的策略。别担心,你可以稍后手动创建它们。
还有另外两个重要的菜单 - Secure>Certificates 和 Secure>Policies。
证书
在 Certificates 菜单中,你可以创建证书、激活它们并将它们附加到策略。Things 配置应该已经创建了证书。只需确保证书已"激活"。为此,请选择该证书,点击 "Actions" 并点击 "Activate"。

策略

步骤 1:创建一个新策略。点击 "Create" 并输入策略的名称。在 "Create a policy" 选项卡中,你可以选择设置 "actions"。在此示例中,我们将使用 4 个操作(iot:Publish、iot:Receive、iot:Subscribe 和 iot:Connect)。你可以通过此链接找到有关策略的更多信息和示例。
步骤 2:导航到 "Add statements" 部分。在 "Action" 字段中,选择 iot:Publish 和 iot:Receive 操作,并用逗号 "," 将它们分隔开。在 "Resource ARN" 字段中,将文本 "replaceWithATopic" 替换为实际相关的 topic(例如 "my/test/topic")。在 "Effect" 复选框中勾选 "Allow"。

步骤 3:通过点击 "Add statement" 添加一条新语句。通过选择 iot:Subscribe 为该语句添加一个操作,并将 "Resource ARN" 字段中的文本 "replaceWithATopicFilter" 替换为实际相关的 topic(例如 "my/test/topic")。在 "Effect" 复选框中勾选 "Allow"。

步骤 4:添加最后一条语句并设置 iot:Connect 操作。仅将 "replaceWithAClientId" 部分更改为适当的 client-id(例如 "test-client")并勾选 "Allow" 复选框。要完成设置,请点击 "Create"。
步骤 5:你需要将证书附加到新创建的策略。为此,请转到 Secure>Certificates,选择该证书,点击 "Actions" 和 "Attach policy"。

选择该策略并点击 "Attach"。

至此,AWS 设置就完成了。
端点地址(Broker 的地址)
你可以在 "Settings" 部分(Settings>Device data endpoint)中找到 Endpoint 地址。此地址应在 RouterOS 设置中配置为 broker 的 IP/FQDN 地址。

RouterOS 配置
信息
为了配置 MQTT,请确保事先安装了 iot 软件包。 在我们的示例中,我们使用 KNOT 作为网关,并使用一个脚本(专为 KNOT 创建)来发布数据。在你的特定应用场景中,你可以使用任何 RouterOS 设备(已安装 iot 软件包)来发布你需要的任何数据。
证书
由于 AWS 使用 SSL 连接,我们将需要之前下载的证书和密钥。
转到 "Files" 菜单并将 AmazonRootCA1.pem、xxxx.cert.pem.crt 和 xxxx.private.pem.crt 添加到 "File list" 中。

转到 System>Certificates 并导入全部 3 个文件(通过 "Import" 按钮 - 逐个导入):

确保两个证书都是受信任的(T),并且 "xxxx.certificate.pem.crt" 带有私钥(K)。
MQTT Broker
导航到 IoT>MQTT 并添加一个新 broker("+" 按钮)。

在 "Name" 字段中为 broker 命名(例如,AWS)。
在 "Address" 字段中输入/粘贴 AWS 地址(你可以在 AWS 设置 "Settings>Device data endpoint" - Endpoint address 中找到它)。
在 "Port" 字段中配置 broker 使用的端口,并通过 SSL 复选框启用 SSL(在这种情况下,AWS 使用 8883,并且由于我们使用证书,它要求启用 SSL)。
在此场景中不需要 "Username" 和 "Password" 字段(取决于 broker 的配置 - 跳过这两个字段)。
在 "Client Id" 字段中设置 client-id(它是在 AWS 策略中配置的 ID - 在此示例中为 test-client)。
由于使用了 SSL,必须在 "Certificate" 字段中选择证书(选择 xxxx.certificate.pem.crt)。
点击 "Apply" 和 "OK" 完成 broker 的设置。
使用脚本进行 MQTT Publish
为了将 Bluetooth 标签(在我们的示例中为 TG-BT5-IN)的数据发布到 AWS,我们将使用脚本。脚本示例如下所示。
每一行以符号 # 开头的都是说明,它描述了将在该行下方配置的参数。将引号 "" 内的参数更改为适用于你特定情况的参数。
## Required packages: iot
################################ Configuration ################################
## Name of an existing MQTT broker that should be used for publishing
:local broker "AWS"
## MQTT topic where the message should be published
:local topic "my/test/topic"
## Interface whose MAC should be used as 'Locator ID'
:local locatorIface "ether1"
## POSIX regex for filtering advertisement Bluetooth addresses. E.g. "^BC:33:AC"
## would only include addresses which start with those 3 octets.
## To disable this filter, set it to ""
:local addressRegex ""
## POSIX regex for filtering Bluetooth advertisements based on their data. Same
## usage as with 'addressRegex'.
:local advertisingDataRegex ""
## Signal strength filter. E.g. -40 would only include Bluetooth advertisements
## whose signal strength is stronger than -40dBm.
## To disable this filter, set it to ""
:local rssiThreshold "-40"
#################################### System ###################################
:put ("[*] Gathering system info...")
:local ifaceMac [/interface get [/interface find name=$locatorIface] mac-address]
:local cpuLoad [/system resource get cpu-load]
:local freeMemory [/system resource get free-memory]
:local usedMemory ([/system resource get total-memory] - $freeMemory)
:local rosVersion [/system package get value-name=version \
[/system package find where name ~ "^routeros"]]
:local model [/system routerboard get value-name=model]
:local serialNumber [/system routerboard get value-name=serial-number]
## Health is a bit iffy since '/system health' does not have 'find' in ROS6
:local health [/system health print as-value]
:local supplyVoltage 0
:local boardTemp 0
:foreach entry in=$health do={
:if ($entry->"name" = "voltage") do={:set $supplyVoltage ($entry->"value")}
:if ($entry->"name" = "board-temperature1") do={:set $boardTemp ($entry->"value")}
}
################################## Bluetooth ##################################
:put ("[*] Gathering Bluetooth info...")
:global btOldestAdvertisementTimestamp
:if ([:typeof $btOldestAdvertisementTimestamp] = "nothing") do={
# First time this script has been run since booting, need to initialize
# persistent variables
:set $btOldestAdvertisementTimestamp 0
}
:local btProcessingStart [/system clock get time]
:local advertisements [/iot bluetooth scanners advertisements print detail \
as-value where \
epoch > $btOldestAdvertisementTimestamp and \
address ~ $addressRegex and \
data ~ $advertisingDataRegex and \
rssi > $rssiThreshold
]
:local advJson ""
:local advCount 0
:local advSeparator ""
:local lastAdvTimestamp 0
## Remove semicolons from MAC/Bluetooth addresses
:local minimizeMac do={
:local minimized
:local lastIdx ([:len $address] - 1)
:for idx from=0 to=$lastIdx step=1 do={
:local char [:pick $address $idx]
:if ($char != ":") do={
:set $minimized "$minimized$char"
}
}
:return $minimized
}
:foreach adv in=$advertisements do={
:local address ($adv->"address")
:local ts ($adv->"epoch")
:local rssi ($adv->"rssi")
:local ad ($adv->"data")
:local obj "\
{\
\"id\":\"$[$minimizeMac address=$address]\",\
\"ts\":$ts,\
\"rssi\":$rssi,\
\"ed\":{\
\"ad\":\"$ad\"\
}\
}"
:set $advCount ($advCount + 1)
:set $lastAdvTimestamp $ts
# Ensure that the last object is not terminated by a comma
:set $advJson "$advJson$advSeparator$obj"
:if ($advSeparator = "") do={
:set $advSeparator ","
}
}
:if ($advCount > 0) do={
:set $btOldestAdvertisementTimestamp $lastAdvTimestamp
}
:put ("[*] Found $advCount new advertisements \
(processing time: $[([/system clock get time] - $btProcessingStart)])")
#################################### MQTT #####################################
:local message \
"{\
\"clientId\":\"$[/iot mqtt brokers get value-name=client-id \
[/iot mqtt brokers find name=$broker]]\",\
\"t\":0,\
\"v\":1,\
\"OldestAdvertisementTimestamp\":$btOldestAdvertisementTimestamp,\
\"locs\":[{\
\"id\":\"$[$minimizeMac address=$ifaceMac]\",\
\"tags\":[$advJson],\
\"ed\":{\
\"model\":\"$model\",\
\"sn\":\"$serialNumber\",\
\"ros\":\"$rosVersion\",\
\"cpu\":$cpuLoad,\
\"umem\":$usedMemory,\
\"fmem\":$freeMemory,\
\"psu\":$supplyVoltage,\
\"temp\":$boardTemp\
}\
}]\
}"
:log info "$message";
:put ("[*] Total message size: $[:len $message] bytes")
:put ("[*] Sending message to MQTT broker...")
/iot mqtt publish broker=$broker topic=$topic message=$message
:put ("[*] Done")
应考虑 2 行脚本。
:local broker "AWS"
该行,你应该在引号 "" 内指定 broker 的名称(在我们的示例中为 AWS)。
:local topic "my/test/topic"
该行,你应该在引号 "" 内指定正确的 topic。该 topic 是在 AWS 策略中配置的(在我们的示例中为 my/test/topic)。
脚本配置的其余部分取决于整体需求。脚本解释了哪些确切参数被配置为发布。
导航到 System>Scripts,在那里添加一个新脚本,并粘贴上方显示的脚本(例如,将其命名为 script1)。
要运行脚本,你可以使用命令行:
/system script run script1
使用脚本进行 HTTPS post
可用于将数据从网关发布到云的另一种协议是 - HTTPS。这可以使用 fetch 工具来实现。在 RouterOS 7.1beta6 版本之前,fetch 没有选择客户端证书进行身份验证的选项(意味着不支持带客户端证书的 HTTPS 提交),而在 7.1beta6 中添加了一项新功能来启用此设置/支持。使用 CURL 和 Python 的 HTTPS 提交示例(以及其他有用信息)显示在 AWS HTTPS 手册中。
为了将 Bluetooth 标签(在我们的示例中为 TG-BT5-IN)的数据发布到 AWS,我们将使用脚本。脚本示例如下所示。
每一行以符号 # 开头的都是说明,它描述了将在该行下方配置的参数。将引号 "" 内的参数更改为适用于你特定情况的参数。
################################ Configuration ################################
## Interface whose MAC should be used as 'Locator ID'
:local locatorIface "ether1"
## POSIX regex for filtering advertisement Bluetooth addresses. E.g. "^BC:33:AC"
## would only include addresses which start with those 3 octets.
## To disable this filter, set it to ""
:local addressRegex ""
## POSIX regex for filtering Bluetooth advertisements based on their data. Same
## usage as with 'addressRegex'.
:local advertisingDataRegex ""
## Signal strength filter. E.g. -40 would only include Bluetooth advertisements
## whose signal strength is stronger than -40dBm.
## To disable this filter, set it to ""
:local rssiThreshold "-40"
#################################### System ###################################
:put ("[*] Gathering system info...")
:local ifaceMac [/interface get [/interface find name=$locatorIface] mac-address]
:local cpuLoad [/system resource get cpu-load]
:local freeMemory [/system resource get free-memory]
:local usedMemory ([/system resource get total-memory] - $freeMemory)
:local rosVersion [/system package get value-name=version \
[/system package find where name ~ "^routeros"]]
:local model [/system routerboard get value-name=model]
:local serialNumber [/system routerboard get value-name=serial-number]
## Health is a bit iffy since '/system health' does not have 'find' in ROS6
:local health [/system health print as-value]
:local supplyVoltage 0
:local boardTemp 0
:foreach entry in=$health do={
:if ($entry->"name" = "voltage") do={:set $supplyVoltage ($entry->"value")}
:if ($entry->"name" = "board-temperature1") do={:set $boardTemp ($entry->"value")}
}
################################## Bluetooth ##################################
:put ("[*] Gathering Bluetooth info...")
:global btOldestAdvertisementTimestamp
:if ([:typeof $btOldestAdvertisementTimestamp] = "nothing") do={
# First time this script has been run since booting, need to initialize
# persistent variables
:set $btOldestAdvertisementTimestamp 0
}
:local btProcessingStart [/system clock get time]
:local advertisements [/iot bluetooth scanners advertisements print detail \
as-value where \
epoch > $btOldestAdvertisementTimestamp and \
address ~ $addressRegex and \
data ~ $advertisingDataRegex and \
rssi > $rssiThreshold
]
:local advJson ""
:local advCount 0
:local advSeparator ""
:local lastAdvTimestamp 0
## Remove semicolons from MAC/Bluetooth addresses
:local minimizeMac do={
:local minimized
:local lastIdx ([:len $address] - 1)
:for idx from=0 to=$lastIdx step=1 do={
:local char [:pick $address $idx]
:if ($char != ":") do={
:set $minimized "$minimized$char"
}
}
:return $minimized
}
:foreach adv in=$advertisements do={
:local address ($adv->"address")
:local ts ($adv->"epoch")
:local rssi ($adv->"rssi")
:local ad ($adv->"data")
:local obj "\
{\
\"id\":\"$[$minimizeMac address=$address]\",\
\"ts\":$ts,\
\"rssi\":$rssi,\
\"ed\":{\
\"ad\":\"$ad\"\
}\
}"
:set $advCount ($advCount + 1)
:set $lastAdvTimestamp $ts
# Ensure that the last object is not terminated by a comma
:set $advJson "$advJson$advSeparator$obj"
:if ($advSeparator = "") do={
:set $advSeparator ","
}
}
:if ($advCount > 0) do={
:set $btOldestAdvertisementTimestamp $lastAdvTimestamp
}
:put ("[*] Found $advCount new advertisements \
(processing time: $[([/system clock get time] - $btProcessingStart)])")
#################################### MQTT #####################################
:local message \
"{\
\"t\":0,\
\"v\":1,\
\"OldestAdvertisementTimestamp\":$btOldestAdvertisementTimestamp,\
\"locs\":[{\
\"id\":\"$[$minimizeMac address=$ifaceMac]\",\
\"tags\":[$advJson],\
\"ed\":{\
\"model\":\"$model\",\
\"sn\":\"$serialNumber\",\
\"ros\":\"$rosVersion\",\
\"cpu\":$cpuLoad,\
\"umem\":$usedMemory,\
\"fmem\":$freeMemory,\
\"psu\":$supplyVoltage,\
\"temp\":$boardTemp\
}\
}]\
}"
:log info "$message";
:put ("[*] Total message size: $[:len $message] bytes")
/tool fetch certificate=f79dda4ec5-certificate.pem.crt.txt_0 http-method=post output=user http-header-field="Content-Type:application/json" http-data=$message url="https://xxxxx.iot.us-east-2.amazonaws.com:8443/topics/my/test/topic"
:put ("[*] Done")
应考虑 1 行脚本:
/tool fetch certificate=f79dda4ec5-certificate.pem.crt.txt_0 http-method=post output=user http-header-field="Content-Type:application/json" http-data=$message url="https://xxxxxx.iot.us-east-2.amazonaws.com:8443/topics/my/test/topic"
根据你的具体情况更改证书名称和 URL:
url="https://xxxx:8443/topics/yyyy",其中 "xxxx" 是 AWS 端点地址,"yyyy" 是在 AWS 策略中配置的 topic - 步骤 2 和 3(在我们的示例中为 my/test/topic)。
脚本配置的其余部分取决于整体需求。脚本解释了哪些确切参数被配置为发布。
导航到 System>Scripts,在那里添加一个新脚本,并粘贴上方显示的脚本(例如,将其命名为 script2)。
要运行脚本,你可以使用命令行:
/system script run script2
验证
要验证 AWS 是否收到数据,请返回 AWS 门户。导航到 Test 页面。使用 Subscribe to a topic 选项。在 Topic filter 字段中输入在 AWS 策略中配置的 topic(在我们的示例中为 my/test/topic)并按 Subscribe。在下方,你应该会看到 Subscriptions 部分,数据将在此处显示。运行脚本(在 RouterOS 中),你应该会在 "Subscriptions" 选项卡中看到收集到的数据。

同一页面/选项卡也应显示 HTTPS 提交的数据包。