-
-
📡
-
Cliquez sur "Connecter un module" pour ajouter un ESP32
+
+
+
🎉 Quiz terminé !
+
+
@@ -247,225 +435,404 @@
const SERVICE_UUID = '4fafc201-1fb5-459e-8fcc-c5c9c331914b';
const CHARACTERISTIC_UUID = 'beb5483e-36e1-4688-b7f5-ea07361b26a8';
- // Stockage des modules connectés
- let connectedModules = new Map();
+ let questions = [];
+ let currentQuestionIndex = 0;
+ let connectedModules = new Map(); // moduleName -> { device, characteristic, answer, scores }
+ let resultsShown = false;
- // Éléments DOM
+ const fileInput = document.getElementById('fileInput');
const connectBtn = document.getElementById('connectBtn');
- const statusDiv = document.getElementById('status');
- const modulesContainer = document.getElementById('modulesContainer');
+ const quizContainer = document.getElementById('quizContainer');
+ const scoreContainer = document.getElementById('scoreContainer');
+ const modulesStatus = document.getElementById('modulesStatus');
+ const modulesPanel = document.getElementById('modulesPanel');
+ const prevBtn = document.getElementById('prevBtn');
+ const nextBtn = document.getElementById('nextBtn');
+ const showResultsBtn = document.getElementById('showResultsBtn');
+ const correctScoreInput = document.getElementById('correctScore');
+ const incorrectScoreInput = document.getElementById('incorrectScore');
// Vérifier si Web Bluetooth est disponible
if (!navigator.bluetooth) {
- alert('Web Bluetooth n\'est pas supporté par votre navigateur. Utilisez Chrome, Edge ou Opera.');
+ alert('Web Bluetooth non supporté. Utilisez Chrome, Edge ou Opera.');
connectBtn.disabled = true;
}
- // Fonction pour afficher une notification
- function showNotification(message) {
- const notification = document.createElement('div');
- notification.className = 'notification';
- notification.textContent = message;
- document.body.appendChild(notification);
-
- setTimeout(() => {
- notification.remove();
- }, 3000);
- }
-
- // Fonction pour mettre à jour le statut
- function updateStatus() {
- const count = connectedModules.size;
- statusDiv.textContent = count === 0
- ? 'Aucun module connecté'
- : `${count} module${count > 1 ? 's' : ''} connecté${count > 1 ? 's' : ''}`;
+ // Fonction pour obtenir le scoring actuel
+ function getScoringConfig() {
+ return {
+ correctAnswer: parseInt(correctScoreInput.value) || 1,
+ incorrectAnswer: parseInt(incorrectScoreInput.value) || 0
+ };
}
- // Fonction pour créer la carte d'un module
- function createModuleCard(moduleName, device) {
- const card = document.createElement('div');
- card.className = 'module-card';
- card.id = `module-${moduleName}`;
-
- card.innerHTML = `
-
-
-
En attente de données...
-
-
- Dernière mise à jour: --
-
- `;
-
- return card;
- }
+ fileInput.addEventListener('change', handleFileSelect);
+ connectBtn.addEventListener('click', connectModule);
+ prevBtn.addEventListener('click', showPreviousQuestion);
+ nextBtn.addEventListener('click', showNextQuestion);
+ showResultsBtn.addEventListener('click', showResults);
- // Fonction pour mettre à jour l'affichage des GPIO
- function updateGPIODisplay(moduleName, gpioData) {
- console.log(`[updateGPIODisplay] Module: ${moduleName}`, gpioData);
- const gpioList = document.getElementById(`gpio-list-${moduleName}`);
- const lastUpdate = document.getElementById(`last-update-${moduleName}`);
-
- if (!gpioList) {
- console.error(`[updateGPIODisplay] Élément gpio-list non trouvé pour ${moduleName}`);
- return;
- }
-
- gpioList.innerHTML = '';
-
- gpioData.forEach(gpio => {
- // Convertir en string pour comparaison cohérente
- const stateStr = String(gpio.state);
- const isLow = (stateStr === '1' || stateStr === 1);
-
- console.log(` GPIO ${gpio.pin}: state=${gpio.state} (${typeof gpio.state}), isLow=${isLow}`);
-
- const gpioItem = document.createElement('div');
- gpioItem.className = `gpio-item ${isLow ? 'active' : ''}`;
-
- gpioItem.innerHTML = `
-
GPIO ${gpio.pin}
-
- ${isLow ? 'BAS' : 'HAUT'}
-
- `;
-
- gpioList.appendChild(gpioItem);
- });
-
- // Mettre à jour l'horodatage
- const now = new Date();
- lastUpdate.textContent = `Dernière mise à jour: ${now.toLocaleTimeString()}`;
+ function updateModulesStatus() {
+ const count = connectedModules.size;
+ modulesStatus.textContent = count === 0
+ ? 'Aucun module'
+ : `${count} module${count > 1 ? 's' : ''}`;
}
- // Fonction pour connecter un module
async function connectModule() {
try {
- console.log('Recherche d\'un appareil Bluetooth...');
+ console.log('Recherche ESP32...');
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: [SERVICE_UUID] }]
});
- console.log('Appareil trouvé:', device.name);
- showNotification(`Connexion à ${device.name}...`);
+ console.log('ESP32 trouvé:', device.name);
const server = await device.gatt.connect();
- console.log('Connecté au serveur GATT');
-
const service = await server.getPrimaryService(SERVICE_UUID);
const characteristic = await service.getCharacteristic(CHARACTERISTIC_UUID);
- // Lire l'état initial immédiatement
+ // Lire l'état initial
const initialValue = await characteristic.readValue();
const initialData = new TextDecoder().decode(initialValue);
console.log('État initial:', initialData);
- // Traiter l'état initial
- try {
- const data = JSON.parse(initialData);
- const moduleName = data.module;
-
- // Créer la carte du module
- const emptyState = modulesContainer.querySelector('.empty-state');
- if (emptyState) emptyState.remove();
-
- const card = createModuleCard(moduleName, device);
- modulesContainer.appendChild(card);
-
- // Stocker les informations
- connectedModules.set(moduleName, {
- device: device,
- characteristic: characteristic,
- gpios: data.gpios
- });
-
- // Afficher l'état initial
- updateGPIODisplay(moduleName, data.gpios);
- updateStatus();
- showNotification(`${moduleName} connecté avec succès!`);
- } catch (e) {
- console.error('Erreur parsing état initial:', e);
- }
+ const data = JSON.parse(initialData);
+ const moduleName = data.module;
+
+ // Stocker le module
+ connectedModules.set(moduleName, {
+ device: device,
+ characteristic: characteristic,
+ answer: null,
+ totalScore: 0,
+ gpios: data.gpios
+ });
+
+ // Créer la carte du module
+ createModuleCard(moduleName);
+ updateModulesStatus();
- // Activer les notifications pour les changements futurs
+ // Activer les notifications
await characteristic.startNotifications();
- console.log('Notifications activées');
- // Écouter les notifications
characteristic.addEventListener('characteristicvaluechanged', (event) => {
const value = new TextDecoder().decode(event.target.value);
- console.log('🔔 [Notification reçue]:', value);
+ console.log('🔔 Notification de', moduleName, ':', value);
try {
const data = JSON.parse(value);
- const moduleName = data.module;
- console.log(`📦 Données parsées:`, data);
-
- // Mettre à jour les informations du module
- if (connectedModules.has(moduleName)) {
- console.log(`✅ Module ${moduleName} trouvé, mise à jour...`);
- connectedModules.get(moduleName).gpios = data.gpios;
- // Mettre à jour l'affichage
- updateGPIODisplay(moduleName, data.gpios);
- } else {
- console.warn(`⚠️ Module ${moduleName} non trouvé dans connectedModules`);
- }
-
+ handleModuleData(moduleName, data);
} catch (e) {
- console.error('❌ Erreur lors du parsing JSON:', e);
+ console.error('Erreur parsing:', e);
}
});
// Gérer la déconnexion
device.addEventListener('gattserverdisconnected', () => {
- console.log(`${device.name} déconnecté`);
- showNotification(`${device.name} déconnecté`);
+ console.log(moduleName, 'déconnecté');
+ connectedModules.delete(moduleName);
+ document.getElementById(`module-${moduleName}`)?.remove();
+ updateModulesStatus();
+ });
+
+ } catch (error) {
+ console.error('Erreur connexion:', error);
+ alert(`Erreur: ${error.message}`);
+ }
+ }
+
+ function createModuleCard(moduleName) {
+ const card = document.createElement('div');
+ card.className = 'module-card';
+ card.id = `module-${moduleName}`;
+
+ card.innerHTML = `
+
+
+ `;
+
+ modulesPanel.appendChild(card);
+ }
+
+ function handleModuleData(moduleName, data) {
+ const module = connectedModules.get(moduleName);
+ if (!module) return;
+
+ // Mettre à jour les GPIO
+ module.gpios = data.gpios;
+
+ console.log('📊 GPIOs pour', moduleName, ':', data.gpios);
+
+ // Détecter la réponse (A=GPIO15, B=GPIO4, C=GPIO5, D=GPIO18)
+ // Réponse détectée si GPIO est à l'état BAS (1)
+ let answer = null;
+ data.gpios.forEach(gpio => {
+ console.log(` GPIO ${gpio.pin} = ${gpio.state} (type: ${typeof gpio.state})`);
+ if (String(gpio.state) === '1') {
+ if (gpio.pin === 15) answer = 'A';
+ else if (gpio.pin === 4) answer = 'B';
+ else if (gpio.pin === 5) answer = 'C';
+ else if (gpio.pin === 18) answer = 'D';
+ }
+ });
+
+ console.log('➡️ Réponse détectée:', answer);
+
+ if (answer && !resultsShown) {
+ // Permet de modifier la réponse tant que les résultats ne sont pas affichés
+ if (module.answer !== answer) {
+ module.answer = answer;
+ updateModuleAnswer(moduleName, answer);
+ checkAllAnswersReceived();
+ }
+ }
+ }
+
+ function updateModuleAnswer(moduleName, answer) {
+ const answerDiv = document.getElementById(`answer-${moduleName}`);
+ if (answerDiv) {
+ answerDiv.textContent = '✓ Répondu';
+ answerDiv.classList.remove('module-waiting');
+ answerDiv.classList.add('module-answered');
+ }
+ }
+
+ function checkAllAnswersReceived() {
+ let allAnswered = true;
+ for (let [name, module] of connectedModules.entries()) {
+ if (!module.answer) {
+ allAnswered = false;
+ break;
+ }
+ }
+
+ showResultsBtn.disabled = !allAnswered || connectedModules.size === 0;
+ }
+
+ function showResults() {
+ if (resultsShown) return;
+ resultsShown = true;
+
+ const question = questions[currentQuestionIndex];
+ const correctAnswerIndex = question.correctAnswer;
+ const correctLetter = ['A', 'B', 'C', 'D'][correctAnswerIndex];
+ const scoringConfig = getScoringConfig();
+
+ // Afficher la bonne réponse
+ const answerDivs = document.querySelectorAll('.answer');
+ answerDivs[correctAnswerIndex].classList.add('correct');
+
+ // Vérifier les réponses de chaque module
+ for (let [name, module] of connectedModules.entries()) {
+ const answerDiv = document.getElementById(`answer-${name}`);
+ const scoreDiv = document.getElementById(`score-${name}`);
+
+ if (answerDiv) {
+ // Afficher la réponse du module
+ answerDiv.textContent = module.answer || '-';
+ answerDiv.classList.remove('module-answered');
- // Trouver et supprimer le module
- for (let [name, module] of connectedModules.entries()) {
- if (module.device === device) {
- connectedModules.delete(name);
- const card = document.getElementById(`module-${name}`);
- if (card) card.remove();
- break;
- }
+ if (module.answer === correctLetter) {
+ answerDiv.classList.add('correct');
+ module.totalScore += scoringConfig.correctAnswer;
+ } else {
+ answerDiv.classList.add('incorrect');
+ module.totalScore += scoringConfig.incorrectAnswer;
}
- updateStatus();
-
- // Afficher l'état vide si aucun module
- if (connectedModules.size === 0) {
- modulesContainer.innerHTML = `
-
-
📡
-
Cliquez sur "Connecter un module" pour ajouter un ESP32
-
- `;
+ // Mettre à jour l'affichage du score
+ if (scoreDiv) {
+ scoreDiv.textContent = module.totalScore;
}
- });
+ }
+ }
+
+ showResultsBtn.disabled = true;
+ }
+
+ function handleFileSelect(event) {
+ const file = event.target.files[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = function(e) {
+ const content = e.target.result;
+ parseQuizFile(content);
+ if (questions.length > 0) {
+ startQuiz();
+ }
+ };
+ reader.readAsText(file);
+ }
+ }
+
+ function parseQuizFile(content) {
+ questions = [];
+ const lines = content.trim().split('\n');
+ let currentQuestion = null;
+
+ for (let line of lines) {
+ line = line.trim();
+ if (!line) continue;
- } catch (error) {
- console.error('Erreur de connexion:', error);
- showNotification(`Erreur: ${error.message}`);
+ if (line.startsWith('* ')) {
+ if (currentQuestion) {
+ questions.push(currentQuestion);
+ }
+ currentQuestion = {
+ question: line.substring(2).trim(),
+ answers: [],
+ correctAnswer: -1
+ };
+ } else if (line.startsWith('+ ')) {
+ if (currentQuestion) {
+ currentQuestion.correctAnswer = currentQuestion.answers.length;
+ currentQuestion.answers.push(line.substring(2).trim());
+ }
+ } else if (line.startsWith('- ')) {
+ if (currentQuestion) {
+ currentQuestion.answers.push(line.substring(2).trim());
+ }
+ }
+ }
+
+ if (currentQuestion) {
+ questions.push(currentQuestion);
}
}
- // Fonction pour déconnecter un module
+ function startQuiz() {
+ quizContainer.classList.remove('hidden');
+ currentQuestionIndex = 0;
+
+ // Réinitialiser les scores
+ for (let [name, module] of connectedModules.entries()) {
+ module.totalScore = 0;
+ }
+
+ showQuestion();
+ }
+
+ function showQuestion() {
+ if (currentQuestionIndex >= questions.length) {
+ showFinalScore();
+ return;
+ }
+
+ resultsShown = false;
+ const question = questions[currentQuestionIndex];
+
+ // Afficher la question
+ document.querySelector('.question-number').textContent =
+ `Question ${currentQuestionIndex + 1}/${questions.length}`;
+ document.querySelector('.question-text').textContent = question.question;
+
+ // Afficher les réponses
+ const answersContainer = document.getElementById('answersContainer');
+ answersContainer.innerHTML = '';
+
+ const letters = ['A', 'B', 'C', 'D'];
+ question.answers.forEach((answer, index) => {
+ const answerDiv = document.createElement('div');
+ answerDiv.className = 'answer';
+
+ answerDiv.innerHTML = `
+
+
${letters[index]}
+
${answer}
+
+ `;
+
+ answersContainer.appendChild(answerDiv);
+ });
+
+ // Réinitialiser les réponses des modules
+ for (let [name, module] of connectedModules.entries()) {
+ module.answer = null;
+ const answerDiv = document.getElementById(`answer-${name}`);
+ if (answerDiv) {
+ answerDiv.textContent = '-';
+ answerDiv.className = 'module-answer-value module-waiting';
+ }
+ }
+
+ // Mettre à jour les contrôles
+ document.getElementById('progress').textContent =
+ `${currentQuestionIndex + 1} / ${questions.length}`;
+ prevBtn.disabled = currentQuestionIndex === 0;
+ nextBtn.textContent = currentQuestionIndex === questions.length - 1 ? 'Voir les scores' : 'Suivant →';
+ showResultsBtn.disabled = true;
+ }
+
+ function showPreviousQuestion() {
+ if (currentQuestionIndex > 0) {
+ currentQuestionIndex--;
+ showQuestion();
+ }
+ }
+
+ function showNextQuestion() {
+ if (currentQuestionIndex < questions.length - 1) {
+ currentQuestionIndex++;
+ showQuestion();
+ } else {
+ showFinalScore();
+ }
+ }
+
+ function showFinalScore() {
+ quizContainer.classList.add('hidden');
+ scoreContainer.classList.remove('hidden');
+
+ const scoresDetails = document.getElementById('scoresDetails');
+ let html = '';
+
+ for (let [name, module] of connectedModules.entries()) {
+ const percentage = Math.round((module.totalScore / questions.length) * 100);
+ html += `
+
+
+ ${name}
+
+
+ ${module.totalScore} / ${questions.length} (${percentage}%)
+
+
+ `;
+ }
+
+ if (html === '') {
+ html = '
Aucun module connecté
';
+ }
+
+ scoresDetails.innerHTML = html;
+ }
+
window.disconnectModule = function(moduleName) {
const module = connectedModules.get(moduleName);
if (module && module.device.gatt.connected) {
module.device.gatt.disconnect();
}
};
-
- // Événement du bouton de connexion
- connectBtn.addEventListener('click', connectModule);
-