diff --git a/index.html b/index.html
index b61a0eb..476d75d 100644
--- a/index.html
+++ b/index.html
@@ -373,11 +373,99 @@
grid-template-columns: 1fr;
}
}
+
+ /* Système de notifications */
+ .notification {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ min-width: 300px;
+ max-width: 500px;
+ padding: 20px 25px;
+ border-radius: 12px;
+ box-shadow: 0 10px 30px rgba(0,0,0,0.3);
+ color: white;
+ font-weight: 500;
+ font-size: 15px;
+ z-index: 10000;
+ animation: slideIn 0.3s ease-out;
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+ line-height: 1.5;
+ }
+
+ .notification.success {
+ background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
+ }
+
+ .notification.error {
+ background: linear-gradient(135deg, #f44336 0%, #da190b 100%);
+ }
+
+ .notification.info {
+ background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);
+ }
+
+ .notification-icon {
+ font-size: 24px;
+ flex-shrink: 0;
+ }
+
+ .notification-content {
+ flex: 1;
+ white-space: pre-line;
+ }
+
+ .notification-close {
+ background: rgba(255,255,255,0.2);
+ border: none;
+ color: white;
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ cursor: pointer;
+ font-size: 16px;
+ line-height: 1;
+ padding: 0;
+ flex-shrink: 0;
+ transition: background 0.2s;
+ }
+
+ .notification-close:hover {
+ background: rgba(255,255,255,0.3);
+ }
+
+ @keyframes slideIn {
+ from {
+ transform: translateX(400px);
+ opacity: 0;
+ }
+ to {
+ transform: translateX(0);
+ opacity: 1;
+ }
+ }
+
+ @keyframes slideOut {
+ from {
+ transform: translateX(0);
+ opacity: 1;
+ }
+ to {
+ transform: translateX(400px);
+ opacity: 0;
+ }
+ }
+
+ .notification.hiding {
+ animation: slideOut 0.3s ease-in forwards;
+ }
-
📝 Quiz Interactif ESP32
+
📝 Quiz Interactif
@@ -452,9 +540,36 @@
const correctScoreInput = document.getElementById('correctScore');
const incorrectScoreInput = document.getElementById('incorrectScore');
+ // Fonction pour afficher une notification
+ function showNotification(message, type = 'info', duration = 5000) {
+ const notification = document.createElement('div');
+ notification.className = `notification ${type}`;
+
+ const icons = {
+ success: 'âś…',
+ error: '❌',
+ info: 'ℹ️'
+ };
+
+ notification.innerHTML = `
+
${icons[type]}
+
${message}
+
+ `;
+
+ document.body.appendChild(notification);
+
+ if (duration > 0) {
+ setTimeout(() => {
+ notification.classList.add('hiding');
+ setTimeout(() => notification.remove(), 300);
+ }, duration);
+ }
+ }
+
// Vérifier si Web Bluetooth est disponible
if (!navigator.bluetooth) {
- alert('Web Bluetooth non supporté. Utilisez Chrome, Edge ou Opera.');
+ showNotification('Web Bluetooth non supporté. Utilisez Chrome, Edge ou Opera.', 'error', 0);
connectBtn.disabled = true;
}
@@ -481,13 +596,13 @@
async function connectModule() {
try {
- console.log('Recherche ESP32...');
+ console.log('Recherche module...');
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: [SERVICE_UUID] }]
});
- console.log('ESP32 trouvé:', device.name);
+ console.log('Module trouvé:', device.name);
const server = await device.gatt.connect();
const service = await server.getPrimaryService(SERVICE_UUID);
@@ -539,7 +654,7 @@
} catch (error) {
console.error('Erreur connexion:', error);
- alert(`Erreur: ${error.message}`);
+ showNotification(`Erreur de connexion: ${error.message}`, 'error');
}
}
@@ -676,9 +791,16 @@
const reader = new FileReader();
reader.onload = function(e) {
const content = e.target.result;
- parseQuizFile(content);
- if (questions.length > 0) {
- startQuiz();
+ try {
+ parseQuizFile(content);
+ if (questions.length > 0) {
+ showNotification(`Quiz chargé avec succès!\n\n${questions.length} question(s) trouvée(s).`, 'success', 3000);
+ startQuiz();
+ }
+ } catch (error) {
+ console.error('Erreur lors du chargement du quiz:', error);
+ questions = [];
+ // L'erreur a déjà été affichée dans parseQuizFile
}
};
reader.readAsText(file);
@@ -689,36 +811,116 @@
questions = [];
const lines = content.trim().split('\n');
let currentQuestion = null;
+ let lineNumber = 0;
+ const errors = [];
for (let line of lines) {
+ lineNumber++;
+ const originalLine = line;
line = line.trim();
if (!line) continue;
if (line.startsWith('* ')) {
+ // Valider la question précédente avant d'en commencer une nouvelle
if (currentQuestion) {
- questions.push(currentQuestion);
+ const questionErrors = validateQuestion(currentQuestion, lineNumber - 1);
+ if (questionErrors.length > 0) {
+ errors.push(...questionErrors);
+ } else {
+ questions.push(currentQuestion);
+ }
}
+
+ const questionText = line.substring(2).trim();
+ if (!questionText) {
+ errors.push(`Ligne ${lineNumber}: Question vide après '*'`);
+ }
+
currentQuestion = {
- question: line.substring(2).trim(),
+ question: questionText,
answers: [],
- correctAnswer: -1
+ correctAnswer: -1,
+ lineNumber: lineNumber
};
} else if (line.startsWith('+ ')) {
- if (currentQuestion) {
- currentQuestion.correctAnswer = currentQuestion.answers.length;
- currentQuestion.answers.push(line.substring(2).trim());
+ if (!currentQuestion) {
+ errors.push(`Ligne ${lineNumber}: Réponse correcte '+' trouvée sans question précédente`);
+ continue;
}
+
+ const answerText = line.substring(2).trim();
+ if (!answerText) {
+ errors.push(`Ligne ${lineNumber}: Réponse correcte vide après '+'`);
+ }
+
+ if (currentQuestion.correctAnswer !== -1) {
+ errors.push(`Ligne ${lineNumber}: Question à la ligne ${currentQuestion.lineNumber} a déjà une réponse correcte (ligne avec '+')`);
+ }
+
+ currentQuestion.correctAnswer = currentQuestion.answers.length;
+ currentQuestion.answers.push(answerText);
} else if (line.startsWith('- ')) {
- if (currentQuestion) {
- currentQuestion.answers.push(line.substring(2).trim());
+ if (!currentQuestion) {
+ errors.push(`Ligne ${lineNumber}: Réponse '-' trouvée sans question précédente`);
+ continue;
+ }
+
+ const answerText = line.substring(2).trim();
+ if (!answerText) {
+ errors.push(`Ligne ${lineNumber}: Réponse vide après '-'`);
}
+
+ currentQuestion.answers.push(answerText);
+ } else {
+ // Ligne avec un format non reconnu
+ errors.push(`Ligne ${lineNumber}: Format non reconnu. Utilisez '* ' pour une question, '+ ' pour la bonne réponse, '- ' pour les mauvaises réponses`);
}
}
+ // Valider la dernière question
if (currentQuestion) {
- questions.push(currentQuestion);
+ const questionErrors = validateQuestion(currentQuestion, lineNumber);
+ if (questionErrors.length > 0) {
+ errors.push(...questionErrors);
+ } else {
+ questions.push(currentQuestion);
+ }
+ }
+
+ // Vérifier s'il y a des erreurs
+ if (errors.length > 0) {
+ const errorMessage = `Erreurs détectées dans le fichier de quiz:\n\n${errors.join('\n')}`;
+ showNotification(errorMessage, 'error', 10000);
+ throw new Error('Format de quiz invalide');
+ }
+
+ // Vérifier qu'il y a au moins une question
+ if (questions.length === 0) {
+ showNotification('Erreur: Aucune question valide trouvée dans le fichier.\n\nFormat attendu:\n* Question ?\n+ Bonne réponse\n- Mauvaise réponse\n- Mauvaise réponse', 'error', 8000);
+ throw new Error('Aucune question trouvée');
}
}
+
+ function validateQuestion(question, lineNumber) {
+ const errors = [];
+
+ // Vérifier qu'il y a au moins 2 réponses
+ if (question.answers.length < 2) {
+ errors.push(`Ligne ${question.lineNumber}: Question "${question.question}" n'a que ${question.answers.length} réponse(s). Minimum requis: 2`);
+ }
+
+ // Vérifier qu'il y a maximum 4 réponses (A, B, C, D)
+ if (question.answers.length > 4) {
+ errors.push(`Ligne ${question.lineNumber}: Question "${question.question}" a ${question.answers.length} réponses. Maximum autorisé: 4 (A, B, C, D)`);
+ }
+
+ // Vérifier qu'il y a une réponse correcte
+ if (question.correctAnswer === -1) {
+ errors.push(`Ligne ${question.lineNumber}: Question "${question.question}" n'a pas de réponse correcte (utilisez '+' devant la bonne réponse)`);
+ }
+
+ return errors;
+ }
function startQuiz() {
quizContainer.classList.remove('hidden');
diff --git a/platformio.ini b/platformio.ini
index 8dd1b2d..af6ef57 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -1,15 +1,19 @@
-; PlatformIO Project Configuration File
-;
-; Build options: build flags, source filter
-; Upload options: custom upload port, speed and extra flags
-; Library options: dependencies, extra library storages
-; Advanced options: extra scripting
-;
-; Please visit documentation for the other options and examples
-; https://docs.platformio.org/page/projectconf.html
-
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
-monitor_speed = 115200
\ No newline at end of file
+monitor_speed = 115200
+
+[env:esp32-s3-supermini]
+platform = espressif32
+board = esp32-s3-devkitc-1
+framework = arduino
+monitor_speed = 115200
+board_build.arduino.memory_type = qio_qspi
+board_build.flash_mode = qio
+board_build.psram_type = qio
+board_upload.flash_size = 4MB
+board_upload.maximum_size = 4194304
+board_build.partitions = default.csv
+board_build.extra_flags =
+ -DBOARD_HAS_PSRAM
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 4ff425b..208a127 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -22,7 +22,7 @@ bool oldDeviceConnected = false;
uint8_t gpioStates = 0; // Stocke l'état des GPIOs (bit à bit)
// Nom du module (peut être modifié pour chaque ESP32)
-String moduleName = "ESP32-Module-2";
+String moduleName = "ESP32S3-Module-1";
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {