MikroTik Tag advertisement formats

TG-BT5-XX 标签可以在 4 种不同的模式下运行:

简介

TG-BT5-XX 标签可以在 4 种不同的模式下运行:

  • 工厂休眠模式
  • 配置模式
  • 广播模式
  • 升级模式

在广播模式下,标签将通过 Bluetooth 广告数据包广播关于自身的信息。信息取决于广告数据包类型。

目前,以下是所有可以使用 MikroTik Beacon Manager 应用配置的受支持类型:

DeviceName、MikroTik、Eddystone-TLM、Eddystone-UID 和 iBeacon。

Bluetooth 技术数据交换期间使用 2 种类型的信道(各自使用不同的频率):

  • 专用于数据传输的数据信道;
  • 专用于广播的广播信道。

共有 40 个唯一的频段(信道),每个频段间隔 2 MHz。信道 37、38 和 39 用于广播,信道 0-36 用于数据传输。

在广播过程中,会广播 BLE 广播数据包。该数据包包含 Preamble、Access Address、PDU 和 CRS 字段。

Preamble 和 Access Address 字段帮助接收方检测帧。CRS 字段用于检查错误。PDU 定义了数据包本身。

MikroTik 标签支持 legacy 不可连接、不可扫描的非定向广播(ADV_NOCONN_IND)。在这种情况下,Payload 由 "AdvA"(包含广播者地址信息的字段)和 "AdvData"(包含数据信息的字段)字段组成。

1 octet = 1 byte = 8 bits

属性描述
Preamble1 octet
Access-Address4 octets
PDUPDU Header = 2 octets PDU Payload = AdvA (6 octets)+AdvData (0...31 octets)
CRS3 octets

MikroTik 数据包结构

"AdvData" 字段结构(最多 31 octets/bytes):

属性描述
FlagsFlags 字段的长度、类型和数据。用于 "告知" 周围的扫描器设备处于何种状态。3 octets (020106): 1st octet - length 2nd octet - type 3d octet - data
Length负载的长度。1 octet (15)
Type制造商特定数据。1 octet (ff)
ManufacturerData公司标识符。2 octets (4F09)
Version此广告结构的版本。1 octet (uint)
UserData用户配置的负载部分。1 octet (uint)
Secret可选的已加密(AES-ECB)负载部分。secret: salt(用于加密)= 2 octets (uint) secret: acceleration(加速度,采用带符号 8.8 定点格式,所有 3 个轴的加速度(0=x, 1=y, 2=z))= 6 octets (uint) secret: temperature(环境温度,摄氏度,采用带符号 8.8 定点格式)= 2 octet (int) secret: uptime(运行时间,以秒为单位)= 4 octets (uint) secret: flags(标志位掩码)= 1 octet (uint) secret: batteryPercentage(电池电量百分比)= 1 octet (uint)

信息

请注意,所有多字节值均采用 little-endian。也就是说,例如,如果你想获取温度值,且第 14 和第 15 个 octet 将温度表示为 "a1 19"("正" 温度)→ 实际温度值为 (0x19a1)/256 = 25.6 C。 稍后将在 PDU payload structure 部分展示如何将 hexadecimal 负载值转换为 decimal 值的详细示例。

UserData 和 Secret 字段通过 flags 进行配置。在 "UserData" 部分中,控制 "Secret" 是否加密的参数称为 FLAG_ENCRYPTED。当 FLAG_ENCRYPTED=0 时,表示 secret 未加密(第 6 个 octet 中的第 1 位将设置为 0);当 FLAG_ENCRYPTED=1 时,表示 secret 已加密(第 6 个 octet 中的第 1 位将设置为 1)。

