ELV Raumklimastation RS 500 mit Raspberry Pi unter Linux auslesen

Der Elektronikversender ELV bietet mit der Raumklimastation RS 500 ein Gerät mit Farbdisplay zur Überwachung von Temperatur und Luftfeuchte an, das gleich mit 5 Funksensoren. Die Station lässt sich mittels einer mitgelieferten PC-Software »EasyTemp« auslesen. Aber nur unter Windows. Wäre es nicht schön, die Daten auch unter Linux zu erhalten? Zum Beispiel mit einem Raspberry Pi?

Das Haus, in dem ich wohne, erstreckt sich über mehrere Stockwerke. Eine Überwachung von Temperatur und Luftfeuchte in verschiedenen Räumen hilft mir sicherzustellen, dass ausreichend gelüftet und so Schimmel vermieden wird. Bisher verwendete ich zu diesem Zweck 4 einzelne Geräte, an denen ich aber auch vorbeikommen musste, um festzustellen, dass bisher noch nicht ausreichend gelüftet wurde. Zeit, das zu ändern. Ein Elektronikversender bietet für kleines Geld eine Raumklimastation an:

  • Farbdisplay
  • 5 Funksensoren (auf 8 erweiterbar)
  • mit PC-Software auszulesen

Klingt erst einmal gut. Da ich meine bisherigen Einzelgerätschaften noch nicht am PC auslesen konnte, war mir zunächst auch egal, dass hier nur eine Windows-Software beilag. Und so habe ich das Teil bestellt und aufgestellt.

ELV Raumklimastation RS 500 mit Sensor

Geht da nicht vielleicht doch was mit Linux?

Kaum ist die Station aufgestellt und alle Sensoren verteilt und verbunden, stellt sich unweigerlich die Frage, ob man nicht doch irgendwie die Messwerte unter Linux gewinnen könnte. Wäre schon schick, könnte man sie auf dem Mobiltelefon oder Tablet anzeigen. Oder besser noch: Mit in die Überwachung durch Icinga 2 übernehmen.

Nun gut. Erst mal schauen, was wir hier vor uns haben. Also steckte ich einfach mal so das mitgelieferte USB-Kabel ein und harrte der Dinge, und tippte dmesg ein. Zunächst eine positive Überraschung:

usb 1-1.3: new full-speed USB device number 5 using dwc_otg
usb 1-1.3: New USB device found, idVendor=0483, idProduct=5750
usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-1.3: Product: By viewtool
usb 1-1.3: Manufacturer: MyUSB_HID
usb 1-1.3: SerialNumber: ************
hid-generic 0003:0483:5750.0005: hiddev0,hidraw0: USB HID v1.10 Device [MyUSB_HID By viewtool] on usb-20980000.usb-1.3/input0

So grundsätzlich kann mein Linux-System etwas mit dem Gerät anfangen. Es meldet sich als USB-HID (Human Interface Device) an. Somit könnte man potentiell schon kommunizieren. Der Beschreibung nach kann die mitgelieferte PC-Software »EasyTemp« neben vielen anderen Funktionen auch Live alle Messwerte darstellen. Das muss also hinzubekommen sein. Doch auf der Schnittstelle herrscht Schweigen im Walde.

Reverse Engineering

ACHTUNG! Das hier beschriebene Vorgehen kann das Gerät irreparabel schädigen! Hier werden Annahmen getroffen, die sich aufgrund fehlender Dokumentation nicht bestätigen ließen! Es wird keinerlei Support geleistet! Es wird keinerlei Garantie oder Gewährleistung für die korrekte Funktionsweise oder Ungefährlichkeit übernommen.

Natürlich war das Internet leer. Ich habe zwar noch mehrere, offensichtlich baugleiche, Raumklimastationen anderer Hersteller entdeckt, aber von wegen Linux-Support oder einer Protokollbeschreibung. Pustekuchen. Scheinbar hat sich bisher noch niemand die Mühe gemacht, sich mit diesem Gerät zu beschäftigen, oder es sehr gut für sich behalten. So komme ich erst einmal nicht weiter. Ich muss wissen, wie »EasyTemp« mit dem Gerät kommuniziert.

Glücklicherweise gibt es selbst bei mir zuhause noch einen Windows-Rechner, auf dem ich »EasyTemp« installieren konnte. Alleine das war schon ein Akt. Die Software gibt es nirgends zum Download, und die Windows-Büchse verfügt über kein CD-ROM-Laufwerk. Der alte USB-Brenner half. Ein Wunder, dass der nach Jahren im Regal noch so schnurrte. Doch nach der Installation funktionierte die Software direkt und bot mir eine Ansicht aller gerade gemessenen Werte an. Die Anzeige aktualisierte sich auch immer dann, wenn das Display andere Werte anzeigte. Es geht also irgendwie.

