433MHz Temperatur-Sensor in HASSIO

Posted on Fr 19 August 2022 in Computer & Electronics

Wie ich in einem früheren Post schon einmal erzählt hatte, habe ich hier diverse billige Funk-Temperatursensoren rumstehen. Die gehören zu einer einfachen Wetterstation, die ich aber aktuell garnicht mehr nutze. Aber es wäre doch ganz schön, wenn ich die Sensoren in meinen Homeassistant einbinden könnte.

Wie schon beim letzten mal beschrieben, ist das Protokoll bekannt und ich sehe zwei denkbare Wege, wie wir das anbinden könnten:

  1. RTL-SDR Empfänger an den Homeserver und Daten auf selbigem decodieren und irgendwie in HASSIO übertragen.
  2. RFlink Firmware auf einen Arduino Mega flashen und selbigem noch ein 433MHz Empfangsmodul spendieren. Die Daten werden dann über die serielle Schnittstelle geliefert, die wir dann an HASSIO anbinden müssten.

Beides sind gangbare Wege. Heute wollen wir mal Variante 1 probieren. Die zweite finde ich sehr spannend, aber es stört mich etwas, dass die RFlink Software zwar umsonst, aber nicht open source ist. Letzteres verhindert, dass sie z.B. auf den ESP8266 portiert wird, was viel mehr Sinn machen würde, da der erheblich billiger ist, als ein Arduino Mega und zudem WLAN kann, was die Anbindung an einen MQTT Server erheblich vereinfachen würde. Es scheint forks basierend auf alten Versionen zu geben, aber die scheinen noch keine richtige community zu haben.

Also mit RTL-SDR!

RTL-SDR installieren

Der RTL-SDR ist bekanntermaßen ein sehr preiswerter und dennoch vielseitiger SDR (Software Defined Radio) Receiver, der ursprünglich als DVB-T Empfänger für den Computer verkauft wurde. Inzwischen ist DVB-T obsolet, aber speziell für Hacker ausgelegte Varianten des Sticks erfreuen sich großer Beliebtheit wenn es darum geht Funksignale im computer zu verarbeiten. Ich habe zwei drei davon in der Bastelkiste...

Also Antenne anschrauben und den Stick an den Server anschließen. LINUX erkennt den Empfänger sofort:

> lsusb
[...]
Bus 003 Device 002: ID 0bda:2838 Realtek Semiconductor Corp. RTL2838 DVB-T

Und auch passende Treiber-Module werden automatisch geladen.

Das ist zwar erfreulich, aber dennoch unerwünscht, denn wir wollen ja nicht fernsehen, sondern den Thermometern ihre Geheimnisse entreißen! Das bedeutet, dass wir verhindern wollen, dass der standard-LINUX-Treiber sich das Gerät schnappt:

sudo sh -c 'echo "blacklist dvb_usb_rtl28xxu" > /etc/modprobe.d/blacklist.conf'

Das sollte eventuelle Konflikte zuverlässig vermeiden ("usb_claim_interface error -6").

Daten empfangen und weiterleiten

Nun brauchen wir noch eine Software, die etwas mit den Signalen anfangen kann, die wir mit dem SDR empfangen. Und die heißt rtl_433 und ist auf github zu finden. Gängige LINUX Distributionen bringen sie aber auch gleich mit und so genügt zur Installation dies:

sudo apt install rtl-433

Nun können wir unseren Lauschangriff starten:

> rtl_433 -C si
rtl_433 version unknown inputs file rtl_tcp RTL-SDR SoapySDR
Use -h for usage help and see https://triq.org/ for documentation.
Trying conf file at "rtl_433.conf"...
Trying conf file at "/home/users/pagel/.config/rtl_433/rtl_433.conf"...
Trying conf file at "/usr/local/etc/rtl_433/rtl_433.conf"...
Trying conf file at "/etc/rtl_433/rtl_433.conf"...
Registered 145 out of 175 device decoding protocols [ 1-4 8 11-12 15-17 19-21 23 25-26 29-36 38-60 63 67-71 73-100 102-105 108-116 119 121 124-128 130-149 151-161 163-168 170-175 ]
Detached kernel driver
Found Rafael Micro R820T tuner
Exact sample rate is: 250000.000414 Hz
[R82XX] PLL not locked!
Sample rate set to 250000 S/s.
Tuner gain set to Auto.
Tuned to 433.920MHz.
Allocating 15 zero-copy buffers
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
time      : 2022-08-19 17:15:37
model     : inFactory-TH ID        : 249
Channel   : 2            Battery OK: 1             Temperature: 23.28 C
Humidity  : 58 %         Integrity : CRC
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
time      : 2022-08-19 17:15:37
model     : inFactory-TH ID        : 249
Channel   : 2            Battery OK: 1             Temperature: 23.28 C
Humidity  : 58 %         Integrity : CRC
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
time      : 2022-08-19 17:15:37
model     : inFactory-TH ID        : 249
Channel   : 2            Battery OK: 1             Temperature: 23.28 C
Humidity  : 58 %         Integrity : CRC
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
time      : 2022-08-19 17:15:39
model     : inFactory-TH ID        : 79
Channel   : 1            Battery OK: 0             Temperature: 24.33 C
Humidity  : 63 %         Integrity : CRC