在 "Secret" 部分中,有 7 个 flags(第 24 个 octet):

  • FLAG_REED_SWITCH(第 1 位 - 如果设置为 1,表示广播时刻簧片开关处于闭合状态)
  • FLAG_ACCEL_TILT(第 2 位 - 如果设置为 1,表示广告是通过倾斜设备发送的)
  • FLAG_ACCEL_FREE_FALL(第 3 位 - 如果设置为 1,表示广告是通过跌落设备发送的)
  • FLAG_IMPACT_X(第 4 位 - 如果设置为 1,表示广告时刻 x 轴发生了撞击)
  • FLAG_IMPACT_Y(第 5 位 - 如果设置为 1,表示广告时刻 y 轴发生了撞击)
  • FLAG_IMPACT_Z(第 6 位 - 如果设置为 1,表示广告时刻 z 轴发生了撞击)
  • FLAG_ACCEL_DISABLED(第 7 位 - 如果设置为 1,表示加速度计已被禁用) 例如,如果你看到十六进制消息的第 24 个 octet 是 "02"(将 hexadecimal 值 "02" 转换为 binary 得到 "0010" → 第 2 位设置为 1)→ 这意味着设备发生了倾斜。如果你看到 "04"(hex "04" 转 bin 为 "0100" → 第 3 位设置为 1)→ 这意味着设备被跌落(触发了自由落体)。如果你看到 "38"(hex 转 bin 为 "00111000" → 第 4、5 和 6 位设置为 1)→ 这意味着在发送广告时,加速度计在所有 3 个 x/y/z 轴上检测到了撞击/唤醒。

更多示例(针对第 24 octet 的值):

  • "08" 表示仅在 x 轴发生撞击;
  • "18" 表示在 x 和 y 轴发生撞击;
  • "28" 表示在 x 和 z 轴发生撞击;
  • "10" 表示仅在 y 轴发生撞击;
  • "30" 表示在 y 和 z 轴发生撞击;
  • "20" 表示仅在 z 轴发生撞击。
  • "40" 表示加速度计已禁用。

MikroTik PDU Payload 结构

OctetValue
002Flagsflags 字段的长度
101Flagsflags 字段的类型
206Flagsflags 字段的数据
315Length负载的长度
4FFType制造商特定数据
54FCompany identifierMikroTik
609Company identifierMikroTik
701Version此广告结构的版本
800UserData用户配置的负载部分
9xx*Secretsecret: salt
10xx*Secretsecret: salt
11xx*Secretsecret: acceleration on the X-axis
12xx*Secretsecret: acceleration on the X-axis
13xx*Secretsecret: acceleration on the Y-axis
14xx*Secretsecret: acceleration on the Y-axis
15xx*Secretsecret: acceleration on the Z-axis
16xx*Secretsecret: acceleration on the Z-axis
17xx*Secretsecret: temperature
18xx*Secretsecret: temperature
19xx*Secretsecret: uptime
20xx*Secretsecret: uptime
21xx*Secretsecret: uptime
22xx*Secretsecret: uptime
2300Secretsecret: flags
24xx*Secretsecret: batteryPercentage

* - 可能发生变化

示例

MikroTik 格式(非加密)配置的负载示例如下:

02010615ff4f090100cea6000000000200a01c91085700005f

020106(前 3 个 octets)→ flags 字段的长度、类型和数据。

15ff4f09(第 4、5、6 和第 7 个 octets)→ Length(0x15 hex-to-dec 为 21)。Type(0xff)。公司标识符(0x4f09)。

01(第 8 个 octet)→ 负载结构的当前版本。对于每个负载都应相同(常量数据)。

00(第 9 个 octet)→ 表示负载未加密。"01" 则表示已加密。

cea6(第 10 和第 11 个 octets)→ Salt。每个新负载都应生成不同的 salt 值。你可以使用此值来检查相同的负载是否以不同的方式加密。该值本身不包含任何有用信息。如果你看到在不同时间间隔内接收到的两个负载的 salt 值相同,则意味着收到的这两个负载完全相同。你可以使用与 uptime 计算相同的原理来计算 salt 值 - 见下文。

0000(第 12 和第 13 个 octets)→ 广播时刻 X 轴上的加速度 = 0 m/s<sup>2</sup>。请查看下方 Z 轴的加速度计算。

0000(第 14 和第 15 个 octets)→ 广播时刻 Y 轴上的加速度 = 0 m/s<sup>2</sup>。请查看下方 Z 轴的加速度计算。

