Table of Contents |
---|
Introduction
TG-BT5-XX tags can operate in 4 different modes:
...
"AdvData" field structure (max 31 octets/bytes):
Length | length of the payload | 1 octet (15 ) |
Type | manufacturer specific data | 1 octet (ff ) |
ManufacturerData | company identifier |
2 octets ( |
15FF4F09
4F09 ) | ||
Version | the version of this advertisement structure | 1 octet (uint) |
UserData | user-configured part of the payload | 1 octet (uint) |
Secret | optionally encrypted (AES-ECB) part of the payload |
|
Info |
---|
Please note that all multi-byte values are in little-endian. Meaning, that if, for example, you want to get the temperature value and #14 and #15 octets indicate the temperature as "a1 19" ("plus" temperature) → the real temperature value is going to be (0x19a1)/256 = 25.6 C. |
...
MikroTik PDU Payload structure
0 | 15 | ManufacturerData | Length | length of the payloadcompany identifier |
1 | FF | ManufacturerData | Type | manufacturer specific datacompany identifier |
2 | 4F | ManufacturerDataCompany identifiercompany identifier | MikroTik | |
3 | 09 | ManufacturerDataCompany identifiercompany identifier | MikroTik | |
4 | 01 | Version | the version of this advertisement structure | |
5 | 00 | UserData | user-configured part of the payload | |
6 | xx* | Secret | secret: salt | |
7 | xx* | Secret | secret: salt | |
8 | xx* | Secret | secret: acceleration on the X-axis | |
9 | xx* | Secret | secret: acceleration on the X-axis | |
10 | xx* | Secret | secret: acceleration on the Y-axis | |
11 | xx* | Secret | secret: acceleration on the Y-axis | |
12 | xx* | Secret | secret: acceleration on the Z-axis | |
13 | xx* | Secret | secret: acceleration on the Z-axis | |
14 | xx* | Secret | secret: temperature | |
15 | xx* | Secret | secret: temperature | |
16 | xx* | Secret | secret: uptime | |
17 | xx* | Secret | secret: uptime | |
18 | xx* | Secret | secret: uptime | |
19 | xx* | Secret | secret: uptime | |
20 | 00 | Secret | secret: flags | |
2164 | xx* | Secret | secret: batteryPercentage |
...
- - can vary
Example
An example of the payload configured in MikroTik's format (non-encrypted) would be:
15ff4f090100cea6000000000200a01c91085700005f |
---|
15ff4f09 (first 4 octets) → ManufacturerData. Is the same (constant data) for every MikroTik format's payloadLength (0x15 hex-to-dec is 21). Type (0xff). Company identifier (0x4f09).
01 (5th 4th octet) → Current version of the payload's structure. Should be the same for every payload (constant data).
00 (6th 5th octet) → Indicates that the payload is not encrypted. "01" would mean it is encrypted.
cea6 (6th and 7th and 8th octets) → data used for encryption.
0000 (9th and 10th octets) → acceleration on the X-axis at the moment of the broadcast = 0 m/s2. Check acceleration calculation for the Z-axis below.
Salt. Each new payload should have a different salt value generated. You can use this value to check whether the identical payloads are encrypted differently. The value itself does not contain any useful information. If you see that the salt value is identical for two payloads received during different time intervals, it would mean that the two payloads received are exactly identical. You can calculate the salt value using the same principle that applies to the uptime calculation (17th to 20th octets) - see below.
0000 (8th and 9th 0000 (11th and 12th octets) → acceleration on the YX-axis at the moment of the broadcast = 0 m/s2. Check acceleration calculation for the Z-axis below.
0200 0000 (13th 10th and 14th 11th octets) → acceleration on the ZY-axis at the moment of the broadcast = 0 .0078m/s2. Check acceleration calculation for the Z-axis below.
0200 (12th and 13th octets) → acceleration on the Z-axis at the moment of the broadcast = 0.0078 m/s2. To To get the decimal value out of the hex format you will need to follow the steps:
- As noted before, multi-byte values are in little-endian and that means, to calculate the realm value, you will need to switch octets places (switch octets order). So the first step is to swap places for the values from 0x0200 to 0x0002. 0x0002 converted from hexadecimal to decimal is 02.
- Keep in mind that acceleration is in signed 8.8 fixed point format (two's complement) and that means that you basically need to divide the result by "256". The second step is to divide the value by 256 → (0x0002 hex or 02 dec)/256 = 0.0078 m/s2.
- The same calculation principle applies to the acceleration for the X and Y-axis. In our example, they just happen to be 0 → 0x0000/256=0.
a01c (14th and 15th and 16th octets) → temperature detected by the tag in Celsius = 28.625 C. Temperature is in little-endian (as it is a multi-byte value) and it is in signed 8.8 fixed point format, so the same "formula" applies here as well:
- 0x1ca0/256=28.625 C.
...
octets) → temperature detected by the tag in Celsius = 28.625 C. Temperature is in little-endian (as it is a multi-byte value) and it is insigned 16-bit integer [twos complement] 8.8 fixed point format, so the same "formula" applies here as well:
- 0x1ca0/256=28.625 C.
91085700 (16th to 19th octets) → tag's uptime in seconds = 5703825 s. 0x91085700 is in little-endian, so just swap the octets to 0x00570891 and the result is 5703825 in decimal. That is 1584.395833 hours or66-day uptime.
00 (20th octet) → trigger (flag) that sent the payload. If it is "00" it means that no trigger was detected and that it is just a periodically broadcasted payload (based on the advertisement interval configured for the tag). If the value would be "04" it would mean that the device was dropped (freefalling triggered). You can find more information on the "flags" and the "Secret" section above in the packet structure section.
5f (21st octet) → battery percentage of the tag = 95 %. 0x5f from hex to dec is95.
Info |
---|
Starting with v7.11, you can use the Peripheral Device section or/and Decode-ad feature to view decoded values! |
Script for decoding
Add a new script under the "System>Scripts" tab and import the script there (for non-encrypted payloads).
# 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 "2C:C8:1B:4B:BB:0A"# 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 ""################################## Bluetooth ##################################
:global invertU16 do={
:local inverted 0
:for idx from=0 to=15 step=1 do={
:local mask (1 << $idx)
:if ($1 & $mask = 0) do={
:set $inverted ($inverted | $mask)
}
}
return $inverted
}:global le16ToHost do={
:local lsb [:pick $1 0 2]
:local msb [:pick $1 2 4]:return [:tonum "0x$msb$lsb"]
}:local le32ToHost do={
:local lsb [:pick $1 0 2]
:local midL [:pick $1 2 4]
:local midH [:pick $1 4 6]
:local msb [:pick $1 6 8]:return [:tonum "0x$msb$midH$midL$lsb"]
}:local from88 do={
:global invertU16
:global le16ToHost
:local num [$le16ToHost $1]# Handle negative numbers
:if ($num & 0x8000) do={
:set num (-1 * ([$invertU16 $num] + 1))
}# Convert from 8.8. Scale by 1000 since floating point is not supported
:return (($num * 125) / 32)
}:local flagStr do={
:local str "":if ($1 & 0x01) do={ :set $str " switch" }
:if ($1 & 0x02) do={ :set $str "$str tilt" }
:if ($1 & 0x04) do={ :set $str "$str free_fall" }
:if ($1 & 0x08) do={ :set $str "$str impact_x" }
:if ($1 & 0x10) do={ :set $str "$str impact_y" }
:if ($1 & 0x20) do={ :set $str "$str impact_z" }:if ([:len $str] = 0) do={ :return "" }
:return [:pick $str 1 [:len $str]]
}# Find fresh Bluetooth advertisements
: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 advertisements [/iot bluetooth scanners advertisements print detail \
as-value where \
epoch > $btOldestAdvertisementTimestamp and \
address ~ $addressRegex and \
data ~ $advertisingDataRegex and \
rssi > $rssiThreshold
]
:local advCount 0
:local lastAdvTimestamp 0
:local advJson ""
:local advSeparator ""# 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 rssi ($adv->"rssi")
:local epoch ($adv->"epoch")
:local ad ($adv->"data")
:local version [:tonum "0x$[:pick $ad 8 10]"]
:local encrypted [:tonum "0x$[:pick $ad 10 12]"]
:local salt [$le16ToHost [:pick $ad 12 16]]
:local accelX [$from88 [:pick $ad 16 20]]
:local accelY [$from88 [:pick $ad 20 24]]
:local accelZ [$from88 [:pick $ad 24 28]]
:local temp [$from88 [:pick $ad 28 32]]
:local uptime [$le32ToHost [:pick $ad 32 40]]
:local flags [:tonum "0x$[:pick $ad 40 42]"]
:local bat [:tonum "0x$[:pick $ad 42 44]"]:put ("$advCount: \
address=$address \
ts=$epoch \
rssi=$rssi \
version=$version \
encrypted=$encrypted \
salt=$salt \
accelX=$accelX \
accelY=$accelY \
accelZ=$accelZ \
temp=$temp \
uptime=$uptime \
flags=\"$[$flagStr $flags]\" \
bat=$bat" \
)
:set $advCount ($advCount + 1)
:set $lastAdvTimestamp $epoch
}
:if ($advCount > 0) do={
:set $btOldestAdvertisementTimestamp $lastAdvTimestamp
}
The only line that you need to alter is the:
:local addressRegex "2C:C8:1B:4B:BB:0A"
line, where you need to input the MAC address of the tag.
Save the script with whichever name you like, for example, decode.
Run the script via the command line interface ("New Terminal" button in Winbox/Webfig):
Code Block | ||||
---|---|---|---|---|
| ||||
[admin@MikroTik] > system script run decode
0: address=2C:C8:1B:4B:BB:0A ts=1662553431348 rssi=-45 version=1 encrypted=0 salt=57919 accelX=3 accelY=-35 accelZ=-70 temp=25535 uptime=1046174 flags="" bat=99
1: address=2C:C8:1B:4B:BB:0A ts=1662553436349 rssi=-40 version=1 encrypted=0 salt=24154 accelX=-19 accelY=-23 accelZ=0 temp=25546 uptime=1046179 flags="" bat=99
2: address=2C:C8:1B:4B:BB:0A ts=1662553446351 rssi=-37 version=1 encrypted=0 salt=37822 accelX=-15 accelY=35 accelZ=15 temp=25550 uptime=1046189 flags="" bat=99 |
As you can see from the example above, the script will "translate" all payloads from a hexadecimal format to a decimal format and print them into the terminal.
You can also alter the script further to structure a message out of the "already decoded" values and post it to an EMAIL, MQTT, or HTTP server of your choice but! please keep in mind that it might load the device more. So you need to test the performance when running the script. It will be easier on RouterOS resources when the decoding is done on the server side.
Info |
---|
Because of the fact that floating point is not supported → every calculation behind a decimal point will be "rounded up" to a whole number. This is why the script will calculate the temperature and acceleration values scaled by 1000 (multiplied by 1000). |
...
iBeacon packet structure
iBeacon is one of the supported advertising packet types. You can find more information about the protocol following the link.
...
0 | 1a | ManufacturerData | company identifier |
1 | ff | ManufacturerData | company identifier |
2 | 4c | ManufacturerData | company identifier |
3 | 00 | ManufacturerData | company identifier |
4 | 02 | BeaconType | a secondary identifier |
5 | 1521 | RemainingDataLength | defines the remaining length for the payload in bytes |
6b2 | xx* | UserData | Proximity UUID |
7b9 | xx* | UserData | Proximity UUID |
88d | xx* | UserData | Proximity UUID |
9e4 | xx* | UserData | Proximity UUID |
10c8 | xx* | UserData | Proximity UUID |
111c | xx* | UserData | Proximity UUID |
1247 | xx* | UserData | Proximity UUID |
13c2 | xx* | UserData | Proximity UUID |
14b1 | xx* | UserData | Proximity UUID |
154e | xx* | UserData | Proximity UUID |
1679 | xx* | UserData | Proximity UUID |
171b | xx* | UserData | Proximity UUID |
183e | xx* | UserData | Proximity UUID |
1955 | xx* | UserData | Proximity UUID |
2087 | xx* | UserData | Proximity UUID |
21ec | xx* | UserData | Proximity UUID |
22 | xx* | UserData | Major Number |
23 | xx* | UserData | Major Number |
24 | xx* | UserData | Minor Number |
25 | xx* | UserData | Minor Number |
26 | xx* | TxPower | indicates the signal strength at one meter from the device |
...
0 | 03 | CommonPayload | CompleteUUID |
1 | 03 | CommonPayload | CompleteUUID |
2 | aa | CommonPayload | CompleteUUID |
3 | fe | CommonPayload | CompleteUUID |
4 | 17 | CommonPayload | ServiceData |
5 | 16 | CommonPayload | ServiceData |
6 | aa | CommonPayload | ServiceData |
7 | fe | CommonPayload | ServiceData |
8 | 00 | CommonPayload | FrameType |
9 | xx* | UidPayload | Ranging Data |
10b2 | xx* | UidPayload | Nspace |
11b9 | xx* | UidPayload | Nspace |
128d | xx* | UidPayload | Nspace |
13e4 | xx* | UidPayload | Nspace |
14c8 | xx* | UidPayload | Nspace |
151c | xx* | UidPayload | Nspace |
1647 | xx* | UidPayload | Nspace |
17c2 | xx* | UidPayload | Nspace |
18b1 | xx* | UidPayload | Nspace |
194e | xx* | UidPayload | Nspace |
20 | xx* | UidPayload | Instance |
21 | xx* | UidPayload | Instance |
22 | xx* | UidPayload | Instance |
23 | xx* | UidPayload | Instance |
24 | xx* | UidPayload | Instance |
25 | xx* | UidPayload | Instance |
26 | 00 | UidPayload | RFU1 |
27 | 00 | UidPayload | RFU2 |
...