Installation des paquets nécessaires

sudo apt update
sudo apt install apache2 php libapache2-mod-php gpiod

Configuration des permissions GPIO

# Ajouter l'utilisateur www-data au groupe gpio
sudo usermod -a -G gpio www-data

# Vérifier les groupes
groups www-data

# Redémarrer Apache
sudo systemctl restart apache2

# Vérifier les devices GPIO
ls -la /dev/gpiochip*

# Tester les commandes gpiod
gpiodetect

Script PHP principal (/var/www/html/index.php)

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');

class GPIOControl {
    private $chip = 'gpiochip0';
    
    public function setGPIO($pin, $state) {
        try {
            // Commande SANS sudo
            $command = "gpioset {$this->chip} {$pin}={$state}";
            exec($command . " 2>&1", $output, $return_var);
            
            if ($return_var === 0) {
                return ['success' => true, 'pin' => $pin, 'state' => $state];
            } else {
                return ['success' => false, 'error' => implode(', ', $output)];
            }
        } catch (Exception $e) {
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }
    
    public function getGPIOStatus($pin) {
        try {
            // Commande SANS sudo
            exec("gpioget {$this->chip} {$pin} 2>&1", $output, $return_var);
            
            if ($return_var === 0 && isset($output[0])) {
                $state = intval(trim($output[0]));
                return ['success' => true, 'pin' => $pin, 'state' => $state];
            } else {
                return ['success' => false, 'error' => implode(', ', $output)];
            }
        } catch (Exception $e) {
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }
    
    public function getAllStatus($pins) {
        $status = [];
        foreach ($pins as $pin) {
            $result = $this->getGPIOStatus($pin);
            if ($result['success']) {
                $status[$pin] = $result['state'];
            }
        }
        return $status;
    }
}

// Configuration des GPIOs disponibles
$availablePins = [17, 27, 22];
$gpio = new GPIOControl();

// Traitement des requêtes AJAX
if ($_SERVER['REQUEST_METHOD'] === 'POST' || isset($_GET['ajax'])) {
    $action = $_POST['action'] ?? $_GET['action'] ?? '';
    
    switch ($action) {
        case 'set':
            $pin = intval($_POST['pin'] ?? $_GET['pin'] ?? 0);
            $state = intval($_POST['state'] ?? $_GET['state'] ?? 0);
            
            if (in_array($pin, $availablePins) && ($state === 0 || $state === 1)) {
                $result = $gpio->setGPIO($pin, $state);
                echo json_encode($result);
            } else {
                echo json_encode(['success' => false, 'error' => 'Paramètres invalides']);
            }
            exit;
            
        case 'status':
            $pin = intval($_POST['pin'] ?? $_GET['pin'] ?? 0);
            if (in_array($pin, $availablePins)) {
                $result = $gpio->getGPIOStatus($pin);
                echo json_encode($result);
            } else {
                echo json_encode(['success' => false, 'error' => 'GPIO non configuré']);
            }
            exit;
            
        case 'all_status':
            $result = $gpio->getAllStatus($availablePins);
            echo json_encode(['success' => true, 'status' => $result]);
            exit;
            
        case 'system_info':
            $info = [
                'php_version' => phpversion(),
                'gpio_chip' => 'gpiochip0',
                'available_pins' => $availablePins,
                'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'Inconnu'
            ];
            echo json_encode(['success' => true, 'info' => $info]);
            exit;
            
        default:
            echo json_encode(['success' => false, 'error' => 'Action non reconnue']);
            exit;
    }
}
?>

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Contrôle GPIOs - Apache2 + PHP + gpiod</title>
    <style>
        :root {
            --primary-color: #667eea;
            --success-color: #4CAF50;
            --error-color: #f44336;
            --warning-color: #ff9800;
        }
        
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1000px;
            margin: 0 auto;
        }
        
        .header {
            background: white;
            padding: 30px;
            border-radius: 15px 15px 0 0;
            box-shadow: 0 5px 15px rgba(0,0,0,0.1);
            text-align: center;
        }
        
        h1 {
            color: #333;
            font-size: 2.5em;
            margin-bottom: 10px;
        }
        
        .subtitle {
            color: #666;
            font-size: 1.2em;
        }
        
        .main-content {
            background: white;
            padding: 30px;
            border-radius: 0 0 15px 15px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        }
        
        .gpios-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        
        .gpio-control {
            border: 2px solid #e0e0e0;
            border-radius: 12px;
            padding: 25px;
            background: #fafafa;
            transition: all 0.3s ease;
        }
        
