Xiaomi Plant Sensor

NIT: Xiaomi Plant Sensor (2. Python Programming)

Hier im 2. Teil der NIT (Neu im Test) um den Xiaomi Plant Sensor soll es nun um die Abfrage und Decodierung der Xiaomi Plant Sensoren gehen.

(Zum 1. Teil geht es hier).

Der Artikel wird etwas länger und (leider auch) textlastiger, da ja eine Menge zu beschreiben ist.

Was wird gebraucht?

  • Natürlich zunächst einen Xiaomi Plant Sensor. Woher ihr den bekommt, hab ich im 1. Teil geschrieben.
  • Ein Rechner mit Bluetooth-Adapter, welcher BLE (Bluetooth Low Energie) „kann“ (Ältere Adapter können das nicht).
    Eine gute Idee ist z.B. ein RaspberryPi 3 oder ein RaspberryPi Zero W, jedoch eignen sich auch Mikrocontroller wie z.B. der ESP32, dem ich dann einen weiteren Teil dieser Serie widmen werde, da er anders programmiert wird (C++ statt Python).
    Wann ich allerdings zu diesem Teil komme, steht in den Sternen: Ich bin zur Zeit etwas „unter Wasser..“
  • Einen Editor, mit dem ihr das Python-Programm komfortabel schreiben/entwickeln könnt: Ich verwende seit Jahren PyCharm in der Community-Ausgabe, welche für den Heimgebrauch absolut ausreicht.
    Für die simple Eingabe des Codes reicht natürlich auch ein beliebiger Texteditor, welcher allerdings die Fehlersuche u.U. etwas beschwerlicher macht.

Basics

Mein Code basiert auf dem „miflora“ – Code, geht jedoch in der Umsetzung einerseits einen etwas anderen Weg und in der Verarbeitung der Daten auch weiter:
Zum Einen habe ich den eigentlichen Scan-Code für die Xiaomi Plant Sensoren stark eingedampft (und für mich unwesentliche Teile entfernt), zum anderen werden die erhaltenen Daten per WLAN zu mehreren Datenbanken gesendet (in meinem Fall zu zwei MySQL-DBs und einer Influx-Datenbank).

Mein gesamter Code liegt nun im Github, so dass ich hier nur besonders interessante Stellen herausstellen werde (und warum das so ist).

Aktuell können derzeit bis zu 20 Xiaomi Plant Sensoren (nacheinander) abgefragt werden. Mehr sind jedoch kein Problem (ist eine Konstante im Code, die einfach hochgesetzt werden muss).

Die Konfiguration des Python (Version 2.7) -Programms wird über ein Konfigurationsfile (config.cfg) vorgenommen, welches jedes mal gelesen und ausgewertet wird, wenn das Programm startet.

Gestartet wird das Pythonprogramm (bei mir) über einen cron-job periodisch alle 15min.

Das (BLE-) Protokoll des Xioami Sensors

Der Xiaomi Plant Sensor sendet seine Daten mit Hilfe generischer Attribute (GATT) und verwendet da ein Profile (ich habe jedoch noch nicht weiter nachgeforscht, welches).

Aus diesem (mir unbekannten Profil) werden nur einige Schlüsselwerte verwendet:

GATT Code (hex)Beschreibung
0x03Device Name
0x38Firmwareversion und Batteriestatus (in %)
0x33(Re-)Initialisierung der Messung einleiten
0x35Datenabruf (Feuchte, Temperatur, Licht, Leitfähigkeit)

Als Ergebnis des Datenabrufs 0x35 liefert der Abruf ein Array mit einer Länge von 16 Byte.  In diesem Array sind die Werte für Temperatur, Erdfeuchte, Lichtintensität und Bodenleitfähigkeit abgelegt.

Die (für uns interessante) Decodierung der ersten 10 Byte dieses Array sieht folgendermaßen aus:

