|
| 1 | +# BTHome v2 Advertising Payload for MicroPython |
| 2 | + |
| 3 | +The BTHome v2 format reference is published at https://bthome.io/format/. This document will distill the format into a blueprint for MicroPython code. |
| 4 | + |
| 5 | +Given the following sample BTHome advertising payload: |
| 6 | + |
| 7 | +``` |
| 8 | +020106 0B094449592D73656E736F72 0A16D2FC4002C40903BF13 |
| 9 | +``` |
| 10 | + |
| 11 | +The advertisement is one continuous string of hex digits, but is separated into three distint parts using spaces for ease of reading. These parts are: |
| 12 | + |
| 13 | +1. Flags |
| 14 | +2. Local Name |
| 15 | +3. Service Data |
| 16 | + |
| 17 | +## Advertisement Flags |
| 18 | + |
| 19 | +The first part contains Bluetooth flags. It is always the same for any BTHome advertisements. |
| 20 | + |
| 21 | +``` |
| 22 | +020106 |
| 23 | +``` |
| 24 | + |
| 25 | +* 02 is the length byte |
| 26 | +* 01 is an indicator for flags |
| 27 | +* 06 is flag data (0b00000110) |
| 28 | + * bit 1: LE General Discoverable Mode |
| 29 | + * bit 2: BR/EDR Not Supported |
| 30 | + |
| 31 | +Because it never changes, flags can be represented as a static bytestring. |
| 32 | + |
| 33 | +``` |
| 34 | +BTHOME_FLAGS = bytes.fromhex('020106') |
| 35 | +``` |
| 36 | + |
| 37 | +## Device Name |
| 38 | + |
| 39 | +The second part contains the name of the device encoded as ASCII with bytes prepended to indicate name length and type. |
| 40 | + |
| 41 | +``` |
| 42 | +0B094449592D73656E736F72 |
| 43 | +``` |
| 44 | + |
| 45 | +* 0B is the length byte (does not include the length byte itself in calculations, but does inslude the type indicator for complete or shortened) |
| 46 | +* 09 is an indicator for complete name (as opposed to shortened name being 0x08) |
| 47 | +* 4449592D73656E736F72 ASCII encoded hex string reading "DIY-sensor" |
| 48 | + |
| 49 | +The device name should be user defined and flexible in length. But, it is competing for the small space available for advertising data. Ten bytes (the same as used by the example name "DIY-sensor" seems to be a reasonable limit. |
| 50 | + |
| 51 | +See https://stackoverflow.com/questions/65568893/how-to-know-the-maximum-length-of-bt-name#65577574 for a good discussion of this limit. |
| 52 | + |
| 53 | +Because the name is user definded and may vary in length, it will be provided as a string and: |
| 54 | +* The string length must be non-zero and not exceeding maximum length |
| 55 | +* The string must be converted to ASCII-encoded bytes |
| 56 | +* The length of the string must be calculated |
| 57 | +* The complete name indicator (0x09) must be prepended to the byte string. |
| 58 | +* The length of the string (including the "complete name" indicator) must be calculated and prepended to the final byte string. |
| 59 | + |
| 60 | + |
| 61 | +## Service Data |
| 62 | + |
| 63 | +The third part is service data. It contains the information to indicate a BTHome advertisement as well as values gathered from various sensors. |
| 64 | + |
| 65 | +``` |
| 66 | +0A16D2FC4002C40903BF13 |
| 67 | +``` |
| 68 | + |
| 69 | +* 0A is the length byte |
| 70 | +* 16 indicates a service data type of UUID16 follows |
| 71 | +* D2FC is the UUID16 indicating BTHome data follows (little endian format for FCD2, the UUID16 reserved for BTHome) |
| 72 | +* 40 is flag data |
| 73 | + * bit 0: encrypted |
| 74 | + * bit 1: reserved |
| 75 | + * bit 2: trigger based (irregular advertisement triggered by some user interaction) |
| 76 | + * bit 3: reserved |
| 77 | + * bit 4: reserved |
| 78 | + * bits 5..7: BTHome version (010 for version 2) |
| 79 | +* 02C40903BF13 represents sensor data |
| 80 | + * 02 indicates temperature measurement |
| 81 | + * C409 is 25 C temperature in little endian format with a two-place fixed-point decimal (similar to GATT characteristic) |
| 82 | + * 03 indicates humidity measurement |
| 83 | + * BF13 is 50.55% humidity in little endian with a two-place fixed-point decimal (similar to GATT characteristic) |
| 84 | + |
| 85 | +Additional sensor types and value may be found under the heading of "sensor data" on https://bthome.io/format/ |
| 86 | + |
| 87 | +Because an arbitrary number of zero or more sensors can be configured, the byte string should start as 16D2FC (UUID16 indicator and BTHome UUID.) |
| 88 | +* Flag data should be 0x00 for regular interval devices or 0x40 for triggered devices. |
| 89 | + * Encryption will remain unimplemented at this time, so the bit 0 will be 0. |
| 90 | + * Sensors configured to send updates at regular intervals should have a bit 2 of 0, triggered advertisements have a bit 2 of 1. |
| 91 | +* Sensor readings should be put into proper format and appended. |
| 92 | + * Each reading starts with a one-byte object ID to indicate the type |
| 93 | + * Reading values are always little endian |
| 94 | + * Non-integer values used fix-point decimal (often two decimal places) |
| 95 | + * Length of values can vary between one to three bytes depending on object ID |
| 96 | + |
| 97 | +`struct.pack()` is helpful in converting object ID and value to little endian bytes. 24-bit values pose a challenge because there is no format string for a 3-byte value available to `struct.pack()`. This can be overcome by using a 32-bit long int (L) format and removing the final 0x00 byte. |
| 98 | + |
| 99 | +Examples for encoding environmental data include the following: |
| 100 | + |
| 101 | +``` |
| 102 | +temperature_bytes = pack('<Bh', BTHOME_TEMPERATURE_SINT16, temperature_celsius * 100) |
| 103 | +humidity_bytes = pack('<Bh', BTHOME_HUMIDITY_UINT16, round(humidity_percent * 100)) |
| 104 | +illuminance_bytes = pack('<BL', BTHOME_ILLUMINANCE_UINT24, illuminance_lux)[:-1] |
| 105 | +``` |
0 commit comments