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>