Idx:    0   1   2   3   4   5   6   7   8   9 ...
Hex: [ 75  00  00  4c  00  00  00  40  72  04 ...
Dez: [117,  0,  0, 76,  0,  0,  0, 64,114,  4 ...

temp   ++  ++
moist                              ++
light              ++  ++
Cond                                    ++  ++

Die „++“ – Zeichen zeigen die Position der entsprechenden Daten im Array an. Diese müssen ausgelesen und ggf. konvertiert werden.

Sensor Abfrage per gatttool und Einrichtung

Der einfachste Weg, diese Abfragen unter Linux zu realisieren, ist die Verwendung des Linux-Kommandos „gatttool„. Dieses Kommando ist Bestandteil des „bluez“ Paketes, welches installiert werden muss, falls es noch nicht verfügbar ist.

Inzwischen (Stand heute: 16.07.17) gibt es mindestens ein ‚fertiges‘ Python-Module für diesen Zweck (pygatt), ich habe mich jedoch für die Umsetzung per Python-CLI Aufruf entschieden: Sollte die Zahl der Sensoren zu groß werden, könnte es passieren, dass die Dauer der Abfrage aller Sensoren zu lang wird. In diesem Fall könnte man versuchen, die CLI-Aufrufe zu parallelisieren (nicht getestet!!).

Der Aufruf per gatttool sieht nun folgendermaßen aus (alles in einer Zeile!):

gatttool --device=<mac> --char-read -a 0x03 --adapter=hci0

Wir benötigen also zunächst die (Bluetooth) MAC – Adresse des Sensors (Sensor noch nicht aktivieren, also Batterie raus!!):
Dafür nutze ich (unter Linux) das Kommando bluetoothctl (muss ggf. nachinstalliert werden. Das Kommando kann auf dem RasPi oder auf einem anderen Rechner mit aktivem BT-Modul ausgeführt werden.

Nach dem Aufruf des Programms sieht man eine Kommandozeile, „help“ zeigt alle möglichen Kommandos an, ich benutze nur „scan on“, um einen „Umgebungsscan“ zu machen:

wiese@zentris:~$ bluetoothctl 
[NEW] Controller 44:85:00:8D:A5:1C zentris [default]
[bluetooth]# scan on
Discovery started
[CHG] Controller 44:85:00:8D:A5:1C Discovering: yes
[NEW] Device C4:7C:8D:62:67:CA Flower mate
[NEW] Device C4:7C:8D:62:68:BE Flower mate
[NEW] Device C4:7C:8D:62:D4:95 Flower care
[NEW] Device C4:7C:8D:63:40:3D Flower care
[NEW] Device C4:7C:8D:62:D3:E4 Flower care
[NEW] Device C4:7C:8D:63:40:27 Flower care
[NEW] Device C4:7C:8D:62:67:DB Flower mate
[NEW] Device C4:7C:8D:62:64:65 Flower mate
[NEW] Device C4:7C:8D:62:DE:11 Flower care
[NEW] Device 18:4F:32:9B:5D:BC KD-49X8305C

Nun den Sensor aktivieren (Batterie rein): Der Xiaomi Plant Sensor versucht sofort, sich bemerkbar zu machen und wird vom Controller aufgelistet.

Je mehr Sensoren man hat, desto schwieriger wird es, den neuen Sensor zu erkennen. Ggf. muss man sich räumlich etwas außerhalb der schon aktiven Sensoren begeben…

Die nun ermittelte MAC wird in das config.cfg – File in der Sektion [XiaomiMiSensors] eingetragen, wobei das Tag sens_ jeweils weiter gezählt wird.

Speicherung der Messwerte

Allgemeines

Ich habe mich für die Speicherung der Messwert in eine (abgesetzte) MySQL-DB entschieden – klare Trennung von Datenerhebung und Speicherung.

Wie man eine MySQL (oder auch eine Maria-SQL-DB) einrichtet, würde diesen Beitrag sprengen – bitte schaut auf die zahlreichen Tutorials im Internet. Wichtig ist es in diesem Zusammenhang noch, für einen User den Remote-Zugang zur DB frei zu schalten.

Das Datebankmodel (SQL-Script zum Anlegen der Datenbank), welches ich verwende und für das der Code zugeschnitten ist, liegt im Verzeichnis database im File DB-Giesssensoren.sql.

Einrichtung

Die Zugriffsdaten der DB als auch der Datenbankname werden wieder in das config.cfg – File eingetragen, diesmal in eine Sektion mit einem beliebig wählbaren Namen.

Ich habe meine Sektion mal [mysql-cubi-Giessensoren] genannt, diese Sektionsbezeichnung taucht im Python-Quellcode in der Zeile 118 wieder auf:

[mysql-cubi-Giessensoren]
DbHost = <IP of your DB Server>
DbUser = <db username>
DbPassword = <db passwort for the user>
DbName = <database name>
DbTableName = <Tablename>

Die Werte im config.cfg – File müssen nun mit deinen spezifischen Werten konfiguriert werden.

Wenn man nun seine Daten (z.B. aus Redundanzgründen) in eine weitere DB schreiben lassen will, so legt man einerseits im config.cfg – File eine weitere Sektion (mit anderem Namen) an und erweitert im Python-Script den Bereich ab Zeile 226 um eine weitere Schreiboperation…

Visualisierung der Daten

Allgemeines

Zur Visualisierung der Messdaten habe ich für die Kombination Influx (als Datenbank) und Grafana (als Visualisierungsoberfläche) entschieden.

Beide SW-Module laufen wiederum auf einem gesonderten Rechner (in meinem Fall eine virtuelle Maschine unter ESXi) – wieder entsprechend meiner Maxime: Funktionstrennung.
Als Besonderheit ist noch anzumerken, dass meine Visualisierung von Vornherein zum Zugriff aus dem Internet geplant war, somit ist ein gesonderter Rechner sowieso die beste Lösung.

Wie bei der DB-Einrichtung schon erwähnt: Das Einrichten von Influx und Grafana ist relativ einfach und dauert kaum mehr als 15min – die Tutorials im Internet sind verfügbar und ausreichend.

Zur Beachtung: Man benötigt root-Rechte auf der Maschine beim Einrichten – wenn man also diese Kombination auf einem Host Server im Internet betreiben will, kommt man um einen Root-Server nicht herum.
Die Leistungsanforderungen sind minimal: 2vCPU, 2GB RAM und eine Platte (Minimum 20GB) reichen auch für mehrere Millionen Datensätze. Die Auslieferung der Daten zu den Clients (HTML-Browser) erfordert kaum CPU.

Einrichtung

Als erstes wird die Influx-DB eingerichtet. Die dort konfigurierten Daten werden in das config.cfg – File in der Sektion [influx-vm2-sensors] eingetragen:

[influx-vm2-sensors]
# IP of our influx server>
influxHost = 192.168.178.157

# Port of our influx server>
influxPort = 8086

# Admin user of our influx server>
influxUser = <our admin user>

influxPassword = <our admin password>
influxDbName = <our influx db name for the measurement data>
influxDbUser = <our influx db user for the measurement data>
influxDbPassword = <our influx db password for the measurement data>
influxMeasurement = <our influx measurement section name>

Danach wird im Grafana die eingerichtete Influx-DB als DataSource eingebunden. Das ist (fast) schon alles.

Im Grafana selbst legt man nun ein Dashboard an und kann dort die Daten je nach eigenen Vorstellungen visualisieren.

Wie so etwas (live) aussieht ? ==> http://bss135.selfhost.eu:58888/dashboard/db/xiaomi-me-sensorpark 

Schlussworte

Mir ist klar, dass meine Beschreibung Fragen aufwerfen bzw. offen lassen (das ist bei etwas komplexeren Anwendungen immer so…) und dass ich diese mit überschaubarem Aufwand hier nicht alle lösen kann.

Wenn also Fragen auftauchen bzw. Hilfe und Hinweise notwendig sind, bitte ich eich, diese entweder hier im Blog zu stellen oder (besser!) diese mit mir im deutschen RaspberryPi Forum zu behandeln (Gute Idee: Neues Thema dort aufmachen 🙂 ).

In diesem Forum agiere ich ebenfalls unter meinem Nick „Zentris“, zum Thema Erdfeuchte gibt es inzwischen einen (zugegebener weise sehr langen) Thread… wo es primär um Erdfeuchtesensoren „an sich“ geht…

Ich hoffe euch nicht gelangweilt zu haben und würde mich freuen, wenn der eine oder andere eine (wie auch immer geartete) Rückmeldung schreibt, ob der Beitrag so ok war bzw. was ich verbessern kann.

Grüße, das Zen 🙂

20 Gedanken zu „NIT: Xiaomi Plant Sensor (2. Python Programming)“

  1. Du wolltest im ursprünglichen Beitrag – oder gerne auch hier – noch ergänzen, wie die Sensoren aktuell aussehen….
    Ich war ja skeptisch bzgl. der Chrom-Messpunkte.

    1. In der momentanen Vegetationsphase möchte ich ungern die Sensoren ohne zwingenden Grund ziehen.
      Mal sehen, wann es besser passt. Ich mache dann auf jeden Fall hier einen Bericht über den Zustand der Sensoren, also sowohl des Giess-o-mat-Sensors mit PUR-Beschichtung als auch der / des XaiomiMe-Sensors…

  2. Moin Zentris,

    vielen Dank für deine Einblicke!
    Das gatttool ist im Paket „bluez“ enthalten. Im Text fehlt der Buchstabe e 😉

    VG Maik

  3. Hallo Zentris,

    habe gestern Abend ein wenig mit dem Skript gespielt weil es genau das ist wonach ich gesucht hatte 😉

    Jedoch benutze ich Python3 auf einem Raspi (auf dem sonst nur node-red läuft).

    Ein paar Schwierigkeiten auf die ich gestoßen bin. Vielleicht hilft es jemanden weiter.

    Erster Error bei helper.py Zeile 349 „object has no attribute ‚has_key‘.
    Laut google „Removed. dict.has_key() – use the in operator instead.“ ab Version 3.0.

    Habe es versuch es umzuschreiben, aber als nicht Programmierer .. meh…
    Habe daher die 4 Zeilen auskommentiert um weiter zu gehen.

    Bei der Installation von PyMySQL gab es eine weitere Fehlermeldung.
    Geholfen hat die Deinstallation mit:
    sudo apt-get remove python3-pip

    und reinstall mit

    easy_install3 -U pip

    Anschließend gings fehlerfrei weiter mit

    sudo pip3 install PyMySQL

    Skript läuft nun durch, aber es werden keine Daten in der SQL DB geschrieben noch in der Influx.

    Log sagt [XiaomiMiReader::236]: Cubi MySQL database not rechable or other error: ‚dictionary changed size during iteration‘

    Aber das schaue ich mir die Tage in Ruhe an und werde hier ein update posten.

    Gruß und nochmals Dank

    Alexandre

    1. Influx Daten werden geschrieben und sauber angezeigt.
      Beim SQL klemmt es noch. Ich vermute mal weil einige Felder in den Tabellen fehlen, die vom Skript übermittelt werden.

      [08.08.2017 17:48:46 [INFO ] [SQLConnector:writeData:101]: incoming data set: {‚temperature‘: 25.8, ‚light‘: 0, ‚cputmp‘: 43.312, ‚collectorip‘: ‚10.0.0.11‘, ‚mac‘: ‚xxxxxxxxxx‘, ‚date‘: ‚2017.08.08‘, ‚battery‘: 99, ’sensorname‘: ‚Flower care‘, ‚time‘: ’17:48:46′, ‚firmwareversion‘: ‚3.1.8‘, ‚moisture‘: 0, ‚conductivity‘: 0, ‚collectormac‘: ‚xxxxxxxxxxxx‘}
      08.08.2017 17:48:46 [WARNING ] [SQLConnector:writeData:119]: MySQLConnector: Tag ’sensorname‘ not accepted, ignored and remove from data set
      08.08.2017 17:48:46 [ERROR ] [XiaomiMiReader::236]: Cubi MySQL database not rechable or other error: ‚dictionary changed size during iteration‘

      In diesem Fall sensorname. In keiner Tabelle. battery vs. batterie.

      Mehr in Kürze

  4. Oh, !
    Ok, zunächst einmal vielen Danke für deinen Test.
    Ich habe den Code aus der laufenden Maschine genommen und „Internetgängig“ gemacht, offenbar sind mir da Fehler unterlaufen…
    Das SQL-Script zum Anlegen der Tabelle „sollte“ passen, ich werden das nochmal kontrollieren…
    bis demnächst 🙂

  5. Bezüglich des Fehlers:
    08.08.2017 17:48:46 [ERROR ] [XiaomiMiReader::236]: Cubi MySQL database not rechable or other error: ‚dictionary changed size during iteration‘
    muss ich mal sehen: Ich prüfe zu Beginn der Transaktionen nicht die Struktur der DB, erschien mir überflüssig… natürlich wäre das fehlertoleranter…
    Mit der angepassten SQL-DB dürfte dieser Fehler (auch) nicht mehr kommen…
    Ich habe weiterhin den weggefallenen .has_key() umgeschrieben.
    (Bei mir läuft das im Original unter Python 2.7, ließ sich jedoch mit PyCharm ohne Hinweise als 3.5 bearbeiten… – deshalb ist mir das nicht aufgefallen)

    1. Super. Vielen Dank.

      Habe die Dateien und die DB aktualisiert. Habe jedoch immer noch die SQL Fehlermeldung.

      MySQLConnector: Tag ‚firmwareversion‘ not accepted, ignored and remove from data set

      Habe mal auf der DB Sensoren.Spalten.osversion nach …firmwareversion geändert. Fehler bleibt. Dann habe ich in XiaomiMiConnector.py Zeile 28 angepaßt. Immer noch Fehler.

      Ich werde am Wochenende nochmal in Ruhe testen, logs genauer lesen und ein Feedback geben.

      Eine Frage noch. In der config.cfg, letzte Zeile bei DbTableName. Was soll da stehen? Sensoren oder Sensordaten?

      Vielen Dank

      Grüße

      Alexandre

  6. „Sensordaten“
    Die Tabelle „Sensoren“ ist angelegt, weil ich auf diese mit einer anderen Applikation zugreife. Damit kann schneller auf die verfügbaren Sensoren + weiterer Metadaten zugreifen…

    Die Meldung
    „MySQLConnector: Tag ‚firmwareversion‘ not accepted, ignored and remove from data set“

    ist nur eine Warnung: Sie bedeutet, dass das Datum „firmwareversion“ nicht zur DB gesendet wird… (ist nicht in der Mappingliste „SQLTableMapping“ enthalten.

    Diese Mappingliste dient quasi zur Übersetzung der internen Datenbezeichnungen in die Spaltennamen der DB…

    Prima, dass du das testest !!!

    Viel Grüße,
    Zentris

    1. Ich habe heute Nacht noch ein wenig weiter getestet.
      – SQL Teil habe ich auskommentiert. Es wurden weiterhin keine Daten geschrieben. Habe mal aus Neugierde ein kleines Python Skript erstellt um die DB Connection zu testen. Das klappte.
      – manuell ausgeführt werden Daten in die InfluxDB geliefert. Aber es dauert bis zu 60 Minuten bis die Daten dort angezeigt werden. (Daten die von MQTT mit Nodered in die InfluxDB importiert werden erscheinen beinahe instant.). Hier muß ich noch mal genauer nachsehen woran es liegen könnte. (Edit. wenn ich den Zeitstempel im Importlog mit den Daten im Grafana vergleiche ist es ziemlich genau eine Stunde.)
      – als Cronjob wird es nicht ausgeführt. Bei einem anderen Python Skript läuft der Cronjob super. Auch hier muß ich schauen warum.

      Weitere Details in Kürze

      Gruß und Dank

      Alexandre

  7. 🙂
    Ja, den TimeShift musste ich einfügen, weil Grafana zusammen mit der Influx-DB irgendwie die Zeiten nicht auf die Reihe bekommt (Vor allem die Sommerzeit):
    Grafana stellt enweder alles zur Basis der lokalen Zeit dar oder UTC.
    Als ich die Zeit in UTC gespeichert hatte, hatte ich nach Beginn der Sommerzeit einen TimeGap von 1h…
    Zu sehen ist das auf meiner Grafana-Datendarstellung http://bss135.selfhost.eu:58888/dashboard/db/xiaomi-me-sensorpark (Das ist genau das, was du gerade aufbaust – ich weiss nicht, ob du das schon gesehen hast) – da ist diese 1h auch da.

    Ich hab mich dran gewöhnt und korrigiere das direkt im Grafana… schön ist das (das geben ich zu) nicht und wenn sich da eine Lösung bei dir zeigt, wäre ich sehr daran interessiert (notfalls muss ich alle Daten in der DB konvertieren, das geht mit Python innerhalb einer Stunde (bei mir) ).

  8. PS:
    Was hältst du davon, wenn wir das Ganze hier im RaspberryPi-Forum (http://www.forum-raspberrypi.de/) weiter führen:
    Da kann man besser schreiben und ggf. auch Code posten, auch sind da ggf. noch weitere User unterwegs, die vielleicht Interesse an der Sache haben.
    Mein Blog wird relativ wenig angesteuert (ca. 20-40 User/Tag), die meist nur lesen… im RasPi-Forum ist da schon wesentlich mehr los 🙂

  9. Schöner Artikel, ich bin gespannt wann es mit dem ESP32 weiter geht. Die könnte man schön im Haus verstecken und so alle Bereiche abdecken.

  10. Hi Zentris, gut geschriebener Artikel mit einer Menge an Info’s. Vielen Dank.
    Ich habe mir auch ein paar von den Sensoren bestellt und schaue mir gerade die BLE API des ESP32 an. Ziel ist ähnlich wie bei dir die Sensoren reihum auszulesen und dann in eine SQL DB zu schieben. Der ESP wird als Central nach Plant Sensoren scannen und die jeweils connecten, das GATT und seine Characteristics lesen und dann disconnecten. Ich hoffe dass der ESP32 genug parallele Verbindungen halten kann um das zu bewerkstelligen. Nebenbei sollen später auch noch eine Handvoll TI Sensor TAGs und andere Sensoren gelesen werden. Zur Not werdens eben mehrere ESPs 🙂

    Grüße, Peter

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.