Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

015ManufacturerDatacompany identifier
1FFManufacturerDatacompany identifier
24FManufacturerDatacompany identifier
309ManufacturerDatacompany identifier
401Versionthe version of this advertisement structure
500UserDatauser-configured part of the payload
6xx*Secretsecret: salt
7xx*Secretsecret: salt
8xx*Secretsecret: acceleration on the X-axis
9xx*Secretsecret: acceleration on the X-axis
10xx*Secretsecret: acceleration on the Y-axis
11xx*Secretsecret: acceleration on the Y-axis
12xx*Secretsecret: acceleration on the Z-axis
13xx*Secretsecret: acceleration on the Z-axis
14xx*Secretsecret: temperature
15xx*Secretsecret: temperature
16xx*Secretsecret: uptime
17xx*Secretsecret: uptime
18xx*Secretsecret: uptime
19xx*Secretsecret: uptime
2000Secretsecret: flags
2164Secretsecret: batteryPercentage

* - can vary

...

Example

An example of the payload configured in MikroTik's format (non-encrypted) would be:

15ff4f090100cea6000000000200a01c91085700005f

...

5f (22nd octet) → battery percentage of the tag = 95 %. 0x5f from hex to dec is95.

...

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
}

...

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
[admin@MikroTik] > 

As you can see from the example above, the script will "translate" all payloads from a hexadecimal format to a decimal format.

Info
titlenote!

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.
So, if you see the temperature as temp=25546, the real temperature is 25.546 C and if you see accelZ=15, the real acceleration against the z-axis will be 0.015 m/s2.

iBeacon packet structure

iBeacon is one of the supported advertising packet types. You can find more information about the protocol following the link.

...