Mit einem USB Monitoring Tool zeichnete ich die USB-Kommunikation zwischen PC und Raumklimastation auf. Dabei kamen wider Erwarten eine ganze Menge an Daten zusammen. Identifizieren konnte ich jedoch, dass die PC-Software Anfragen schickt, die vom Gerät beantwortet werden. Eine 64 Bytes lange Anfrage erhält eine 64 Bytes lange Antwort. Also schaue ich mir zunächst die Anfragen an. Es stellt sich heraus, dass es nur sieben verschiedene Anfragen gibt, die sich ständig wiederholen, immer in der gleichen Reihenfolge. Zwar ist die gesamte Anfrage 64 Bytes lang, aber nur die ersten vier Bytes sind nicht 0x00. Dazu beginnt eine Anfrage immer mit 0x7B und endet mit 0x40 0x7D:

  1. 0x7B 0x04 0x40 0x7D (der Einfachheit halber im weiteren Verlauf als Anfrage 0x04 beschrieben)
  2. 0x7B 0x41 0x40 0x7D (der Einfachheit halber im weiteren Verlauf als Anfrage 0x41 beschrieben)
  3. 0x7B 0x06 0x40 0x7D (der Einfachheit halber im weiteren Verlauf als Anfrage 0x06 beschrieben)
  4. 0x7B 0x08 0x40 0x7D (der Einfachheit halber im weiteren Verlauf als Anfrage 0x08 beschrieben)
  5. 0x7B 0x09 0x40 0x7D (der Einfachheit halber im weiteren Verlauf als Anfrage 0x09 beschrieben)
  6. 0x7B 0x05 0x40 0x7D (der Einfachheit halber im weiteren Verlauf als Anfrage 0x05 beschrieben)
  7. 0x7B 0x03 0x40 0x7D (der Einfachheit halber im weiteren Verlauf als Anfrage 0x03 beschrieben)

Sieben Anfragen. Sieben mal 64 Bytes Antwort. Sieben deckt sich keinesfalls mit der Anzahl der möglichen (das wären 8) oder der verwendeten (das sind 5) Kanäle. Also ein direkter Rückschluss auf eine 1:1 Beziehung zwischen Anfrage und Kanal fällt raus.

Bei den Antworten sticht ins Auge, dass zumindest die letzten 32 Bytes über alle Anfragen hinweg immer identisch sind. Vielleicht werden also auch nur 32 Bytes pro Antwort für Nutzdaten verwendet.

Das ist im Trüben fischen. Das Problem muss verkleinert werden. Ich entnehme also aus allen Sensoren bis auf einen (Kanal 1) die Batterien und starte die Raumklimastation neu. Ich beginne einen neuen Mitschnitt der USB-Kommunikation. Jetzt sorge ich für eine Temperaturänderung. Der Vergleich der beiden Mitschnitte bringt dann endlich ein brauchbares Ergebnis.

Vergleich der Aufzeichnungen vor und nach der Temperaturänderung"

Ein Sensor, eine Temperaturänderung, nur ein Byte, ein einziges im gesamten Mitschnitt hat sich verändert. Es sprang von 0xCC auf 0xCB. Nichts liegt also näher, als sich die Integer-Werte mal anzusehen:

Byte Integer-Wert
0xCC 204
0xCB 203

Das entspricht exakt dem Zehnfachen der angezeigten Temperatur, nämlich 20,4 °C und 20,3 °C. Nächste Mutmaßung: Das folgende Byte könnte die relative Luftfeuchte sein:

Byte Integer-Wert
0x32 50

Auch das stimmt exakt mit der angezeigten Luftfeuchte von 50% überein.

Ansonsten gibt es nun eine ganze Menge weitere Bytes. Gehen wir nun davon aus, dass bei der Temperatur auch Werte über 25,5 °C (Integer 255, Hex 0xFF) angezeigt werden sollten, gehe ich davon aus, dass das Byte vor dem sich geänderten noch zur Temperatur gehört. Damit haben wir drei Bytes pro Kanal.

