433 Mhz-Funksteckdose in Amazon Alexa-Routine schalten

Es könnte ja eigentlich so einfach sein, aber ist es nicht: Mehrere Geräte zusammen ausschalten, z. B. wenn man ins Bett geht: Fernseher und Wohnzimmerlicht aus und Schlafzimmerlicht an.

"Könnte" weil: Alexa kennt Routinen, in denen man so etwas zusammenfassen kann. Aber eben auch nur, wenn es sich um WLAN-Geräte handelt.


Einen beliebigen oder eigenen Skill in einer Routine aufzurufen ist nicht möglich. Welch eine Schande! Es sind eine ganze Reihe von Aktionen möglich, aber die wichtigste - die, die universell Probleme lösen würde - fehlt natürlich: Man kann keine Sprachbefehle simulieren / ausführen in Routinen. Ja, nicht mal die Reihenfolge der Aktionen lässt sich ändern. Möchte man eine neue Aktion irgendwo einschieben, muss man alle nachfolgenden löschen, die neue Aktion anlegen und die alten dann danach anlegen. In meinen Augen ist das grausame Usability und grenzt an Folter. Die Android-Alexa-App gibt auch insgesamt ein schwaches und unfertiges Bild ab.

Wir können also unser Schlafzimmerlicht nicht einschalten, weil das über eine 433 Mhz-Funksteckdose über unseren eigenen Raspi-Skill geschaltet wird.

Die Sprachbefehle, die wir zusammenfassen wollen, sind eigentlich: Jetzt könnte ich mir natürlich jedesmal zur Zubettgehzeit den Mund fusselig reden und alle 3 Sätze aufsagen, aber wenn ich müde bin, habe ich da gewöhnlicherweise wenig Lust zu und möchte einfach nur "Alexa, gute Nacht" sagen.

Also braucht es einen Workaround, um das auszumerzen, was die Alexa App sträflicherweise nicht selbst kann.

1. Lösungvariante: Alexa über Raspi-Script fernsteuern

Alexa, sage DISPLAY Gute Nacht!
Wir haben ja bereits unser eigenen Display-Skill programmiert. Wenn wir den Gute-Nacht Befehl mit "Alexa, sage Display Gute Nacht" dort hinschicken, können wir die 433 Mhz-Steckdose schalten und müssten danach irgendwie noch die weiteren Alexa-Befehle ausführen.

In diesem Blog von Lötzimmer.de hat der Blog-Autor schon das Bedürfnis gehabt, Alexa von der Kommandozeile aus fernzusteutern und ein Script geschrieben, das unter anderem ermöglicht, in der Alexa-App definierte Routinen aufzurufen.

Die Lösung ist also, per Display-Skill unseren Raspi aufzurufen, dort den nötigen 433-Mhz-Befehl zu senden und dann dort das Script aufzurufen, dass sich dann wieder mit dem Echo verbindet und dort die Gute-Nacht-Routine aufruft und den Rest erledigt, der dort zu erledigen möglich ist.

Zuerst holen wir uns, nachdem wir die Funktionsweise auf der Blogseite studiert haben, das entsprechende script auf unseren Raspi und machen es ausführbar. cd ~ wget https://loetzimmer.de/patches/alexa_remote_control.sh chmod 777 alexa_remote_control.sh Danach müssen wir in dem Script noch unsere e-mail-Adresse und Passwort bei amazon eintragen, damit die Verbindung zu unserem Echo hergestellt werden kann.

Zum Parsen der JSON-Elemente in den Responses (kennen wir ja schon aus dem Blogbeitrag, in dem wir unseren eigenen Skill programmierten) müssen wir ggf. noch JQ installieren, falls noch nicht vorhanden: sudo apt-get install jq Danach können wir unseren Echo fernsteuern und die in der Alexa-App auf dem Smartphone angelegte Routine aufrufen. Da kommt wie gesagt alles rein, was Alexa selbst ausführen kann (siehe Screenshot oben): ~/alexa_remote_control.sh -d 'Olivers Echo Dot' -e automation:'gute Nacht' Nachdem dieser kleine Test funktioniert hat (natürlich den eigenen Gerätenamen nehmen), schreiben wir uns ein zweites Script ...

alexa_gute_nacht.sh /home/pi/alexa_remote_control.sh -d 'Olivers Echo Dot' -e automation:'gute Nacht' /home/pi/433Utils/RPi_utils/send 11111 2 1 ... das wir dann nur noch in unserer alexa-skill.php aufrufen müssen, wenn wir den entsprechenden Skill-Befehl 'Gute Nacht' bekommen.

Natürlich müssen wir noch die entsprechenden Erweiterungen an unserem Skill auf Amazon-Seite vornehmen, nämlich den Intent 'gutenacht' hinzufügen und ein paar Utterances angeben wie z. B. "gute nacht", "schlafenszeit" oder "bis morgen", damit der Sprachbefehl auch an unseren Raspi weitergeleitet wird. Danach das Model speichern und neu bauen nicht vergessen.

Unsere alexa-skill.php auf dem Raspi erweitern wir um ein paar Zeilen, um den neuen Intent abzufangen und das Script aufzurufen. Zum Schluss noch alles testen, ob es auch funktioniert. Bei eventuellen Fehler wegen Zugriffsverletzungen müssen wir ggf. den User www-data (den der Webserver und damit PHP benutzt) für die Scripte mit chgrp freischalten oder wir benutzen gleich usermod, um den User www-data permanent der Gruppe pi hinzuzufügen. pi@raspberrypi:~ $ sudo -u www-data /home/pi/alexa_gute_nacht.sh sudo: unable to execute /home/pi/alexa_gute_nacht.sh: Permission denied pi@raspberrypi:~ $ sudo usermod -G pi -a www-data Schlussendlich haben wir bekommen, was wir wollten, auch wenn die Frage "Warum einfach, wenn es auch kompliziert geht?" hier durchaus berechtigt ist.

2. Lösungsvariante: Mit dem Raspi Geräte simulieren

Alexa, Gute Nacht!
Ein weiterer Lösungsweg ist, mit unserem Raspi (der ja wegen des Alway-On-Displays eh ständig läuft) dem Echo virtuelle Steckdosen vorzugaukeln.

Das Projekt Fauxmo realisiert dies. Wenn Alexa neue Geräte sucht, fragt sie per WLAN in die Runde, wer ein Gerät ist, und was es kann. Fauxmo meldet sich dann - ggf. mehrfach - für alle virutellen Geräte, die dort definiert sind und gibt sich jeweils als Funksteckdose der Marke Belkin WeMo aus. Diese wird von Alexa unterstützt.

Alexa nimmt fortan an, dies seien echte Geräte und schickt dann Befehle an die IP unseres Raspberry Pis. Dieser fängt sie ab, führt die unter dem jeweiligen Gerät definierten Scripte aus und meldet Alexa den Schaltzustand zurück ("on" oder "off"). Entspricht der zurück gemeldete Schaltzustand dem gewünschten Ergebnis, sagt Alexa "okay". Falls nicht, gibt Alexa "Das Gerät reagiert gerade nicht" aus.

In den frei definierbaren Scriptaufrufen in Fauxmo rufen wir dann unsere 433Utils auf und senden den entsprechenden Schaltcode zum ein- nzw. ausschalten unserer 433-Mhz-Funksteckdose.


Danach taucht die Steckdose in der Alexa-App auf, kann darüber geschaltet werden oder per Sprachbefehl (etwa "Alexa, schalte Schlafzimmerlicht an") und verhält sich Alexa gegenüber wie eine echte WLAN-Steckdose. Auch wenn es in Wahrheit nur eine billige 433-MHz-Funksteckdose ist.

Fauxmo braucht in der derzeitigen Version mindestens Version 3.6 von Python. Per apt-get war bei mir nur max. Version 3.5.3-1 abrufbar. Darum muss zuerst Python 3.6 installiert werden.

Bevor wir Python 3.6.5 installieren, holen wir uns aber noch ein paar von Python 3.6 unterstützte Tools, damit die entsprechenden Python-Libraries in einem Abwasch gleich mit installiert werden. Eventuell werden wir sie noch an anderer Stelle brauchen. Wer Speicherplatzprobleme auf seinem Pi hat, kann diesen Schritt überspringen. sudo apt-get install libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev Danach installieren wir Python 3.7 mittel pyenv. Pyenv ist eine Art Versions-Management, um mehrere Python Versionen parallel nutzen zu können. So bleibt die alte Python-Version für unsere alten Scripte vorhanden.

sudo install -o $(whoami) -g $(whoami) -d /opt/pyenv git clone https://github.com/yyuu/pyenv /opt/pyenv echo 'export PYENV_ROOT="/opt/pyenv"' >> ~/.bashrc echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc echo 'eval "$(pyenv init -)"' >> ~/.bashrc source ~/.bashrc pyenv install 3.7.0 Auf meinen Raspi Zero hat die Installation eine Ewigkeit gedauert. Die Zeit reicht locker für einen ausgiebigen Spaziergang. Also nicht abbrechen, sondern Gedulg zeigen.

Danach holen wir uns noch python3-venv sudo apt-get install python3-venv Und schließlich auch Fauxmo, dass wir gleich installieren git clone https://github.com/n8henrie/fauxmo.git cd fauxmo python3 -m venv .venv source ./.venv/bin/activate pip install -e .[dev] Nun brauchen wir noch eine Konfigurationsdatei, in der definiert wird, was Faxumo bei welchem ankommenden Alexa-Befehl tun soll. Hier können wir schon einmal einen Blick hineinwerfen, auch wenn unsere config zum Schluss ganz anders aussehen wird. cp config-sample.json config.json nano config.json Denn wir brauchen ein Plugin, das Befehle auf der Kommandozeile ausführen kann und keins, das Webseiten aufruft, so wie das SimpleHTTPPlugin aus der Beispiel-Config.

Unter https://github.com/n8henrie/fauxmo-plugins/blob/master/commandlineplugin.py findet man ein solches Plugin.

Ich fand es allerdings ein wenig umständlich bezüglich der Generation des richtigen Rückgabewertes an Alexa. Darum habe ich es etwas umgeschrieben, damit es den Einschaltzustand eines Gerätes selbst verwaltet und zurückmeldet. Hier ist der Source-Code, den wir als fauxmo-cli-plugin.py in unserem Home-Verzeichnis speichern. fauxmo-cli-plugin.py """Fauxmo plugin that runs a command on the local machine. edit: Oliver Kuhlemann (cool-web.de), 2019-06-04: handle state by plugin, not by state_cmd Runs a `shlex`ed command using `subprocess.run`, keeping the default of `shell=False`. This is probaby frought with security concerns, which is why this plugin is not included by default in `fauxmo.plugins`. By installing or using it, you acknowledge that it could run commands from your config.json that could lead to data compromise, corruption, loss, etc. Consider making your config.json read-only. If there are parts of this you don't understand, you should probably not use this plugin. If the command runs with a return code of 0, Alexa should respond prompty "Okay" or something that indicates it seems to have worked. If the command has a return code of anything other than 0, Alexa stalls for several seconds and subsequently reports that there was a problem (which should notify the user that something didn't go as planned). Note that `subprocess.run` as implemented in this plugin doesn't handle complex commands with pipes, redirection, or multiple statements joined by `&&`, `||`, `;`, etc., so you can't just use e.g. `"command that sometimes fails || true"` to avoid the delay and Alexa's response. If you really want to handle more complex commands, consider using this plugin as a template for another one using `os.system` instead of `subprocess.run`, but realize that this comes with substantial security risks that exceed my ability to explain. Example config: ``` { "FAUXMO": { "ip_address": "auto" }, "PLUGINS": { "CommandLinePlugin": { "path": "/path/to/commandlineplugin.py", "DEVICES": [ { "name": "output stuff to a file", "port": 49915, "on_cmd": "touch testfile.txt", "off_cmd": "rm testfile.txt" } ] } } } ``` """ import shlex import subprocess from fauxmo.plugins import FauxmoPlugin class CommandLinePlugin(FauxmoPlugin): """Fauxmo Plugin for running commands on the local machine.""" SwitchState = "unknown" def __init__(self, name: str, port: int, on_cmd: str, off_cmd: str) -> None: """Initialize a CommandLinePlugin instance. Args: name: Name for this Fauxmo device port: Port on which to run a specific CommandLinePlugin instance on_cmd: Command to be called when turning device on off_cmd: Command to be called when turning device off state_cmd: Command to check device state (return code 0 == on) """ self.on_cmd = on_cmd self.off_cmd = off_cmd super().__init__(name=name, port=port) def run_cmd(self, cmd: str) -> bool: """Partialmethod to run command. Args: cmd: Command to be run Returns: True if command seems to have run without error """ shlexed_cmd = shlex.split(cmd) process = subprocess.run(shlexed_cmd) return process.returncode == 0 def on(self) -> bool: """Run on command. Returns: True if command seems to have run without error. """ self.run_cmd(self.on_cmd) self.SwitchState = "on" return True; def off(self) -> bool: """Run off command. Returns: True if command seems to have run without error. """ self.run_cmd(self.off_cmd) self.SwitchState = "off" return True; def get_state(self) -> str: """Get device state. NB: Return code of `0` (i.e. ran without error) indicates "on" state, otherwise will be off. making it easier to have something like `ls path/to/pidfile` suggest `on`. Many command line switches may not actually have a "state" per se (just an arbitary command you want to run), in which case you could just put "false" as the command, which should always return "off". Returns: "on" or "off" if `state_cmd` is defined, "unknown" if undefined """ print ("State of " + self.name + " is " + self.SwitchState + ".") return self.SwitchState Wie wir erkennen können gibt es vier Attribute, die pro Gerät zu definieren sind: Unsere ~/fauxmo/config.jason sieht dann also so aus: config.jason { "FAUXMO": { "ip_address": "auto" }, "PLUGINS": { "CommandLinePlugin": { "path": "/home/pi/fauxmo-cli-plugin.py", "DEVICES": [ { "name": "Schlafzimmerlicht", "port": 12345, "on_cmd": "/home/pi/433Utils/RPi_utils/send 11111 2 1", "off_cmd": "/home/pi/433Utils/RPi_utils/send 11111 2 0" } ] } } } Danach starten wir Fauxmo einfach mit pi@raspberrypi:~/fauxmo $ fauxmo Wenn alles richtig konfiguriert ist, läuft Fauxmo nun und beantwortet Alexa-Anfragen.


