This is the documentation for BTHome v1. This API is considered legacy and should not be used for new projects.
Example Data
The BTHome v1 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 0B161C182302C4090303BF13
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 is included in the element
- AD data – one or more bytes - the meaning is defined by the AD type
The advertising payload should contain the following three AD elements:
- Flags (
0x01
) -
Shortened local name (
0x08
) or Complete local name (0x09
) - UUID16 (
0x16
)
In the example, we have:
-
First AD element:
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” -
This part always has to be added, and is always the same
(
0x020106
)
-
Second AD element:
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.
-
Third AD element:
0B 16 1C182302C4090303BF13
(BTHome data)0x0B
= length (11 bytes)0x16
= Service Data - 16-bit UUID0x1C182302C4090303BF13
= BTHome data
BTHome Data format
The BTHome data can contain multiple measurements. We continue with the example from above.
BTHome data =0x1C18 2302C409 0303BF13
-
1C18
= The first two bytes are the UUID16 in reversed order (UUID16 =0x181C
). Use UUID160x181C
for non-encrypted messages and UUID160x181E
for encrypted data. This data should be used by receivers to recognize BTHome messages.
More information about encryption can be found further down this page. -
0x23 02 C409
= Temperature packet -
0x03 03 BF13
= Humidity packet
Lets explain how the last two data packets work. The temperature packet is
used as example. The first byte
0x23
(in bits 00100 011
) is giving information
about:
-
The object length (bit 0-4):
00011
= 3 bytes (excluding the length byte itself) -
The object format (bit 5-7)
001
= 1 = signed integer (see table below)
Type | Bit 5-7 | Format | Data Type |
---|---|---|---|
0
|
000
|
uint | unsigned integer |
1
|
001
|
int | signed integer |
2
|
010
|
float | float |
3
|
011
|
string | string |
4
|
100
|
MAC | MAC address (reversed) |
-
The second byte
0x02
is defining the type of measurement (temperature, see table below) -
The remaining bytes
0xC409
comprise the object value (little-endian), which will be multiplied with the factor in the table below to get a sufficient number of digits.- The object length is telling us that the value is 2 bytes long (object length = 3 bytes minus the second byte) and the object format is telling us that the value is a signed integer (positive or negative integer).
-
0xC409
as unsigned 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
Supported data
Full example payloads for each data type.
Sensor data
Object id | Property | Preferred data type | Factor | Example | Result | Unit |
---|---|---|---|---|---|---|
0x01
|
battery | uint8 (1 byte) | 1 |
020161
|
97 |
%
|
0x02
|
temperature | sint16 (2 bytes) | 0.01 |
2302CA09
|
25.06 |
°C
|
0x03
|
humidity | uint16 (2 bytes) | 0.01 |
0303BF13
|
50.55 |
%
|
0x2E
|
humidity | uint1 (1 byte) | 1 |
022E23
|
35 |
%
|
0x04
|
pressure | uint24 (3 bytes) | 0.01 |
0404138A01
|
1008.83 |
hPa
|
0x05
|
illuminance | uint24 (3 bytes) | 0.01 |
0405138A14
|
13460.67 |
lux
|
0x06
|
mass (kg) | uint16 (2 byte) | 0.01 |
03065E1F
|
80.3 |
kg
|
0x07
|
mass (lb) | uint16 (2 byte) | 0.01 |
03073E1D
|
74.86 |
lb
|
0x08
|
dewpoint | sint16 (2 bytes) | 0.01 |
2308CA06
|
17.38 |
°C
|
0x09
|
count | uint (4 bytes) | 1 |
020960
|
96 | |
0X0A
|
energy | uint24 (3 bytes) | 0.001 |
040A138A14
|
1346.067 |
kWh
|
0x0B
|
power | uint24 (3 bytes) | 0.01 |
040B021B00
|
69.14 |
W
|
0x0C
|
voltage | uint16 (2 bytes) | 0.001 |
030C020C
|
3.074 |
V
|
0x0D
|
pm2.5 | uint16 (2 bytes) | 1 |
030D120C
|
3090 |
ug/m3
|
0x0E
|
pm10 | uint16 (2 bytes) | 1 |
030E021C
|
7170 |
ug/m3
|
0x12
|
co2 | uint16 (2 bytes) | 1 |
0312E204
|
1250 |
ppm
|
0x13
|
tvoc | uint16 (2 bytes) | 1 |
03133301
|
307 |
ug/m3
|
0x14
|
moisture | uint16 (2 bytes) | 0.01 |
0314020C
|
30.74 |
%
|
0x2F
|
moisture | uint8 (1 byte) | 1 |
022F23
|
35 |
%
|
0x50
|
timestamp | uint48 (4 bytes) | - |
05505396164
|
see below |
|
0x51
|
acceleration | uint16 (2 bytes) | 0.001 |
03518756
|
22.151 |
m/s²
|
0x52
|
gyroscope | uint16 (2 bytes) | 0.001 |
03528756
|
22.151 |
°/s
|
0x53
|
text | see below | - |
0D5348656C6C6F20576F726C6421
|
Hello World! |
|
0x54
|
raw | see below | - |
0D5448656C6C6F20576F726C6421
|
48656c6c6f20576f726c6421 |
|
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
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.
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. Note: Binary
sensors will be supported in Home Assistant 2022.10.
Object id | Property | Data type | Example | Result |
---|---|---|---|---|
0x0F
|
generic boolean | uint8 (1 byte) |
020F01
|
1 (True = On) |
0x10
|
power | uint8 (1 byte) |
021001
|
1 (True = On) |
0x11
|
opening | uint8 (1 byte) |
021100
|
0 (False = Closed) |
0x15
|
battery | uint8 (1 byte) |
021501
|
1 (True = Low) |
0x16
|
battery charging | uint8 (1 byte) |
021601
|
1 (True = Charging) |
0x17
|
carbon monoxide | uint8 (1 byte) |
021700
|
0 (False = Not detected) |
0x18
|
cold | uint8 (1 byte) |
021801
|
1 (True = Cold) |
0x19
|
connectivity | uint8 (1 byte) |
021900
|
0 (False = Disconnected) |
0x1A
|
door | uint8 (1 byte) |
021A00
|
0 (False = Closed) |
0x1B
|
garage door | uint8 (1 byte) |
021B01
|
0 (False = Closed) |
0x1C
|
gas | uint8 (1 byte) |
021C01
|
1 (True = Detected) |
0x1D
|
heat | uint8 (1 byte) |
021D00
|
0 (False = Normal) |
0x1E
|
light | uint8 (1 byte) |
021E01
|
1 (True = Light detected) |
0x1F
|
lock | uint8 (1 byte) |
021F01
|
1 (True = Unlocked) |
0x20
|
moisture | uint8 (1 byte) |
022001
|
1 (True = Wet) |
0x21
|
motion | uint8 (1 byte) |
022100
|
0 (False = Clear) |
0x22
|
moving | uint8 (1 byte) |
022201
|
1 (True = Moving) |
0x23
|
occupancy | uint8 (1 byte) |
022301
|
1 (True = Detected) |
0x24
|
plug | uint8 (1 byte) |
022400
|
0 (False = Unplugged) |
0x25
|
presence | uint8 (1 byte) |
022500
|
0 (False = Away) |
0x26
|
problem | uint8 (1 byte) |
022601
|
1 (True = Problem) |
0x27
|
running | uint8 (1 byte) |
022701
|
1 (True = Running) |
0x28
|
safety | uint8 (1 byte) |
022800
|
0 (False = Unsafe) |
0x29
|
smoke | uint8 (1 byte) |
022901
|
1 (True = Detected) |
0x2A
|
sound | uint8 (1 byte) |
022A00
|
0 (False = Clear) |
0x2B
|
tamper | uint8 (1 byte) |
022B00
|
0 (False = Off) |
0x2C
|
vibration | uint8 (1 byte) |
022C01
|
1 (True = Detected) |
0x2D
|
window | uint8 (1 byte) |
022D01
|
0 (False = Closed) |
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 |
023A00
|
||
|
0x01
|
press |
023A01
|
press | ||
|
0x02
|
double_press |
023A02
|
double_press | ||
|
0x03
|
triple_press |
023A03
|
triple_press | ||
|
0x04
|
long_press |
023A04
|
long_press | ||
|
0x05
|
long_double_press |
023A05
|
long_double_press | ||
|
0x06
|
long_triple_press |
023A06
|
long_triple_press | ||
0x3C
|
dimmer |
0x00
|
None |
023C00
|
||
|
0x01
|
rotate left | # steps |
033C0103
|
rotate left 3 steps | |
|
0x02
|
rotate right | # steps |
033C020A
|
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
023A00203A01
will send no event for the first button, and an
event "press" for the second button.
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 the packet id
is not used in the
Home Assistant integration, as Home Assistant has its own deduplication
mechanism.
Object id | Property | Data Type | Example | Result |
---|---|---|---|---|
0x00
|
packet id | uint8 (1 byte) |
020009
|
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 information on how to encrypt your messages is demonstrated in this script.
Scope & Constraints
The goal of the BTHome standard is to share sensor data efficiently via Bluetooth LE discovery packets. It is not the goal to offer a way for devices to share control.