Skip to content

Commit c6efc02

Browse files
bthome.py - add object_ids
1 parent d802f3a commit c6efc02

File tree

1 file changed

+61
-23
lines changed

1 file changed

+61
-23
lines changed

src/bthome.py

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ class BTHome:
2727
_local_name = ""
2828

2929
# Naming convention is:
30-
# <property> "_" <data-type> "_x" <inverse of factor> = <object-id>
31-
# For example, 0x02 temperature sint16 0.01 becomes:
32-
# TEMPERATURE _ SINT16 _x 100 = 0x02
30+
# <const_name> ::= <property> "_" <data-type> "_x" <inverse of factor>
31+
# For example, temperature sint16 0.01 becomes:
32+
# TEMPERATURE _ SINT16 _x 100
3333
# See "Sensor Data" table at https://bthome.io/format/
3434
BATTERY_UINT8_X1 = const(0x01) # %
3535
TEMPERATURE_SINT16_X100 = const(0x02) # °C
@@ -75,6 +75,17 @@ class BTHome:
7575
TEXT_BYTES = const(0x53)
7676
RAW_BYTES = const(0x54)
7777
VOLUME_STORAGE_UINT32_X1000 = const(0x55) # L
78+
CONDUCTIVITY_UINT16_X1 = const(0x56) # µS/cm
79+
TEMPERATURE_SINT8_X1 = const(0x57) # °C
80+
# Skipping 0x58 due to strange factor of 0.35
81+
COUNT_SINT8_X1 = const(0x59)
82+
COUNT_SINT16_X1 = const(0x5A)
83+
COUNT_SINT32_X1 = const(0x5B)
84+
POWER_SINT16_X100 = const(0x5C) # W
85+
CURRENT_SINT16_X1000 = const(0x5D) # A
86+
DIRECTION_UINT16_X100 = const(0x5E) # °
87+
PRECIPITATION_UINT16_X1 = const(0x5F) # mm
88+
CHANNEL_UINT8_X1 = const(0x60)
7889

7990
# There is more than one way to represent most sensor properties. This
8091
# dictionary maps the object id to the property name.
@@ -119,7 +130,17 @@ class BTHome:
119130
TIMESTAMP_UINT48_X1: "timestamp",
120131
ACCELERATION_UINT16_X1000: "acceleration",
121132
GYROSCOPE_UINT16_X1000: "gyroscope",
122-
VOLUME_STORAGE_UINT32_X1000: "volume_storage"
133+
VOLUME_STORAGE_UINT32_X1000: "volume_storage",
134+
CONDUCTIVITY_UINT16_X1: "conductivity",
135+
TEMPERATURE_SINT8_X1: "temperature",
136+
COUNT_SINT8_X1: "count",
137+
COUNT_SINT16_X1: "count",
138+
COUNT_SINT32_X1: "count",
139+
POWER_SINT16_X100: "power",
140+
CURRENT_SINT16_X1000: "current",
141+
DIRECTION_UINT16_X100: "direction",
142+
PRECIPITATION_UINT16_X1: "precipitation",
143+
CHANNEL_UINT8_X1: "channel"
123144
}
124145

125146
# Properties below are updated externally when sensor values are read.
@@ -170,15 +191,6 @@ def __init__(self, local_name="BTHome", debug=False):
170191
def local_name(self):
171192
return self._local_name
172193

173-
def pack_local_name(self):
174-
name_type = bytes.fromhex("09") # indicator for complete name
175-
local_name_bytes = name_type + self._local_name.encode()
176-
local_name_bytes = bytes([len(local_name_bytes)]) + local_name_bytes
177-
if self.debug:
178-
print("Local name:", self._local_name)
179-
print("Packed representation:", local_name_bytes.hex().upper())
180-
return local_name_bytes
181-
182194
# Technically, the functions below could be static methods, but @staticmethod
183195
# on a dictionary of functions only works with Python >3.10, and MicroPython
184196
# is based on 3.4. Also, __func__ and __get()__ workarounds throw errors in
@@ -192,13 +204,9 @@ def _pack_uint8_x1(self, object_id, value):
192204
def _pack_uint8_x10(self, object_id, value):
193205
return pack("BB", object_id, round(value * 10))
194206

