#include #include #include #include #include #include #include #include // Configuration LED WS2812 #define LED_PIN 48 #define LED_COUNT 1 Adafruit_NeoPixel led(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); // Couleurs #define COLOR_OFF led.Color(0, 0, 0) #define COLOR_WHITE led.Color(255, 255, 255) #define COLOR_RED led.Color(255, 0, 0) #define COLOR_GREEN led.Color(0, 255, 0) #define COLOR_BLUE led.Color(0, 0, 255) #define COLOR_YELLOW led.Color(255, 255, 0) // UUID pour le service et la caractéristique BLE #define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" #define COMMAND_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a9" // Configuration des GPIO à surveiller // GPIO compatibles ESP32 Wrover ET ESP32-C3 : 2, 4, 5, 6 const int GPIO_PINS[] = {2, 4, 5, 6}; // GPIOs à surveiller (A, B, C, D) const int NUM_PINS = sizeof(GPIO_PINS) / sizeof(GPIO_PINS[0]); // GPIO pour l'identification du module (8, 9, 10) const int ID_GPIO_PINS[] = {8, 9, 10}; // Bit 0, Bit 1, Bit 2 const int NUM_ID_PINS = sizeof(ID_GPIO_PINS) / sizeof(ID_GPIO_PINS[0]); // Variables globales BLEServer* pServer = NULL; BLECharacteristic* pCharacteristic = NULL; BLECharacteristic* pCommandCharacteristic = NULL; bool deviceConnected = false; bool oldDeviceConnected = false; uint8_t gpioStates = 0; // Stocke l'état des GPIOs (bit à bit) unsigned long lastBlinkTime = 0; bool blinkState = false; bool gpioEnabled = false; // false = GPIO désactivés (résultats ou scores), true = GPIO actifs // Nom du module (sera déterminé automatiquement) String moduleName = "BleQuiz-1"; // Callback pour la caractéristique de commande class CommandCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic* pCharacteristic) { std::string value = pCharacteristic->getValue(); if (value.length() > 0) { String command = String(value.c_str()); Serial.println("Commande reçue: " + command); if (command == "RESET") { // Garder la LED dans sa couleur actuelle, désactiver les GPIO gpioEnabled = false; Serial.println("Résultats affichés - LED figée, GPIO désactivés"); } else if (command == "START") { // Passer en blanc fixe, activer les GPIO gpioEnabled = true; led.setPixelColor(0, COLOR_WHITE); led.show(); Serial.println("Question démarrée - Blanc fixe, GPIO actifs"); } else if (command == "SCORES") { // Passer en blanc fixe, désactiver les GPIO gpioEnabled = false; led.setPixelColor(0, COLOR_WHITE); led.show(); Serial.println("Scores affichés - Blanc fixe, GPIO désactivés"); } } } }; class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; gpioEnabled = true; led.setPixelColor(0, COLOR_WHITE); led.show(); Serial.println("Client connecté - Blanc fixe"); }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; gpioEnabled = false; Serial.println("Client déconnecté - Mode clignotant"); } }; void setup() { Serial.begin(115200); Serial.println("Démarrage du module ESP32 Bluetooth..."); // Initialiser la LED WS2812 (éteinte au démarrage) led.begin(); led.setPixelColor(0, COLOR_OFF); led.show(); // Configuration des GPIO d'identification (8, 9, 10) en entrée avec pull-up for (int i = 0; i < NUM_ID_PINS; i++) { pinMode(ID_GPIO_PINS[i], INPUT_PULLUP); } // Lire l'ID du module (GPIO 8=bit0, 9=bit1, 10=bit2) uint8_t moduleId = 0; for (int i = 0; i < NUM_ID_PINS; i++) { if (digitalRead(ID_GPIO_PINS[i]) == LOW) { moduleId |= (1 << i); } } moduleId += 1; // ID de 1 à 8 (000=1, 001=2, ..., 111=8) moduleName = "BleQuiz-" + String(moduleId); Serial.print("ID du module détecté: "); Serial.println(moduleId); Serial.print("Nom du module: "); Serial.println(moduleName); // Configuration des GPIO en entrée avec pull-up for (int i = 0; i < NUM_PINS; i++) { pinMode(GPIO_PINS[i], INPUT_PULLUP); } // Initialisation BLE BLEDevice::init(moduleName.c_str()); // --- Configuration sécurité BLE pour compatibilité Android/iOS --- // Mode avec bonding pour mémoriser l'appairage BLESecurity *pSecurity = new BLESecurity(); pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND); // Appairage sécurisé avec bonding pSecurity->setCapability(ESP_IO_CAP_OUT); // Affichage d'un code (via Serial) pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); // Activer le static passkey (code fixe : 123456) uint32_t passkey = 123456; esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t)); Serial.println("Code d'appairage : 123456"); // Créer le serveur BLE pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); // Créer le service BLE BLEService *pService = pServer->createService(SERVICE_UUID); // Créer la caractéristique BLE pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_INDICATE ); // Créer un descripteur BLE2902 pour les notifications pCharacteristic->addDescriptor(new BLE2902()); // Créer la caractéristique de commande (pour RESET) pCommandCharacteristic = pService->createCharacteristic( COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE ); pCommandCharacteristic->setCallbacks(new CommandCallbacks()); // Démarrer le service pService->start(); // Démarrer l'advertising BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->setScanResponse(true); // Activer scan response pour iOS pAdvertising->setMinPreferred(0x06); // iOS recommande 20ms minimum pAdvertising->setMaxPreferred(0x12); // iOS recommande 40ms maximum BLEDevice::startAdvertising(); Serial.println("Advertising BLE démarré"); // Initialiser l'état des GPIO for (int i = 0; i < NUM_PINS; i++) { int pinState = digitalRead(GPIO_PINS[i]); if (pinState == LOW) { gpioStates |= (1 << i); } } Serial.println("En attente de connexion..."); } void loop() { // Gestion du clignotement blanc uniquement si déconnecté if (!deviceConnected) { unsigned long currentTime = millis(); if (currentTime - lastBlinkTime >= 500) { lastBlinkTime = currentTime; blinkState = !blinkState; if (blinkState) { led.setPixelColor(0, COLOR_WHITE); } else { led.setPixelColor(0, COLOR_OFF); } led.show(); } } // Lire l'état de toutes les GPIO uint8_t currentStates = 0; for (int i = 0; i < NUM_PINS; i++) { int pinState = digitalRead(GPIO_PINS[i]); if (pinState == LOW) { // GPIO à l'état bas currentStates |= (1 << i); } } // Détecter les changements d'état des GPIO (passage à LOW) // Uniquement si connecté ET GPIO activés uint8_t newPresses = currentStates & ~gpioStates; if (newPresses != 0 && deviceConnected && gpioEnabled) { // Allumer la LED selon le GPIO pressé if (newPresses & (1 << 0)) { // GPIO 2 led.setPixelColor(0, COLOR_RED); led.show(); Serial.println("GPIO 2 pressé - LED ROUGE"); } else if (newPresses & (1 << 1)) { // GPIO 4 led.setPixelColor(0, COLOR_GREEN); led.show(); Serial.println("GPIO 4 pressé - LED VERTE"); } else if (newPresses & (1 << 2)) { // GPIO 5 led.setPixelColor(0, COLOR_BLUE); led.show(); Serial.println("GPIO 5 pressé - LED BLEUE"); } else if (newPresses & (1 << 3)) { // GPIO 6 led.setPixelColor(0, COLOR_YELLOW); led.show(); Serial.println("GPIO 6 pressé - LED JAUNE"); } } // Créer un message JSON avec l'état des GPIO String message = "{\"module\":\"" + moduleName + "\",\"gpios\":["; for (int i = 0; i < NUM_PINS; i++) { message += "{\"pin\":" + String(GPIO_PINS[i]) + ",\"state\":"; message += ((currentStates & (1 << i)) ? "1" : "0"); message += "}"; if (i < NUM_PINS - 1) message += ","; } message += "]}"; // Mettre à jour la valeur de la caractéristique pCharacteristic->setValue(message.c_str()); // Si l'état a changé et qu'un client est connecté, envoyer notification if (currentStates != gpioStates && deviceConnected) { gpioStates = currentStates; pCharacteristic->notify(); Serial.println("État changé: " + message); } // Gestion de la reconnexion if (!deviceConnected && oldDeviceConnected) { delay(500); pServer->startAdvertising(); Serial.println("Redémarrage de l'advertising"); oldDeviceConnected = deviceConnected; } if (deviceConnected && !oldDeviceConnected) { oldDeviceConnected = deviceConnected; } delay(100); // Délai pour éviter une lecture trop fréquente }