GPIO-Steuerung in PHP-Scripts

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 Array output sowie eine Referenz auf die Variable returnvar übergeben. output enthält nach dem Aufruf ein Array mit allen Ergebniszeilen des Kommandos (Standardausgabe), returnvar den Return-Status des Kommandos.

  • shell_exec (Dokumentation): Auch shell_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 mit exec 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");
?>

7 Gedanken zu „GPIO-Steuerung in PHP-Scripts“

  1. 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)

    1. 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

  2. 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

  3. 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.

    1. 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.

Kommentare sind geschlossen.