[...]

Das sieht ja schon ganz gut aus. Und wir haben gelernt, dass der Sensor mit der ID 79 gerne mal eine neue Batterie hätte. Wenn ich jetzt noch wüsste, welcher das ist ...

Freundlicherweise bietet rtl_433 diverse Output-Optionen an, u.a. csv, json und vor allem mqtt! Und so können wir die Ergebnisse direkt an unseren MQTT-Server senden. Wie man den aufsetzt erkläre ich hier nicht – das findet man überall im Netz, u.a. auch in einem älteren Post von mir. Also werfen wir das mal auf unserem Server an:

rtl_433 -F mqtt://hal:1883,user=phil,pass='TopSecret'

Um zu sehen, ob und was da ankommt starten wir anderswo einen mqtt client:

mosquitto_sub -u phil -P TopSecret -t '#' -h hal

Und in der Tat kommen dort Daten an:

{"time":"2022-08-19 17:27:10","model":"inFactory-TH","id":172,"channel":2,"battery_ok":1,"temperature_F":73.7,"humidity":62,"mic":"CRC"}
2022-08-19 17:27:10
172
2
1
73.7
62
CRC
{"time":"2022-08-19 17:27:10","model":"inFactory-TH","id":172,"channel":2,"battery_ok":1,"temperature_F":73.7,"humidity":62,"mic":"CRC"}
2022-08-19 17:27:10
172
2
1
73.7
62
CRC

Hm – irgendwie scheint es, dass wir jedes Event einmal im JSON Format und einmal in separaten Zeilen bekommen.

Also lesen wir doch mal den relevanten Abschnitt in man rtl_433:

Output format option
    [ -F kv|json|csv|mqtt|influx|syslog|null ]
          Produce decoded output in given format.
          Without this option the default is KV output. Use "-F null" to remove
          the default.
          Append output to file with :<filename> (e.g. -F csv:log.csv),
          defaults to stdout.

          Specify MQTT server with e.g. -F mqtt://localhost:1883
          Add MQTT options with e.g. -F "mqtt://host:1883,opt=arg"
          MQTT options are: user=foo, pass=bar, retain[=0|1], <format>[=topic]
          Supported MQTT formats: (default is all)
            events: posts JSON event data
            states: posts JSON state data
            devices: posts device and sensor info in nested topics
          The topic string will expand keys like [/model]
          E.g. -F "mqtt://localhost:1883,user=USERNAME,pass=PASSWORD,retain=0,devices=rtl_433[/id]"

          [...]

Wir sind eigentlich nur an events interessiert. Und wir wollen bitte SI-Einheiten – also °C statt F:

rtl_433 -C si -F mqtt://hal:1883,user=phil,pass='TopSecret',retain=0,events=rtl_433[/model][/id]

Das sieht auf der Empfangsseite schon viel aufgeräumter aus:

{"time":"2022-08-19 17:42:23","model":"inFactory-TH","id":249,"channel":2,"battery_ok":1,"temperature_C":23.27778,"humidity":58,"mic":"CRC"}
{"time":"2022-08-19 17:42:24","model":"inFactory-TH","id":249,"channel":2,"battery_ok":1,"temperature_C":23.27778,"humidity":58,"mic":"CRC"}
{"time":"2022-08-19 17:42:24","model":"inFactory-TH","id":249,"channel":2,"battery_ok":1,"temperature_C":23.27778,"humidity":58,"mic":"CRC"}

rtl_433 als service

Das funktioniert zwar schon sehr schön, aber sobald wir das Programm abbrechen ist die ganze Pracht dahin. D.h. als nächstes müssen wir dafür sorgen, dass rtl_433 quasi als daemon/service auf unserem Server läuft.

Dazu erzeugen wir ein entsprechendes service file für systemd (/etc/systemd/system/rtl_433.service):

# See https://github.com/merbanan/rtl_433/issues/1651 for original 
# suggestion
[Unit]
Description=RTL_433 service
Documentation=man:rtl_433
StartLimitIntervalSec=10
After=syslog.target network.target

[Service]
Type=exec
ExecStart=/usr/bin/rtl_433 -c /etc/rtl_433.conf
Restart=always
RestartSec=30s

# View with: sudo journalctl -f -u rtl_433 -o cat
SyslogIdentifier=rtl_433

[Install]
WantedBy=multi-user.target

Und wir brauchen noch ein config file für rtl_433 selbst (/etc/rtl_433.conf):

convert si
output mqtt://hal:1883,user=phil,pass='TopSecret',retain=0,events=rtl_433[/model][/id]
report_meta time:utc

Sobald wir das alles haben können wir unseren neuen Service starten:

