Skip to content

Commit 0dbe8eb

Browse files
authored
feat: add support for raw data parsing (#89)
1 parent 3b560e6 commit 0dbe8eb

File tree

2 files changed

+146
-25
lines changed

2 files changed

+146
-25
lines changed

src/thermopro_ble/parser.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
"""Parser for ThermoPro BLE advertisements.
1+
"""
2+
Parser for ThermoPro BLE advertisements.
23
34
This file is shamelessly copied from the following repository:
45
https://github.com/Ernst79/bleparser/blob/c42ae922e1abed2720c7fac993777e1bd59c0c93/package/bleparser/thermopro.py
@@ -12,11 +13,12 @@
1213
from math import tanh
1314
from struct import Struct
1415

15-
from bluetooth_data_tools import short_address
16+
from bluetooth_data_tools import parse_advertisement_data_bytes, short_address
1617
from bluetooth_sensor_state_data import BluetoothData
17-
from home_assistant_bluetooth import BluetoothServiceInfo
1818
from sensor_state_data import SensorLibrary
1919

20+
from habluetooth import BluetoothServiceInfoBleak
21+
2022
_LOGGER = logging.getLogger(__name__)
2123

2224

@@ -45,7 +47,7 @@ def tp96_battery(voltage: int) -> float:
4547
class ThermoProBluetoothDeviceData(BluetoothData):
4648
"""Date update for ThermoPro Bluetooth devices."""
4749

48-
def _start_update(self, service_info: BluetoothServiceInfo) -> None:
50+
def _start_update(self, service_info: BluetoothServiceInfoBleak) -> None:
4951
"""Update from BLE advertisement data."""
5052
_LOGGER.debug("Parsing thermopro BLE advertisement data: %s", service_info)
5153
name = service_info.name
@@ -58,7 +60,14 @@ def _start_update(self, service_info: BluetoothServiceInfo) -> None:
5860
self.set_device_name(name)
5961
self.set_precision(2)
6062
self.set_device_manufacturer("ThermoPro")
61-
changed_manufacturer_data = self.changed_manufacturer_data(service_info)
63+
if service_info.raw:
64+
# If we have the raw data we don't need to work out
65+
# which one is the newest.
66+
_, _, _, changed_manufacturer_data, _ = parse_advertisement_data_bytes(
67+
service_info.raw
68+
)
69+
else:
70+
changed_manufacturer_data = self.changed_manufacturer_data(service_info)
6271

6372
if not changed_manufacturer_data or len(changed_manufacturer_data) > 1:
6473
# If len(changed_manufacturer_data) > 1 it means we switched

tests/test_parser.py

Lines changed: 132 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
from bluetooth_sensor_state_data import BluetoothServiceInfo, SensorUpdate
1+
from uuid import UUID
2+
3+
from bluetooth_data_tools import monotonic_time_coarse
4+
from bluetooth_sensor_state_data import SensorUpdate
25
from sensor_state_data import (
36
DeviceKey,
47
SensorDescription,
@@ -7,15 +10,50 @@
710
SensorValue,
811
Units,
912
)
10-
1113
from thermopro_ble.parser import ThermoProBluetoothDeviceData
1214

15+
from bleak.backends.device import BLEDevice
16+
from habluetooth import BluetoothServiceInfoBleak
17+
18+
19+
def make_bluetooth_service_info(
20+
name: str,
21+
manufacturer_data: dict[int, bytes],
22+
service_uuids: list[str],
23+
address: str,
24+
rssi: int,
25+
service_data: dict[UUID, bytes],
26+
source: str,
27+
tx_power: int = 0,
28+
raw: bytes | None = None,
29+
) -> BluetoothServiceInfoBleak:
30+
return BluetoothServiceInfoBleak(
31+
name=name,
32+
manufacturer_data=manufacturer_data,
33+
service_uuids=service_uuids,
34+
address=address,
35+
rssi=rssi,
36+
service_data=service_data,
37+
source=source,
38+
device=BLEDevice(
39+
name=name,
40+
address=address,
41+
details={},
42+
rssi=rssi,
43+
),
44+
time=monotonic_time_coarse(),
45+
advertisement=None,
46+
connectable=True,
47+
tx_power=tx_power,
48+
raw=raw,
49+
)
50+
1351

1452
def test_can_create():
1553
ThermoProBluetoothDeviceData()
1654

1755

18-
TP357 = BluetoothServiceInfo(
56+
TP357 = make_bluetooth_service_info(
1957
name="TP357 (2142)",
2058
manufacturer_data={61890: b"\x00\x1d\x02,"},
2159
service_uuids=[],
@@ -25,7 +63,19 @@ def test_can_create():
2563
source="local",
2664
)
2765

28-
TP357_ADD = BluetoothServiceInfo(
66+
67+
TP357_RAW = make_bluetooth_service_info(
68+
name="TP357 (2142)",
69+
manufacturer_data={1: b"\x02,"},
70+
service_uuids=[],
71+
address="aa:bb:cc:dd:ee:ff",
72+
rssi=-60,
73+
service_data={},
74+
source="local",
75+
raw=b"\x07\xff\x82\xf1\x00\x1d\x02,",
76+
)
77+
78+
TP357_ADD = make_bluetooth_service_info(
2979
name="TP357 (2142)",
3080
manufacturer_data={63938: b"\x00\x10\x02,"},
3181
service_uuids=[],
@@ -35,7 +85,7 @@ def test_can_create():
3585
source="local",
3686
)
3787

38-
TP357_S = BluetoothServiceInfo(
88+
TP357_S = make_bluetooth_service_info(
3989
name="TP357S (2142)",
4090
manufacturer_data={
4191
61122: b'\x00)"\x0b\x01',
@@ -48,7 +98,7 @@ def test_can_create():
4898
)
4999

50100

51-
TP357_S_2 = BluetoothServiceInfo(
101+
TP357_S_2 = make_bluetooth_service_info(
52102
name="TP357S (2142)",
53103
manufacturer_data={
54104
61122: b'\x00)"\x0b\x01',
@@ -101,7 +151,7 @@ def test_can_create():
101151
)
102152

103153

104-
TP393 = BluetoothServiceInfo(
154+
TP393 = make_bluetooth_service_info(
105155
name="TP393 (9376)",
106156
manufacturer_data={62146: b"\x005\x02,"},
107157
service_uuids=[],
@@ -112,7 +162,7 @@ def test_can_create():
112162
)
113163

114164

115-
TP393_DETECT_CHANGED_1 = BluetoothServiceInfo(
165+
TP393_DETECT_CHANGED_1 = make_bluetooth_service_info(
116166
name="TP393 (9376)",
117167
manufacturer_data={
118168
194: b"\x00\x00\x00,",
@@ -139,7 +189,7 @@ def test_can_create():
139189
source="local",
140190
)
141191

142-
TP393_DETECT_CHANGED_2 = BluetoothServiceInfo(
192+
TP393_DETECT_CHANGED_2 = make_bluetooth_service_info(
143193
name="TP393 (9376)",
144194
manufacturer_data={
145195
194: b"\x00\x00\x00,",
@@ -166,7 +216,7 @@ def test_can_create():
166216
source="local",
167217
)
168218

169-
TP960R = BluetoothServiceInfo(
219+
TP960R = make_bluetooth_service_info(
170220
name="TP960R (0000)",
171221
manufacturer_data={14848: b"\x000\x088\x00"},
172222
service_uuids=["72fbb631-6f6b-d1ba-db55-2ee6fdd942bd"],
@@ -175,7 +225,7 @@ def test_can_create():
175225
service_data={},
176226
source="local",
177227
)
178-
TP960R_2 = BluetoothServiceInfo(
228+
TP960R_2 = make_bluetooth_service_info(
179229
name="TP960R (0000)",
180230
manufacturer_data={
181231
14848: b"\x000\x088\x00",
@@ -189,7 +239,7 @@ def test_can_create():
189239
source="local",
190240
)
191241

192-
TP962R = BluetoothServiceInfo(
242+
TP962R = make_bluetooth_service_info(
193243
name="TP962R (0000)",
194244
manufacturer_data={14081: b"\x00;\x0b7\x00"},
195245
service_uuids=["72fbb631-6f6b-d1ba-db55-2ee6fdd942bd"],
@@ -198,7 +248,7 @@ def test_can_create():
198248
service_data={},
199249
source="local",
200250
)
201-
TP962R_2 = BluetoothServiceInfo(
251+
TP962R_2 = make_bluetooth_service_info(
202252
name="TP962R (0000)",
203253
manufacturer_data={17152: b"\x00\x17\nC\x00", 14081: b"\x00;\x0b7\x00"},
204254
service_uuids=["72fbb631-6f6b-d1ba-db55-2ee6fdd942bd"],
@@ -208,7 +258,7 @@ def test_can_create():
208258
source="local",
209259
)
210260

211-
TP970R = BluetoothServiceInfo(
261+
TP970R = make_bluetooth_service_info(
212262
name="TP970R",
213263
manufacturer_data={13568: b"\x00F\x0b5\x00"},
214264
service_uuids=["72fbb631-6f6b-d1ba-db55-2ee6fdd942bd"],
@@ -217,7 +267,7 @@ def test_can_create():
217267
service_data={},
218268
source="local",
219269
)
220-
TP970R_2 = BluetoothServiceInfo(
270+
TP970R_2 = make_bluetooth_service_info(
221271
name="TP970R",
222272
manufacturer_data={13312: b"\x00\xae\x0b4\x00"},
223273
service_uuids=["72fbb631-6f6b-d1ba-db55-2ee6fdd942bd"],
@@ -226,7 +276,7 @@ def test_can_create():
226276
service_data={},
227277
source="local",
228278
)
229-
TP357S_UPDATE_1 = BluetoothServiceInfo(
279+
TP357S_UPDATE_1 = make_bluetooth_service_info(
230280
name="TP357S (C890)",
231281
address="C3:18:C9:9C:C8:90",
232282
rssi=-57,
@@ -256,7 +306,7 @@ def test_can_create():
256306
service_uuids=[],
257307
source="2C:CF:67:1B:03:3A",
258308
)
259-
TP357S_UPDATE_2 = BluetoothServiceInfo(
309+
TP357S_UPDATE_2 = make_bluetooth_service_info(
260310
name="TP357S (C890)",
261311
address="C3:18:C9:9C:C8:90",
262312
rssi=-56,
@@ -286,7 +336,7 @@ def test_can_create():
286336
service_uuids=[],
287337
source="2C:CF:67:1B:03:3A",
288338
)
289-
TP357S_UPDATE_3 = BluetoothServiceInfo(
339+
TP357S_UPDATE_3 = make_bluetooth_service_info(
290340
name="TP357S (C890)",
291341
address="C3:18:C9:9C:C8:90",
292342
rssi=-65,
@@ -317,7 +367,7 @@ def test_can_create():
317367
service_uuids=[],
318368
source="2C:CF:67:1B:03:3A",
319369
)
320-
TP357S_UPDATE_4 = BluetoothServiceInfo(
370+
TP357S_UPDATE_4 = make_bluetooth_service_info(
321371
name="TP357S (C890)",
322372
address="C3:18:C9:9C:C8:90",
323373
rssi=-55,
@@ -352,7 +402,7 @@ def test_can_create():
352402

353403
def test_supported_set_the_title():
354404
parser = ThermoProBluetoothDeviceData()
355-
parser.supported(TP357) is True
405+
assert parser.supported(TP357) is True
356406
assert parser.title == "TP357 (2142) EEFF"
357407

358408

@@ -418,6 +468,68 @@ def test_tp357():
418468
)
419469

420470

471+
def test_tp357_raw():
472+
parser = ThermoProBluetoothDeviceData()
473+
assert parser.update(TP357_RAW) == SensorUpdate(
474+
title="TP357 (2142) EEFF",
475+
devices={
476+
None: SensorDeviceInfo(
477+
name="TP357 (2142)",
478+
model="TP357",
479+
manufacturer="ThermoPro",
480+
sw_version=None,
481+
hw_version=None,
482+
)
483+
},
484+
entity_descriptions={
485+
DeviceKey(key="temperature", device_id=None): SensorDescription(
486+
device_key=DeviceKey(key="temperature", device_id=None),
487+
device_class=SensorDeviceClass.TEMPERATURE,
488+
native_unit_of_measurement=Units.TEMP_CELSIUS,
489+
),
490+
DeviceKey(key="humidity", device_id=None): SensorDescription(
491+
device_key=DeviceKey(key="humidity", device_id=None),
492+
device_class=SensorDeviceClass.HUMIDITY,
493+
native_unit_of_measurement=Units.PERCENTAGE,
494+
),
495+
DeviceKey(key="signal_strength", device_id=None): SensorDescription(
496+
device_key=DeviceKey(key="signal_strength", device_id=None),
497+
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
498+
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
499+
),
500+
DeviceKey(key="battery", device_id=None): SensorDescription(
501+
device_key=DeviceKey(key="battery", device_id=None),
502+
device_class=SensorDeviceClass.BATTERY,
503+
native_unit_of_measurement=Units.PERCENTAGE,
504+
),
505+
},
506+
entity_values={
507+
DeviceKey(key="temperature", device_id=None): SensorValue(
508+
device_key=DeviceKey(key="temperature", device_id=None),
509+
name="Temperature",
510+
native_value=24.1,
511+
),
512+
DeviceKey(key="humidity", device_id=None): SensorValue(
513+
device_key=DeviceKey(key="humidity", device_id=None),
514+
name="Humidity",
515+
native_value=29,
516+
),
517+
DeviceKey(key="signal_strength", device_id=None): SensorValue(
518+
device_key=DeviceKey(key="signal_strength", device_id=None),
519+
name="Signal Strength",
520+
native_value=-60,
521+
),
522+
DeviceKey(key="battery", device_id=None): SensorValue(
523+
device_key=DeviceKey(key="battery", device_id=None),
524+
name="Battery",
525+
native_value=100,
526+
),
527+
},
528+
binary_entity_descriptions={},
529+
binary_entity_values={},
530+
)
531+
532+
421533
def test_tp960r():
422534
parser = ThermoProBluetoothDeviceData()
423535
assert parser.update(TP960R) == SensorUpdate(

0 commit comments

Comments
 (0)