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:
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