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 提交的数据包。