Um die Theorie zu bestätigen, schließe ich alle Sensoren wieder an und fertige einen neuen Mitschnitt. Und tatsächlich. In Dreiergruppen zerlegt erhalte ich in der Antwort der 0x03er-Anfrage alle Messwerte der Kanäle:

                        Kanal 2                       Kanal 4                       Kanal 6                       Kanal 8
                    +--------------+              +--------------+              +--------------+              +--------------+
                    |              |              |              |              |              |              |              |
  ??                |  Temp     RH |              |  Temp     RH |              |  Temp     RH |              |  Temp     RH |
+----+              +---------+----+              +---------+----+              +---------+----+              +---------+----+
|    |              |         |    |              |         |    |              |         |    |              |         |    |
 0x7b 0x00 0xcb 0x35 0x01 0x18 0x28 0x00 0xd6 0x34 0x00 0xff 0x2b 0x00 0xd0 0x35 0x7f 0xff 0xff 0x7f 0xff 0xff 0x7f 0xff 0xff [... mehr Bytes, mit denen ich (noch) nichts anfangen kann ...] 
     |         |    |              |         |    |              |         |    |              |         |    |
     +---------+----+              +---------+----+              +---------+----+              +---------+----+
     |  Temp     RH |              |  Temp     RH |              |  Temp     RH |              |  Temp     RH |
     |              |              |              |              |              |              |              |
     +--------------+              +--------------+              +--------------+              +--------------+
         Kanal 1                       Kanal 3                       Kanal 5                       Kanal 7

Nicht verwendete Kanäle haben also den Wert 0x7f 0xff 0xff. Das deckt sich auch mit den Erfahrungen aus den Mitschnitten mit nur einem Sensor. Wertet man das hier gezeigte Beispiel aus, so kommt man zu diesem Ergebnis:

Kanal Temperatur Luftfeuchte
1 20,3 °C 53 % rel.
2 28,0 °C 40 % rel.
3 21,4 °C 52 % rel.
4 25,5 °C 43 % rel.
5 20,8 °C 53 % rel.
6 --- ---
7 --- ---
8 --- ---

Bleibt noch die Frage, was mit negativen Temperaturwerten ist. Mit zwei Bytes lässt sich das zwar locker bewerkstelligen, aber ich will das lieber testen. Also wickele ich einen Sensor in einen Gefrierbeutel und ab damit in die Tiefkühltruhe. Zum Glück empfängt die Basisstation weiter das Signal, und so kann ich erneut zur Auswertung schreiten, als eine Temperatur kleiner als 0 °C angezeigt wird. Ich fertige zwei Mitschnitte von zwei unterschiedlich angezeigten Temperaturen und Luftfeuchten:

Antwort Erwartete Temperatur Erwartete Luftfeuchte
0xff 0xee 0x26 -1,8 °C 38 % rel.
0xff 0xe7 0x27 -2,5 °C 39 % rel.

Bei der Luftfeuchte ergibt sich kein Problem. Warum auch. Hier gibt es sowieso nur positive Werte. Die Temperaturen wären mit 0xff 0xee = 65518 = 6551,8 °C (nach bisheriger Annahme) wohl wesentlich zu hoch. Auf die richtige Temperatur käme man also mit 0xffee - 0xffff - 0x1 = -18 = -1,8 °C.

Zum Glück springt mir hier die Python-Standardbibliothek mit int.from_bytes zur Seite:

int.from_bytes([0xff, 0xee], byteorder='big', signed=True)
# Liefert -18 -> das ist das erwartete Ergebnis!

Das klappt auch mit den oben schon genannten positiven Werten:

int.from_bytes([0x01, 0x18], byteorder='big', signed=True)
# Liefert 280 -> das ist das erwartete Ergebnis!

Damit haben wir doch alles zusammen.

Ans Auslesen!

Raspberry Pi vorbereiten

Ich setze ein Raspberry Pi mit Raspbian Stretch Lite auf. Dazu verwende ich ein altes Raspberry Pi 2. Das lag hier noch rum. Neben der Standardinstallation kommen noch diese Pakete hinzu:

  • make
  • gcc
  • python3
  • python3-dev
  • python3-virtualenv
  • virtualenv
  • libusb-1.0-0-dev
  • libudev-dev
  • redis-server (Cache für die ausgelesenen Temperaturdaten)
  • redis-tools

Dazu kommen noch einige andere kleine Arbeiten, z. B. die Entsorgung des Standard-Users pi und noch ein paar andere Dinge, die aber nichts direkt mit der Verwendung der Raumklimastation zu tun haben und der Absicherung des Raspberries für den Betrieb im internen Netzwerk dienen.

