Aller au contenu
Mission 1Objectif 1 · Planète Endor-Pad

Le réveil de la manette

Chevalier du câble DupontApprenti Sith du câble Dupont ★★ 🎮 Utilise la manette, Luc… euh, Nathan.🎮 Désormais, c'est une machine qui obéit à Nathan. Le haut commandement approuve.

📡 Transmission de l'Alliance · R2-DuPont

« Cette fois, tes mains entrent réellement dans la boucle : axes, boutons, commandes. Un conseil : quand tu pousses la manette à droite et que tout part à gauche, ce n'est pas la Force. C'est probablement une valeur inversée. »

🔊 Écouter le briefing de R2-DuPont

🛰️ Haut commandement · Opération Contre-Circuit

« L'Alliance veut empêcher toute prise de contrôle direct du système. Tu vas faire exactement l'inverse. Active l'interface. Teste les axes. Teste les boutons. Prends les commandes. À partir de maintenant, c'est une machine qui obéit à Nathan. Le haut commandement considère cela comme une amélioration. »

🔊 Écouter le briefing de R2-DuPont

Objectif : coupler la manette PS4 à l'ESP32 en Bluetooth, puis tester toutes ses commandes une par une : 4 boutons de face, 4 flèches, 4 gâchettes, 2 joysticks (et leurs clics), SHARE, OPTIONS et PS. Chacune fait réagir quelque chose : la LED bleue de la carte, la barre lumineuse de la manette, ses vibrations et le moniteur série. Toujours aucun câblage !

🧭 Notions de la mission : Le PWM · Anatomie d'un programme · Tableaux, boucles, fonctions · Le temps

Équipement de mission

🧰 Matériel

  • ESP32 (sur son support ou non)
  • Câble USB-C de données
  • Manette PS4 chargée, et son câble de recharge (micro-USB)

💻 Logiciels

  • Arduino IDE 2 (mission 0)
  • Cartes « ESP32 + Bluepad32 » de Ricardo Quesada

🌐 Connexion internet

Oui : environ 200 Mo pour Bluepad32

🔓 Missions à réussir avant

Mission 0 réussie

🛠️ Outils

Aucun

⏱️ Durée

45 min à 1 h

Comment ça marche ?

L'ESP32 a le Bluetooth intégré, comme une console. Un logiciel gratuit, Bluepad32, lui apprend à parler aux manettes de jeu : la manette se connecte directement à la carte, sans téléphone ni ordinateur au milieu. Plusieurs fois par seconde, elle envoie l'état de tous ses boutons et joysticks, et notre programme décide quoi en faire.

Pourquoi notre carte convient

La manette PS4 utilise le Bluetooth « classique ». Notre ESP32 (ESP-WROOM-32) le gère. Ce n'est pas le cas de toutes les cartes : les ESP32-S3 et ESP32-C3, par exemple, ne font que le Bluetooth basse consommation et ne savent pas parler à une manette PS4.

Étape 1 · Préparer la manette

  1. Charger la manette avec son câble, branché sur un chargeur ou un ordinateur. Pendant la charge, la barre lumineuse pulse doucement en orange.
  2. Éloigner la PS4 (ou la débrancher) si une console est dans la maison : sinon, la manette risque de se reconnecter à elle au lieu de l'ESP32.

Étape 2 · Ajouter Bluepad32 à l'IDE

On ajoute une deuxième adresse dans les préférences, à côté de celle d'Espressif installée à la mission 0.

  1. Ouvrir Fichier → Préférences.
  2. À droite du champ « URL de gestionnaire de cartes supplémentaires », cliquer sur la petite icône de fenêtre : une liste s'ouvre, avec une adresse par ligne.
  3. Garder la ligne d'Espressif et ajouter, sur une nouvelle ligne, l'adresse officielle de Bluepad32 :

    https://raw.githubusercontent.com/ricardoquesada/esp32-arduino-lib-builder/master/bluepad32_files/package_esp32_bluepad32_index.json
    
  4. Valider avec OK, puis encore OK.

Étape 3 · Installer les cartes « ESP32 + Bluepad32 »

  1. Ouvrir le gestionnaire de cartes : Outils → Carte → Gestionnaire de carte…
  2. Chercher bluepad32.
  3. Installer « esp32_bluepad32 » par Ricardo Quesada (l'auteur de Bluepad32).

C'est un gros téléchargement (environ 200 Mo) : Bluepad32 arrive avec sa propre copie des outils ESP32. Pas d'inquiétude, les cartes « esp32 » de la mission 0 restent installées à côté.

Étape 4 · Choisir la bonne carte

Dans le menu Outils :

Réglage Choix Remarque
Carte ESP32 + Bluepad32 Arduino → ESP32 Dev Module Le même modèle qu'à la mission 0, mais dans la famille Bluepad32
Port COM3 (Windows) ou /dev/ttyUSB0 (Linux) Comme à la mission 0
Tous les autres Laisser par défaut

Le piège de la mission 1

Si tu restes sur esp32 → ESP32 Dev Module (la carte de la mission 0), la vérification échoue avec :

fatal error: Bluepad32.h: No such file or directory

Bluepad32 n'existe que dans la famille « ESP32 + Bluepad32 Arduino ». À partir d'ici, toutes les missions se téléversent avec cette carte.

Étape 5 · Premier contact avec la manette

On commence par l'exemple fourni avec Bluepad32, pour vérifier que tout fonctionne avant d'écrire notre propre programme.

  1. Ouvrir Fichier → Exemples → Bluepad32_ESP32 → Controller (dans la partie « Exemples pour ESP32 Dev Module », plus bas dans la liste).
  2. Téléverser (la flèche →). Si le téléversement n'arrive pas à se connecter, maintenir le bouton BOOT de la carte au moment où « Connecting… » s'affiche, comme à la mission 0.
  3. Ouvrir le moniteur série (la loupe en haut à droite) et régler la vitesse sur 115200 bauds.
  4. Mettre la manette en mode couplage : maintenir SHARE puis PS enfoncés ensemble pendant environ 3 secondes, jusqu'à ce que la barre lumineuse clignote très vite (deux flashs rapprochés, qui se répètent).
  5. Au bout de quelques secondes, la barre lumineuse devient fixe et le moniteur série annonce que la manette est connectée.
  6. Appuyer sur des boutons, bouger les joysticks : des lignes de nombres défilent.

Lire ce que dit la manette

Chaque ligne du moniteur série est une « photo » de la manette. Voici comment la déchiffrer :

Dans la ligne Ce que c'est Valeurs
dpad les 4 flèches 0 si rien n'est appuyé ; chaque flèche allume un « bit » : ↑ = 1, ↓ = 2, → = 4, ← = 8
buttons ✕ ○ □ △, L1, R1, L2, R2, L3, R3 un nombre qui change selon les boutons enfoncés
axis L joystick gauche (X puis Y) environ −508 à +512, 0 au centre
axis R joystick droit (X puis Y) pareil
brake gâchette L2, enfoncée à moitié ou à fond 0 à 1020 : plus on appuie, plus c'est grand
throttle gâchette R2 0 à 1020
misc PS, SHARE, OPTIONS un nombre qui change selon le bouton

Le Y est à l'envers

Pour les joysticks, pousser vers le haut donne un Y négatif, vers le bas un Y positif. C'est une habitude héritée des écrans d'ordinateur, où l'on compte les lignes de haut en bas.

Les gâchettes sont des boutons et des curseurs

L2 et R2 existent deux fois : comme boutons (appuyé ou non), et comme curseurs brake / throttle qui mesurent jusqu'où on appuie. C'est ce qui permettra plus tard d'accélérer doucement ou à fond.

Étape 6 · Le banc d'essai Endor-Pad

Place à notre programme. Il transforme la manette en tableau de bord : chaque commande a un effet, et une check-list compte les commandes testées. Quand les 19 sont validées… surprise.

Qui fait quoi

Commande Effet Barre lumineuse
✕ LED bleue : clignotement lent bleue
○ LED bleue : clignotement rapide rouge
△ LED bleue : SOS en morse verte
□ LED bleue : « respiration » (s'allume et s'éteint doucement) rose
↑ / ↓ le scénario accélère / ralentit (de 25 % à 400 %)
← / → scénario précédent / suivant
L1 mode pilote : la LED bleue brille selon le joystick gauche poussé vers le haut jaune
R1 extinction de la LED presque éteinte
L2 fait vibrer le gros moteur, plus ou moins fort selon l'appui
R2 fait vibrer le petit moteur, plus ou moins fort
Joystick droit la barre lumineuse prend la couleur de l'arc-en-ciel qui correspond à sa direction 🌈
Joystick gauche pilote la LED en mode pilote (L1)
L3 / R3 (clic sur un joystick) affiche les valeurs exactes de ce joystick
SHARE active / coupe le radar : les valeurs des joysticks et gâchettes défilent 4 fois par seconde
OPTIONS affiche le bilan : ce qui reste à tester, le scénario, la batterie
PS petite vibration « bzzz »

Pour valider un joystick, il faut le pousser au bout dans ses 4 directions (↑ ↓ ← →).

Le code

  1. Fichier → Nouveau croquis, effacer tout ce qui est écrit.
  2. Déplier le programme ci-dessous et le copier (bouton de copie en haut à droite du bloc), ou télécharger le fichier.
  3. Enregistrer : Fichier → Enregistrer sous… → mission1_endor_pad.
Le programme · cliquer pour l'afficher
// Mission 1 · Endor-Pad : le banc d'essai de la manette
// Chaque bouton, flèche, gâchette et joystick fait réagir quelque chose :
// la LED bleue de l'ESP32, la barre lumineuse de la manette, ses vibrations
// et le moniteur série. Quand les 19 commandes sont validées : célébration !

#include <Bluepad32.h>

// ================= Réglages =================
const int LED = 2;             // LED bleue intégrée (GPIO 2)
const int ZONE_MORTE = 100;    // joystick en dessous de 100 : considéré au repos
const int SEUIL_DIRECTION = 400; // joystick poussé presque au bout (max ~510)

// ================= La manette =================
ControllerPtr manette = nullptr;

// ================= Les scénarios de la LED bleue =================
// 0 éteint, 1 lent, 2 rapide, 3 SOS, 4 respiration, 5 pilote (au joystick)
const int NB_SCENARIOS = 6;
const char* NOMS_SCENARIOS[NB_SCENARIOS] = {
  "éteint", "clignotement lent", "clignotement rapide", "SOS", "respiration", "pilote"
};
int scenario = 0;
int vitesse = 100;             // en % : 100 = normal, 200 = deux fois plus vite
unsigned long horloge = 0;     // horloge des scénarios (avance selon la vitesse)
unsigned long dernierTemps = 0;

// ================= La check-list des commandes =================
enum {
  T_CROIX, T_ROND, T_TRIANGLE, T_CARRE,
  T_HAUT, T_BAS, T_GAUCHE, T_DROITE,
  T_L1, T_R1, T_L2, T_R2, T_L3, T_R3,
  T_SHARE, T_OPTIONS, T_PS,
  T_STICK_G, T_STICK_D,
  NB_TESTS
};
const char* NOMS_TESTS[NB_TESTS] = {
  "✕", "○", "△", "□",
  "flèche ↑", "flèche ↓", "flèche ←", "flèche →",
  "L1", "R1", "L2", "R2", "L3 (clic joystick gauche)", "R3 (clic joystick droit)",
  "SHARE", "OPTIONS", "PS",
  "joystick gauche (4 directions)", "joystick droit (4 directions)"
};
bool teste[NB_TESTS];
int nbTestes = 0;

// Directions atteintes par chaque joystick : 1 haut, 2 bas, 4 gauche, 8 droite
int directionsG = 0;
int directionsD = 0;

// ================= Barre lumineuse et vibrations =================
uint8_t couleurR = 0, couleurG = 0, couleurB = 255;  // couleur choisie aux boutons
uint8_t envoyeR = 1, envoyeG = 1, envoyeB = 1;       // dernière couleur envoyée
unsigned long dernierEnvoiCouleur = 0;
unsigned long dernierEnvoiVibration = 0;
unsigned long finCelebration = 0;

// Radar (SHARE) : affiche en continu les valeurs des joysticks et des gâchettes
bool radar = false;
unsigned long dernierRadar = 0;

// ================= Mémoire des boutons (pour détecter l'appui) =================
bool avant[NB_TESTS];

// Renvoie true UNIQUEMENT au moment où le bouton vient d'être enfoncé.
bool vientDAppuyer(int numero, bool appuye) {
  bool nouveau = appuye && !avant[numero];
  avant[numero] = appuye;
  return nouveau;
}

// ------------------------------------------------------------------
void celebrer() {
  Serial.println("\n🎉🎉🎉 MANETTE 100 % OPÉRATIONNELLE ! 🎉🎉🎉");
  Serial.println("Mission 1 réussie. Que la Force soit avec toi, Nathan !");
  finCelebration = millis() + 4000;             // 4 secondes d'arc-en-ciel
  manette->playDualRumble(0, 800, 255, 255);    // grosse vibration
}

void valider(int numero) {
  if (teste[numero]) return;                    // déjà validé : rien à faire
  teste[numero] = true;
  nbTestes++;
  Serial.printf("   ✔ Nouveau : %s  (%d/%d)\n", NOMS_TESTS[numero], nbTestes, NB_TESTS);
  if (nbTestes == NB_TESTS) celebrer();
}

void afficherBilan() {
  Serial.printf("\n📋 Bilan : %d/%d commandes validées\n", nbTestes, NB_TESTS);
  for (int i = 0; i < NB_TESTS; i++) {
    if (!teste[i]) Serial.printf("   ☐ reste à tester : %s\n", NOMS_TESTS[i]);
  }
  Serial.printf("   Scénario : %s, vitesse %d %%, batterie %d %%\n",
                NOMS_SCENARIOS[scenario], vitesse, manette->battery() * 100 / 255);
}

void choisirScenario(int s, uint8_t r, uint8_t g, uint8_t b) {
  scenario = s;
  couleurR = r; couleurG = g; couleurB = b;
  Serial.printf("   → LED : %s\n", NOMS_SCENARIOS[scenario]);
}

// Couleur de l'arc-en-ciel : angle de 0 à 359 degrés → rouge, vert, bleu
void arcEnCiel(int angle, uint8_t &r, uint8_t &g, uint8_t &b) {
  angle = (angle % 360 + 360) % 360;
  int zone = angle / 60;
  int x = (angle % 60) * 255 / 60;              // monte de 0 à 255 dans la zone
  switch (zone) {
    case 0: r = 255;     g = x;       b = 0;       break;
    case 1: r = 255 - x; g = 255;     b = 0;       break;
    case 2: r = 0;       g = 255;     b = x;       break;
    case 3: r = 0;       g = 255 - x; b = 255;     break;
    case 4: r = x;       g = 0;       b = 255;     break;
    default: r = 255;    g = 0;       b = 255 - x; break;
  }
}

// ================= Connexion / déconnexion =================
void onConnectedController(ControllerPtr ctl) {
  if (manette != nullptr) {
    Serial.println("Une manette est déjà connectée : la nouvelle est ignorée.");
    return;
  }
  manette = ctl;
  Serial.printf("\n🎮 Manette connectée : %s\n", ctl->getModelName().c_str());
  Serial.println("Teste chaque bouton, flèche, gâchette et joystick.");
  Serial.println("OPTIONS = bilan, SHARE = radar des joysticks.");
  envoyeR = envoyeG = envoyeB = 1;              // force le renvoi de la couleur
}

void onDisconnectedController(ControllerPtr ctl) {
  if (manette == ctl) {
    manette = nullptr;
    Serial.println("\n❌ Manette déconnectée");
  }
}

// ================= Lecture des boutons =================
void lireBoutons() {
  // --- Les 4 boutons de face : un scénario + une couleur chacun ---
  if (vientDAppuyer(T_CROIX, manette->a())) {
    Serial.println("Bouton ✕"); valider(T_CROIX);
    choisirScenario(1, 0, 0, 255);              // bleu
  }
  if (vientDAppuyer(T_ROND, manette->b())) {
    Serial.println("Bouton ○"); valider(T_ROND);
    choisirScenario(2, 255, 0, 0);              // rouge
  }
  if (vientDAppuyer(T_TRIANGLE, manette->y())) {
    Serial.println("Bouton △"); valider(T_TRIANGLE);
    choisirScenario(3, 0, 255, 0);              // vert
  }
  if (vientDAppuyer(T_CARRE, manette->x())) {
    Serial.println("Bouton □"); valider(T_CARRE);
    choisirScenario(4, 255, 0, 160);            // rose
  }

  // --- Les flèches : ↑ ↓ changent la vitesse, ← → changent de scénario ---
  uint8_t f = manette->dpad();
  if (vientDAppuyer(T_HAUT, f & DPAD_UP)) {
    vitesse = min(vitesse + 25, 400);
    Serial.printf("Flèche ↑ : vitesse %d %%\n", vitesse); valider(T_HAUT);
  }
  if (vientDAppuyer(T_BAS, f & DPAD_DOWN)) {
    vitesse = max(vitesse - 25, 25);
    Serial.printf("Flèche ↓ : vitesse %d %%\n", vitesse); valider(T_BAS);
  }
  if (vientDAppuyer(T_GAUCHE, f & DPAD_LEFT)) {
    Serial.println("Flèche ←"); valider(T_GAUCHE);
    choisirScenario((scenario + NB_SCENARIOS - 1) % NB_SCENARIOS, couleurR, couleurG, couleurB);
  }
  if (vientDAppuyer(T_DROITE, f & DPAD_RIGHT)) {
    Serial.println("Flèche →"); valider(T_DROITE);
    choisirScenario((scenario + 1) % NB_SCENARIOS, couleurR, couleurG, couleurB);
  }

  // --- Les boutons de tranche ---
  if (vientDAppuyer(T_L1, manette->l1())) {
    Serial.println("L1 : mode pilote, la LED suit le joystick gauche"); valider(T_L1);
    choisirScenario(5, 255, 160, 0);            // jaune
  }
  if (vientDAppuyer(T_R1, manette->r1())) {
    Serial.println("R1 : extinction"); valider(T_R1);
    choisirScenario(0, 20, 20, 20);             // presque éteinte
  }
  if (vientDAppuyer(T_L2, manette->l2())) {
    Serial.println("L2 : appuie plus ou moins fort… (gros moteur)"); valider(T_L2);
  }
  if (vientDAppuyer(T_R2, manette->r2())) {
    Serial.println("R2 : appuie plus ou moins fort… (petit moteur)"); valider(T_R2);
  }

  // --- Clic sur les joysticks : affiche leurs valeurs exactes ---
  if (vientDAppuyer(T_L3, manette->thumbL())) {
    Serial.printf("L3 : joystick gauche X=%d Y=%d\n", manette->axisX(), manette->axisY());
    valider(T_L3);
  }
  if (vientDAppuyer(T_R3, manette->thumbR())) {
    Serial.printf("R3 : joystick droit X=%d Y=%d\n", manette->axisRX(), manette->axisRY());
    valider(T_R3);
  }

  // --- Les boutons du milieu ---
  if (vientDAppuyer(T_OPTIONS, manette->miscStart())) {
    Serial.println("OPTIONS"); valider(T_OPTIONS);
    afficherBilan();
  }
  if (vientDAppuyer(T_SHARE, manette->miscSelect())) {
    radar = !radar;
    Serial.printf("SHARE : radar %s\n", radar ? "activé" : "désactivé"); valider(T_SHARE);
  }
  if (vientDAppuyer(T_PS, manette->miscSystem())) {
    Serial.println("PS : bzzz !"); valider(T_PS);
    manette->playDualRumble(0, 300, 200, 0);    // petite vibration
  }
}

// ================= Lecture des joysticks =================
// Note : pour Y, vers le HAUT = négatif, vers le BAS = positif.
int directionsAtteintes(int x, int y) {
  int d = 0;
  if (y < -SEUIL_DIRECTION) d |= 1;   // haut
  if (y >  SEUIL_DIRECTION) d |= 2;   // bas
  if (x < -SEUIL_DIRECTION) d |= 4;   // gauche
  if (x >  SEUIL_DIRECTION) d |= 8;   // droite
  return d;
}

void suivreJoystick(int x, int y, int &directions, int numero, const char* nom) {
  int nouvelles = directionsAtteintes(x, y) & ~directions;
  if (nouvelles == 0) return;
  directions |= nouvelles;
  Serial.printf("Joystick %s : %s%s%s%s  (%d/4 directions)\n", nom,
                (nouvelles & 1) ? "↑ " : "", (nouvelles & 2) ? "↓ " : "",
                (nouvelles & 4) ? "← " : "", (nouvelles & 8) ? "→ " : "",
                __builtin_popcount(directions));
  if (directions == 15) valider(numero);
}

void lireJoysticks() {
  suivreJoystick(manette->axisX(),  manette->axisY(),  directionsG, T_STICK_G, "gauche");
  suivreJoystick(manette->axisRX(), manette->axisRY(), directionsD, T_STICK_D, "droit");

  // Radar : 4 fois par seconde, toutes les valeurs d'un coup
  if (radar && millis() - dernierRadar > 250) {
    dernierRadar = millis();
    Serial.printf("📡 G X=%4d Y=%4d | D X=%4d Y=%4d | L2=%4d R2=%4d\n",
                  manette->axisX(), manette->axisY(), manette->axisRX(), manette->axisRY(),
                  manette->brake(), manette->throttle());
  }
}

// ================= Barre lumineuse et vibrations =================
void piloterManette() {
  unsigned long maintenant = millis();

  // Couleur à afficher : arc-en-ciel (célébration), joystick droit, ou bouton
  uint8_t r = couleurR, g = couleurG, b = couleurB;
  int rx = manette->axisRX(), ry = manette->axisRY();
  if (maintenant < finCelebration) {
    arcEnCiel(maintenant / 4, r, g, b);                  // défile tout seul
  } else if (abs(rx) > 2 * ZONE_MORTE || abs(ry) > 2 * ZONE_MORTE) {
    int angle = atan2(-ry, rx) * 180 / PI;               // direction du joystick
    arcEnCiel(angle, r, g, b);                           // = une couleur
  }

  // On n'envoie la couleur que si elle a changé, et pas plus de 20 fois/s
  if ((r != envoyeR || g != envoyeG || b != envoyeB) && maintenant - dernierEnvoiCouleur > 50) {
    manette->setColorLED(r, g, b);
    envoyeR = r; envoyeG = g; envoyeB = b;
    dernierEnvoiCouleur = maintenant;
  }

  // Gâchettes : plus on appuie, plus ça vibre (L2 = gros moteur, R2 = petit)
  int fort = manette->brake() / 4;      // 0 à 255
  int doux = manette->throttle() / 4;   // 0 à 255
  if ((fort > 10 || doux > 10) && maintenant - dernierEnvoiVibration > 100) {
    manette->playDualRumble(0, 150, doux, fort);
    dernierEnvoiVibration = maintenant;
  }
}

// ================= La LED bleue =================
void jouerScenario() {
  unsigned long maintenant = millis();
  horloge += (maintenant - dernierTemps) * vitesse / 100;   // l'horloge avance plus ou moins vite
  dernierTemps = maintenant;
  unsigned long t = horloge;

  int lumiere = 0;
  if (maintenant < finCelebration) {
    lumiere = (maintenant / 60) % 2 ? 255 : 0;               // célébration : stroboscope
  } else {
    switch (scenario) {
      case 1: lumiere = (t / 800) % 2 ? 255 : 0; break;      // lent
      case 2: lumiere = (t / 120) % 2 ? 255 : 0; break;      // rapide
      case 3: {                                              // S O S : ...  ---  ...
        const char* sos = "1010100011101110111000101010000000";
        lumiere = sos[(t / 150) % strlen(sos)] == '1' ? 255 : 0;
        break;
      }
      case 4: {                                              // respiration
        int v = (t / 6) % 512;
        lumiere = v < 256 ? v : 511 - v;
        break;
      }
      case 5: {                                              // pilote : joystick gauche vers le haut
        int y = manette ? -manette->axisY() : 0;
        lumiere = y > ZONE_MORTE ? map(y, ZONE_MORTE, 508, 0, 255) : 0;
        lumiere = constrain(lumiere, 0, 255);
        break;
      }
    }
  }
  analogWrite(LED, lumiere);
}

// ================= Programme principal =================
void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  BP32.setup(&onConnectedController, &onDisconnectedController);
  // Oublie les manettes déjà couplées : il faudra refaire SHARE + PS à chaque démarrage.
  // Une fois que tout marche, mets cette ligne en commentaire (//) :
  // un simple appui sur PS suffira alors pour reconnecter la manette.
  BP32.forgetBluetoothKeys();
  Serial.println("Endor-Pad prêt. Maintiens SHARE + PS pour coupler la manette.");
}

