fix(gateway): use exact payload size filter and improve robustness
- Use exact 12-byte payload size filter instead of minimum to avoid false positives from non-Thingy devices with same company_id - Discard CO2 value 0xFFFFFFFF indicating sensor not ready or failed - Downgrade unknown key and empty payload logs to DEBUG level - Add on_connect callback to confirm successful broker connection - Add on_publish callback to confirm message delivery to broker - Rename MIN_PAYLOAD_SIZE to EXPECTED_PAYLOAD_SIZE for clarity Validated: data received and confirmed by broker after Thingy reboot
This commit is contained in:
@@ -24,6 +24,13 @@ class Gateway:
|
|||||||
KEY_TEMP = 0x03
|
KEY_TEMP = 0x03
|
||||||
KEY_CO2 = 0x04
|
KEY_CO2 = 0x04
|
||||||
|
|
||||||
|
# Sentinel value indicating sensor failure or not ready
|
||||||
|
INVALID_VALUE = 0xFFFFFFFF
|
||||||
|
|
||||||
|
# Expected payload size in bytes:
|
||||||
|
# 4 keys (1B each) + window(1B) + humidity(1B) + temp(2B) + co2(4B) = 12 bytes
|
||||||
|
EXPECTED_PAYLOAD_SIZE = 12
|
||||||
|
|
||||||
def __init__(self, config: dict):
|
def __init__(self, config: dict):
|
||||||
self.gateway_id = config["gateway_id"]
|
self.gateway_id = config["gateway_id"]
|
||||||
self.mqtt_broker = config["mqtt"]["broker"]
|
self.mqtt_broker = config["mqtt"]["broker"]
|
||||||
@@ -81,6 +88,9 @@ class Gateway:
|
|||||||
0x02 : humidity (1 byte, integer %)
|
0x02 : humidity (1 byte, integer %)
|
||||||
0x03 : temperature (2 bytes big-endian, integer / 10 = degrees C)
|
0x03 : temperature (2 bytes big-endian, integer / 10 = degrees C)
|
||||||
0x04 : CO2 ppm (4 bytes big-endian, integer)
|
0x04 : CO2 ppm (4 bytes big-endian, integer)
|
||||||
|
|
||||||
|
Values equal to 0xFFFFFFFF indicate sensor failure or not ready
|
||||||
|
and are discarded.
|
||||||
"""
|
"""
|
||||||
result = {}
|
result = {}
|
||||||
i = 0 # no preamble to skip — company_id is not in raw bytes
|
i = 0 # no preamble to skip — company_id is not in raw bytes
|
||||||
@@ -100,10 +110,16 @@ class Gateway:
|
|||||||
result["temp"] = raw / 10
|
result["temp"] = raw / 10
|
||||||
i += 2
|
i += 2
|
||||||
elif key == self.KEY_CO2 and i + 3 < len(data):
|
elif key == self.KEY_CO2 and i + 3 < len(data):
|
||||||
result["co2_ppm"] = int.from_bytes(data[i:i+4], byteorder='big')
|
co2 = int.from_bytes(data[i:i+4], byteorder='big')
|
||||||
|
# 0xFFFFFFFF indicates sensor not ready or failed
|
||||||
|
if co2 != self.INVALID_VALUE:
|
||||||
|
result["co2_ppm"] = co2
|
||||||
|
else:
|
||||||
|
log.debug(f"CO2 sensor not ready — discarding value 0xFFFFFFFF")
|
||||||
i += 4
|
i += 4
|
||||||
else:
|
else:
|
||||||
log.warning(f"Unknown key 0x{key:02x} at offset {i-1}")
|
# Unknown key — likely a non-Thingy device, ignore silently
|
||||||
|
log.debug(f"Unknown key 0x{key:02x} at offset {i-1}")
|
||||||
break
|
break
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -132,15 +148,19 @@ class Gateway:
|
|||||||
if 0xffff not in adv_data.manufacturer_data:
|
if 0xffff not in adv_data.manufacturer_data:
|
||||||
return
|
return
|
||||||
|
|
||||||
# company_id 0xffff is defined in the firmware spec
|
|
||||||
# the raw bytes do not include the company_id itself
|
|
||||||
raw = adv_data.manufacturer_data[0xffff]
|
raw = adv_data.manufacturer_data[0xffff]
|
||||||
|
|
||||||
|
# Filter on exact payload size to avoid false positives from
|
||||||
|
# other BLE devices using the same company_id
|
||||||
|
if len(raw) != self.EXPECTED_PAYLOAD_SIZE:
|
||||||
|
log.debug(f"{device.address} | ignored — unexpected payload size: {len(raw)}")
|
||||||
|
return
|
||||||
|
|
||||||
log.debug(f"{device.address} | Thingy detected, raw: {list(raw)}")
|
log.debug(f"{device.address} | Thingy detected, raw: {list(raw)}")
|
||||||
|
|
||||||
data = self.decode_payload(raw)
|
data = self.decode_payload(raw)
|
||||||
if not data:
|
if not data:
|
||||||
log.warning(f"{device.address} | empty decoded payload")
|
log.debug(f"{device.address} | empty decoded payload — ignored")
|
||||||
return
|
return
|
||||||
|
|
||||||
log.debug(f"{device.address} | decoded: {data}")
|
log.debug(f"{device.address} | decoded: {data}")
|
||||||
|
|||||||
Reference in New Issue
Block a user