You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 66 Next »

Introduction

Bluetooth interface implementation in RouterOS allows the device to capture Bluetooth advertising packets that are broadcasted over 37, 38, and 39 advertising channels. More information can be found in the guide here.

Bluetooth tags like, for example, TG-BT5-IN and TG-BT5-OUT, do exactly that. They broadcast advertising payloads over the mentioned channels. To understand what kind of information is stored in the payload, make sure to check the link. The tags can be configured (using the MikroTik Beacon Manager app) to broadcast the payloads automatically, with an interval or/and when a movement, tilt, or free-fall trigger is detected. In simple terms, the tag will "tell" (broadcast to) all the surrounding Bluetooth scanners (like the KNOT) information about itself periodically.

When the payload is broadcasted by the tag, and the tag is within the KNOT's Bluetooth operating range, the KNOT will capture and display the payload under its "scanner" Bluetooth interface section. It would look like this:

/iot bluetooth scanners advertisements print
Columns: DEVICE, PDU-TYPE, TIME, ADDRESS-TYPE, ADDRESS, RSSI, LENGTH, DATA
#  DEVICE  PDU-TYPE        TIME                  ADDRESS-TYPE  ADDRESS            RSSI    LENGTH  DATA                                        
0  bt1     adv-noconn-ind  mar/07/2023 12:11:57  public        DC:2C:6E:0F:C0:3D  -51dBm      22  15ff4f09010079100000ffff0000cf188a6b2b000064
1  bt1     adv-noconn-ind  mar/07/2023 12:11:58  public        2C:C8:1B:4B:BB:0A  -49dBm      22  15ff4f090100168dfefffffffeffa51ae1362200005e 

The example above shows us, that the KNOT sees two Bluetooth tags within its operating range with MAC addresses "DC:2C:6E:0F:C0:3D" and "2C:C8:1B:4B:BB:0A", their respective payloads ("DATA" field) and the signal strength ("RSSI" field).

With the help of RouterOS scripting and scheduling, we can make the KNOT automatically-periodically scan the payload list and, in case, a specific payload or a specific tag's MAC address is found on the list, we can make the KNOT structure an MQTT message (out of the printed information shown in the example above) and send it to the configured server via MQTT, e-mail or HTTP post. Script examples will be shown later on in the guide.

As the title suggests, the goal is to implement a Bluetooth tag-tracking solution and the idea is quite simple. When you have 2 KNOTs (KNOT-A and KNOT-B), running the same script on a scheduler, and the tag moves between their Bluetooth operating ranges, the data on the server will indicate whether it was KNOT-A or KNOT-B that have sent the tag's payload. That will help you figure out the proximity of the tag. Whether the tag is broadcasting payloads in the KNOT-A zone, or in the KNOT-B zone.

You will need a server where the data is going to be stored and visualized. In this guide, we will showcase a server called ThingsBoard and how to communicate with it using the MQTT protocol.

ThingsBoard has a cloud solution and different local installation options (on different OS). Since we've added a container feature, it became possible to also run the platform within RouterOS. In order to do that, you will need a RouterOS device that has at least 2 GB RAM, has an option to increase storage (for example, an additional USB port), and is either ARM64 or AMD64 architecture. CHR machine or CCR2004-16G-2S+ could be a good fit.

Setup requirements:

  • a running ThingsBoard server;
  • 2+ KNOTs with access to the server's network via ethernet, Wi-Fi, or cellular connection (the amount of the units required depends on the size of the area that needs to be covered);
  • 1+ Bluetooth TG-BT5-IN and/or TG-BT5-OUT tags (depending on how many assets you need to track - 1 tag per asset).

Scenario explanation

Imagine LTE base station roaming or Wi-Fi roaming topologies.

In the LTE case, you have multiple base stations that cover specific areas in your city. In populated areas, they are scattered around so that they cover the whole city. You have a smartphone with an LTE connection up. When your stay at home, you are most likely connected to a single base station (not always but most of the time), which provides the best signal. ISP knows that your phone is connected to a specific base station, for example, base station #5. When you go to work, your phone will roam between the base stations on the way, and once you arrive at your office, your phone will, most likely, be connected to another base station, for example, #10, because the base station #5 will be out of reach. Now, your ISP knows that your device is connected to station #10.

In the Wi-Fi roaming case, you have multiple access points broadcasting the same SSID. When you stay at your workstation, you will be, most likely, connected to AP #1. Then, if you go downstairs to visit your coworker on another floor, you will be roamed to AP#2, as AP#1 will be out of reach.