Die Raumklimastation verbinde ich mit dem Raspberry Pi. dmesg bestätigt, dass das USB-HID-Gerät erkannt und bereit ist.

Jetzt haben wir etwas zum Ausprobieren.

Proof of Concept: Anfrage 0x03 mit Python auf dem Raspberry ausführen

Zum Glück gibt es hidapi. Hier kann man sehr einfach mit USB-HID-Geräten kommunizieren. Entsprechend richten wir uns auf dem Raspberry in unserem Arbeitsverzeichnis ein Python Virtual Environment ein, in dem wir hidapi installieren.

virtualenv -p /usr/bin/python3.5 venv
. ./venv/bin/activate
pip install hidapi

Jetzt ist Zeit für ein ausgedehntes Mittags- oder Abendessen. Die Installation der hidapi wird auf einem Raspberry Pi etwas in der Größenordnung von Stunden dauern. Aber es ist es wert. In der Zwischenzeit kann man einen Blick auf das Skript try.py aus dem oben verlinkten GitHub-Repo werfen. Mit kleinen Veränderungen taugt es auch für einen Test für das Auslesen der Raumklimastation.

# Original: https://github.com/trezor/cython-hidapi/blob/master/try.py

from __future__ import print_function

import hid
import time

# enumerate USB devices

for d in hid.enumerate():
    keys = list(d.keys())
    keys.sort()
    for key in keys:
        print("%s : %s" % (key, d[key]))
    print()

# try opening a device, then perform write and read

try:
    print("Opening the device")

    h = hid.device()
    h.open(0x0483, 0x5750)  # AENDERUNG: Vendor-ID und Product-ID aus der dmesg Meldung

    print("Manufacturer: %s" % h.get_manufacturer_string())
    print("Product: %s" % h.get_product_string())
    print("Serial No: %s" % h.get_serial_number_string())

    # enable non-blocking mode
    h.set_nonblocking(1)

    # write some data to the device
    print("Write the data")
    h.write([0x7b, 0x03, 0x40, 0x7d] + [0x00] * 60)  # AENDERUNG: Unsere Anfrage 0x03

    # wait
    time.sleep(0.75)  # AENDERUNG: Wir brauchen mehr Zeit

    # read back the answer
    print("Read the data")
    while True:
        d = h.read(64)
        if d:
            print(d)
        else:
            break

    print("Closing the device")
    h.close()

except IOError as ex:
    print(ex)
    print("You probably don't have the hard coded device. Update the hid.device line")
    print("in this script with one from the enumeration list output above and try again.")

print("Done")

Wenn hidapi fertig installiert ist, kann dieses Skript ausgeführt werden. Es sollte dann ein Ergebnis wie dieses hier liefern:

interface_number : 0
manufacturer_string : MyUSB_HID
path : 0001:0004:00
product_id : 22352
product_string : By viewtool
release_number : 512
serial_number : xxxxxxxxxxxx
usage : 0
usage_page : 0
vendor_id : 1155

Opening the device
Manufacturer: MyUSB_HID
Product: By viewtool
Serial No: xxxxxxxxxxxx
Write the data
Read the data
[123, 0, 204, 58, 0, 225, 52, 0, 214, 59, 1, 8, 44, 0, 209, 55, 127, 255, 255, 127, 255, 255, 127, 255, 255, ...noch mehr...]
Closing the device
Done

Damit ist bewiesen: Wir können die Raumklimastation auslesen.

Raspberry Pi als »Datenquelle« einrichten

Wir wollen die Daten für mehrere verschiedene Zwecke haben. Da bietet es sich an, dass ein einzelnes Programm alle 30 Sekunden die Daten ausliest und für alle anderen Anwendungen zur Verfügung stellt. Als »Zwischenspeicher« eignet sich perfekt Redis. Das ist eine schnelle und schlanke Key-Value-Datenbank. Doch bevor Redis verwendet werden kann, deaktivieren wir in der Konfiguration das Persistieren der Daten. Zum einen sind Momentaufnahmen von Temperatur- und Luftfeuchtewerten sowieso sehr kurzlebig, zum anderen schonen wir damit die SD-Karte des Raspberries.

In der /etc/redis.conf werden dazu alle Zeilen auskommentiert, die mit save beginnen:

[...]
#   Note: you can disable saving completely by commenting out all "save" lines.
#
#   It is also possible to remove all the previously configured save
#   points by adding a save directive with a single empty string argument
#   like in the following example:
#
#save ""