195-
# 16-bit signed integer with scalling of 10 (1 decimal place)
196-
def _pack_sint16_x10(self, object_id, value):
197-
return pack("<Bh", object_id, round(value * 10))
198-
199-
# 16-bit signed integer with scalling of 100 (2 decimal places)
200-
def _pack_sint16_x100(self, object_id, value):
201-
return pack("<Bh", object_id, round(value * 100))
207+
# 8-bit signed integer with scalling of 1 (no decimal places)
208+
def _pack_sint8_x1(self, object_id, value):
209+
return pack("BB", object_id, round(value))
202210

203211
# 16-bit unsigned integer with scalling of 1 (no decimal places)
204212
def _pack_uint16_x1(self, object_id, value):
@@ -216,6 +224,22 @@ def _pack_uint16_x100(self, object_id, value):
216224
def _pack_uint16_x1000(self, object_id, value):
217225
return pack("<BH", object_id, round(value * 1000))
218226

227+
# 16-bit signed integer with scalling of 1 (no decimal places)
228+
def _pack_sint16_x1(self, object_id, value):
229+
return pack("<BH", object_id, round(value))
230+
231+
# 16-bit signed integer with scalling of 10 (1 decimal place)
232+
def _pack_sint16_x10(self, object_id, value):
233+
return pack("<Bh", object_id, round(value * 10))
234+
235+
# 16-bit signed integer with scalling of 100 (2 decimal places)
236+
def _pack_sint16_x100(self, object_id, value):
237+
return pack("<Bh", object_id, round(value * 100))
238+
239+
# 16-bit signed integer with scalling of 1000 (3 decimal places)
240+
def _pack_sint16_x1000(self, object_id, value):
241+
return pack("<Bh", object_id, round(value * 1000))
242+
219243
# 24-bit unsigned integer with scaling of 100 (2 decimal places)
220244
def _pack_uint24_x100(self, object_id, value):
221245
return pack("<BL", object_id, round(value * 100))[:-1]
@@ -232,12 +256,16 @@ def _pack_uint32_x1(self, object_id, value):
232256
def _pack_uint32_x1000(self, object_id, value):
233257
return pack("<BL", object_id, round(value * 1000))
234258

259+
# 32-bit signed integer with scalling of 1 (no decimal places)
260+
def _pack_sint32_x1(self, object_id, value):
261+
return pack("<BL", object_id, round(value))
262+
235263
# 48-bit unsigned integer with scaling of 1 (no decimal places)
236264
def _pack_uint48_x1(self, object_id, value):
237265
return pack("<BQ", object_id, value)[:-2]
238266

239267
def _pack_raw_text(self, object_id, value):
240-
packed_value = bytes(object_id) + value.encode()
268+
packed_value = pack("B", object_id) + value.encode()
241269
packed_value = bytes([len(packed_value)]) + packed_value
242270
return packed_value
243271

@@ -285,7 +313,17 @@ def _pack_raw_text(self, object_id, value):
285313
GYROSCOPE_UINT16_X1000: _pack_uint16_x1000,
286314
TEXT_BYTES: _pack_raw_text,
287315
RAW_BYTES: _pack_raw_text,
288-
VOLUME_STORAGE_UINT32_X1000: _pack_uint32_x1000
316+
VOLUME_STORAGE_UINT32_X1000: _pack_uint32_x1000,
317+
CONDUCTIVITY_UINT16_X1: _pack_uint16_x1,
318+
TEMPERATURE_SINT8_X1: _pack_sint8_x1,
319+
COUNT_SINT8_X1: _pack_sint8_x1,
320+
COUNT_SINT16_X1: _pack_sint16_x1,
321+
COUNT_SINT32_X1: _pack_sint32_x1,
322+
POWER_SINT16_X100: _pack_sint16_x100,
323+
CURRENT_SINT16_X1000: _pack_sint16_x1000,
324+
DIRECTION_UINT16_X100: _pack_uint16_x100,
325+
PRECIPITATION_UINT16_X1: _pack_uint16_x1,
326+
CHANNEL_UINT8_X1: _pack_uint8_x1
289327
}
290328

291329
# Concatenate an arbitrary number of sensor readings using parameters
@@ -312,7 +350,7 @@ def _pack_service_data(self, *args):
312350

313351
def pack_advertisement(self, *args):
314352
advertisement_bytes = self._ADVERT_FLAGS # All BTHome adverts start this way.
315-
advertisement_bytes += self.pack_local_name()
353+
advertisement_bytes += self._pack_raw_text(0x09, self.local_name) # 0x09 indicates complete name
316354
advertisement_bytes += self._pack_service_data(*args)
317355
if self.debug:
318356
print("BLE Advertisement:", advertisement_bytes.hex().upper())

0 commit comments

Comments
 (0)