The same principle can be applied to Bluetooth tag-tracking. Instead of the "LTE base stations" or "Wi-Fi access points", you will have the KNOTs (Bluetooth scanners) and instead of the smartphone that roams between the base stations or access points, you will have the Bluetooth tag that roams between the KNOTs.

The actual Bluetooth operating distance can vary from site to site because a lot of different factors can impact it, like 2.4 GHz interference or the surrounding materials used. For example, in line of sight, with no interference, the distance at which the KNOT is able to capture the tag's broadcasted payload, can be up to 180 meters (KNOT — ~180 meters — TG-BT5-OUT). But you also have to keep in mind that with further distance, more packets will be lost on the way. In the office environment, the range can go down to 30-100 meters.

Let's take a look at a basic-rough example first. We have two KNOTs (KNOT-A and KNOT-B). We've tested Bluetooth ranges in our environment and could verify that both KNOTs are able to capture the tag at a 70-meter distance. If we install KNOT-A and KNOT-B 140 meters apart, their Bluetooth ranges will not overlap or overlap just slightly. When the tag moves into the KNOT-A range, the payload from the monitored tag will appear under the Bluetooth scanner list, an MQTT message with a report will be sent to the server and the server will display that the tag is in the KNOT-A zone. When the tag moves into the KNOT-B range, the same happens, and the server will display that the tag is inside the KNOT-B zone.

Logically, if the Bluetooth operating ranges overlap and the tag is within the overlapped area (at the same time within KNOT-A and KNOT-B Bluetooth ranges), both KNOTs will send the data and the server is going to show that the tag is reported by both devices at the same time.

Surely, there will be zones where two or more KNOT's Bluetooth ranges overlap and you can use it to your advantage → basically, you will have information that the tag, right now, is on the edges of the Bluetooth ranges in-between specific KNOT zones. In other words, when the asset moves into the overlapped area, you will have information on the server that the asset is somewhere between the two KNOT operating ranges, which is useful information to have as it gives more precision.

Additionally, the script we've prepared allows you to set up a filter (that will be shown later on), which will make the KNOT ignore the payloads that the scanner captures unless the signal strength (RSSI) is stronger than the specified value. In the Bluetooth scanner example print shown above under the "Introduction" section, we can see that the KNOT sees one of the tags with an RSSI signal strength of -51 dBm (tag with a MAC address "DC:2C:6E:0F:C0:3D") and the other one with an RSSI signal strength of -49 dBm (tag with a MAC address "2C:C8:1B:4B:BB:0A"). So, if we apply a filter to the script to ignore all payloads that are received with signal strength (RSSI) weaker than -50 dBm, our KNOT would report that only tag "2C:C8:1B:4B:BB:0A" is within the Bluetooth range, because its RSSI is -49 dBm, and the second tag (with RSSI -51 dBm) will be ignored. What this means, essentially, is that you can "tweak" the "range" of reception. The actual signal strength will vary between different locations (as mentioned before, because of interference and surrounding materials), so it needs to be tested on sight.

Scenario example

To understand the scenario and the idea behind it better, take a look at the topology shown below:

Bluetooth distances that you can see in the picture above, in real life will be much bigger (unless the signal-absorbing-reflecting materials are used inside the area, there is a lot of 2.4 GHz interference or the RSSI filter is used in the script to help reduce the range of reception)! The scale of objects and Bluetooth operating ranges are just shown as an example, to help visually understand and imagine the topology.

We have a warehouse area and we have 3 assets (pallets) that we are interested in tracking. We also have 3 zones: zone A, where newly arrived pallets are stored; zone B, where our assets are relocated to be inspected; and zone C, where assets are moved after inspection. To achieve Bluetooth asset-tracking, just install x1 KNOT per zone and x1 tag per asset.

If TAG 1 and TAG 2 (pallets) arrive in the "arrival" zone A, KNOT A will report to the server that both assets are within its Bluetooth range. If TAG 3 gets moved to zone C, the server will indicate it is within the KNOT C range. If TAG 1 and TAG 2 move toward the B zone, and stay on the edges between A and B zones, the server will show that it is in the overlapped area (at the same time within KNOT-A and KNOT-B ranges). If the tags move to the middle of the warehouse, the server will indicate that they are in all 3 zones at once, in the overlapping area.

Configuration

In this example, we will showcase a basic topology, when two KNOTs are used and we just want to know whether the tag/asset, is located in one part of the building or the other one (whether it is inside zone A or zone B).

ThingsBoard preparation

Check the guide over here to understand how the ThingsBoard and the RouterOS can be set up to utilize MQTT communication.

