🕒 : 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