0200(第 16 和第 17 个 octets)→ 广播时刻 Z 轴上的加速度 = 0.0078 m/s<sup>2</sup>。要从十六进制格式得出十进制值,你需要按照以下步骤操作:

  • 如前所述,多字节值采用 little-endian,这意味着要计算实际值,你需要切换 octets 的位置(交换 octets 顺序)。因此第一步是将值从 0x0200 交换为 0x0002。0x0002 从 hexadecimal 转换为 decimal 为 02。
  • 请记住,加速度采用带符号的 8.8 定点格式(二进制补码),这意味着你基本上需要将结果除以 "256"。第二步是将该值除以 256 → (0x0002 hex 或 02 dec)/256 = 0.0078 m/s<sup>2</sup>。
  • 相同的计算原理适用于 X 和 Y 轴的加速度。在我们的示例中,它们恰好为 0 → 0x0000/256=0。

a01c(第 18 和第 19 个 octets)→ 标签检测到的摄氏温度 = 28.625 C。温度采用 little-endian(因为它是多字节值),并且采用 signed 16-bit integer [twos complement] 8.8 fixed point format,因此同样的 "公式" 在这里也适用: 0x1ca0/256=28.625 C。

91085700(第 20、21、22、23 个 octets)→ 标签的 uptime(以秒为单位)= 5703825 s。0x91085700 采用 little-endian,所以只需将 octets 交换为 0x00570891,结果就是十进制的 5703825。即 1584.395833 小时或 66 天的运行时间。

00(第 24 个 octet)→ 发送该负载的触发器(flag)。如果为 "00",表示未检测到触发器,只是基于为标签配置的广告间隔周期性广播的负载。如果该值为 "04",则表示设备被跌落(触发了自由落体)。你可以在上文的 packet structure 部分中的 "flags" 和 "Secret" 部分找到更多信息。

5f(第 25 个 octet)→ 标签的电池百分比 = 95 %。0x5f 从 hex 到 dec 为 95。

信息

从 v7.11 开始,你可以使用 Peripheral Device 部分和/或 Decode-ad 功能查看解码后的值!

#### 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 &lt;&lt; $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 14 16]"]
    :local encrypted [:tonum "0x$[:pick $ad 16 18]"]
    :local salt [$le16ToHost [:pick $ad 18 22]]
    :local accelX [$from88 [:pick $ad 22 26]]
    :local accelY [$from88 [:pick $ad 26 30]]
    :local accelZ [$from88 [:pick $ad 30 34]]
    :local temp [$from88 [:pick $ad 34 38]]
    :local uptime [$le32ToHost [:pick $ad 38 46]]
    :local flags [:tonum "0x$[:pick $ad 46 48]"]
    :local bat [:tonum "0x$[:pick $ad 48 50]"]
    :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
}

你唯一需要修改的行是:

:local addressRegex "2C:C8:1B:4B:BB:0A"

该行中你需要输入标签的 MAC 地址。

用你喜欢的任意名称保存脚本,例如 decode。

通过命令行界面运行脚本(Winbox/Webfig 中的 "New Terminal" 按钮):