#save 900 1
#save 300 10
#save 60 10000
[...]

Da wir nur in unserem geschützten internen Netz arbeiten, bietet sich Redis auch als Schnittstelle für die Konsumenten der Daten an. Dazu muss aber Redis so konfiguriert werden, dass es auf die externe IP-Adresse (hier im Beispiel: 192.168.1.234) hört und den sicheren Modus deaktiviert. Entsprechend werden weitere Änderungen in der redis.conf vorgenommen:

bind 127.0.0.1 192.168.1.234
protected-mode no

Nun noch Redis neu starten:

systemctl restart redis

Wichtiger Hinweis: Im späteren Verlauf sollte die Verwendung des Redis-Ports mittels Shorewall oder einer anderen Firewall auf bestimmte Konsumenten eingeschränkt werden. Dieses absolute Minimum an Sicherheit sollte man schon einplanen. Aktuell kann jeder, der die IP-Adresse des Raspberries erreichen kann, auf die Redis-Datenbank lesend und schreibend zugreifen. Allerdings geht eine Konfigurationsanleitung für Shorewall eindeutig über den Fokus dieses Beitrags hinaus.

Redis befüttern

Man wählt sich sein Zielverzeichnis (hier im Beispiel: /opt/raumklima) und checkt dorthin das Github-Repository juergen-rocks/raumklima.git aus. Hier ist alles enthalten, was man benötigt. Damit alles funktioniert, erzeugt man für sein Zielverzeichnis ebenfalls ein Python Virtual Environment.

mkdir /opt/raumklima
cd /opt/raumklima
virtualenv -p /usr/bin/python3.5 venv
# Dauert ein wenig...
pip install -r src/requirements-rs5002redis.txt
# Das dauert extrem ewig - hier ist wieder hidapi mit im Spiel

Hat man das geschafft, konfiguriert man noch in der src/rs5002redis.ini den Zugang zu Redis. Allerdings sollten die Standardwerte dort soweit passen (lokales Redis, Datenbank Nummer 3). Es fehlt nun nur noch ein Cronjob, der alle 30 Sekunden das Auslesen startet. Damit das auch mit einem Python Virtual Environment funktionieren kann, gibt es das Skript src/start_save_rs500_to_redis.sh. Es muss, wie alle *.py-Dateien auch, ausführbar sein (zur Not mit chmod +x src/*.sh src/*.py nachhelfen). Mit crontab -e öffnet man den Editor für die Cronjob-Tabelle. Man fügt diese Zeile hinzu:

  * *  *   *   *     /opt/raumklima/src/start_save_rs500_to_redis.sh; sleep 29; /opt/raumklima/src/start_save_rs500_to_redis.sh

Cron hat als kleinste Einheit eine Minute, deshalb muss man hier etwas tricksen. Nach spätestens einer Minute sollte Redis nun befüllt sein. Das kann man wie folgt nachprüfen:

redis-cli -n 3 get rs500_c1_temp

Das Ergebnis sollte die Temperatur auf Kanal 1 ausgeben:

"19.8"

Hinweis: Die Werte werden mit einer maximalen Lebensdauer von 45 Sekunden in Redis gespeichert, danach automatisch gelöscht, wenn sie nicht erneuert wurden. So ist sichergestellt, dass, wenn der Cronjob mal nicht funktioniert, keine alten Werte gelesen werden können.

Was haben wir bisher erreicht? Wie geht es weiter?

Wir haben die Raumklimastation RS 500 mit einem Raspberry Pi verbunden und lassen sie alle halbe Minute auslesen. Die Ergebnisse werden in einer Redis-Datenbank gespeichert und damit für andere Anwendungen zur Verfügung gestellt. In den folgenden Schritten wird nun noch ein Monitoring in Icinga 2 eingebunden. So lässt sich die Einhaltung von Grenzwerten für Temperaturen und Luftfeuchte überwachen und gegebenenfalls mit Alarm-Mails oder anderen Benachrichtigungen reagieren. Der folgende Blog-Post beschreibt das Vorgehen:

Außerdem kann man nun auf dem Raspberry Pi noch ein Web-Interface bereitstellen, welches das direkte Auslesen der Messewerte ermöglicht:


Kommentare

  • GEKU schrieb am 14. März 2018 09:47

    Sehr gute Seite! Prima analysiert und aufbereitet. 0xff 0xee 0x26 ==> 0xffee ist vom Typ unsigned short printf("%d", 0xffee); ==> 65518 printf("%d",(unsigned short)0xffee); ==> -18