Hőmérséklet monitorozása
Nagyon régóta érdekel az IoT (Internet of Things, azaz Internetre köthető kütyük) világa, ugyanis ezek azok az eszközök, melyek összekötik a virtuális világót a való világgal. Már régóta rendelkezem egy Raspberry PI számítógéppel, melyet azóta is lelkesen használok egy alacsony fogyasztású home serverként. Telepítve van rá a Prometheus és Grafana, mely a DevOps világban egy kvázi standard monitorozó eszköz. Mindkettő ingyenesen használható, nyílt forráskódú eszköz. A Prometheus különösen alkalmas idősorok hatékony tárolására, gyors lekérdezésére, aggregált műveletek végrehajtására. Az idősorok olyan megfigyelések, melyeket egymást követő időpontokban (időszakokban) regisztrálták, és ez az időbeliség az adatok fontos tulajdonsága. Ilyen például egy szerverrel kapcsolatban a bizonyos időpontokban lekérdezett CPU és memóriahasználat, vagy a háttértárakon lévő szabad hely mérete. A Grafana használatával ezeket tudjuk vizualizálni, gyönyörű dashboardokat létrehozni és akár riasztásokat beállítani.
De melyik is lehet az az IoT eszköz, melyet a legegyszerűbben, lehetőleg barkácsolás nélkül és olcsón lehetne bekötni ebbe a rendszerbe? Már régóta szemezem a Xiaomi Mi Temperature and Humidity Monitor 2 hőmérséklet-, és páratartalom mérővel, amihez most 2000 Ft-ért lehet hozzájutni az mStore akciója keretében.
Ez pontosan a LYWSD03MMC modell, mely egy precíz Sensirion szenzorral, 1,5”-os LCD kijelzővel rendelkezik, és Bluetooth 4.2 BLE vezeték nélküli kapcsolaton keresztül kommunikál. Egy CR2032 elemmel működik, ezt külön szerezzük be, mert nem része a csomagnak. Ezzel akár fél-egy évig képes üzemelni. Természetesen Bluetooth-on tud kapcsolódni mobiltelefonhoz, illetve Bluetooth Gateway-hez, azonban én direktbe, egy Linuxos számítógéppel, jelen esetben egy Raspberry PI-vel szeretnék hozzá kapcsolódni, és kinyerni belőle az adatokat. (Nincs szükség egyedi firmware telepítésére.)
Ezt utána csak be kell kötni a Prometheusba, mely időközönként lekérdezi és eltárolja az adatokat, majd egy Grafana dashboardot létrehozni, mely megjeleníti azokat.
Közben természetesen szerettem volna megismerni a kapcsolódó technológiákat is.
A szenzor a BLE (Bluetooth Low Energy) vezeték nélküli kommunikációs technológiát használja, amelyet alacsony energiafogyasztású eszközök közötti adatátvitelre terveztek. Persze a hatótávolság és adatátvitel korlátozottabb, mint a hagyományos Bluetooth esetén. Az eszközök az ún. Generic Attribute Profile (GATT) specifikáció alapján kommunikálnak. Ez a következő fogalmakat használja:
- Client: ami az eszközről lekérdezi az adatokat, kezdeményezi a kommunikációt, kéréseket küld az eszköz számára, és fogadja a válaszokat. Ez lesz a Raspberry PI.
- Server: maga az eszköz, jelen esetben a szenzor.
- Service: az eszközön belül egy kisebb részegység, mely az egybe tartozó funkciókért felelős. Ezt kell megszólítani, ez adja vissza az adatokat.
- Characteristic: átküldött adat, ilyen a hőmérséklet, a páratartalom, és az elem állapota.
Ezek az eszközök ezen kívül apró csomagokat szórnak szét, mellyel jelzik, hogy lehet hozzájuk csatlakozni.
Érdekes módon Bluetooth eszközöket a Chrome-ban is fel lehet deríteni, ehhez ne felejtsük el bekapcsolni a számítógépünkön a Bluetooth kapcsolatot.
Utána a chrome://bluetooth-internals/#devices
címre kell ellátogatni, és már láthatjuk is a Bluetooth eszközöket. Nálam bőven tízes nagyságrendű eszköz van,
ezek közül többhöz csatlakozni is lehet, ilyen pl. a hőmérséklet szenzor, okosmérleg, fogkefék, stb. A keresett eszköznek megjelenik a modell azonosítója (LYWSD03MMC),
valamint a MAC-címe (ez egy egyedi cím, formátuma: 1A-E3-2C-9F-68-8D
). Ezen a felületen lehet is csatlakozni, és lekérdezni a service-eket,
valamint az alatt a characteristics-et. Minden service egyedi UUID-val is rendelkezik (pl. 0000180f-0000-1000-8000-00805f9b34fb
).
De persze sokkal izgalmasabb, hogy hogy lehet ezt Raspberry PI-n elvégezni. Vigyázat, a Raspberry PI 3 lapkán egy Broadcom BCM43438 LAN és Bluetooth Low Energy (BLE) chip van, mely nem engedélyezi egyszerre a WiFi-t és a Bluetooth-t használni. Így ki kell kapcsolni a WiFi-t, vagy venni egy külön USB-s WiFi vagy Bluetooth adaptert.
A Bluetooth interfész a hci0
, mely a következő paranccsal ellenőrizhető:
hciconfig
Ennek az eredménye valami hasonló:
hci0: Type: Primary Bus: UART
BD Address: B8:27:EB:FE:FE:3B ACL MTU: 1021:8 SCO MTU: 64:1
UP RUNNING
RX bytes:31612 acl:387 sco:0 events:1812 errors:0
TX bytes:19387 acl:60 sco:0 commands:1147 errors:0
A Bluetooth eszközök felderítése a következő paranccsal lehetséges:
sudo hcitool lescan
Ennek eredménye valami hasonló:
61-0D-49-18-9F-C8 (unknown)
49-49-73-D6-F3-47 (unknown)
24-48-8C-94-96-4C (unknown)
24-48-8C-94-96-4C LYWSD03MMC
C8-6A-2F-5E-10-22 (unknown)
98-87-5C-C1-DC-5E MI_SCALE
A LYWSD03MMC
megnevezéssel találjuk meg a szenzorunk, és annak a MAC-címét.
Ezek után hozzá kell kapcsolódni az eszközöz, és
beállítani, hogy a mért értékekről küldjön értesítéseket. (Ezt mobiltelefonról is
meg lehetne tenni, de így legalább látunk egy példát, hogy kell alacsony szintű parancsokat
kiadni. Ha mégis telefonról akarjuk aktiválni, akkor
csatlakozzunk az eszközhöz a Mi Home alkalmazásból, majd várjuk meg, míg letölti az adatokat.)
Ha mégis parancssorból szeretnénk beállítani, akkor a gatttool
eszközt kell használni.
gatttool -I -b 24-48-8C-94-96-4C
[24-48-8C-94-96-4C][LE]> connect
Attempting to connect to 24-48-8C-94-96-4C
Connection successful
Notification handle = 0x0036 value: e4 0a 43 c8 0b
[24-48-8C-94-96-4C][LE]>
Itt le lehet kérni a service-eket:
[24-48-8C-94-96-4C][LE]> primary
attr handle: 0x0001, end grp handle: 0x0007 uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x0008, end grp handle: 0x000b uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x000c, end grp handle: 0x0018 uuid: 0000180a-0000-1000-8000-00805f9b34fb
attr handle: 0x0019, end grp handle: 0x001c uuid: 0000180f-0000-1000-8000-00805f9b34fb
attr handle: 0x001d, end grp handle: 0x0020 uuid: 00010203-0405-0607-0809-0a0b0c0d1912
attr handle: 0x0021, end grp handle: 0x004e uuid: ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6
attr handle: 0x004f, end grp handle: 0x005d uuid: 8edffff0-3d1b-9c37-4623-ad7265f14076
attr handle: 0x005e, end grp handle: 0x0071 uuid: 0000fe95-0000-1000-8000-00805f9b34fb
attr handle: 0x0072, end grp handle: 0x007a uuid: 00000100-0065-6c62-2e74-6f696d2e696d
És a service-ben lévő characteristics értékeket a service attr handle
és end grp handle
kiválasztásával.
[24-48-8C-94-96-4C][LE]> characteristics 0x0021 0x004e
handle: 0x0022, char properties: 0x0a, char value handle: 0x0023, uuid: ebe0ccb7-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x0025, char properties: 0x02, char value handle: 0x0026, uuid: ebe0ccb9-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x0028, char properties: 0x0a, char value handle: 0x0029, uuid: ebe0ccba-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x002b, char properties: 0x02, char value handle: 0x002c, uuid: ebe0ccbb-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x002e, char properties: 0x10, char value handle: 0x002f, uuid: ebe0ccbc-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x0032, char properties: 0x0a, char value handle: 0x0033, uuid: ebe0ccbe-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x0035, char properties: 0x12, char value handle: 0x0036, uuid: ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x0039, char properties: 0x02, char value handle: 0x003a, uuid: ebe0ccc4-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x003c, char properties: 0x08, char value handle: 0x003d, uuid: ebe0ccc8-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x003f, char properties: 0x08, char value handle: 0x0040, uuid: ebe0ccd1-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x0042, char properties: 0x0a, char value handle: 0x0043, uuid: ebe0ccd7-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x0045, char properties: 0x08, char value handle: 0x0046, uuid: ebe0ccd8-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x0048, char properties: 0x18, char value handle: 0x0049, uuid: ebe0ccd9-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x004c, char properties: 0x0a, char value handle: 0x004d, uuid: ebe0cff1-7a0a-4b0c-8a1a-6ff2997da3a6
Azonban ismert, hogy a 0x0036
char value handle
címet kell beállítani 0010
, hogy a szenzor küldje az adatokat.
Először olvassuk ki:
[24-48-8C-94-96-4C][LE]> char-read-hnd 38
Characteristic value/descriptor: 00 00
Látszik, hogy most az érték 0000
. Állítsuk át 0010
-re a következő paranccsal.
[24-48-8C-94-96-4C][LE]> char-write-req 0x0038 0100
Characteristic value was written successfully
Notification handle = 0x0036 value: e4 0a 43 c8 0b
Notification handle = 0x0036 value: e4 0a 43 c8 0b
Ezek után máris látható, hogy küldi is az értékeket. A küldött érték a e4 0a 43 c8 0b
.
Ennek első két bájtja a hőmérséklet little endian kódolással. Tehát meg kell cserélni a bájtokat,
ez hexában 0a e4
, ami decimálisan 2788
, ezt el kell osztani 100
-zal, így kapjuk a
hőmérsékletet, ami 27,88
fok.
Második bájt a páratartalom, melynek értéke hexában 43
, ami decimálisan 67
, tehát az érték 67 %
.
A legutolsó két bájt pedig az elem feszültsége, ugyanígy kell számolni, mint a hőmérsékletet.
Azaz c8 0b
, megcseréljük a byte-okat, az hexában 0b c8
, ami decimálisan 3016
, amit 100
-zal
osztva a 3,016 V
értéket kapjuk.
Az exit
paranccsal kiléphetünk.
Érdekessége, hogy kinyerhetünk információt a gyártóról, és ott a http://www.miaomiaoce.com/ címet találjuk, ami egy kínai oldal, ahol sok hasonló terméket találhatunk.
De persze miért is használnánk ilyen alapszintű műveleteket, ha Pythonban kész könyvtár van rá. Ez a bluepy, mely csak Linuxon működik.
Működéséhez először telepítsük a libglib2.0-dev
könyvtárat a következő paranccsal:
sudo apt-get install libglib2.0-dev
Majd inicializáljunk egy Python projektet, Virtual Environmenttel (hogy ne globálisan telepítsük a függőségeket), és telepítsük a bluepy
függőséget.
sudo apt-get install python3-venv
python -m venv venv
venv/bin/activate
pip install bluepy
A dokumentáció szerint Peripheral
osztállyal csatlakozhatunk, és
át kell adni egy callbacket (DefaultDelegate
leszármazott), amit visszahív, ha érték érkezik. Itt már csak el kell végezni a fenti számításokat.
from bluepy import btle
class MyDelegate(btle.DefaultDelegate):
def __init__(self):
btle.DefaultDelegate.__init__(self)
def handleNotification(self, cHandle, data):
databytes = bytearray(data)
temperature = int.from_bytes(databytes[0:2],"little") / 100
humidity = int.from_bytes(databytes[2:3],"little")
battery = int.from_bytes(databytes[3:5],"little") / 1000
print(f"Temperature: {temperature}, humidity: {humidity}, battery: {battery}")
mac = "24-48-8C-94-96-4C"
print(f"Connecting to {mac}")
connected = False
try:
# Timeout not released: https://github.com/IanHarvey/bluepy/pull/374
dev = btle.Peripheral(mac)
connected = True
print("Connection done...")
delegate = MyDelegate()
dev.setDelegate(delegate)
print("Waiting for data...")
dev.waitForNotifications(15.0)
except btle.BTLEDisconnectError as error:
print(error)
finally:
if connected:
dev.disconnect()
Most már csak arra van szükség, hogy ezt eljuttassuk a Prometheusba. ez ún. pull modellel dolgozik, azaz bizonyos időközönként a Prometheus hív ki HTTP(S) protokollon, és válaszban kapott értékeket menti el.
A Prometheus az ún. OpenMetrics formátumot képes feldolgozni. Ez valami hasonló:
#HELP xiaomi_sensor_exporter_number_of_sensors Number of sensors
#TYPE xiaomi_sensor_exporter_number_of_sensors gauge
xiaomi_sensor_exporter_number_of_sensors 1
#HELP xiaomi_sensor_exporter_temperature_celsius Temperature
#TYPE xiaomi_sensor_exporter_temperature_celsius gauge
xiaomi_sensor_exporter_temperature_celsius{name="workroom",address="A4:C1:38:78:88:5A"} 25.66
#HELP xiaomi_sensor_exporter_humidity_percent Humidity
#TYPE xiaomi_sensor_exporter_humidity_percent gauge
xiaomi_sensor_exporter_humidity_percent{name="workroom",address="A4:C1:38:78:88:5A"} 65
#HELP xiaomi_sensor_exporter_battery_volt Battery
#TYPE xiaomi_sensor_exporter_battery_volt Volt
xiaomi_sensor_exporter_battery_volt{name="workroom",address="A4:C1:38:78:88:5A"} 2.997
Látható, hogy van négy metrika, xiaomi_sensor_exporter_number_of_sensors
, xiaomi_sensor_exporter_temperature_celsius
, stb.
Nézzük a xiaomi_sensor_exporter_temperature_celsius
metrikát. Ennek meg van adva a neve a #HELP
sorban, értéke Temperature
,
a típusa a #TYPE
sorban, értéke gauge
(mely egy adott pillanatban mért értéket ír le), és maga az érték, mely 25.66
. Sőt, ezeket az értékeket címkézni is lehet,
látható, hogy van egy name="workroom"
értékpár és egy address="A4:C1:38:78:88:5A"
értékpár. Ezek alapján később szűrni is lehet.
Találtam olyan projektet, mely képes arra, hogy lekérdezze a hőmérsékleti értékeket, és azokat exportálja OpenMetrics formátumban, blexy néven. Ez azonban folyamatosan nyitva tartotta a kapcsolatot, és félő, hogy ez több energiát használ. Docker image sem volt belőle. Valamint ennek használatával nem élhettem volna át az alkotás örömét. Ezért saját projektet hoztam létre xiaomi-sensor-exporter néven. Ez futtat egy http szervert, és kérésre csatlakozik a szenzorhoz, lekérdezi az értékeket és OpenMetrics formátumban visszaadja.
Én az egyszerűség kedvéért Dockerben futtatom, melyhez az image megtalálható a Docker Hubon.
Használatához először létre kell hozni egy konfigurációs állományt, amibe megadjuk az eszközök MAC-címeit, valamint a http szerver portját.
port: 9093
devices:
- name: workroom
address: 24-48-8C-94-96-4C
Utána a következő Docker paranccsal indíthatjuk:
docker run -v /home/pi/xiaomi-sensor-exporter/config.yaml:/app/config/config.yaml --net=host --privileged --name xiaomi -d vicziani/xiaomi-sensor-exporter:0.0.1
Sajnos csak --net=host --privileged
paraméterekkel tudtam működésre bírni, így fért hozzá a konténer a Bluetooth stackhez. A Stackoverflow-n javasolt megoldások sajnos nekem nem működtek.
Amint elindult, a következőképp érhetők el a metrikák:
$ curl http://localhost:9093/metrics
#HELP xiaomi_sensor_exporter_number_of_sensors Number of sensors
#TYPE xiaomi_sensor_exporter_number_of_sensors gauge
xiaomi_sensor_exporter_number_of_sensors 1
#HELP xiaomi_sensor_exporter_temperature_celsius Temperature
#TYPE xiaomi_sensor_exporter_temperature_celsius gauge
xiaomi_sensor_exporter_temperature_celsius{name="workroom",address="24-48-8C-94-96-4C"} 26.26
#HELP xiaomi_sensor_exporter_humidity_percent Humidity
#TYPE xiaomi_sensor_exporter_humidity_percent gauge
xiaomi_sensor_exporter_humidity_percent{name="workroom",address="24-48-8C-94-96-4C"} 65
#HELP xiaomi_sensor_exporter_battery_volt Battery
#TYPE xiaomi_sensor_exporter_battery_volt Volt
xiaomi_sensor_exporter_battery_volt{name="workroom",address="24-48-8C-94-96-4C"} 3.102
Ha ez a http://raspberry.local:9093/metrics
címen is elérhető, akkor a Prometheusba a következőképp kell
bekötni a prometheus.yaml
állományba:
scrape_configs:
- job_name: xiaomi_sensor
scrape_interval: 15m
scrape_timeout: 30s
static_configs:
- targets: ['raspberry.local:9093']
Ez 15 percenként fogja lekérni a megadott címről az értékeket.
Ehhez már csak egy Grafana dashboard kell, melyet szintén feltöltöttem a GitHub-ra.