[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

从上方的示例可以看出,脚本会将所有负载从 hexadecimal 格式 "转换" 为 decimal 格式,并将它们打印到终端。

你还可以进一步修改脚本,将 "已解码" 的值组织成消息,并发布到你选择的 EMAIL、MQTT 或 HTTP 服务器 但是! 请记住,这可能会增加设备负载。因此,你需要测试运行脚本时的性能。在服务器端进行解码对 RouterOS 资源更友好。

信息

由于不支持浮点运算 → 小数点后的每次计算都将 "向上取整" 为整数。这就是脚本会将温度和加速度值 放大 1000 倍(乘以 1000)来计算的原因。 因此,如果你看到温度显示为 temp=25546,实际温度是 25.546 C(25546/1000);如果你看到 accelZ=15,z 轴上的实际加速度是 0.015 m/s<sup>2</sup>(15/1000)。

iBeacon 数据包结构

iBeacon 是受支持的广告数据包类型之一。你可以通过链接找到有关该协议的更多信息。

在这种情况下,PDU Payload 由 "AdvA"(6 个 octets 长)和 "AdvData"(包含数据信息的字段)字段组成。Legacy Bluetooth 设备只能支持 31 字节长的 beacon 消息。UUID 为 16 字节长(MikroTik 默认 UID=b2b98de4-c81c-47c2-b14e-791b3e5587ec)。

"AdvData" 字段结构:

属性描述
FlagsFlags 字段的长度、类型和数据。用于 "告知" 周围的扫描器设备处于何种状态。3 octets (020106): 1st octet - length 2nd octet - type 3d octet - data
ManufacturerData公司标识符4 octets (1aff4c00)
BeaconType次要标识符1 octet (const)
RemainingDataLength定义负载的剩余长度(以字节为单位)1 octet (const)
UserData用户配置的负载部分Proximity UUID(通用唯一标识符)= 16 octets (uint) Major Number(特定组标识符)= 2 octets (uint) Minor Number(特定 beacon 标识符)= 2 octets (uint)
TxPower表示距设备一米处的信号强度1 octet (int)

iBeacon PDU Payload 结构

OctetValue
002Flagsflags 字段的长度
101Flagsflags 字段的类型
206Flagsflags 字段的数据
31aManufacturerData公司标识符
4ffManufacturerData公司标识符
54cManufacturerData公司标识符
600ManufacturerData公司标识符
702BeaconType次要标识符
815RemainingDataLength定义负载的剩余长度(以字节为单位)
9xx*UserDataProximity UUID
10xx*UserDataProximity UUID
11xx*UserDataProximity UUID
12xx*UserDataProximity UUID
13xx*UserDataProximity UUID
14xx*UserDataProximity UUID
15xx*UserDataProximity UUID
16xx*UserDataProximity UUID
17xx*UserDataProximity UUID
18xx*UserDataProximity UUID
19xx*UserDataProximity UUID
20xx*UserDataProximity UUID
21xx*UserDataProximity UUID
22xx*UserDataProximity UUID
23xx*UserDataProximity UUID
24xx*UserDataProximity UUID
25xx*UserDataMajor Number
26xx*UserDataMajor Number
27xx*UserDataMinor Number
28xx*UserDataMinor Number
29xx*TxPower表示距设备一米处的信号强度

* - 可能发生变化

Eddystone-TLM 数据包结构

Eddystone-TLM 是受支持的广告数据包类型之一。你可以通过链接找到有关该协议的更多信息。

在这种情况下,PDU Payload 由 "AdvA"(6 个 octets 长)和 "AdvData"(包含数据信息的字段)字段组成。MikroTik 默认 CompleteUUID=03 03 aa fe;ServiceData=11 16 aa fe。

"AdvData" 字段结构:

属性描述
FlagsFlags 字段的长度、类型和数据。用于 "告知" 周围的扫描器设备处于何种状态。3 octets (020106): 1st octet - length 2nd octet - type 3d octet - data
CommonPayload对所有 Eddystone 帧类型通用的广告负载部分CompleteUUID(通用唯一标识符)= 4 octets (const) ServiceData(16 bit UUID 数据类型)= 4 octets (const) FrameType(值 = 0x20)= 1 octet (const)
TlmPayloadEddystone-TLM 帧负载Version(TLM 版本)= 1 octet (const) BatteryVoltageMv(电池电压,1 mV/bit)= 2 octets (uint) TemperatureC(Beacon 温度,摄氏度)= 2 octets (int) AdvertisementCount(广告 PDU 计数)= 4 octets (uint) UptimeCounter(自开机或重启以来的时间)= 4 octets (uint)

Eddystone-TLM PDU Payload 结构

OctetValue
002Flagsflags 字段的长度
101Flagsflags 字段的类型
206Flagsflags 字段的数据
303CommonPayloadCompleteUUID
403CommonPayloadCompleteUUID
5aaCommonPayloadCompleteUUID
6feCommonPayloadCompleteUUID
711CommonPayloadServiceData
816CommonPayloadServiceData
9aaCommonPayloadServiceData
10feCommonPayloadServiceData
1120CommonPayloadFrameType
1200TlmPayloadVersion
13xx*TlmPayloadBatteryVoltageMv
14xx*TlmPayloadBatteryVoltageMv
15xx*TlmPayloadTemperatureC
16xx*TlmPayloadTemperatureC
17xx*TlmPayloadAdvertisementCount
18xx*TlmPayloadAdvertisementCount
19xx*TlmPayloadAdvertisementCount
20xx*TlmPayloadAdvertisementCount
21xx*TlmPayloadUptimeCounter
22xx*TlmPayloadUptimeCounter
23xx*TlmPayloadUptimeCounter
24xx*TlmPayloadUptimeCounter

* - 可能发生变化

Eddystone-UID 数据包结构

Eddystone-UID 是受支持的广告数据包类型之一。你可以通过链接找到有关该协议的更多信息。

在这种情况下,PDU Payload 由 "AdvA"(6 个 octets 长)和 "AdvData"(包含数据信息的字段)字段组成。MikroTik 默认 CompleteUUID=03 03 aa fe;ServiceData=17 16 aa fe。

"AdvData" 字段结构:

属性描述
FlagsFlags 字段的长度、类型和数据。用于 "告知" 周围的扫描器设备处于何种状态。3 octets (020106): 1st octet - length 2nd octet - type 3d octet - data
CommonPayload对所有 Eddystone 帧类型通用的广告负载部分CompleteUUID(通用唯一标识符)= 4 octets (const) ServiceData(16 bit UUID 数据类型)= 4 octets (const) FrameType(值 = 0x00)= 1 octet (const)
UidPayloadEddystone-UID 帧负载Ranging Data(0 m 处校准的 Tx 功率)= 1 octet (int) Nspace(唯一的自定义 beacon ID 命名空间)= 10 octets (uint) Instance(命名空间内的唯一 ID)= 6 octets (uint) RFU1(保留供将来使用,值=0x00)= 1 octet (const) RFU2(保留供将来使用,值=0x00)= 1 octet (const)

Eddystone-UID PDU Payload 结构

OctetValue
002Flagsflags 字段的长度
101Flagsflags 字段的类型
206Flagsflags 字段的数据
303CommonPayloadCompleteUUID
403CommonPayloadCompleteUUID
5aaCommonPayloadCompleteUUID
6feCommonPayloadCompleteUUID
717CommonPayloadServiceData
816CommonPayloadServiceData
9aaCommonPayloadServiceData
10feCommonPayloadServiceData
1100CommonPayloadFrameType
12xx*UidPayloadRanging Data
13xx*UidPayloadNspace
14xx*UidPayloadNspace
15xx*UidPayloadNspace
16xx*UidPayloadNspace
17xx*UidPayloadNspace
18xx*UidPayloadNspace
19xx*UidPayloadNspace
20xx*UidPayloadNspace
21xx*UidPayloadNspace
22xx*UidPayloadNspace
23xx*UidPayloadInstance
24xx*UidPayloadInstance
25xx*UidPayloadInstance
26xx*UidPayloadInstance
27xx*UidPayloadInstance
28xx*UidPayloadInstance
2900UidPayloadRFU1
3000UidPayloadRFU2

* - 可能发生变化

Device Name

启用后,标签将广播一个包含所配置 "Device Name" 的短负载。

属性描述
FlagsFlags 字段的长度、类型和数据。用于 "告知" 周围的扫描器设备处于何种状态。3 octets (020106): 1st octet - length 2nd octet - type 3d octet - data
Complette Local Name广告负载的一部分,包含 Local Name 信息。Length = 1 octet AD type = 1 octet Name bytes = x octets.

Device Name PDU Payload 结构

如果我们将 "Device Name" 配置为 "TG-BT5-OUT":

OctetValue
002Flagsflags 字段的长度
101Flagsflags 字段的类型
206Flagsflags 字段的数据
30bLength长度字节
409AD TypeComplete Local Name 类型字节
554Name byte #1ASCII "T"
647Name byte #2ASCII "G"
72dName byte #3ASCII "-"
842Name byte #4ASCII "B"
954Name byte #5ASCII "T"
1035Name byte #6ASCII "5"
112dName byte #7ASCII "-"
124fName byte #8ASCII "O"
1355Name byte #9ASCII "U"
1454Name byte #10ASCII "T"