Skip to content

Bluetooth tag-tracking using MQTT and ThingsBoard

Bluetooth tag-tracking using MQTT and ThingsBoard

Section titled “Bluetooth tag-tracking using MQTT and ThingsBoard”

Implement Bluetooth tag-tracking solutions using MQTT protocol and ThingsBoard IoT platform with MikroTik KNOT devices. This guide covers topology planning, ThingsBoard configuration, RouterOS scripting, and data visualization.

Bluetooth interface implementation in RouterOS allows devices to capture Bluetooth advertising packets broadcasted over channels 37, 38, and 39. MikroTik Bluetooth tags like TG-BT5-IN and TG-BT5-OUT broadcast advertising payloads over these channels, enabling asset tracking when used with KNOT devices.

DeviceDescription
KNOTIoT gateway with Bluetooth scanning capabilities
TG-BT5-INIndoor Bluetooth asset tracking tag
TG-BT5-OUTOutdoor Bluetooth asset tracking tag with extended range
  • Track assets equipped with Bluetooth tags across multiple KNOT devices
  • Monitor tag proximity and movement between zones
  • Capture RSSI signal strength for distance estimation
  • Integrate with ThingsBoard via MQTT for visualization
  • Configure filters for MAC addresses, payload data, and signal strength

/iot bluetooth scanners

Before implementing Bluetooth tag tracking, ensure you have:

RequirementSpecification
KNOT deviceWith Bluetooth interface
Bluetooth tagsTG-BT5-IN or TG-BT5-OUT
Network connectivityEthernet, Wi-Fi, or cellular
ThingsBoard serverCloud or local installation
  • RouterOS 7.x with IoT package installed
  • ThingsBoard instance (cloud or container-based)
  • MQTT broker configuration on KNOT

Tags can be configured using the MikroTik Beacon Manager app to broadcast payloads:

  • Automatically at fixed intervals
  • When movement is detected
  • When tilt is detected
  • When free-fall is detected

Deploy two KNOT devices (KNOT-A and KNOT-B) to create separate tracking zones. When a tag moves between zones, the server indicates which KNOT detected the tag.

For larger areas, deploy multiple KNOT devices with overlapping and non-overlapping Bluetooth ranges:

# Example: KNOT-A covers Zone A, KNOT-B covers Zone B
# Distance between KNOTs: 140m for non-overlapping ranges
# Each KNOT range: ~70m (environment-dependent)
EnvironmentTypical Range
Line of sight, no interferenceUp to 180m
Office environment30-100m
Industrial settingVariable based on interference

Use RSSI threshold filtering to control detection range:

RSSI ValueInterpretation
-40 dBmVery close proximity
-60 dBmModerate distance
-80 dBmEdge of detection range

Configure RSSI filtering in scripts to ignore weak signals from distant tags.

Before configuring RouterOS, set up ThingsBoard for MQTT communication. This guide uses the access-token authentication method for simplicity.

Create each KNOT as a gateway device in ThingsBoard:

  1. Navigate to Devices section
  2. Click + and select Add new device
  3. Configure device settings:
    • Name: Descriptive identifier (e.g., “KNOT-A-Warehouse”)
    • Check Is gateway option
  4. Repeat for each KNOT device

Set up unique access tokens for each KNOT:

  1. Select the device in ThingsBoard
  2. Navigate to Manage credentials
  3. Generate or assign access token
  4. Record credentials for RouterOS configuration

KNOT devices publish telemetry to the gateway topic:

topic = "v1/gateway/telemetry"

Configure MQTT broker connection on each KNOT:

/iot/mqtt/brokers/add name=tb address=x.x.x.x port=1883 username=KNOT-A_ACCESS_TOKEN
ParameterDescription
nameBroker identifier (used in scripts)
addressThingsBoard server IP or hostname
portMQTT port (1883 for non-SSL, 8883 for SSL)
usernameDevice access token from ThingsBoard

Verify tag detection before implementing tracking scripts:

/iot bluetooth scanners advertisements print
# 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

Filter for specific tag:

/iot bluetooth scanners advertisements print where address=DC:2C:6E:0F:C0:3D

Create a script to process advertisements and publish MQTT messages:

/system script add dont-require-permissions=no name=tracking 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 "tb"\r\
\n\r\
\n# MQTT topic where the message should be published\r\
\n:local topic "v1/gateway/telemetry"\r\
\n\r\
\n# POSIX regex for filtering advertisement Bluetooth addresses\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\r\
\n# To disable this filter, set it to ""\r\
\n:local advertisingDataRegex ""\r\
\n\r\
\n# Signal strength filter (e.g. -40 ignores signals weaker than -40dBm)\r\
\n# To disable this filter, set it to ""\r\
\n:local rssiThreshold ""\r\
\n\r\
\n# KNOT identifier for MQTT message\r\
\n:local gwName "KNOT_A"\r\
\n\r\
\n################################## Bluetooth ###############################\r\
\n:put ("[*] Gathering Bluetooth info...")\r\
\n\r\
\n:global makeRecord do={\r\
\n :local jsonStr "{\"ts\":\$ts,\"values\":{\"reporter\":\"\$gwName\",\"rssi\":\$rssi}}"\r\
\n :return \$jsonStr\r\
\n}\r\
\n\r\
\n# array of record strings collected for each advertising MAC address\r\
\n:global macRecords [:toarray ""]\r\
\n\r\
\n# process advertisements and update macRecords\r\
\n:local advertisements [/iot bluetooth scanners advertisements print detail as-value where\r\
\naddress ~ \$addressRegex and\r\
\ndata ~ \$advertisingDataRegex and\r\
\nrssi > \$rssiThreshold]\r\
\n\r\
\n/iot/bluetooth/scanners/advertisements clear\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 \r\
\n :local recordStr [\$makeRecord ts=\$epoch gwName=\$gwName rssi=\$rssi]\r\
\n \r\
\n :if ([:len (\$macRecords->\$address)] > 0) do={\r\
\n :local str (\$macRecords->\$address)\r\
\n :local newStr "\$str,\$recordStr"\r\
\n :set (\$macRecords->\$address) \$newStr\r\
\n } else={:set (\$macRecords->\$address) \$recordStr}\r\
\n}\r\
\n\r\
\n:local sendData true\r\
\n\r\
\n:if (\$sendData) do={\r\
\n :local jsonStr "{"\r\
\n \r\
\n :foreach addr,advRec in \$macRecords do={\r\
\n :set jsonStr "\$jsonStr\"\$addr\":[\$advRec],"}\r\
\n \r\
\n :local payloadlength\r\
\n :set payloadlength [:len (\$jsonStr)]\r\
\n :local remcom\r\
\n :set remcom [:pick \$jsonStr 0 (\$payloadlength-1)]\r\
\n :set jsonStr "\$remcom}"\r\
\n :local message\r\
\n :set message "\$jsonStr"\r\
\n :log info "\$message";\r\
\n :put ("[*] Message structured: \$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}"
ParameterDescriptionExample
brokerMQTT broker name"tb"
topicMQTT topic path"v1/gateway/telemetry"
addressRegexMAC address filter (POSIX regex)"^DC:2C:6E" or "" to disable
advertisingDataRegexPayload data filter"15ff4f09" for MikroTik tags
rssiThresholdMinimum RSSI value"-40" or "" to disable
gwNameKNOT identifier"KNOT_A"

The script generates JSON messages formatted for ThingsBoard Gateway API:

{
"2C:C8:1B:4B:BB:0A": [
{
"ts": 1678967250600,
"values": {
"reporter": "KNOT_A",
"rssi": -47
}
}
],
"DC:2C:6E:0F:C0:3D": [
{
"ts": 1678967247850,
"values": {
"reporter": "KNOT_A",
"rssi": -59
}
}
]
}
FieldDescription
MAC addressTag identifier
tsUnix timestamp in milliseconds
reporterKNOT device that detected the tag
rssiSignal strength in dBm

Automate script execution with scheduler:

/system/scheduler/add name=bluetoothscheduler interval=30s on-event="/system/script/run tracking"
Use CaseIntervalNotes
Real-time tracking10-15sHigher data freshness
Standard monitoring30s-1mBalanced approach
Low-frequency updates15m+Reduced message volume

For TG-BT5-OUT tags with temperature sensing, use this enhanced script:

/system script add dont-require-permissions=no name=tracking+temp owner=admin policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="# Required packages: iot\r\
\n\r\
\n################################ Configuration ##############################\r\
\n:local broker "tb"\r\
\n:local topic "v1/gateway/telemetry"\r\
\n:local addressRegex ""\r\
\n:local advertisingDataRegex ""\r\
\n:local rssiThreshold ""\r\
\n:local gwName "1"\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 :return [:tonum "0x\$msb\$lsb"]\r\
\n}\r\
\n\r\
\n:global from88 do={\r\
\n :global invertU16\r\
\n :global le16ToHost\r\
\n :local num [\$le16ToHost \$1]\r\
\n :if (\$num & 0x8000) do={\r\
\n :set \$num (-1 * ([\$invertU16 \$num] + 1))\r\
\n }\r\
\n :return ((\$num * 125) / 32)\r\
\n}\r\
\n\r\
\n:put ("[*] Gathering Bluetooth info...")\r\
\n\r\
\n:global makeRecord do={\r\
\n :local jsonStr "{\"ts\":\$ts,\"values\":{\"KNOT_\$gwName\":\"\$gwName\",\"temp\":\$temp}}"\r\
\n :return \$jsonStr\r\
\n}\r\
\n\r\
\n:global macRecords [:toarray ""]\r\
\n\r\
\n:local advertisements [/iot bluetooth scanners advertisements print detail as-value where\r\
\naddress ~ \$addressRegex and\r\
\ndata ~ \$advertisingDataRegex and\r\
\nrssi > \$rssiThreshold]\r\
\n\r\
\n/iot/bluetooth/scanners/advertisements clear\r\
\n\r\
\n:foreach adv in \$advertisements do={\r\
\n :local address (\$adv->"address")\r\
\n :local ad (\$adv->"data")\r\
\n :local rssi (\$adv->"rssi")\r\
\n :local epoch (\$adv->"epoch")\r\
\n :local temp [\$from88 [:pick \$ad 28 32]]\r\
\n \r\
\n :local recordStr [\$makeRecord ts=\$epoch gwName=\$gwName temp=\$temp]\r\
\n \r\
\n :if ([:len (\$macRecords->\$address)] > 0) do={\r\
\n :local str (\$macRecords->\$address)\r\
\n :local newStr "\$str,\$recordStr"\r\
\n :set (\$macRecords->\$address) \$newStr\r\
\n } else={:set (\$macRecords->\$address) \$recordStr}\r\
\n}\r\
\n\r\
\n:local sendData true\r\
\n\r\
\n:if (\$sendData) do={\r\
\n :local jsonStr "{"\r\
\n :foreach addr,advRec in \$macRecords do={\r\
\n :set jsonStr "\$jsonStr\"\$addr\":[\$advRec],"}\r\
\n :local payloadlength\r\
\n :set payloadlength [:len (\$jsonStr)]\r\
\n :local remcom\r\
\n :set remcom [:pick \$jsonStr 0 (\$payloadlength-1)]\r\
\n :set jsonStr "\$remcom}"\r\
\n :local message\r\
\n :set message "\$jsonStr"\r\
\n :log info "\$message";\r\
\n :put ("[*] Message structured: \$message")\r\
\n /iot mqtt publish broker="\$broker" topic="\$topic" message=\$message\r\
\n}"

The temperature value is decoded from the 8.8 fixed-point format in the tag payload at bytes 28-32.

When the script runs, ThingsBoard automatically creates devices for new MAC addresses:

  1. Navigate to Devices section
  2. New tags appear as devices
  3. Each tag shows latest telemetry data
  1. Select a tag device
  2. Go to Latest telemetry
  3. Select reporter or temp parameter
  4. Click Show on widget
  5. Choose widget type (e.g., Timeseries table)
  6. Add to new or existing dashboard

Configure time windows for data display:

# Recommended: Realtime-current day for ongoing tracking

Create a consolidated dashboard showing all tracked assets:

  1. Add widget for each unique tag
  2. Configure time window
  3. Enable auto-refresh
  4. Add alert thresholds if needed

Track pallets and inventory across warehouse zones:

  • Install KNOT at zone entry/exit points
  • Attach TG-BT5-IN tag to each asset
  • Monitor asset movement between zones
  • Generate dwell time reports

Track assets during transport:

  • Install KNOT in delivery vehicles
  • Attach TG-BT5-OUT tag to cargo
  • Monitor cargo location during transit
  • Receive alerts when cargo enters/exits vehicles

Monitor temperature-sensitive shipments:

  • Use TG-BT5-OUT tags with temperature sensing
  • Configure temperature alerts in ThingsBoard
  • Track temperature history during transit
  • Generate compliance reports

Implement location-based triggering:

  • Configure RSSI thresholds for zone boundaries
  • Trigger actions when tags enter/exit zones
  • Integrate with other automation systems
  • Log access events for audit trails
  1. Verify Bluetooth interface is enabled
  2. Check tag is within KNOT range
  3. Confirm tag is broadcasting (Beacon Manager app)
  4. Review KNOT logs for Bluetooth errors
/log print where message~"bluetooth"
  1. Verify broker address and port
  2. Check access token validity
  3. Confirm network connectivity
  4. Review firewall rules
/iot/mqtt/brokers/print
/iot/mqtt/publish print
  1. Verify script is running via scheduler
  2. Check script execution logs
  3. Confirm topic format matches ThingsBoard API
  4. Validate JSON message structure
/system/script/run tracking
:put ([/log info])
  1. Adjust RSSI threshold for environment
  2. Reduce tag broadcast interval
  3. Check for 2.4GHz interference
  4. Verify KNOT placement for optimal coverage
IssueSolution
High CPU usageIncrease scheduler interval
Missing detectionsReduce number of tracked tags
Slow updatesDecrease scheduler interval
Memory issuesRestart KNOT device
  • /iot bluetooth scanners advertisements print - View detected advertisements
  • /iot bluetooth scanners advertisements clear - Clear advertisement cache
  • /iot mqtt publish - Publish MQTT messages
  • /system script run - Execute tracking script
  • /system scheduler - Configure automated script execution