        .gpio-control:hover {
            border-color: #bbb;
            box-shadow: 0 5px 15px rgba(0,0,0,0.1);
        }
        
        .gpio-control.active {
            border-color: var(--success-color);
        }
        
        .gpio-control.inactive {
            border-color: var(--error-color);
        }
        
        .gpio-title {
            font-size: 1.3em;
            font-weight: bold;
            margin-bottom: 15px;
            color: #333;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }
        
        .gpio-icon {
            font-size: 1.5em;
        }
        
        .status {
            margin: 15px 0;
            padding: 12px;
            font-size: 16px;
            font-weight: bold;
            border-radius: 8px;
            text-align: center;
            transition: all 0.3s ease;
        }
        
        .status.on {
            background-color: #e8f5e8;
            color: var(--success-color);
            border: 2px solid var(--success-color);
        }
        
        .status.off {
            background-color: #ffeaea;
            color: var(--error-color);
            border: 2px solid var(--error-color);
        }
        
        .status.unknown {
            background-color: #fff3e0;
            color: var(--warning-color);
            border: 2px solid var(--warning-color);
        }
        
        .gpio-buttons {
            display: flex;
            gap: 10px;
        }
        
        button {
            padding: 12px 20px;
            font-size: 14px;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.3s ease;
            font-weight: bold;
            flex: 1;
        }
        
        .btn-on {
            background: var(--success-color);
            color: white;
        }
        
        .btn-off {
            background: var(--error-color);
            color: white;
        }
        
        button:hover:not(:disabled) {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(0,0,0,0.2);
        }
        
        button:active:not(:disabled) {
            transform: translateY(0);
        }
        
        button:disabled {
            opacity: 0.6;
            cursor: not-allowed;
        }
        
        .system-info {
            background: #f8f9fa;
            border-radius: 10px;
            padding: 25px;
            margin-top: 30px;
            border-left: 4px solid var(--primary-color);
        }
        
        .system-info h3 {
            margin-bottom: 15px;
            color: #333;
        }
        
        .info-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
        }
        
        .info-item {
            padding: 10px;
            background: white;
            border-radius: 5px;
            border-left: 3px solid var(--primary-color);
        }
        
        .message {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 15px 25px;
            border-radius: 8px;
            color: white;
            font-weight: bold;
            z-index: 1000;
            opacity: 0;
            transform: translateX(100px);
            transition: all 0.3s ease;
        }
        
        .message.show {
            opacity: 1;
            transform: translateX(0);
        }
        
        .message.success {
            background: var(--success-color);
        }
        
        .message.error {
            background: var(--error-color);
        }
        