Lassen wir Alexa mit "Alexa, suche nach neuen Geräten" nach den Fauxmo-Geräten suchen.

Sie sollte dann unter "Geräte" so etwas wie "x Geräte gefunden anzeigen". Unter Steckdosen finden wir dann unsere neuen Geräte.

Nun kann man das AN bzw. AUS Feld antippen, um die Steckdose zu schalten. Alternativ funktioniert auch der Sprachbefehl "Alexa, schalte Schlafzimmerlicht an bzw. aus".

Gleichzeitig gibt Fauxmo entsprechende Meldungen aus: pi@raspberrypi:~/fauxmo $ fauxmo sending systemCode[11111] unitCode[01000] command[0] State of Schlafzimmerlicht is off.
Da die Steckdose jetzt endlich als Alexa-Gerät vorhanden ist, kann sie auch in die Routine "Gute Nacht" mit aufgenommen werden.

Fauxmo sollten wir abschließend so einrichten, dass es sich automatisch mit dem System startet: sudo nano /etc/.rclocal #!/bin/sh -e # # rc.local # # This script is executed at the end of each multiuser runlevel. # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. # Print the IP address #_IP=$(hostname -I) || true #if [ "$_IP" ]; then # printf "My IP address is %s\n" "$_IP" #fi python /home/pi/monitor.py & /opt/pyenv/versions/3.7.0/bin/python3.7 -m fauxmo.cli -c /home/pi/fauxmo/config.json & exit 0 Stellenweise wird empfohlen, Fauxmo über die crontab zu starten. Das hat bei mir aber nie geklappt. Die Methode über rc.local funktioniert bei mir hingegen reibungslos.

Diese Lösungs-Methode ist vielleicht noch ein bisschen komfortabler in der Bedienung als die Erste, weil hier nur "Alexa, Schlafzimmerlicht an" statt "Alexa, sage Display schalte Schlafzimmerlicht an" gesagt werden muss. Bzw. "Alexa, gute Nacht" statt "Alexa, sage Display gute Nacht". Außerdem tauchen die Steckdosen in der Alexa-App auf und sind dort per Smartphone schaltbar.

Abwägung Nutzen / Aufwand

"So viel Aufwand, nur weil die Alexa-App zu doof ist, Skills in Routinen aufzurufen?" könnte man jetzt fragen. Ja, die Workarounds sind nicht ganz unkompliziert und es dauert sicher eine Weile, bis sie eingerichtet sind. Normalerweise würde ich sagen, es lohnt den Aufwand nicht, nur um abends zur Bettgehzeit einen statt drei Sätze zu sagen. Aber wenn man technisch interessiert ist so wie ich, und es als Herausforderung sieht, dann kann man das schon mal machen.

Da die zweite Lösung bis zu 16 Geräte bereitstellen kann, habe ich außerdem für die Zukunft vorgesorgt, wenn ich statt teurer WLAN-Steckdosen billige 433Mhz-Steckdosen benutzen will. Hier muss ich jeweils nur den Eintrag in der config.json von Fauxmo kopieren und editieren und kann die Steckdose dann ohne großen weiteren Aufwand mit Alexa fernsteuern.