systemctl start rtl_433
systemctl daemon-reload

und mal prüfen, ob er auch läuft:

> sudo systemctl status rtl_433.service 
● rtl_433.service - RTL_433 service
     Loaded: loaded (/etc/systemd/system/rtl_433.service; disabled; vendor preset: en>
     Active: active (running) since Sat 2022-08-20 10:11:52 CEST; 22s ago
       Docs: man:rtl_433
   Main PID: 1040053 (rtl_433)
      Tasks: 2 (limit: 19096)
     Memory: 2.9M
        CPU: 313ms
     CGroup: /system.slice/rtl_433.service
             └─1040053 /usr/bin/rtl_433 -c /etc/rtl_433.conf

Aug 20 10:11:52 hal rtl_433[1040053]: Registered 145 out of 175 device decoding proto>
Aug 20 10:11:52 hal rtl_433[1040053]: Found Rafael Micro R820T tuner
Aug 20 10:11:52 hal rtl_433[1040053]: Exact sample rate is: 250000.000414 Hz
Aug 20 10:11:52 hal rtl_433[1040053]: [R82XX] PLL not locked!
Aug 20 10:11:52 hal rtl_433[1040053]: Sample rate set to 250000 S/s.
Aug 20 10:11:52 hal rtl_433[1040053]: Tuner gain set to Auto.
Aug 20 10:11:52 hal rtl_433[1040053]: Tuned to 433.920MHz.
Aug 20 10:11:52 hal rtl_433[1040053]: Allocating 15 zero-copy buffers
Aug 20 10:11:54 hal rtl_433[1040053]: MQTT Connected...
Aug 20 10:11:54 hal rtl_433[1040053]: MQTT Connection established.

An dieser Stelle wäre noch zu erwägen, diesen Service nicht als root laufen zu lassen, denn rtl_433 benötigt nur normale user permissions und aus Security-Sicht ist es sinnvoll, einem Service nicht mehr Rechte zu geben, als er wirklich braucht. D.h. wir legen einen speziellen user dafür an:

sudo useradd rtl_433

Außerdem muss dieser user Zugriff auf USB-Geräte haben. D.h. er muss Mitglied der Gruppe plugdev sein:

sudo adduser rtl_433 plugdev

Damit der Service unter diesem User läuft ändern wir die [Service] Sektion in /etc/systemd/system/rtl_433.service indem wir User und Group wie folgt festlegen:

[Service]
Type=exec
User=rtl_433
Group=rtl_433
ExecStart=/usr/bin/rtl_433 -c /etc/rtl_433.conf

Und wir ändern die Besitzverhältnisse des Config files:

sudo chown rtl_433:rtl_433 /etc/rtl_433.conf

Nun noch den Service neu starten

sudo systemctl restart rtl_433.service 
sudo systemctl daemon-reload

Und schon passt das:

> ps aux | grep rtl_433
rtl_433  1023646  1.2  0.0  56256  7516 ?        Ssl  23:20   0:04 /usr/bin/rtl_433 -c /etc/rtl_433.conf
[...]

Zum Abschluss sollten wir noch sicherstellen, dass der Service automatisch gestartet wird:

sudo systemctl enable rtl_433

Homeassistant konfigurieren

Nun müssen wir unserem Homeassistant noch erklären, das da neue Sensoren sind. Dazu müssen wir auf dem Server das configuration.yaml file editieren. In einer HASSIO Installation liegt das in /config, so auch in meinem Docker Container. Dieses Verzeichnis ahbe ich mir als Docker-Volume auf dem Server unter /srv/homeassistant bereitgestellt. Folgendes muss dort hinein:

sensor:
  - platform: mqtt
    name: Temp_Wohnzimmer
    device_class: temperature
    unit_of_measurement: '°C'
    value_template: '{{ value_json.temperature_C | round(1) }}'
    state_topic: rtl_433/inFactory-TH/79
    json_attributes_topic: rtl_433/inFactory-TH/79
  - platform: mqtt
    name: Hum_Wohnzimmer
    device_class: Humidity
    unit_of_measurement: '%'
    value_template: '{{ value_json.humidity }}'
    state_topic: rtl_433/inFactory-TH/79
    json_attributes_topic: rtl_433/inFactory-TH/79

Für jeden Sensor brauchen wir zwei solche Einträge. Die Details müsst Ihr natürlich Euren konkreten Sensoren und IDs anpassen.

Achtung: Bei meinen Sensoren ändert sich die ID jedes mal, wenn die Batterie gewechselt wird. Dann muss ich die Items state_topic und json_attributes_topic entsprechend anpassen.

Und nun ist es soweit – im Web Interface von Homeassistant können wir nun eine entsprechende Karte anlegen und die ganzen Sensoren darstellen. Die einzelnen Sensoren findet Ihr unter Config > Devices & Services > Entities. Und im Dashboard Editor kann man sie ganz normal aus der Liste auswählen:

Und schon haben wir alle eingebunden: