fix(gateway): fix BLE filtering and MQTT connection validation
BLE filtering: - Replace UUID service filter with company_id 0xffff filter in manufacturer_data — Zephyr firmware does not announce a service UUID - Remove preamble skip in decode_payload (i=0 instead of i=2) since company_id is handled by the BLE stack and not in raw bytes - Fix endianness: switch temperature and CO2 decoding from little-endian to big-endian to match Zephyr firmware implementation MQTT validation: - Add on_connect callback to confirm successful connection to broker - Log error with reason code if connection fails - Add on_publish callback to confirm message delivery to broker - Replace synchronous connected log with async connecting log Validated end-to-end: Thingy:52 data received and confirmed by broker
This commit is contained in:
@@ -54,22 +54,36 @@ class Gateway:
|
||||
self.mqttc.tls_set()
|
||||
log.info("TLS enabled")
|
||||
|
||||
# Callback to confirm connection to broker
|
||||
def on_connect(client, userdata, flags, reason_code, properties):
|
||||
if reason_code == 0:
|
||||
log.info("Successfully connected to MQTT broker")
|
||||
else:
|
||||
log.error(f"Failed to connect to MQTT broker — reason code: {reason_code}")
|
||||
|
||||
# Callback to confirm message delivery to broker
|
||||
def on_publish(client, userdata, mid, reason_code, properties):
|
||||
log.info(f"Message confirmed by broker — mid: {mid}")
|
||||
|
||||
self.mqttc.on_connect = on_connect
|
||||
self.mqttc.on_publish = on_publish
|
||||
|
||||
self.mqttc.connect(self.mqtt_broker, self.mqtt_port)
|
||||
self.mqttc.loop_start()
|
||||
log.info("MQTT client connected")
|
||||
log.info("MQTT client connecting...")
|
||||
|
||||
def decode_payload(self, data: bytes) -> dict:
|
||||
"""Decode key/value pairs from BLE advertising payload.
|
||||
|
||||
Format per firmware spec:
|
||||
2 bytes preamble (company id = 0xffff) — skipped
|
||||
Format per firmware spec (no preamble in raw bytes — company_id
|
||||
is handled by the BLE stack and not included in manufacturer data):
|
||||
0x01 : window open (1 byte, 0 or 1)
|
||||
0x02 : humidity (1 byte, integer %)
|
||||
0x03 : temperature (2 bytes big-endian, integer / 10 = degrees C)
|
||||
0x04 : CO2 ppm (4 bytes big-endian, integer)
|
||||
"""
|
||||
result = {}
|
||||
i = 2 # skip 2-byte preamble (company id 0xffff)
|
||||
i = 0 # no preamble to skip — company_id is not in raw bytes
|
||||
while i < len(data):
|
||||
key = data[i]
|
||||
i += 1
|
||||
@@ -114,15 +128,13 @@ class Gateway:
|
||||
log.info(f"Published to {topic} : {payload}")
|
||||
|
||||
def on_device_found(self, device, adv_data):
|
||||
"""BLE scan callback — filters on Adrien's preamble (0xffff) in manufacturer data."""
|
||||
if not adv_data.manufacturer_data:
|
||||
"""BLE scan callback — filters on company_id 0xffff in manufacturer data."""
|
||||
if 0xffff not in adv_data.manufacturer_data:
|
||||
return
|
||||
|
||||
raw = list(adv_data.manufacturer_data.values())[0]
|
||||
|
||||
# Filter on preamble 0xffff (company id defined in firmware spec)
|
||||
if len(raw) < 2 or raw[0] != 0xff or raw[1] != 0xff:
|
||||
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]
|
||||
|
||||
log.debug(f"{device.address} | Thingy detected, raw: {list(raw)}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user