Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions custom_components/ble_monitor/ble_parser/chefiq.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@
_LOGGER = logging.getLogger(__name__)


# CQ60 firmware emits these sentinel values when a probe ring has nothing
# to read (probe partially inserted, broken probe wire, ring not in
# contact with food). They decode as ~3,276 °C if treated as a real value,
# which trips HA's temperature device-class limits and produces nonsense
# graphs / alerts. Mask them to ``None`` so the entity reads as
# ``unavailable`` instead.
TEMP_SENTINEL_MIN = 0x7FF0


def _decode_temp(raw: int) -> float | None:
"""Decode a little-endian uint16 temperature in tenths of °C."""
if raw >= TEMP_SENTINEL_MIN:
return None
return round(raw / 10, 1)


def parse_chefiq(self, data: str, mac: bytes):
"""Parse Chef iQ advertisement."""
msg_length = len(data)
Expand All @@ -21,12 +37,16 @@ def parse_chefiq(self, data: str, mac: bytes):
log_cnt = "no packet id"
result = {
"battery": batt,
"meat temperature": temp_meat / 10,
"temperature probe tip": temp_tip / 10,
"temperature probe 1": temp_probe_1 / 10,
"temperature probe 2": temp_probe_2 / 10,
"temperature probe 3": temp_probe_3,
"ambient temperature": temp_ambient / 10,
"meat temperature": _decode_temp(temp_meat),
"temperature probe tip": _decode_temp(temp_tip),
"temperature probe 1": _decode_temp(temp_probe_1),
"temperature probe 2": _decode_temp(temp_probe_2),
# Probe 3 is an 8-bit °C value (no /10 scaling).
# 0xFE / 0xFF are the disconnected sentinels.
"temperature probe 3": (
float(temp_probe_3) if temp_probe_3 < 0xFE else None
),
"ambient temperature": _decode_temp(temp_ambient),
}
else:
if self.report_unknown == "Chef iQ":
Expand Down
27 changes: 27 additions & 0 deletions custom_components/ble_monitor/test/test_chefiq.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,30 @@ def test_chefiq_cq60(self):
assert sensor_msg["ambient temperature"] == 19.2
assert sensor_msg["battery"] == 99
assert sensor_msg["rssi"] == -52

def test_chefiq_cq60_sentinels(self):
"""CQ60 emits 0x7FFB / 0xFE on probe rings that are not in contact
with anything. The parser should mask these as ``None`` so that the
downstream Home Assistant entities become *unavailable* instead of
reporting bogus 3,276 °C values that trip the temperature
device-class limits.
"""
# Same packet header as the working test, but every ring temperature
# is the not-measured sentinel:
# meat / tip / probe 1 / probe 2 / ambient = 0xFB7F (LE 0x7FFB)
# probe 3 = 0xFE
data_string = "043E250201000073332e3638d91902010615ffcd05014063FEFB7FFB7FFB7FFB7FFB7FFB7F8d11CC"
data = bytes(bytearray.fromhex(data_string))
ble_parser = BleParser()
sensor_msg, _ = ble_parser.parse_raw_data(data)

assert sensor_msg["firmware"] == "Chef iQ"
assert sensor_msg["type"] == "CQ60"
assert sensor_msg["meat temperature"] is None
assert sensor_msg["temperature probe tip"] is None
assert sensor_msg["temperature probe 1"] is None
assert sensor_msg["temperature probe 2"] is None
assert sensor_msg["temperature probe 3"] is None
assert sensor_msg["ambient temperature"] is None
# Battery and identity fields still populate normally
assert sensor_msg["battery"] == 99