On a réussi à piloter la LED avec des commandes en python, j'aimerais maintenant faire la même chose depuis une page web.
Mes habitudes me poussent vers un script PHP (j'ai installé le serveur) qui appelle le script python avec un simple shell_exec($command)
Par ailleurs je me demande si on peut générer la page web directement avec du python, j'ai entendu dire que...
Recherche faite, oui on peut. Il faut utiliser des frameworks comme Django, mais ça demande un peu trop d'investissement pour ce que je veux faire. J'y reviendrai peut être un de ces jours, mais pour l'instant on va se contenter d'un simple PHP qui appelle du python. Enfin si on y arrive...
On se fait donc une petite page web qui sort le résultat d'une commande passée par shell_exec
Déjà se monter un disque logique sur le répertoire des pages web située dans le Rasp:
xxxxxxxxxx
sshfs pi@192.168.0.29:/var/www/html /home/jcfrog/Documents/myRaspHtml/
Paramètres du sshfs :
On commence par une commande système simple, lister les fichier locaux : ls -al
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="utf-8">
<title>LED test</title>
<meta name="description" content="LED">
<meta name="author" content="jerome">
<link rel="stylesheet" href="https://jcfrog.com/jcfrog2.css"> // du CSS à moi, pas nécessaire
</head>
<body>
<div class="container">
$command = escapeshellcmd('ls -al');
$result = shell_exec($command);
<h1>LED control</h1>
<p>Results : echo("<pre>".$result."</pre>");</p>
</div>
</body>
</html>
Paf !
On voit que dans mon répertoire j'ai :
J'ai recopié localement les 2 scripts python disponibles en bas de la note sur la LED
Passons aux choses sérieuses : exécuter le script d'allumage. Exécution !
xxxxxxxxxx
$command = escapeshellcmd('./on-led.py');
Fail !
Évidemment ça ne marche pas. Heureusement d'ailleurs. Problème de droits. PHP n'est pas autorisé à exécuter python j'imagine ( NON, on va voir plus bas que j'ai tort !). PHP est lancé avec quel user? Don't know. Faut que je cherche un peu.
Ah oui, bien sûr, l'utilisateur qui fait tourner PHP est www-data. Et là ça se corse pour moi, comment je fais pour lui donner cette autorisation ? ...
Pour essayer de comprendre je regarde dans les logs apache (ils sont dans /var/log/apache2) :
xxxxxxxxxx
Traceback (most recent call last):
File "on-led.py", line 11, in <module>
GPIO.setup(LED, GPIO.OUT) #Active le contrôle du GPIO
RuntimeError: No access to /dev/mem. Try running as root!
Je pressens que je me trompe. Pour être sûr je fais un petit script python test.py de 3 lignes.
xxxxxxxxxx
#!/usr/bin/env python3
#-- coding: utf-8 --
print("this is a python script")
Et je teste en modifiant led.php pour appeler ce script de test
xxxxxxxxxx
$command = escapeshellcmd('python3 test.py');
$result = shell_exec($command);
Ça marche. J'avais donc bien tort : le PHP peut appeler du python. Le problème est ailleurs, dans l'accès aux GPIO.
J'essaye de comprendre l’enchaînement.
Le navigateur accède à la page > apache sert cette page > dedans y'a du PHP qui se déclenche > PHP lance shell_exec qui déclenche du python > accès librairie GPIO 🛑 blocage !
En farfouillant je vois qu'il y a un groupe gpio et je me dis que ce serait pas idiot d'y ajouter l'exécuteur de tout ça, l'utilisateur www-data
Si je comprends bien les groupes et leurs utilisateurs sont dans /etc/group. J'utilise grep pour trouver le groupe gpio, je vois le user pi, mais pas www-data bien sûr. Je cherche comment l'ajouter : on va utiliser usermod.
xxxxxxxxxx
sudo usermod -a -G gpio www-data
Je remets la $command = escapeshellcmd('python3 on-led.py');
F5 (recharge la page web)
Fail...
Peut être un chtio reboot de M Raspberry pour prendre en compte les modifs de groupes?...
sudo reboot
F5...
Victory !!!!! ✊
Bon côté page web c'est moins impressionnant mais c'est du détail, on va s'occuper de ça.
Crois moi si tu veux, "python off-led.py" marche aussi ! 😜
Y'a plus qu'à passer des paramètres à la page pour demander ce qu'on veut exécuter comme script. On fait encore dans le simple : on utilise la transmission de paramètres via l'URL. Je propose un champ action qui prendra la valeur on ou off.
On s'occupe déjà de récupérer le champs action.
xxxxxxxxxx
if (isset($_GET["action"])){ // vérifie si le champs action était disponible dans l'URL
$action = $_GET["action"] ; // récupération de la valeur du champs action
$script = "" ;
switch($action){ // selon la valeur du champ action
default: // par défaut on ne fait rien
break;
case "on": // si action = 'on'
$script = "on-led.py";
break;
case "off": // si action = 'off'
$script = "off-led.py";
break;
}
}
if (strlen($script)){ // si le nom du script a été renseigné (longueur non nulle)
$command = escapeshellcmd("python3 $script"); // construction de la commande
$result = shell_exec($command); // exécution de la commande
}else{
$result = "no action ...";
}
Et dans la partie HTML j'ajoute 2 liens qui permettent de recharger la page avec ce champs action positionné selon notre bon vouloir.
xxxxxxxxxx
<p><a href="?action=on">Allumer LED</a></p>
<p><a href="?action=off">Eteindre LED</a></p>
Le survol du lien indique la bonne URL, et faudra me croire sur parole, ça fonctionne.
Pour traiter le clignotement, on va éviter d'appeler notre script sans fin avec un lamentable while True:
, ici on peut utiliser un petit bout de javascript côté client qui s'occupera de séquencer les on et off.
Je vais utiliser un peu brutalement des appels ajax qui permettent de solliciter le serveur sans recharger la page dans le navigateur. Ne pas trop baisser la fréquence (j'ai mis 500ms), ça ne se passe pas bien :)
Le code final avec l'ajout du javascript et des liens HTML pour démarrer ou stopper ce magistral clignotement.
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="utf-8">
<title>LED test</title>
<meta name="description" content="LED">
<meta name="author" content="jcfrog">
<link rel="stylesheet" href="https://jcfrog.com/jcfrog2.css">
</head>
<body>
<script>
// pour qui connait pas ici c'est du javascript
var freq = 500 ; // frenqency, in ms
var ledstate = 0 ; // état de la LED, 0 = off, 1 = on
var interval = 0 ; // id du timer pour le clignottement
function switchLed(){ // switch on/off
if (ledstate%2 == 0) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", "?action=off", true);
xmlhttp.send();
console.log("off");
}else{
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", "?action=on", true);
xmlhttp.send();
console.log("on");
}
ledstate = (ledstate + 1) % 2 ; // pout alterner entre les valeurs 0 et 1
}
function stopClignote(){
clearInterval(interval);
}
function startClignote(){
stopClignote(); // pour ne pas en cumuler plusieurs
interval = setInterval(switchLed, freq);
}
</script>
<div class="container">
if (isset($_GET["action"])){ // vérifie si le champs action était disponible dans l'URL
$action = $_GET["action"] ; // récupération de la valeur du champs action
$script = "" ;
switch($action){ // selon la valeur du champ action
default: // par défaut on ne fait rien
break;
case "on": // si action = 'on'
$script = "on-led.py";
break;
case "off": // si action = 'off'
$script = "off-led.py";
break;
}
}
if (strlen($script)){ // si le nom du script a été renseigné (longueur non nulle)
$command = escapeshellcmd("python3 $script"); // construction de la commande
$result = shell_exec($command); // exécution de la commande
}else{
$result = "no action ...";
}
<h1>LED control</h1>
<p><a href="?action=on">Allumer LED</a></p>
<p><a href="?action=off">Eteindre LED</a></p>
if (isset($_GET["freq"]))
<p><a href="javascript:startClignote()">Démarrer clignotement</a> / <a href="javascript:stopClignote()">Arrêter clignotement</a></p>
<p>Results : echo("<pre>".$result."</pre>");</p>
</div>
</body>
</html>
Tout marche. Je suis joie.