qrdn

quite random domain name

Dez 27, 2022

Reading BTLE advertisements on Linux

Project context: reading environmental sensor data from card10 into influxDB.

GATT / ESS

epicardium (C Firmware) exports sensor data over Bluetooth GATT ESS (environmental sensing service).

GATT requires pairing, connecting → only 1:1, encrypted

Reading possible with card10_iaq_notify.py from https://git.card10.badge.events.ccc.de/card10/firmware/-/merge_requests/508/diffs

Turns out the eCO2 values computed by the BSEC values are quite bogus, compared to a proper CO2 NDIR sensor: https://dm1cr.de/co2-sensor-vergleich. I replicated these results:

Correlate BME680 with MH-Z19c

Now the idea is to check if the raw resistance values from the BME680 have some better correlation to actual CO2 values, without the intelligence from the proprietary BSEC library.

Epicardium has these values, but doesn't export these over bluetooth ESS. So instead, use a python script to do BT-LE advertisements ourselves: https://codeberg.org/scy/sensible-card10/ And extend it to include the raw resistance value.

reading GAP / BLE advertisements on linux

This is a real problem due to lack of documentation of bluez5. Most online searches turn up the deprecated hcitool and friends. There is a python library bluepy which implements BLE advertisement scanning, but docs are limited too: https://ianharvey.github.io/bluepy-doc/. Some blogpost mentions the backend binary bluepy-helper which can also be talked to directly: https://macchina.io/blog/internet-of-things/communication-with-low-energy-bluetooth-devices-on-linux/

GAP ~= beacons, limited data rate, 1:n, unencrypted. See https://elinux.org/images/3/32/Doing_Bluetooth_Low_Energy_on_Linux.pdf, https://learn.adafruit.com/introduction-to-bluetooth-low-energy?view=all

Check source is sending advertisements (card10 serial, sudo picocom -b 115200 /dev/ttyACM0 had python stacktrace in my case).

bluetooth snooping using bluez' btmon: no filtering, e.g. A2DP clutters.

Better: the reference tool blescan from bluepy:

$ sudo blescan -a -t0
    Device (new): ca:4d:10:XX:XX:XX (public), -70 dBm
        16b Service Data: <1a18c2cccc071b16187e0f00b100016c00860260>
        Complete Local Name: 'card10'

Copy implementation to implement own data parsing:

from bluepy import btle

class ScanPrint(btle.DefaultDelegate):
    def handleDiscovery(self, dev: btle.ScanEntry, isNewDev: bool, isNewData: bool):
        magic, version, temp, hum, press, gr, iaqa, iaq, eco2, battery = \
            struct.unpack('<HHhHLhBHHB', dev.getValue(btle.ScanEntry.SERVICE_DATA_16B))
        # ScanEntry.getValueText() parses bytes into hex-digits, but struct.unpack() needs bytes from getValue