BLE advertising in the BTHome v2 format
BTHome v2 was released in November 2022. Click here for v1 docs.
The BTHome v2 format can best be explained with an example. First, the basics:
Bluetooth defines a single packet format for both advertising and data transmissions.
This packet consist of four components: preamble (1 octet), access address (4 octets),
Protocol Data Unit - PDU (2-257 octets), and Cyclic Redundancy Check - CRC (3 octets).
If you want to learn more about the BLE format, you can
read this.
Advertising payload
The actual data is being sent as part of the PDU, as the Payload, as indicated in green.
The advertising payload is a long message with bytes (bytestring).
An example of a BTHome payload is:
020106 0B094449592D73656E736F72 0A16D2FC4002C40903BF13
The advertising payload can consist of one or more Advertising Data (AD) elements. Each element contains the following:
- 1st byte: length of the element (excluding the length byte itself)
- 2nd byte: AD type - specifies what data type is included in the element
- AD data - one or more bytes - the meaning is defined by the AD type
The advertising payload in BTHome format should at least contain the AD element Service Data (16 bits UUID). It is also strongly advised to include the AD element Flags. Optionally, you can add a Local Name.
Flags (0x01
)
The flag value has several bits indicating the capabilities of the device.
Although not required, it is still strongly advised to include this element in your
advertisement, especially when using passive scanning on the receiving side.
Bluez, which e.g. is used in the Home Assistant Bluetooth integration,
won't be able to parse the data without this flags value.
This part is always the same for BTHome (0x020106
).
020106
0x02
= length (2 bytes)0x01
= Flags-
0x06
= in bits, this is00000110
.
bit 1 and bit 2 are 1, meaning:
bit 1: “LE General Discoverable Mode”
bit 2: “BR/EDR Not Supported”
0x16
)The service data contains the actual data. In the example, we have:
-
Service data (16-bit UUID):
0A 16 D2FC4002C40903BF13
(BTHome data)0x0A
= length (10 bytes)0x16
= Service Data - 16-bit UUID0xD2FC4002C40903BF13
= BTHome data (see below)
0x08
or 0x09
)
Optional AD element is the local name, either the Shortened local name
(0x08
) or Complete local name (0x09
).
-
Example of Complete local name:
0B094449592D73656E736F72
0x0B
= length (11 bytes)0x09
= Complete local name-
0x4449592D73656E736F72
= the complete local name. After converting it to text , it corresponds to "DIY-sensor". The name can be used to identify your sensor.
BTHome Data format
The BTHome (service) data contains the 16 bit UUID, the
BTHome Device Information and one or multiple
Measurements.
In the example we have the
following BTHome data:
0xD2FC 40 02C409 03BF13
D2FC
)
The UUID has to be read reversed per byte, so the UUID is 0xFCD2
.
This UUID should be used by receivers to recognize BTHome messages.
Allterco Robotics, the manufacturer of Shelly devices, has sponsored this UUID
for BTHome and it's free to use for everyone with
this license.
0x40
)
The first byte after the UUID 0x40
is the BTHome device info
byte, which has several bits indicating the capabilities of the device.
- bit 0: “Encryption flag”
- bit 1: “Reserved for future use”
- bit 2: “Trigger based device flag”
- The trigger based device flag is telling the receiver that it should expect
that the device is sending BLE advertisements at a regular interval (bit 2 =
0
) or at an irregular interval (bit 2 =1
), e.g. only when someone pushes a button. This can be useful information for a receiver, e.g. to prevent the device from going to unavailable. - bit 3-4: “Reserved for future use”
- bit 5-7: “BTHome Version”
- This represents the BTHome verion. Currently only BTHome version 1 or 2
are allowed, where 2 is the latest version (bit 5-7 =
010
).
-
The Encryption flag is telling the receiver whether the device is sending
non-encrypted data (bit 0 =
0
) or encrypted data (bit 0 = 1
).
In the example, we have 0x40
. After
converting
this to bits, we get 01000000
.
Note. bit 0 is the most right number, bit 7 is the most left number
- bit 0:
0
= False: No encryption - bit 2:
0
= False: Device is sending regular data updates -
bit 5-7:
010
= 2: BTHome Version 2.
0x02 C409
)-
The first measurement is a temperature measurement in the example. The first
byte
0x02
is defining the type of measurement (temperature, see table below) -
The remaining bytes
0xC409
comprise the object value (little-endian). This value has to be in the format as given in the table below. The value will be multiplied with a factor, as given in the table below, to get a sufficient number of digits.-
According to the table, a 2 bytes long signed integer is used in
little-endian with a factor 0.01, for temperature measurements with
object id
0x02
. -
0xC409
as signed integer in little-endian is equal to 2500. - The factor for a temperature measurement is 0.01, resulting in a temperature of 25.00°C
-
According to the table, a 2 bytes long signed integer is used in
little-endian with a factor 0.01, for temperature measurements with
object id
0x03 BF13
)-
The second measurement is a humidity measurement in the example. The first
byte
0x03
is defining the type of measurement (humidity), 2 bytes, as a unsigned integer (see table below). -
0xBF13
as unsigned integer in little-endian is equal to 5055, multiplied with a factor 0.01, this becomes a humidity of 50.55%.
Object ids have to be applied in numerical order (from low to high) in your advertisement. This will make sure that if you have a device (sensor) that is broadcasting a new measurement type that is added in a new (minor) BTHome update, while your BTHome receiver isn't updated yet to the same version, it will still be able to receive the older supported measurement types. A BTHome receiver will stop parsing object ids as soon as it finds an object id that isn't supported.
Supported data types
Full example payloads for each data type.
Sensor data
Object id | Property | Data type | Factor | Example | Result | Unit |
---|---|---|---|---|---|---|
0x51
|
acceleration | uint16 (2 bytes) | 0.001 |
518756
|
22.151 |
m/s²
|
0x01
|
battery | uint8 (1 byte) | 1 |
0161
|
97 |
%
|
0x12
|
co2 | uint16 (2 bytes) | 1 |
12E204
|
1250 |
ppm
|
0x56
|
conductivity | uint16 (2 bytes) | 1 |
56E803
|
1000 |
µS/cm
|
0x09
|
count | uint8 (1 byte) | 1 |
0960
|
96 | |
0x3D
|
count | uint16 (2 bytes) | 1 |
3D0960
|
24585 | |
0x3E
|
count | uint32 (4 bytes) | 1 |
3E2A2C0960
|
1611213866 | |
0x59
|
count | sint8 (1 byte) | 1 |
59EA
|
-22 | |
0x5A
|
count | sint16 (2 bytes) | 1 |
5AEAEA
| -5398 | |
0x5B
|
count | sint32 (4 bytes) | 1 |
5BEA0234EA
|
-365690134 | |
0x43
|
current | uint16 (2 bytes) | 0.001 |
434E34
|
13.39 |
A
|
0x5D
|
current | sint16 (2 bytes) | 0.001 |
5D02EA
|
-5.63 |
A
|
0x08
|
dewpoint | sint16 (2 bytes) | 0.01 |
08CA06
|
17.38 |
°C
|
0x40
|
distance (mm) | uint16 (2 bytes) | 1 |
400C00
|
12 |
mm
|
0x41
|
distance (m) | uint16 (2 bytes) | 0.1 |
414E00
|
7.8 |
m
|
0x42
|
duration | uint24 (3 bytes) | 0.001 |
424E3400
|
13.390 |
s
|
0x4D
|
energy | uint32 (4 bytes) | 0.001 |
4d12138a14
|
344593.170 |
kWh
|
0x0A
|
energy | uint24 (3 bytes) | 0.001 |
0A138A14
|
1346.067 |
kWh
|
0x4B
|
gas | uint24 (3 bytes) | 0.001 |
4B138A14
|
1346.067 |
m3
|
0x4C
|
gas | uint32 (4 bytes) | 0.001 |
4C41018A01
|
25821.505 |
m3
|
0x52
|
gyroscope | uint16 (2 bytes) | 0.001 |
528756
|
22.151 |
°/s
|
0x03
|
humidity | uint16 (2 bytes) | 0.01 |
03BF13
|
50.55 |
%
|
0x2E
|
humidity | uint8 (1 byte) | 1 |
2E23
|
35 |
%
|
0x05
|
illuminance | uint24 (3 bytes) | 0.01 |
05138A14
|
13460.67 |
lux
|
0x06
|
mass (kg) | uint16 (2 bytes) | 0.01 |
065E1F
|
80.3 |
kg
|
0x07
|
mass (lb) | uint16 (2 bytes) | 0.01 |
073E1D
|
74.86 |
lb
|
0x14
|
moisture | uint16 (2 bytes) | 0.01 |
14020C
|
30.74 |
%
|
0x2F
|
moisture | uint8 (1 byte) | 1 |
2F23
|
35 |
%
|
0x0D
|
pm2.5 | uint16 (2 bytes) | 1 |
0D120C
|
3090 |
ug/m3
|
0x0E
|
pm10 | uint16 (2 bytes) | 1 |
0E021C
|
7170 |
ug/m3
|
0x0B
|
power | uint24 (3 bytes) | 0.01 |
0B021B00
|
69.14 |
W
|
0x5C
|
power | sint32 (4 bytes) | 0.01 |
5C02FBFFFF
|
-12.78 |
W
|
0x04
|
pressure | uint24 (3 bytes) | 0.01 |
04138A01
|
1008.83 |
hPa
|
0x54
|
raw | see below | - |
540C48656C6C6F 20576F726C6421
|
48656c6c6f20 576f726c6421 |
|
0x3F
|
rotation | sint16 (2 bytes) | 0.1 |
3F020C
|
307.4 |
°
|
0x44
|
speed | uint16 (2 bytes) | 0.01 |
444E34
|
133.90 |
m/s
|
0x57
|
temperature | sint8 (1 byte) | 1 |
57EA
|
-22 |
°C
|
0x58
|
temperature | sint8 (1 byte) | 0.35 |
58EA
|
-7.7 |
°C
|
0x45
|
temperature | sint16 (2 bytes) | 0.1 |
451101
|
27.3 |
°C
|
0x02
|
temperature | sint16 (2 bytes) | 0.01 |
02CA09
|
25.06 |
°C
|
0x53
|
text | see below | - |
530C48656C6C6F 20576F726C6421
|
Hello World! |
|
0x50
|
timestamp | uint48 (4 bytes) | - |
505d396164
|
see below |
|
0x13
|
tvoc | uint16 (2 bytes) | 1 |
133301
|
307 |
ug/m3
|
0x0C
|
voltage | uint16 (2 bytes) | 0.001 |
0C020C
|
3.074 |
V
|
0x4A
|
voltage | uint16 (2 bytes) | 0.1 |
4A020C
|
307.4 |
V
|
0x4E
|
volume | uint32 (4 bytes) | 0.001 |
4E87562A01
|
19551.879 |
L
|
0x47
|
volume | uint16 (2 bytes) | 0.1 |
478756
|
2215.1 |
L
|
0x48
|
volume | uint16 (2 bytes) | 1 |
48DC87
|
34780 |
mL
|
0x55
|
volume storage | uint32 (4 bytes) | 0.001 |
5587562A01
|
19551.879 |
L
|
0x49
|
volume flow rate | uint16 (2 bytes) | 0.001 |
49DC87
|
34.780 |
m3/hr
|
0x46
|
UV index | uint8 (1 byte) | 0.1 |
4632
|
5.0 |
|
0x4F
|
water | uint32 (4 bytes) | 0.001 |
4F87562A01
|
19551.879 |
L
|
Timestamp
The timestamp sensor needs a little more explanation. The timestamp sensor
is defined as number of seconds since 1970-1-1,
00:00:00, UTC, (also called epoch time), and is returning a datetime
object with timezone UTC.
0x5D396164
is, after converting from bytes (little endian)
to an integer, 1684093277 seconds, which corresponds to 2023-5-14,
19:41:17. The corresponding datetime object that is returned is:
datetime(2023, 5, 14, 19, 41, 17, tzinfo=timezone.utc)
Text and Raw sensors
The text and raw sensor have a variable length. You therefore have to specify
the length in the first byte after the `object id`. In the example of the text sensor
0x530C48656C6C6F20576F726C6421
, the 2nd byte (0x0C
)
gives the length of the text string (12 bytes). Text has to be encoded in
UTF-8, while the raw sensor is reported back as hexadecimal string. The byte string
0x48656C6C6F20576F726C6421
in text is Hello World!
, while
in raw sensor will give 48656c6c6f20576f726c6421
as result.
Make sure the length is specified correct in the second byte.
Multiple measurements of the same type
If you want to send multiple measurements of the same type, e.g. three temperatures, you can just add multiple measurements of the same type to the payload. A postfix will be added to the measurement name (e.g. temperature_2) in the order of which you define the measurements. Note that this implies that you will need to use the same order in each advertisement, to prevent measurements being assigned to the wrong entity. If only one measurement of a certain type is sent, no postfix will be used.
Binary Sensor data
Binary sensor data should always be an uint8 of a single byte. Its value
should be 1
for on, and 0
for off.
Object id | Property | Data type | Example | Result |
---|---|---|---|---|
0x15
|
battery | uint8 (1 byte) |
1501
|
0 (False = Normal) 1 (True = Low) |
0x16
|
battery charging | uint8 (1 byte) |
1601
|
0 (False = Not Charging) 1 (True = Charging) |
0x17
|
carbon monoxide | uint8 (1 byte) |
1700
|
0 (False = Not detected) 1 (True = Detected) |
0x18
|
cold | uint8 (1 byte) |
1801
|
0 (False = Normal) 1 (True = Cold) |
0x19
|
connectivity | uint8 (1 byte) |
1900
|
0 (False = Disconnected) 1 (True = Connected) |
0x1A
|
door | uint8 (1 byte) |
1A00
|
0 (False = Closed) 1 (True = Open) |
0x1B
|
garage door | uint8 (1 byte) |
1B01
|
0 (False = Closed) 1 (True = Open) |
0x1C
|
gas | uint8 (1 byte) |
1C01
|
0 (False = Clear) 1 (True = Detected) |
0x0F
|
generic boolean | uint8 (1 byte) |
0F01
|
0 (False = Off) 1 (True = On) |
0x1D
|
heat | uint8 (1 byte) |
1D00
|
0 (False = Normal) 1 (True = Hot) |
0x1E
|
light | uint8 (1 byte) |
1E01
|
0 (False = No light) 1 (True = Light detected) |
0x1F
|
lock | uint8 (1 byte) |
1F01
|
0 (False = Locked) 1 (True = Unlocked) |
0x20
|
moisture | uint8 (1 byte) |
2001
|
0 (False = Dry) 1 (True = Wet) |
0x21
|
motion | uint8 (1 byte) |
2100
|
0 (False = Clear) 1 (True = Detected) |
0x22
|
moving | uint8 (1 byte) |
2201
|
0 (False = Not moving) 1 (True = Moving) |
0x23
|
occupancy | uint8 (1 byte) |
2301
|
0 (False = Clear) 1 (True = Detected) |
0x11
|
opening | uint8 (1 byte) |
1100
|
0 (False = Closed) 1 (True = Open) |
0x24
|
plug | uint8 (1 byte) |
2400
|
0 (False = Unplugged) 1 (True = Plugged in) |
0x10
|
power | uint8 (1 byte) |
1001
|
0 (False = Off) 1 (True = On) |
0x25
|
presence | uint8 (1 byte) |
2500
|
0 (False = Away) 1 (True = Home) |
0x26
|
problem | uint8 (1 byte) |
2601
|
0 (False = OK) 1 (True = Problem) |
0x27
|
running | uint8 (1 byte) |
2701
|
0 (False = Not Running) 1 (True = Running) |
0x28
|
safety | uint8 (1 byte) |
2800
|
0 (False = Unsafe) 1 (True = Safe) |
0x29
|
smoke | uint8 (1 byte) |
2901
|
0 (False = Clear) 1 (True = Detected) |
0x2A
|
sound | uint8 (1 byte) |
2A00
|
0 (False = Clear) 1 (True = Detected) |
0x2B
|
tamper | uint8 (1 byte) |
2B00
|
0 (False = Off) 1 (True = On) |
0x2C
|
vibration | uint8 (1 byte) |
2C01
|
0 (False = Clear) 1 (True = Detected) |
0x2D
|
window | uint8 (1 byte) |
2D01
|
0 (False = Closed) 1 (True = Open) |
Events
Devices can also send events. Events are a combination of a device type (like a button or dimmer) and an event (like "press" or "long press" or "rotate left 3 steps"). Note: Events will be supported in Home Assistant 2023.5 and higher.
Object id | Device type | Event id | Event type | Event property | Example | Result |
---|---|---|---|---|---|---|
0x3A
|
button |
0x00
|
None |
3A00
|
||
|
0x01
|
press |
3A01
|
press | ||
|
0x02
|
double_press |
3A02
|
double_press | ||
|
0x03
|
triple_press |
3A03
|
triple_press | ||
|
0x04
|
long_press |
3A04
|
long_press | ||
|
0x05
|
long_double_press |
3A05
|
long_double_press | ||
|
0x06
|
long_triple_press |
3A06
|
long_triple_press | ||
|
0x80
|
hold_press |
3A80
|
hold_press | ||
0x3C
|
dimmer |
0x00
|
None |
3C0000
|
||
|
0x01
|
rotate left | # steps |
3C0103
|
rotate left 3 steps | |
|
0x02
|
rotate right | # steps |
3C020A
|
rotate right 10 steps |
Multiple events of the same type
Similar as for sensors and binary sensors, you can also send multiple events
of the same type, by just adding them behind each other. The event `0x00`
(None) is a special event that can be used in case you e.g. have a device with
two buttons, and you only want to send an event for the 2nd button. An event
3A003A01
will send no event for the first button, and an event
"press" for the second button.
Device
Device information
The following device information can be included in the BLE advertisment, a
device type id
and the installed fw version
. The
device type id
is 2 bytes long in little endian and can be used to
make a distinction between device types. The firmware version
can be
send in 0.0.0.1
format or in 0.0.1
format.
Object id | Property | Data Type | Example | Result |
---|---|---|---|---|
0xF0
|
device type id | uint16 (2 bytes) |
F00100
|
1 |
0xF1
|
firmware version | uint32 (4 bytes) |
F100010204
|
4.2.1.0 |
0xF2
|
firmware version | uint24 (3 bytes) |
F2000106
|
6.1.0 |
Misc data
Packet id
The
packet id
is optional and can be used to filter duplicate data.
This allows you to send multiple advertisements that are exactly the same, to
improve the chance that the advertisement arrives. BTHome receivers should
only process the advertisement if the packet id
is different
compared to the previous one. The packet id
is a value between 0
(0x00
) and 255 (0xFF
), and should be increased on
every change in data. Note that most home automation software already have
some other filtering for unchanged data. The use of a
packet id
is therefore often not needed.
Object id | Property | Data Type | Example | Result |
---|---|---|---|---|
0x00
|
packet id | uint8 (1 byte) |
0009
|
9 |
Encryption
Unencrypted BLE advertisements can be read by anyone nearby listening for Bluetooth packets. BTHome supports AES encryption (CCM mode) which works with a pre-shared key. When encrypted, the data can only be read by knowing the encryption key. The encryption key should be a 16 bytes long key (32 characters).
More detailed information about encryption in BTHome is given on this page.