void loop() {
  BP32.update();
  if (manette && manette->isConnected()) {
    lireBoutons();
    lireJoysticks();
    piloterManette();
  }
  jouerScenario();
  delay(10);
}

Comment il est construit

Le programme paraît long, mais il est fait de petits blocs qui ont chacun un seul travail :

Bloc Son travail
onConnectedController / onDisconnectedController Bluepad32 les appelle tout seul quand la manette arrive ou s'en va. On garde la manette dans la variable manette.
vientDAppuyer Répond « oui » une seule fois, au moment précis où un bouton s'enfonce. Sans elle, un appui d'une demi-seconde serait compté 50 fois !
lireBoutons Regarde chaque bouton et chaque flèche, et déclenche son effet.
lireJoysticks Note les directions atteintes par les joysticks, et gère le radar.
piloterManette Choisit la couleur de la barre lumineuse et fait vibrer la manette.
jouerScenario Allume la LED bleue selon le scénario choisi.
valider / afficherBilan Tiennent la check-list à jour, et lancent la célébration à 19/19.

Ce qu'on apprend ici

  • Les événements : on réagit au moment où un bouton s'enfonce, pas tant qu'il est enfoncé (vientDAppuyer).
  • Le temps sans bloquer : grâce à millis(), la LED clignote pendant que le programme continue de lire la manette. Avec delay(800), la manette serait ignorée pendant presque une seconde à chaque clignotement.
  • Les tableaux : teste[] et NOMS_TESTS[] rangent les 19 commandes dans des cases numérotées, qu'on parcourt avec une boucle for.
  • Ne pas saturer le Bluetooth : on n'envoie une nouvelle couleur que si elle a changé, et au plus 20 fois par seconde. La manette n'aime pas être bombardée d'ordres.

