Immer wieder taucht die Frage auf, wie GPIOs aus PHP-Scripts heraus gesteuert bzw. ausgelesen werden können. Hier finden Sie einige Lösungsvorschläge.
exec und shell_exec
PHP enthält keine GPIO-Funktionen. Zur GPIO-Steuerung müssen Sie also auf externe Kommandos, bash- oder Python-Scripts zurückgreifen und diese aus dem PHP-Code heraus aufrufen. Das gelingt unter anderem mit den folgenden zwei PHP-Funktionen:
exec
(Dokumentation):exec("cmd")
führt das Kommando aus und liefert die letzte Zeile des Ergebnisses (also der Standardausgabe) zurück. In zwei optionalen Parametern können Sie eine Referenz auf das Arrayoutput
sowie eine Referenz auf die Variablereturnvar
übergeben.output
enthält nach dem Aufruf ein Array mit allen Ergebniszeilen des Kommandos (Standardausgabe),returnvar
den Return-Status des Kommandos.-
shell_exec
(Dokumentation): Auchshell_exec("cmd")
führt das angegebene Kommando aus. Die Funktion liefert das gesamte Ergebnis (also die Standardausgabe) des Kommandos zurück, nicht nur die letzte Zeile. Der Rückgabe-Status geht allerdings verloren. Wenn Sie diesen benötigen, müssen Sie mitexec
arbeiten.
exec
und shell_exec
arbeiten synchron. Das PHP-Script wird also erst fortgesetzt, wenn die Ausführung des Kommandos abgeschlossen ist. Vergessen Sie nicht, bei eigenen Scripts in "cmd"
den vollständigen Pfad anzugeben, also z.B. "/home/pi/mein-tolles-script.py"
.
Backticks (also nach rechts gerichtete Apostrophe, Dokumentation) können als Kurzschreibweise zur shell_exec
-Funktion verwendet werden:
echo "<pre>" . shell_exec("ls -l /etc") . "</pre>";
// gleichwertig
echo "<pre>" . `ls -l /etc` . "</pre>";`
exec und das gpio-Kommando von wiringPi
Der einfachste Weg besteht zur GPIO-Steuerung darin, dass Sie mit exec
oder shell_exec
das Kommando gpio
aus der WiringPi-Bibliothek aufrufen:
<?php
// Pin 26 als Ausgang verwenden und auf High stellen
exec("gpio -1 mode 26 out");
exec("gpio -1 write 26 1");
// so funktioniert es auch:
`gpio -1 mode 26 out`;
`gpio -1 write 26 0`;
// Zustand des GPIO-Pin 21
exec("gpio -1 mode 21 in");
$state = exec("gpio -1 read 21");
echo "<p>Pin 21 des J8-Headers hat den Zustand $state";
?>
Das gpio
-Kommando funktioniert bei aktuellen Raspbian-Versionen ohne sudo
-Rechte, auch aus PHP-Scripts heraus.
Python-Scripts aufrufen
Mit exec
können Sie natürlich auch ein Python-Script aufrufen, das seinerseits auf das Modul RPi.GPIO
zurückgreift, beispielsweise so:
exec("/home/pi/mein-script.py");
Allerdings wird die GPIO-Steuerung an anzureichenden Zugriffsrechten scheitern. Wie Sie dieses Problem lösen können, wird unten gleich beschrieben (Überschrift Apache zur gpio-Gruppe hinzufügen).
Davon losgelöst stellt sich oft die Frage, wie PHP Daten an ein Python-Script übergibt bzw. von diesem wieder zurückerhält. Zum Informationsfluss in das Python-Script verwenden Sie am einfachsten Parameter, die Sie an exec
übergeben. Innerhalb des Python-Scripts können Sie mit sys.argv[n]
auf den n-ten Parameter zurückgreifen, beginnend mit n=1
. Für n=0
erhalten Sie den Script-Namen zurück.
Nehmen wir an, Sie haben das folgende Script, das zwei Fließkommazahlen als Parameter erwartet und daraus den Flächeninhalt und den Umfang eines Kreises ausrechnet:
#!/usr/bin/python3
# Datei /home/pi/rechteck.py
import sys;
l = float(sys.argv[1]);
b = float(sys.argv[2]);
print(l*b);
print((l+b)*2);
Ein Test im Terminal sieht so aus:
./rechteck.py 2.4 1.6
3.84
8.0
Aus einem PHP-Script können Sie dieses Beispiel-Script so aufrufen:
<?php
$l = 1.5;
$b = 0.8;
$result = array();
exec("/home/pi/rechteck.py $l $b", $result);
echo "<p>Flächeninhalt: ", $result[0];
echo "<p>Umfang: ", $result[1];
?>
Wenn größere Datenmengen ausgetauscht werden, können Sie dazu natürlich auch temporäre Dateien verwenden. Achten Sie aber darauf, dass die Zugriffsrechte stimmen. PHP-Scripts werden durch Apache im Account www-data
ausgeführt. Dieser Account muss Lese- und Schreibrechte auf die Datei haben.
Apache zur gpio-Gruppe hinzufügen
Der Webserver Apache wird unter Raspbian also durch den Account www-data
ausgeführt. Viele GPIO-Kommandos und -Bibliotheken kommunizieren mit der Device-Datei /dev/gpiomem
. Unter Raspbian ist diese Datei so eingerichtet, dass root
sowie alle Mitglieder der Gruppe gpio
Schreib- und Leserechte auf diese Datei haben. Der Standardbenutzer pi
gehört der gpio
-Gruppe an, der Account www-data
aber nicht.
groups pi
pi : pi adm dialout cdrom sudo audio video plugdev
games users input netdev spi i2c gpio lpadmin
groups www-data
www-data : www-data
ls -l /dev/gpiomem
crw-rw---- 1 root gpio 244, 0 Feb 10 08:51 /dev/gpiomem
PHP-Scripts werden von Apache mit den Rechten des Accounts www-data
ausgeführt. Deswegen tritt z.B. bei der Ausführung eines Python-Scripts, das das Modul RPi.GPIO
verwendet, ein Fehler auf. Diesen Fehler entdecken Sie, wenn Sie einen Blick an das Ende der Datei /var/log/apache2/error.log
werfen:
sudo tail /var/log/apache2/error.log
RuntimeError: No access to /dev/mem. Try running as root!
Traceback (most recent call last):
File "/home/pi/python/led-on-off.py", line 9, in <module>
gpio.setup(26, gpio.OUT)
RuntimeError: No access to /dev/mem. Try running as root!
Wenn Sie möchten, dass PHP-Scripts bzw. von dort aus mit exec
/shell_exec
ausgeführte Kommandos Zugriff auf /dev/gpiomem
haben sollen, dann fügen Sie den Account www-data
zur Gruppe gpio
hinzu. Das gelingt ganz einfach so:
sudo adduser www-data gpio
sudo systemctl restart apache2
Bei Bedarf machen Sie diese Änderung so rückgängig:
sudo deluser www-data gpio
sudo systemctl restart apache2
sudo-Rechte für einzelne Scripts
Je nachdem, welche (Python-)Bibliotheken Sie einsetzen und was sie steuern möchten, reicht die obige einfache Lösung mitunter nicht aus. Die Hardware-Steuerung gelingt nur, wenn das betreffende Script mit root-Rechten, also mit sudo
ausgeführt wird.
Aus Sicherheitsgründen ist es nicht wünschenswert, dass Apache oder der Python-Interpreter generell mit root
-Rechten ausgeführt wird. Vielmehr soll nur das betreffende Python-Script mit diesen Rechten ausgeführt werden — und das ohne Passwortangabe.
Die erforderliche Konfiguration erfolgt in der Datei /etc/sudoers
. Diese Datei darf nur mit Administratorrechten geändert werden, d.h., Sie starten aus einem Terminal heraus den Editor mit sudo
:
sudo leafpad /etc/sudoers
Nun fügen Sie am Ende dieser Datei die folgende Zeile hinzu:
# am Ende von /etc/sudoers
www-data ALL=(ALL) NOPASSWD: /home/pi/led-on.py
Das bedeutet, dass der Account www-data
(und somit Apache) das Script /home/pi/led-on-off.py
ohne Passwort-Angaben mit sudo
ausführen darf. Halten Sie sich exakt an die obige Syntax und ersetzen Sie lediglich /home/pi/led-on.py
durch den vollständigen Pfad zu Ihrem Script. Wenn es mehrere Scripts gibt, die von PHP aus aufgerufen werden sollen, können Sie deren Namen durch Kommata trennen oder für jedes Script eine eigene Zeile in /etc/sudoers
angeben.
In Ihrem PHP-Script müssen Sie sudo
nun auch bei exec
verwenden, beispielsweise so:
<?php
exec("sudo /home/pi/led-on.py");
?>
zum Buch 2. Auflage S. 763:
Kommando: gpio.setup(26, gpio.OUT, gpio.PUD_OFF, gpio.LOW)
hat bei mir nur so funktioniert (zugegebenermaßen Pi3)
gpio.setup() kann in aktuellen rpi.gpio-Versionen wie folgt verwendet werden:
* mit zwei Parametern, z.B. gpio.setup(26, gpio.OUT)
* mit drei Parametern, wenn der dritte Parameter benannt wird, z.B. gpio.setup(pin, gpio.IN, pull_up_down=gpio.PUD_UP) oder gpio.setup(pin, gpio.OUT, initial=gpio.HIGH)
* oder mit vier Parametern, wie in Ihrem Kommentar
Die im Buch beschriebene Variante mit drei Parametern ohne explizite Bezeichnung des Parameternamens funktioniert nicht mehr.
Mit freundlichen Grüßen,
Michael Kofler
Vielen Dank für den informativen Beitrag! Für meine Projekte mit Raspberry + HTML/PHP und GPIO sind Ihre Tipps sehr nützlich. Vom Konzept her stört mich nur die etwas umständliche Umsetzung, bei der Shell- oder Python-Skripts aus PHP heraus aufgerufen werden. Python am Raspberry unterstützt die Ansteuerung der GPIOs ja sehr gut, könnte man also gleich ein Python-Framework für den gesamten Code inkl. Webserver verwenden, z.B. Cherrypy. Eine Option wäre auch nodejs. Was ist Ihre Meinung zu diesen Frameworks auf dem Raspberry und haben sie damit schon experimentiert?
Mit freundlichen Grüßen,
Edgar Neukirchner
Ich muss gestehen, dass ich weder mit Cherrypy noch mit nodejs Erfahrungen habe.
Guten Tag
Vielen Dank für die nützliche Info betreffend Apache zur GPIO-Gruppe hinzufügen!
Ich habe mit gpiozero dasselbe Problem, nämlich dass der Apachewebserver diese Scripte in der Ausführung blockt.
Python und GPIO Scripte funktionieren via Apacheserver tadellos. Scripte mit gpiozero Code werden aber nicht ausgeführt. Wissen Sie auch, wie man gpiozero und Apache konfiguriert? Vielen Dank.
Intern greift gpiozero selbst auf RPi.GPIO zurück. Insofern wundert es mich, dass es bei gpiozero nicht funktioniert. Ich habe aber ad-hoc nicht die Möglichkeit, das zu testen.
Die sudo-Lösung müsste aber auf jeden Fall funktionieren.
ok – habe den Fehler gefunden:
das Script darf nicht „gpiozero.py“ heissen.
Jetzt heisst es gpz.py und es funktioniert – Vielen Dank!