Skip to content

Commit d433137

Browse files
committed
fix: add range check for temperature and humidity
Adds a check to guard against physically impossible reported values. Temperatures below absolute zero, or humidity outside 0-100 %, likely are the result of an invalid received packet and should be disregarded (disregard entire packet, other data probably also wrong).
1 parent d995689 commit d433137

File tree

1 file changed

+36
-13
lines changed

1 file changed

+36
-13
lines changed

src/thermopro_ble/parser.py

+36-13
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import logging
1212
from math import tanh
1313
from struct import Struct
14+
from typing import Union
1415

1516
from bluetooth_data_tools import short_address
1617
from bluetooth_sensor_state_data import BluetoothData
@@ -29,6 +30,9 @@
2930
UNPACK_TEMP_HUMID = Struct("<hB").unpack
3031
UNPACK_SPIKE_TEMP = Struct("<BHHH").unpack
3132

33+
ABSOLUTE_MIN_HUMIDITY__PERCENTAGE = 0
34+
ABSOLUTE_MAX_HUMIDITY__PERCENTAGE = 100
35+
ABSOLUTE_MIN_TEMPERATURE__CELCIUS = -273.15
3236

3337
# TP96x battery values appear to be a voltage reading, probably in millivolts.
3438
# This means that calculating battery life from it is a non-linear function.
@@ -41,6 +45,15 @@ def tp96_battery(voltage: int) -> float:
4145
clamped = max(0, min(raw, 100))
4246
return round(clamped, 2)
4347

48+
def is_temp_hum_invalid(temperature: Union[int, float], humidity: Union[int, float]) -> bool:
49+
"""Returns true if the measured values are outside the physically possible range."""
50+
# Note: This will not catch implausibly high temperature values, but a clear
51+
# upper temperature cutoff is not easy to define
52+
if temperature < ABSOLUTE_MIN_TEMPERATURE__CELCIUS:
53+
return True
54+
if not (ABSOLUTE_MIN_HUMIDITY__PERCENTAGE <= humidity <= ABSOLUTE_MAX_HUMIDITY__PERCENTAGE):
55+
return True
56+
return False
4457

4558
class ThermoProBluetoothDeviceData(BluetoothData):
4659
"""Date update for ThermoPro Bluetooth devices."""
@@ -91,6 +104,11 @@ def _start_update(self, service_info: BluetoothServiceInfo) -> None:
91104
internal_temp = internal_temp - 30
92105
ambient_temp = ambient_temp - 30
93106
battery_percent = tp96_battery(battery_voltage)
107+
108+
if is_temp_hum_invalid(internal_temp, 0) or is_temp_hum_invalid(ambient_temp, 0):
109+
# Invalid packet, probably corrupted
110+
return
111+
94112
self.update_predefined_sensor(
95113
SensorLibrary.TEMPERATURE__CELSIUS,
96114
internal_temp,
@@ -110,16 +128,21 @@ def _start_update(self, service_info: BluetoothServiceInfo) -> None:
110128
key=f"battery_probe_{probe_one_indexed}",
111129
name=f"Probe {probe_one_indexed} Battery",
112130
)
113-
return
114-
115-
# TP357S seems to be in 6, TP397 and TP393 in 4
116-
battery_byte = data[6] if len(data) == 7 else data[4]
117-
if battery_byte in BATTERY_VALUE_TO_LEVEL:
118-
self.update_predefined_sensor(
119-
SensorLibrary.BATTERY__PERCENTAGE,
120-
BATTERY_VALUE_TO_LEVEL[battery_byte],
121-
)
122-
123-
(temp, humi) = UNPACK_TEMP_HUMID(data[1:4])
124-
self.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, temp / 10)
125-
self.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, humi)
131+
else:
132+
# TP357S seems to be in 6, TP397 and TP393 in 4
133+
battery_byte = data[6] if len(data) == 7 else data[4]
134+
(temp_deci, humi) = UNPACK_TEMP_HUMID(data[1:4])
135+
temp = temp_deci / 10
136+
137+
if is_temp_hum_invalid(temp, humi):
138+
# Invalid data, probably corrupted
139+
return
140+
141+
if battery_byte in BATTERY_VALUE_TO_LEVEL:
142+
self.update_predefined_sensor(
143+
SensorLibrary.BATTERY__PERCENTAGE,
144+
BATTERY_VALUE_TO_LEVEL[battery_byte],
145+
)
146+
147+
self.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, temp)
148+
self.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, humi)

0 commit comments

Comments
 (0)