        .loading {
            display: inline-block;
            width: 16px;
            height: 16px;
            border: 2px solid #f3f3f3;
            border-top: 2px solid var(--primary-color);
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }
        
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        
        @media (max-width: 768px) {
            .gpios-grid {
                grid-template-columns: 1fr;
            }
            
            .gpio-buttons {
                flex-direction: column;
            }
            
            h1 {
                font-size: 2em;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🎛️ Contrôle GPIOs Raspberry Pi</h1>
            <div class="subtitle">Apache2 + PHP + gpiod</div>
        </div>
        
        <div class="main-content">
            <div class="gpios-grid">
                <?php foreach ($availablePins as $pin): ?>
                <div class="gpio-control" id="gpio-<?= $pin ?>">
                    <div class="gpio-title">
                        <span>📍 GPIO <?= $pin ?></span>
                        <span id="loading-<?= $pin ?>" class="loading" style="display: none;"></span>
                    </div>
                    <div class="status unknown" id="status-<?= $pin ?>">
                        🔄 Chargement...
                    </div>
                    <div class="gpio-buttons">
                        <button class="btn-on" onclick="setGPIO(<?= $pin ?>, 1)" id="btn-on-<?= $pin ?>">
                            ✅ ACTIVER
                        </button>
                        <button class="btn-off" onclick="setGPIO(<?= $pin ?>, 0)" id="btn-off-<?= $pin ?>">
                            ❌ DÉSACTIVER
                        </button>
                    </div>
                </div>
                <?php endforeach; ?>
            </div>
            
            <div class="system-info">
                <h3>📊 Informations Système</h3>
                <div class="info-grid">
                    <div class="info-item">
                        <strong>Serveur:</strong> Apache2
                    </div>
                    <div class="info-item">
                        <strong>PHP Version:</strong> <?= phpversion() ?>
                    </div>
                    <div class="info-item">
                        <strong>Interface GPIO:</strong> gpiod
                    </div>
                    <div class="info-item">
                        <strong>GPIOs Configurés:</strong> <?= implode(', ', $availablePins) ?>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <div id="message" class="message"></div>

    <script>
        function setGPIO(pin, state) {
            // Désactiver les boutons pendant l'opération
            setButtonsState(pin, true);
            showLoading(pin, true);
            
            const formData = new FormData();
            formData.append('action', 'set');
            formData.append('pin', pin);
            formData.append('state', state);
            
            fetch('index.php', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                setButtonsState(pin, false);
                showLoading(pin, false);
                
                if (data.success) {
                    updateStatus(data.pin, data.state);
                    showMessage('✅ GPIO ' + pin + ' mis à ' + data.state, 'success');
                } else {
                    showMessage('❌ Erreur: ' + data.error, 'error');
                    // Re-vérifier le statut actuel
                    checkPinStatus(pin);
                }
            })
            .catch(error => {
                setButtonsState(pin, false);
                showLoading(pin, false);
                console.error('Erreur:', error);
                showMessage('❌ Erreur de communication avec le serveur', 'error');
            });
        }

        function updateStatus(pin, state) {
            const statusElement = document.getElementById(`status-${pin}`);
            const gpioControl = document.getElementById(`gpio-${pin}`);
            
            if (statusElement && gpioControl) {
                // Mettre à jour le statut
                if (state === 1) {
                    statusElement.innerHTML = '🟢 ÉTAT: ACTIF (1)';
                    statusElement.className = 'status on';
                    gpioControl.classList.add('active');
                    gpioControl.classList.remove('inactive');
                } else {
                    statusElement.innerHTML = '🔴 ÉTAT: INACTIF (0)';
                    statusElement.className = 'status off';
                    gpioControl.classList.add('inactive');
                    gpioControl.classList.remove('active');
                }
            }
        }

        function setButtonsState(pin, disabled) {
            const btnOn = document.getElementById(`btn-on-${pin}`);
            const btnOff = document.getElementById(`btn-off-${pin}`);
            
            if (btnOn) btnOn.disabled = disabled;
            if (btnOff) btnOff.disabled = disabled;
        }

        function showLoading(pin, show) {
            const loadingElement = document.getElementById(`loading-${pin}`);
            if (loadingElement) {
                loadingElement.style.display = show ? 'inline-block' : 'none';
            }
        }

        function checkPinStatus(pin) {
            fetch(`index.php?ajax=1&action=status&pin=${pin}`)
                .then(response => response.json())
                .then(data => {
                    if (data.success) {
                        updateStatus(data.pin, data.state);
                    } else {
                        document.getElementById(`status-${pin}`).innerHTML = '❌ Erreur de lecture';
                        document.getElementById(`status-${pin}`).className = 'status unknown';
                    }
                })
                .catch(error => {
                    console.error('Erreur pour GPIO ' + pin + ':', error);
                    document.getElementById(`status-${pin}`).innerHTML = '❌ Erreur connexion';
                    document.getElementById(`status-${pin}`).className = 'status unknown';
                });
        }

        function checkAllStatus() {
            fetch(`index.php?ajax=1&action=all_status`)
                .then(response => response.json())
                .then(data => {
                    if (data.success) {
                        for (const [pin, state] of Object.entries(data.status)) {
                            updateStatus(parseInt(pin), state);
                        }
                    }
                })
                .catch(error => {
                    console.error('Erreur lors de la vérification des statuts:', error);
                });
        }

        function showMessage(message, type) {
            const messageDiv = document.getElementById('message');
            messageDiv.textContent = message;
            messageDiv.className = `message ${type} show`;
            
            setTimeout(() => {
                messageDiv.classList.remove('show');
            }, 4000);
        }

        // Vérifier l'état au chargement de la page
        document.addEventListener('DOMContentLoaded', function() {
            console.log('🚀 Initialisation du contrôle GPIO...');
            checkAllStatus();
            
            // Vérifier l'état toutes les 3 secondes
            setInterval(checkAllStatus, 3000);
        });
    </script>
</body>
</html>

Vérification finale

# Testez les permissions en accédant à :
http://votre-pi/test_permissions.php

# Puis accédez à l'interface principale :
http://votre-pi/

Sécurisation supplémentaire (optionnel)

# Créer un fichier .htaccess pour sécuriser l'accès
sudo nano /var/www/html/.htaccess
# Empêcher l'accès aux fichiers sensibles
<Files "test_permissions.php">
    Order deny,allow
    Deny from all
    Allow from 127.0.0.1
</Files>