🎓 Académie : Le temps · Tableaux, boucles, fonctions · Le PWM

Étape 7 · Téléverser et jouer

  1. Vérifier (✓), puis téléverser (→).
  2. Ouvrir le moniteur série à 115200 bauds.
  3. Coupler la manette : SHARE + PS jusqu'au clignotement rapide.
  4. Tester les commandes une par une, en regardant le moniteur série.

Voici à quoi ressemble le début d'une partie :

Endor-Pad prêt. Maintiens SHARE + PS pour coupler la manette.

🎮 Manette connectée : DualShock 4
Teste chaque bouton, flèche, gâchette et joystick.
OPTIONS = bilan, SHARE = radar des joysticks.
Bouton ✕
   ✔ Nouveau : ✕  (1/19)
   → LED : clignotement lent
Flèche ↑ : vitesse 125 %
   ✔ Nouveau : flèche ↑  (2/19)
Joystick droit : ↑   (1/4 directions)
Joystick droit : →   (2/4 directions)

Le nom exact affiché après « Manette connectée » dépend de la manette.

Pourquoi faut-il recoupler la manette à chaque démarrage ?

La ligne BP32.forgetBluetoothKeys(); du setup() fait oublier à l'ESP32 les manettes déjà connues. C'est pratique pour les premiers essais, mais il faut refaire SHARE + PS à chaque redémarrage. Quand tout marche, mets cette ligne en commentaire (ajoute // devant) et téléverse à nouveau : un simple appui sur PS suffira alors pour reconnecter la manette.

Si ça coince

Symptôme Cause probable Solution
Bluepad32.h: No such file or directory Mauvaise carte choisie Outils → Carte → ESP32 + Bluepad32 Arduino → ESP32 Dev Module (étape 4)
La barre lumineuse ne clignote pas vite Mode couplage pas activé Maintenir SHARE d'abord, puis PS, pendant 3 secondes
La manette clignote puis s'éteint Batterie vide, ou couplage trop lent Recharger, puis recommencer le couplage
La manette s'allume mais va sur la PS4 La console est à portée Éteindre ou débrancher la PS4
Rien dans le moniteur série Mauvaise vitesse Régler 115200 bauds en bas du moniteur série
Des caractères bizarres à l'écran Mauvaise vitesse Même solution : 115200 bauds
« Failed to connect to ESP32 » au téléversement La carte n'est pas prête Maintenir BOOT pendant « Connecting… »
Un joystick n'arrive jamais à 4/4 Pas poussé assez loin Pousser tout au bout ; vérifier les valeurs avec le radar (SHARE)
Pas de vibrations ni de couleurs Manette compatible non officielle Certaines copies n'ont pas de moteurs ou de barre pilotable : les boutons marchent, c'est l'essentiel

Défis bonus

Les solutions sont rangées dans des holocrons scellés : essaie d'abord, l'indice est là si tu bloques, et le code d'accès du maître de l'atelier ouvre la solution.

Défi 1 · Ton prénom en morse

Ajoute un 7ᵉ scénario qui écrit NATHAN en morse sur la LED bleue : N = —·, A = ·—, T = —, H = ····.

Indice

Inspire-toi de la chaîne sos du scénario SOS, où 1 = allumé et 0 = éteint. En morse, un trait dure trois fois plus longtemps qu'un point, et on laisse un silence plus long entre deux lettres.

Défi 2 · La manette qui bat comme un cœur

🎓 Académie : Le temps

Quand on appuie sur PS, la manette vibre « boum-boum… boum-boum… boum-boum ».

Indice

Piège : un nouvel ordre playDualRumble annule celui en cours. Il faut donc envoyer chaque « boum » à son moment, en mesurant le temps avec millis()… et sans delay(), qui bloquerait la lecture de la manette.

Défi 3 · Tes propres couleurs

Change les couleurs de la barre lumineuse pour les boutons de face.

Indice

Chaque couleur est un mélange de rouge, vert et bleu de 0 à 255 : (255, 128, 0) donne de l'orange. Cherche choisirScenario dans lireBoutons().

Validation

  • Bluepad32 installé, la carte « ESP32 Dev Module » de la famille Bluepad32 est sélectionnée
  • L'exemple Controller affiche les valeurs de la manette
  • Le banc d'essai annonce « Manette connectée »
  • La barre lumineuse change de couleur (boutons de face et joystick droit)
  • Les gâchettes L2 et R2 font vibrer la manette, plus ou moins fort
  • 19/19 et la célébration arc-en-ciel 🎉