Skip to content

Commit f30b41e

Browse files
Create bthome.py
1 parent c10631e commit f30b41e

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed

bthome.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# BTHome MicroPython
2+
# Construct Bluetooth Low Energy (BLE) advertising payloads for BTHome v2.
3+
# See https://bthome.io/ for more information about BTHome.
4+
# See https://github.com/DavesCodeMusings/BTHome-MicroPython for more about this module.
5+
6+
from micropython import const
7+
from struct import pack
8+
9+
# See "Advertising Payload" at https://bthome.io/format/ for details.
10+
_ADVERT_LENGTH_MAX = const(255)
11+
_ADVERT_FLAGS = bytes.fromhex('020106') # length (02), flags indicator (01), flag bits (06)
12+
_DEVICE_NAME_LENGTH_MAX = const(10)
13+
_SERVICE_DATA_UUID16 = const(0x16)
14+
_SERVICE_UUID16 = const(0xFCD2) # See: https://bthome.io/images/License_Statement_-_BTHOME.pdf
15+
_DEVICE_INFO_FLAGS = const(0x40) # Currently hardcoded: no encryption, regular updates, version 2
16+
17+
# See "Sensor Data" table at https://bthome.io/format/ for details.
18+
BATTERY_UINT8 = const(0x01)
19+
TEMPERATURE_SINT16 = const(0x02)
20+
_TEMPERATURE_SINT16_SCALING = const(100)
21+
HUMIDITY_UINT16 = const(0x03)
22+
_HUMIDITY_UINT16_SCALING = const(100)
23+
PRESSURE_UINT24 = const(0x04)
24+
_PRESSURE_UINT24_SCALING = const(100)
25+
ILLUMINANCE_UINT24 = const(0x05)
26+
_ILLUMINANCE_UINT24_SCALING = const(100)
27+
28+
# Default value decimal places indicate precision
29+
device_name = "BTHome-MPY"
30+
battery = 0 # percent
31+
temperature = 0.00 # degrees Celsius
32+
humidity = 0.00 # percent (relative humidity)
33+
pressure = 0.00 # hectoPascals (millibars)
34+
illuminance = 0.0 # Lux
35+
36+
def _pack_device_name():
37+
assert len(device_name) > 0
38+
assert len(device_name) <= _DEVICE_NAME_LENGTH_MAX
39+
name_type = bytes.fromhex('09') # indicator for complete name
40+
device_name_bytes = name_type + device_name.encode()
41+
device_name_bytes = bytes([len(device_name_bytes)]) + device_name_bytes
42+
return device_name_bytes
43+
44+
# Functions to conver integer or float values to little endian fixed-point decimal
45+
def _pack_battery():
46+
return pack('BB', BATTERY_UINT8, battery)
47+
48+
def _pack_temperature(object_id):
49+
if object_id == TEMPERATURE_SINT16:
50+
temperature_bytes = pack('<Bh', TEMPERATURE_SINT16, round(temperature * _TEMPERATURE_SINT16_SCALING))
51+
else:
52+
temperature_bytes = bytes()
53+
return temperature_bytes
54+
55+
def _pack_humidity(object_id):
56+
if object_id == HUMIDITY_UINT16:
57+
humidity_bytes = pack('<Bh', HUMIDITY_UINT16, round(humidity * _HUMIDITY_UINT16_SCALING))
58+
else:
59+
humidity_bytes = bytes()
60+
return humidity_bytes
61+
62+
def _pack_pressure(object_id):
63+
if object_id == PRESSURE_UINT24:
64+
pressure_bytes = pack('<BL', PRESSURE_UINT24, round(pressure * _PRESSURE_UINT24_SCALING))[:-1]
65+
else:
66+
pressure_bytes = bytes()
67+
return pressure_bytes
68+
69+
def _pack_illuminance(object_id):
70+
if object_id == ILLUMINANCE_UINT24:
71+
illuminance_bytes = pack('<BL', ILLUMINANCE_UINT24, round(illuminance * _ILLUMINANCE_UINT24_SCALING))[:-1]
72+
else:
73+
illuminance_bytes = bytes()
74+
return illuminance_bytes
75+
76+
# Concatenate an arbitrary number of sensor readings using parameters of sensor data constants to indicate what's included.
77+
def _pack_service_data(*args):
78+
service_data_bytes = pack('B', _SERVICE_DATA_UUID16) # indicates a 16-bit service UUID follows
79+
service_data_bytes += pack('<h', _SERVICE_UUID16)
80+
service_data_bytes += pack('B', _DEVICE_INFO_FLAGS)
81+
for object_id in args:
82+
if object_id == BATTERY_UINT8:
83+
service_data_bytes += _pack_battery()
84+
if object_id == TEMPERATURE_SINT16:
85+
service_data_bytes += _pack_temperature(TEMPERATURE_SINT16)
86+
if object_id == HUMIDITY_UINT16:
87+
service_data_bytes += _pack_humidity(HUMIDITY_UINT16)
88+
if object_id == PRESSURE_UINT24:
89+
service_data_bytes += _pack_pressure(PRESSURE_UINT24)
90+
if object_id == ILLUMINANCE_UINT24:
91+
service_data_bytes += _pack_illuminance(ILLUMINANCE_UINT24)
92+
service_data_bytes = pack('B', len(service_data_bytes)) + service_data_bytes
93+
return service_data_bytes
94+
95+
# Construct advertising payload suitable for use by MicroPython's aioble.advertise(adv_data)
96+
def pack_advertisement(*args):
97+
advertisement_bytes = _ADVERT_FLAGS # All BTHome adverts start this way.
98+
advertisement_bytes += _pack_device_name()
99+
advertisement_bytes += _pack_service_data(*args)
100+
assert len(advertisement_bytes) < _ADVERT_LENGTH_MAX
101+
return advertisement_bytes

0 commit comments

Comments
 (0)