This example will showcase access-token scenario for simplicity reasons, but you can use other available options as well. For the production environment, it is suggested to use SSL-MQTT, as non-SSL-MQTT can be easily packet captured and inspected.

To understand how to implement SSL-MQTT communication on the instance that is run with the container. Check the guide linked here (Enabling HTTPS and SSL MQTT section).

Create 2 KNOTs under the ThingsBoard GUI and make them "gateways".

Go to the "Devices" section, click on "+" button and "Add new device":

Name the device and checkbox the "Is gateway" option:

Do that for each KNOT that you have:

Set up a unique access token (unique credentials) for each KNOT under the device you've just created, under the "Manage credentials" tab:

RouterOS configuration

Preparation

Before we proceed, we need to confirm that our Bluetooth tag actually appears in the KNOT's Bluetooth range and that the KNOT detects them. To do that, you can issue the command "/iot bluetooth scanners advertisements print":

/iot bluetooth scanners advertisements print
 # DEVICE     PDU-TYPE        TIME                 ADDRESS-TYPE ADDRESS                    RSSI     LENGTH DATA                                           
 0 bt1        adv-noconn-ind  mar/08/2023 12:35:15 public       2C:C8:1B:4B:BB:0A        -50dBm         22 15ff4f090100b0110100ffff00000019d68d2300005d   
 1 bt1        adv-noconn-ind  mar/08/2023 12:35:16 public       DC:2C:6E:0F:C0:3D        -39dBm         22 15ff4f0901008f3cfcfffbfffaff301783c22c000064   
 2 bt1        adv-noconn-ind  mar/08/2023 12:35:35 public       2C:C8:1B:4B:BB:0A        -50dBm         22 15ff4f09010084d500000400ffff0319ea8d2300005d   
 3 bt1        adv-noconn-ind  mar/08/2023 12:35:45 public       2C:C8:1B:4B:BB:0A        -50dBm         22 15ff4f090100e607faffffff03000319f48d2300005d   

Or you can check it using Webfig or Winbox under the IoT>Bluetooth>Advertising reports tab.

The list can be chaotic. Random payloads can appear on the list as the scanner captures everything around it. To help reduce the list, you can filter it using the tag's MAC address "/iot bluetooth scanners advertisements print where address=DC:2C:6E:0F:C0:3D":

/iot bluetooth scanners advertisements print where address=DC:2C:6E:0F:C0:3D
 # DEVICE    PDU-TYPE        TIME                 ADDRESS-TYPE ADDRESS                    RSSI     LENGTH DATA                                           
 0 bt1       adv-noconn-ind  mar/08/2023 12:41:06 public       DC:2C:6E:0F:C0:3D        -49dBm         22 15ff4f0901005ab20100fdfffdff4017e1c32c000064   
 1 bt1       adv-noconn-ind  mar/08/2023 12:41:26 public       DC:2C:6E:0F:C0:3D        -40dBm         22 15ff4f090100349704000000fcff4017f5c32c000064   
 2 bt1       adv-noconn-ind  mar/08/2023 12:41:36 public       DC:2C:6E:0F:C0:3D        -49dBm         22 15ff4f09010073fb0000000000003017ffc32c000064   
 3 bt1       adv-noconn-ind  mar/08/2023 12:41:46 public       DC:2C:6E:0F:C0:3D        -43dBm         22 15ff4f090100b88cffffffffffff401709c42c000064   

This is also the step, where you can see how the tag's RSSI changes based on the distance from the KNOT.

MQTT broker

On each KNOT setup MQTT broker.

For KNOT A:

/iot/mqtt/brokers/add name=tb address=x.x.x.x port=1883 username=knot-A_access_token

Where:

  • name is the name that you wish to give to the broker and this name will be used later in the script;
  • address is the IP address of the broker/ThingsBoard server;
  • port is the TCP port that the broker is listening for → for non-SSL it is typically TCP 1883;
  • username is dictated by the MQTT broker and, in our case, it is an "access token" that was generated in the ThingsBoard management portal.

For KNOT B → Do the same step. Just change username to the respective access token that was generated for the KNOT B device (gateway).

Script

Import the script shown below. To do that, just copy the below shown "code" and paste it into a new terminal window:

/system/script/add dont-require-permissions=no name=tracking owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="    # Name of an existing MQTT broker that should be u\
    sed for publishing\r\
    \n     :local broker \"tb\"\r\
    \n\r\
    \n    # MQTT topic where the message should be published\r\
    \n     :local topic \"v1/gateway/telemetry\"\r\
    \n\r\
    \n\r\
    \n   # POSIX regex for filtering advertisement Bluetooth addresses. E.g. \"^BC:33:AC\"\r\
    \n    # would only include addresses which start with those 3 octets.\r\
    \n    # To disable this filter, set it to \"\"\r\
    \n    :local addressRegex \"\"\r\
    \n\r\
    \n    # POSIX regex for filtering Bluetooth advertisements based on their data. Same\r\
    \n    # usage as with 'addressRegex'.\r\
    \n    :local advertisingDataRegex \"\"\r\
    \n\r\
    \n    # Signal strength filter. E.g. -40 would only include Bluetooth advertisements\r\
    \n    # whose signal strength is stronger than -40dBm.\r\
    \n    # To disable this filter, set it to \"\"\r\
    \n    :local rssiThreshold \"\"\r\
    \n\r\
    \n    ################################## Bluetooth ##################################\r\
    \n    :global invertU16 do={\r\
    \n        :local inverted 0\r\
    \n        :for idx from=0 to=15 step=1 do={\r\
    \n            :local mask (1 << \$idx)\r\
    \n            :if (\$1 & \$mask = 0) do={\r\
    \n                :set \$inverted (\$inverted | \$mask)\r\
    \n            }\r\
    \n        }\r\
    \n        return \$inverted\r\
    \n    }\r\
    \n\r\
    \n    :global le16ToHost do={\r\
    \n        :local lsb [:pick \$1 0 2]\r\
    \n        :local msb [:pick \$1 2 4]\r\
    \n\r\
    \n        :return [:tonum \"0x\$msb\$lsb\"]\r\
    \n    }\r\
    \n\r\
    \n    :local le32ToHost do={\r\
    \n        :local lsb [:pick \$1 0 2]\r\
    \n        :local midL [:pick \$1 2 4]\r\
    \n        :local midH [:pick \$1 4 6]\r\
    \n        :local msb [:pick \$1 6 8]\r\
    \n\r\
    \n        :return [:tonum \"0x\$msb\$midH\$midL\$lsb\"]\r\
    \n    }\r\
    \n\r\
    \n    :local from88 do={\r\
    \n        :global invertU16\r\
    \n        :global le16ToHost\r\
    \n        :local num [\$le16ToHost \$1]\r\
    \n\r\
    \n        # Handle negative numbers\r\
    \n        :if (\$num & 0x8000) do={\r\
    \n            :set num (-1 * ([\$invertU16 \$num] + 1))\r\
    \n        }\r\
    \n\r\
    \n        # Convert from 8.8. Scale by 1000 since floating point is not supported\r\
    \n        :return ((\$num * 125) / 32)\r\
    \n    }\r\
    \n\r\
    \n    :local flagStr do={\r\
    \n        :local str \"\"\r\
    \n\r\
    \n        :if (\$1 & 0x01) do={ :set \$str \" switch\" }\r\
    \n        :if (\$1 & 0x02) do={ :set \$str \"\$str tilt\" }\r\
    \n        :if (\$1 & 0x04) do={ :set \$str \"\$str free_fall\" }\r\
    \n        :if (\$1 & 0x08) do={ :set \$str \"\$str impact_x\" }\r\
    \n        :if (\$1 & 0x10) do={ :set \$str \"\$str impact_y\" }\r\
    \n        :if (\$1 & 0x20) do={ :set \$str \"\$str impact_z\" }\r\
    \n\r\
    \n        :if ([:len \$str] = 0) do={ :return \"\" }\r\
    \n\r\
    \n        :return [:pick \$str 1 [:len \$str]]\r\
    \n    }\r\
    \n\r\
    \n    # Find fresh Bluetooth advertisements\r\
    \n    :global btOldestAdvertisementTimestamp\r\
    \n    :if ([:typeof \$btOldestAdvertisementTimestamp] = \"nothing\") do={\r\
    \n        # First time this script has been run since booting, need to initialize\r\
    \n        # persistent variables\r\
    \n        :set \$btOldestAdvertisementTimestamp 0\r\
    \n    }\r\
    \n    :local advertisements [/iot bluetooth scanners advertisements print detail \\\r\
    \n        as-value where \\\r\
    \n            epoch > \$btOldestAdvertisementTimestamp and \\\r\
    \n            address ~ \$addressRegex and \\\r\
    \n            data ~ \$advertisingDataRegex and \\\r\
    \n            rssi > \$rssiThreshold\r\
    \n    ]\r\
    \n    :local advCount 0\r\
    \n    :local lastAdvTimestamp 0\r\
    \n    :local advJson \"\"\r\
    \n    :local advSeparator \"\"\r\
    \n\r\
    \n    # Remove semicolons from MAC/Bluetooth addresses\r\
    \n    :local minimizeMac do={\r\
    \n        :local minimized\r\
    \n        :local lastIdx ([:len \$address] - 1)\r\
    \n        :for idx from=0 to=\$lastIdx step=1 do={\r\
    \n            :local char [:pick \$address \$idx]\r\
    \n            :if (\$char != \":\") do={\r\
    \n                :set \$minimized \"\$minimized\$char\"\r\
    \n            }\r\
    \n        }\r\
    \n        :return \$minimized\r\
    \n    }\r\
    \n\r\
    \n    :foreach adv in=\$advertisements do={\r\
    \n        :local address (\$adv->\"address\")\r\
    \n        :local rssi (\$adv->\"rssi\")\r\
    \n        :local epoch (\$adv->\"epoch\")\r\
    \n        :local ad (\$adv->\"data\")\r\
    \n        :local version [:tonum \"0x\$[:pick \$ad 8 10]\"]\r\
    \n        :local encrypted [:tonum \"0x\$[:pick \$ad 10 12]\"]\r\
    \n        :local salt [\$le16ToHost [:pick \$ad 12 16]]\r\
    \n        :local accelX [\$from88 [:pick \$ad 16 20]]\r\
    \n        :local accelY [\$from88 [:pick \$ad 20 24]]\r\
    \n        :local accelZ [\$from88 [:pick \$ad 24 28]]\r\
    \n        :local temp [\$from88 [:pick \$ad 28 32]]\r\
    \n        :local uptime [\$le32ToHost [:pick \$ad 32 40]]\r\
    \n        :local flags [:tonum \"0x\$[:pick \$ad 40 42]\"]\r\
    \n        :local bat [:tonum \"0x\$[:pick \$ad 42 44]\"]\r\
    \n\r\
    \n:local obj \"\\\r\
    \n\\\r\
    \n\\\"\$address\\\":[{\\\r\
    \n\\\"reporter\\\":\\\"KNOT_A\\\",\\\r\
    \n\\\"ts\\\":\$epoch,\\\r\
    \n\\\"rssi\\\":\$rssi,\\\r\
    \n\\\"version\\\":\$version,\\\r\
    \n\\\"encrypted\\\":\$encrypted,\\\r\
    \n\\\"salt\\\":\$salt,\\\r\
    \n\\\"accelX\\\":\$accelX,\\\r\
    \n\\\"accelY\\\":\$accelY,\\\r\
    \n\\\"accelZ\\\":\$accelZ,\\\r\
    \n\\\"temp\\\":\$temp,\\\r\
    \n\\\"uptime\\\":\$uptime,\\\r\
    \n\\\"flags\\\":\\\"\$[\$flagStr \$flags]\\\",\\\r\
    \n\\\"bat\\\":\$bat\\\r\
    \n}]\"\r\
    \n\r\
    \n        :put (\"\$advCount: \\\r\
    \n            address=\$address \\\r\
    \n            ts=\$epoch \\\r\
    \n            rssi=\$rssi \\\r\
    \n            version=\$version \\\r\
    \n            encrypted=\$encrypted \\\r\
    \n            salt=\$salt \\\r\
    \n            accelX=\$accelX \\\r\
    \n            accelY=\$accelY \\\r\
    \n            accelZ=\$accelZ \\\r\
    \n            temp=\$temp \\\r\
    \n            uptime=\$uptime \\\r\
    \n            flags=\\\"\$[\$flagStr \$flags]\\\" \\\r\
    \n            bat=\$bat\" \\\r\
    \n        )\r\
    \n        :set \$advCount (\$advCount + 1)\r\
    \n        :set \$lastAdvTimestamp \$epoch\r\
    \n       # Ensure that the last object is not terminated by a comma\r\
    \n        :set \$advJson \"\$advJson\$advSeparator\$obj\"\r\
    \n        :if (\$advSeparator = \"\") do={\r\
    \n            :set \$advSeparator \",\"}\r\
    \n    }\r\
    \n    :if (\$advCount > 0) do={\r\
    \n        :set \$btOldestAdvertisementTimestamp \$lastAdvTimestamp\r\
    \n    }\r\
    \n\r\
    \n    #################################### MQTT #####################################\r\
    \n    :local message \\\r\
    \n        \"{\$advJson}\"\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\")"

The script should appear under the "System>Scripts>Script List" tab with the name "tracking" or with the help of the command "/system script print".

There are a few lines that you need to pay attention to.






FILTERING EXPLAINED!!

  • No labels