đ : 3 h maximum
Prérequis:
- Dev WEB , html , php , javascript , serveur web (apache)
- libgpiod
But:
- Mise en place d’un serveur web (apache) et dâapplication en langage php, html et javascript
- Réaliser une application WEB pour piloter un GPIO (ici GPIO27)
- configuration de firefox pour accéder à notre Raspberry Pi
- Utilisation de l’outil de dĂ©veloppement du navigateur (ici Firefox)
Répertoire de travail:
(compte rendu et fichiers de l’application)
~/Works/RPI_4
Installation des outils logiciels:
apache , et php et gpiod
sudo apt update
sudo apt install apache2 php libapache2-mod-php php-cli
sudo apt install gpiod php-gd # php-gd est souvent utile pour les projets web
Se faire un environnement de développement WEB pour $USER
configuration pour autoriser l’usage du gpio par le groupe www-data et par le $user
sudo groupadd gpio
sudo usermod -a -G gpio www-data
sudo usermod -a -G gpio $USER # Remplacez $USER par votre nom d'utilisateur si nécessaire
# Ajouter votre user au groupe www-data
sudo usermod -a -G www-data $USER
# Redémarrer la session ou faire un login
newgrp www-data
# OU se déconnecter/reconnecter
# VĂ©rifier que vous ĂȘtes dans le groupe
groups
rendre le répertoire /var/www/html disponible pour les utilisateurs du groupe www-data
Plus besoin de faire de sudo pour accéder à ce répertoire
# Aller dans le dossier web
cd /var/www
# Donner les permissions au groupe www-data
sudo chown -R www-data:www-data html/
sudo chmod -R 775 html/
Vérifier que vous pouvez faire du développement web avec votre compte.
Réglage firefox pour accéder à votre RPI
firefox est configuré pour passer à travers le proxy du lycée. (10.0.0.1:3128) (sur la photo ci dessous pas de proxy la ça va fonctionner, mais nous voulons utiliser firefox pour aller sur internet ! Donc nous laisserons la configuration manuelle du proxy.
Et la nous avons besoin dâaccĂ©der Ă notre RPI qui est a l’adresse 172.22.50.XXX
XXX : 101 pour le poste 1 et XXX:114 pour le poste 14 (distribué par le serveur DHCP de la section)

il faudra exclure les adresses internes du réseau local ! et garder la configuration manuelle.

PremiĂšre page html
Nous allons tester notre serveur web (dans le Raspberry Pi)
index.html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ma premiĂšre page</title>
</head>
<body>
<h1>bonjour le monde</h1>
</body>
</html>
et du navigateur on peut voir cette page
Visualiser la page html sur le serveur apache de votre RPI
dans votre firefox configuré correctement pour le lycee: http://raspiYXX.local

c’est bien votre serveur web apache qui rĂ©pond
Dans firefox on peut voir le html, dans outils supplémentaires, et outils de développement

Dans un terminal on peut récupérer le fichier html
vous pouvez rĂ©cupĂ©rer le fichier html (sans passer par firefox qui peut lâinterprĂ©ter)
bruno@elliott:~$ wget 172.22.50.101
--2025-11-12 17:11:44-- http://172.22.50.101/
Connexion à 172.22.50.101:80⊠connecté.
requĂȘte HTTP transmise, en attente de la rĂ©ponse⊠200 OK
Taille : 240 [text/html]
Sauvegarde en : « index.html »
index.html 100%[================================================================>] 240 --.-KB/s ds 0s
2025-11-12 17:11:45 (11,8 MB/s) â « index.html » sauvegardĂ© [240/240]
Découvrons ou redécouvrons PHP
on va mettre ce script bonjour.php sur notre RPI (dans le répertoire du TP)
nous allons vérifier que php (interprÚteur de php) est fonctionnel
bonjour.php:
<?php // bonjour le monde !! echo "Bonjour le monde !\n"; ?>
Nous allons le tester:
bruno@work:~/Works/web_php_gpio/php $ php bonjour.php
Bonjour le monde !
Comment utiliser php et libgpiod
on va mettre une led sur GPIO 27 , vérifier que le montage fonctionne (avec gpioset)
allumer GPIO27 en php
allume.php
<?php
echo "đĄ Allumage GPIO27\n";
system("gpioset gpiochip0 27=1");
file_put_contents('/tmp/gpio27_etat.txt', '1');
echo "â
GPIO27 allumée\n";
exit(0);
?>
la fonction system , nous permet d’utiliser gpioset
cette fonction permet dâexĂ©cuter n’importe quelle fonction de notre systĂšme.
Et file_put_contents , va nous permettre de sauvegarder l’Ă©tat de GPIO27 , dans /tmp/gpio27_etat.txt
Tester le script php allume.php
- verifier que la led sur GPIO27 s’allume
- vérifier le contenu du fichier /tmp/gpio27_etat.txt
Eteindre GPIO27 en php
eteint.php
<?php
// Eteindre GPIO27
echo "đ” Extinction GPIO27\n";
system("gpioset gpiochip0 27=0");
file_put_contents('/tmp/gpio27_etat.txt', '0');
echo "â
GPIO27 Eteint\n";
exit(0);
?>
Tester le script php eteint.php
- verifier que la led sur GPIO27 s’Ă©teint
- vérifier le contenu du fichier /tmp/gpio27_etat.txt
Nous allons commencer notre site pour gérer GPIO27 en ligne
Sur nos Raspberry Pi , le site web est configuré par défaut dans /var/www/html
on rappel que index.html est le premier fichier recherché sur le serveur.
Dans lequel vous devez trouver un fichier index.html que vous devez comprendre et utiliser en le visualisant dans votre navigateur (firefox) rpiYXX.local , une fois que cela est fait renommer cet index.html en index.html.bak
Ici en javascript et html: notre nouveau index.html
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test GPIO</title>
</head>
<body>
<h1>Test GPIO27</h1>
<div id="output" style="padding:20px; border:1px solid #000; margin:20px;"></div>
<button onclick="callPHP('allume.php')">ALLUMER</button>
<button onclick="callPHP('eteint.php')">ETEINDRE</button>
<button onclick="callPHP('statut.php')">STATUT</button>
<script>
function callPHP(script) {
const output = document.getElementById('output');
output.innerHTML = 'Appel de ' + script + '...';
fetch(script)
.then(response => {
output.innerHTML += '<br>Status: ' + response.status;
return response.text();
})
.then(text => {
output.innerHTML += '<br>Réponse: ' + text;
})
.catch(error => {
output.innerHTML += '<br>ERREUR: ' + error;
});
}
</script>
</body>
</html>
Dans quel langage est réalisé la function callPHP(script)
Les scripts PHP pour utiliser la libgpiod
allume.php cli / web
cli : utilisable en ligne de commande (php allume.php)
web : utilisable par le navigateur web
allume.php (a placer dans /var/www/html)
<?php
// allume.php
if (php_sapi_name() === 'cli') {
echo "đĄ Allumage GPIO27\n";
system("gpioset gpiochip0 27=1");
file_put_contents('/tmp/gpio27_etat.txt', '1');
echo "â
GPIO27 allumée\n";
} else {
system("gpioset gpiochip0 27=1");
file_put_contents('/tmp/gpio27_etat.txt', '1');
header('Content-Type: application/json');
echo json_encode([
'status' => 'success',
'message' => "GPIO27 allumée",
'gpio' => 27,
'value' => 1,
'timestamp' => date('Y-m-d H:i:s')
]);
}
?>
eteint.php (a placer dans /var/www/html)
<?php
// eteint.php
if (php_sapi_name() === 'cli') {
echo "â« Extinction GPIO27\n";
system("gpioset gpiochip0 27=0");
file_put_contents('/tmp/gpio27_etat.txt', '0');
echo "â
GPIO27 éteinte\n";
} else {
system("gpioset gpiochip0 27=0");
file_put_contents('/tmp/gpio27_etat.txt', '0');
header('Content-Type: application/json');
echo json_encode([
'status' => 'success',
'message' => "GPIO27 éteinte",
'gpio' => 27,
'value' => 0,
'timestamp' => date('Y-m-d H:i:s')
]);
}
?>
statut.php (a placer dans /var/www/html)
<?php
// statut_sans_affecter.php - Mémorisation de l'état
$state_file = '/tmp/gpio27_etat.txt';
if (php_sapi_name() === 'cli') {
echo "đĄ Ătat mĂ©morisĂ© GPIO27...\n";
if (file_exists($state_file)) {
$etat = trim(file_get_contents($state_file));
if ($etat === '1') {
echo "đŽ GPIO27: ALLUMĂE (dernier Ă©tat connu)\n";
} else {
echo "â« GPIO27: ĂTEINTE (dernier Ă©tat connu)\n";
}
} else {
echo "â Ătat inconnu - ExĂ©cutez allume.php ou eteint.php d'abord\n";
}
} else {
if (file_exists($state_file)) {
$etat = trim(file_get_contents($state_file));
$value_int = intval($etat);
$message = $value_int === 1 ? "GPIO27 allumée" : "GPIO27 éteinte";
} else {
$value_int = 0;
$message = "Ătat inconnu";
}
header('Content-Type: application/json');
echo json_encode([
'status' => 'success',
'message' => $message,
'gpio' => 27,
'value' => $value_int,
'timestamp' => date('Y-m-d H:i:s')
]);
}
?>
Ouvrir les DevTools
F12 ou Ctrl+Shift+I
Ou clic droit â « Inspecter »
inspecteur
on voit la structure de notre page web
Réseau
on va y voir nos requĂȘtes
rafraĂźchir (la flĂšche qui tourne)
Sélectionner XHR ou et fetch

on active bouton allumer !

on peut voir la rĂ©ponse au format Json : l’outil de dĂ©veloppement intĂ©grĂ© est le bienvenu !

Améliorons notre applicatif web

control.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ContrĂŽle GPIO27</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 40px auto;
padding: 20px;
text-align: center;
}
.status {
padding: 20px;
margin: 20px 0;
border-radius: 8px;
font-size: 18px;
font-weight: bold;
}
.on { background: #d4edda; color: #155724; border: 2px solid #c3e6cb; }
.off { background: #f8d7da; color: #721c24; border: 2px solid #f5c6cb; }
button {
padding: 15px 25px;
margin: 10px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
color: white;
}
.btn-on { background: #28a745; }
.btn-off { background: #dc3545; }
.btn-status { background: #17a2b8; }
#message { margin: 15px 0; color: #666; }
</style>
</head>
<body>
<h1>ContrĂŽle GPIO27</h1>
<div id="status" class="status off">Statut: Chargement...</div>
<button class="btn-on" onclick="allumer()">Allumer</button>
<button class="btn-off" onclick="eteindre()">Ăteindre</button>
<button class="btn-status" onclick="getStatut()">Actualiser</button>
<div id="message"></div>
<script>
// Fonctions corrigées
function showMessage(text, isError = false) {
const messageDiv = document.getElementById('message');
messageDiv.textContent = text;
messageDiv.style.color = isError ? '#dc3545' : '#28a745';
}
function updateStatus(message, isOn) {
const statusDiv = document.getElementById('status');
statusDiv.textContent = message;
statusDiv.className = 'status ' + (isOn ? 'on' : 'off');
}
async function allumer() {
showMessage('Allumage en cours...');
try {
const response = await fetch('allume.php');
if (!response.ok) throw new Error('HTTP error: ' + response.status);
const data = await response.json();
updateStatus('ALLUMĂE', true);
showMessage('â
' + data.message);
} catch (error) {
console.error('Erreur allumage:', error);
updateStatus('ERREUR', false);
showMessage('â Erreur: ' + error.message, true);
}
}
async function eteindre() {
showMessage('Extinction en cours...');
try {
const response = await fetch('eteint.php');
if (!response.ok) throw new Error('HTTP error: ' + response.status);
const data = await response.json();
updateStatus('ĂTEINTE', false);
showMessage('â
' + data.message);
} catch (error) {
console.error('Erreur extinction:', error);
updateStatus('ERREUR', false);
showMessage('â Erreur: ' + error.message, true);
}
}
async function getStatut() {
showMessage('Lecture du statut...');
try {
const response = await fetch('statut.php');
if (!response.ok) throw new Error('HTTP error: ' + response.status);
const data = await response.json();
updateStatus(data.message.toUpperCase(), data.value === 1);
showMessage('â
Statut actualisé');
} catch (error) {
console.error('Erreur statut:', error);
updateStatus('HORS LIGNE', false);
showMessage('â Erreur de connexion', true);
}
}
// Charger le statut au démarrage
document.addEventListener('DOMContentLoaded', getStatut);
</script>
</body>
</html>
utilisons les consoles
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ContrĂŽle GPIO27 - Version Console</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 40px auto;
padding: 20px;
text-align: center;
}
.status {
padding: 20px;
margin: 20px 0;
border-radius: 8px;
font-size: 18px;
font-weight: bold;
}
.on { background: #d4edda; color: #155724; border: 2px solid #c3e6cb; }
.off { background: #f8d7da; color: #721c24; border: 2px solid #f5c6cb; }
button {
padding: 15px 25px;
margin: 10px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
color: white;
}
.btn-on { background: #28a745; }
.btn-off { background: #dc3545; }
.btn-status { background: #17a2b8; }
#message { margin: 15px 0; color: #666; }
</style>
</head>
<body>
<h1>ContrĂŽle GPIO27 - Version Console</h1>
<p style="color: #666; font-size: 14px;">Ouvrez la console (F12) pour voir les logs détaillés</p>
<div id="status" class="status off">Statut: Chargement...</div>
<button class="btn-on" onclick="allumer()">Allumer</button>
<button class="btn-off" onclick="eteindre()">Ăteindre</button>
<button class="btn-status" onclick="getStatut()">Actualiser</button>
<div id="message"></div>
<script>
// Fonction utilitaire pour les logs avec timestamp
function log(action, message) {
const timestamp = new Date().toLocaleTimeString();
console.log(`[${timestamp}] ${action}: ${message}`);
}
function showMessage(text, isError = false) {
const messageDiv = document.getElementById('message');
messageDiv.textContent = text;
messageDiv.style.color = isError ? '#dc3545' : '#28a745';
}
function updateStatus(message, isOn) {
const statusDiv = document.getElementById('status');
statusDiv.textContent = message;
statusDiv.className = 'status ' + (isOn ? 'on' : 'off');
}
async function allumer() {
console.group('đ ALLUMAGE GPIO27');
log('DĂBUT', 'Fonction allumer() appelĂ©e');
showMessage('Allumage en cours...');
try {
log('REQUĂTE', 'Envoi vers allume.php');
const response = await fetch('allume.php');
log('RĂPONSE', `Status HTTP: ${response.status}`);
if (!response.ok) throw new Error('HTTP error: ' + response.status);
const data = await response.json();
log('DONNĂES', `JSON reçu: ${JSON.stringify(data)}`);
log('GPIO', `Valeur: ${data.value}`);
updateStatus('ALLUMĂE', true);
showMessage('â
' + data.message);
log('SUCCĂS', 'Interface mise Ă jour');
} catch (error) {
log('ERREUR', error.message);
console.error('đ„ DĂ©tails erreur:', error);
updateStatus('ERREUR', false);
showMessage('â Erreur: ' + error.message, true);
}
console.groupEnd();
}
async function eteindre() {
console.group('đ EXTINCTION GPIO27');
log('DĂBUT', 'Fonction eteindre() appelĂ©e');
showMessage('Extinction en cours...');
try {
log('REQUĂTE', 'Envoi vers eteint.php');
const response = await fetch('eteint.php');
log('RĂPONSE', `Status HTTP: ${response.status}`);
if (!response.ok) throw new Error('HTTP error: ' + response.status);
const data = await response.json();
log('DONNĂES', `JSON reçu: ${JSON.stringify(data)}`);
log('GPIO', `Valeur: ${data.value}`);
updateStatus('ĂTEINTE', false);
showMessage('â
' + data.message);
log('SUCCĂS', 'Interface mise Ă jour');
} catch (error) {
log('ERREUR', error.message);
console.error('đ„ DĂ©tails erreur:', error);
updateStatus('ERREUR', false);
showMessage('â Erreur: ' + error.message, true);
}
console.groupEnd();
}
async function getStatut() {
console.group('đ STATUT GPIO27');
log('DĂBUT', 'Fonction getStatut() appelĂ©e');
showMessage('Lecture du statut...');
try {
log('REQUĂTE', 'Envoi vers statut.php');
const response = await fetch('statut.php');
log('RĂPONSE', `Status HTTP: ${response.status}`);
if (!response.ok) throw new Error('HTTP error: ' + response.status);
const data = await response.json();
log('DONNĂES', `JSON reçu: ${JSON.stringify(data)}`);
log('GPIO', `Valeur: ${data.value}, Ătat: ${data.message}`);
updateStatus(data.message.toUpperCase(), data.value === 1);
showMessage('â
Statut actualisé');
log('SUCCĂS', 'Statut affichĂ©');
} catch (error) {
log('ERREUR', error.message);
console.error('đ„ DĂ©tails erreur:', error);
updateStatus('HORS LIGNE', false);
showMessage('â Erreur de connexion', true);
}
console.groupEnd();
}
// Au chargement de la page
document.addEventListener('DOMContentLoaded', function() {
log('PAGE', 'Page chargée - démarrage automatique du statut');
getStatut();
});
</script>
</body>
</html>

Provoquons un erreur
un jour il y aura des erreurs .. et la nous allons en provoquer une !
renommer le fichier eteint.php en etient.php.bak
et trouver le message d’erreur dans la console avec l’outil de dĂ©veloppement de firefox

