/********************************************************************************************
* Projet : "Show Wahou" ESP32 — effets + musique DFPlayer
* Objectif : Show déclenché par digicode/bouton, avec fumée, strobe, confettis, rideau,
* buzzer, pixels témoins latchés et lecture audio (DFPlayer → DAC_R → DI → XLR).
* Remarques :
* - Fond LED noir (lisible). Témoins latchés par effet (verts/orange/…).
* - Sécurité BYPASS_SAFETY=1 par défaut (ARM/E-STOP ignorés pour la mise au point).
* - Musique : DFPlayer sur UART2 (ESP32 RX2=GPIO16, TX2=GPIO17), fichier 001.mp3.
********************************************************************************************/
#include <Adafruit_NeoPixel.h>
#include <Keypad.h>
#include <DFRobotDFPlayerMini.h> // ← lecteur MP3/WAV microSD
/**************************************
* SECTION 1 — Options debug / sécurité
**************************************/
#define BYPASS_SAFETY 1 // 1 = ignore ARM/E-STOP (test rapide), 0 = activer sécurité
#define DIAG_SLOWMO 0 // 1 = ralentir visuellement (x8)
#if DIAG_SLOWMO
const float SLOW = 8.0;
#else
const float SLOW = 1.0;
#endif
/**********************************************
* SECTION 2 — Mapping matériel (brochage ESP32)
**********************************************/
#define LED_PIN 18
#define NUM_LEDS 16
#define LED_BRIGHTNESS 180
#define RELAY_FOG 23
#define RELAY_STROBE 19
#define RELAY_CONFETTI 5
#define RELAY_CURTAIN 2 // OK en simu ; en prod éviter (pin de boot)
#define BUZZER_PIN 4
#define GO_BTN_PIN 21
// Keypad 4x4 : R={32,33,25,14} / C={12,13,15,27} (⚠ D27 réservé au clavier)
const byte ROWS = 4, COLS = 4;
char keys[ROWS][COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[ROWS] = {32, 33, 25, 14};
byte colPins[COLS] = {12, 13, 15, 27};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
/************************************************
* SECTION 3 — Sécurité (ARM/E-STOP) [optionnelle]
************************************************/
#if BYPASS_SAFETY==0
#define ARM_PIN 26 // ON = LOW (vers GND)
#define ESTOP_PIN 16 // OK = LOW (vers GND) — surtout pas D27
inline bool estopTripped(){ return digitalRead(ESTOP_PIN) == HIGH; }
inline bool armOn(){ return digitalRead(ARM_PIN) == LOW; }
inline bool safetyOK(){ return armOn() && !estopTripped(); }
#else
inline bool safetyOK(){ return true; }
#endif
/***********************************************************
* SECTION 4 — Config effets (relais & comportements)
***********************************************************/
#define RELAY_ACTIVE_HIGH 1
inline void RELAY_WRITE(uint8_t pin, bool on){
digitalWrite(pin, RELAY_ACTIVE_HIGH ? (on?HIGH:LOW) : (on?LOW:HIGH));
}
#define STROBE_MODE 0 // 0 = alim continue (relais méca) ; 1 = clignote (SSR)
const float STROBE_HZ = 5.0; // utilisé si STROBE_MODE=1
#define FOG_TAIL_MS 0 // traîne fumée après P2 (0 = pas de traîne)
#define BEEP_ON_KEY 0 // 1 = bip à chaque touche
#define BEEP_ON_ERROR 1 // 1 = bips en cas d’erreur code
/*****************************************
* SECTION 5 — LED Ring & pixels témoins
*****************************************/
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
// Pixels témoins par effet (latched = restent allumés jusqu’au reset)
#define PIX_CONF 0 // confettis -> vert
#define PIX_LED 2 // “show démarré” -> cyan
#define PIX_FOG 4 // fumée -> bleu
#define PIX_STROBE 8 // strobe -> blanc
#define PIX_BUZZ 12 // buzzer -> magenta
#define PIX_CURT 15 // rideau/aimant -> orange
bool latchedConf=false, latchedLed=false, latchedFog=false, latchedStrobe=false, latchedBuzzer=false, latchedCurt=false;
/*******************************************
* SECTION 6 — FSM & timings du show
*******************************************/
enum ShowState { S_IDLE, S_LOCKOUT, S_SHOW_P1, S_SHOW_P2, S_SHOW_P3, S_SHOW_P4 };
ShowState state = S_IDLE;
const unsigned long LOCKOUT_MS = (unsigned long)(10000 * SLOW);
const unsigned long P1_MS = (unsigned long)(800 * SLOW); // rouge + bip
const unsigned long P2_MS = (unsigned long)(2000 * SLOW); // fumée + strobe
const unsigned long P3_MS = (unsigned long)(5000 * SLOW); // pause noire lisible
const unsigned long P4_MS = (unsigned long)(20000 * SLOW); // party pulses
// Pulses confettis / rideau
const unsigned long CONF_START_MS = (unsigned long)(500 * SLOW);
const unsigned long CURT_START_MS = (unsigned long)(1500 * SLOW);
const unsigned long CONF_PULSE_MS = (unsigned long)(200 * SLOW);
const unsigned long CURT_PULSE_MS = (unsigned long)(150 * SLOW);
// Code secret
const char* PASSCODE = "2500";
/*************************************
* SECTION 7 — Variables runtime
*************************************/
unsigned long t0 = 0;
int wrongCount = 0;
String buffer = "";
// Pulses non bloquants
bool confettiActive=false, confettiShot=false;
bool curtainActive=false, curtainDropped=false;
unsigned long confettiOnAt=0, curtainOnAt=0;
// Strobe SSR (mode 1)
unsigned long strobeLast = 0;
bool strobeRelayState = false;
// Traîne fumée
bool fogTailActive = false;
unsigned long fogTailStart = 0;
// Buzzer non bloquant
bool buzzOn=false;
unsigned long buzzStart=0, buzzDur=0;
/*****************************************
* SECTION 8 — MUSIQUE (DFPlayer Mini)
* - UART2 : RX2=GPIO16, TX2=GPIO17
* - Sortie audio : DFPlayer DAC_R → DI box → XLR → Enceinte
*****************************************/
HardwareSerial MP3Serial(2); // Port série 2 de l’ESP32
DFRobotDFPlayerMini dfplayer;
bool musicReady = false; // vrai si DFPlayer détecté
// Config musique ajustable
#define MUSIC_VOLUME 25 // 0..30 (commence ~15–20, monte doucement)
#define MUSIC_TRACK 1 // 001.mp3 (2 => 002.mp3, etc.)
bool Music_begin() {
MP3Serial.begin(9600, SERIAL_8N1, 16, 17); // RX=16, TX=17
delay(300);
if (!dfplayer.begin(MP3Serial)) {
musicReady = false; // si absent, on continue sans musique
return false;
}
dfplayer.volume(MUSIC_VOLUME);
dfplayer.EQ(DFPLAYER_EQ_NORMAL);
musicReady = true;
return true;
}
void Music_playShowTrack(uint16_t track = MUSIC_TRACK) { if(musicReady) dfplayer.play(track); }
void Music_stop() { if(musicReady) dfplayer.stop(); }
/************************************************
* SECTION 9 — Helpers visuels (WS2812)
************************************************/
inline void ledsFill(uint8_t r,uint8_t g,uint8_t b){
for(int i=0;i<NUM_LEDS;i++) strip.setPixelColor(i, strip.Color(r,g,b));
strip.show();
}
inline void blackout(){ ledsFill(0,0,0); }
void redBlinkTick(){ static bool on=false; on=!on; ledsFill(on?255:0,0,0); }
void strobeTick(){ static bool on=false; on=!on; ledsFill(on?255:0,on?255:0,on?255:0); }
void overlayApply() {
if (latchedConf) strip.setPixelColor(PIX_CONF, strip.Color(0,255,0));
if (latchedLed) strip.setPixelColor(PIX_LED, strip.Color(0,255,255));
if (latchedFog) strip.setPixelColor(PIX_FOG, strip.Color(0,120,255));
if (latchedStrobe) strip.setPixelColor(PIX_STROBE, strip.Color(255,255,255));
if (latchedBuzzer) strip.setPixelColor(PIX_BUZZ, strip.Color(255,0,180));
if (latchedCurt) strip.setPixelColor(PIX_CURT, strip.Color(255,140,0));
strip.show();
}
inline void Vis_Fill(uint8_t r,uint8_t g,uint8_t b){ ledsFill(r,g,b); overlayApply(); }
inline void Vis_Black(){ blackout(); overlayApply(); }
inline void Vis_RedBlink(){ redBlinkTick(); overlayApply(); }
inline void Vis_StrobeBlink(){ strobeTick(); overlayApply(); }
/************************************************
* SECTION 10 — Fonctions PAR effet (modulaires)
************************************************/
// ----- LED / marqueur "show démarré"
void LED_ShowMark(){ latchedLed = true; overlayApply(); }
// ----- BUZZER -----
void Buzzer_on(){ digitalWrite(BUZZER_PIN, HIGH); }
void Buzzer_off(){ digitalWrite(BUZZER_PIN, LOW); buzzOn=false; }
void Buzzer_start(unsigned ms){
Buzzer_on(); buzzOn=true; buzzStart=millis(); buzzDur=ms;
latchedBuzzer=true; overlayApply();
}
void Buzzer_update(unsigned long now){
if(buzzOn && now - buzzStart >= buzzDur) Buzzer_off();
}
// ----- FUMÉE -----
void Fog_on(){
RELAY_WRITE(RELAY_FOG, true);
if(!latchedFog){ latchedFog=true; overlayApply(); }
}
void Fog_off(){
RELAY_WRITE(RELAY_FOG, false);
fogTailActive=false;
}
void Fog_startTail(unsigned long now){
if(FOG_TAIL_MS>0){ fogTailActive=true; fogTailStart=now; } else { Fog_off(); }
}
void Fog_update(unsigned long now){
if(fogTailActive && (now - fogTailStart >= (unsigned long)FOG_TAIL_MS)) Fog_off();
}
// ----- STROBE -----
void Strobe_on(){
#if STROBE_MODE==0
RELAY_WRITE(RELAY_STROBE, true); // alim continue (relais méca)
#else
strobeLast = 0; strobeRelayState=false;
RELAY_WRITE(RELAY_STROBE, false); // togglé par Strobe_update()
#endif
if(!latchedStrobe){ latchedStrobe=true; overlayApply(); }
}
void Strobe_off(){ RELAY_WRITE(RELAY_STROBE, false); }
void Strobe_update(unsigned long now){
#if STROBE_MODE==1
unsigned long periodHalf = (unsigned long)(1000.0 / (STROBE_HZ * 2.0));
if(now - strobeLast >= periodHalf){
strobeLast=now;
strobeRelayState=!strobeRelayState;
RELAY_WRITE(RELAY_STROBE, strobeRelayState);
}
#endif
}
// ----- CONFETTIS (impulsion non bloquante) -----
void Confetti_start(unsigned long now){
RELAY_WRITE(RELAY_CONFETTI, true);
confettiActive=true; confettiOnAt=now;
latchedConf=true; overlayApply(); // témoin vert reste allumé
}
void Confetti_update(unsigned long now){
if(confettiActive && now - confettiOnAt >= CONF_PULSE_MS){
RELAY_WRITE(RELAY_CONFETTI, false);
confettiActive=false; confettiShot=true;
}
}
// ----- RIDEAU / AIMANT (impulsion non bloquante) -----
void Curtain_start(unsigned long now){
RELAY_WRITE(RELAY_CURTAIN, true);
curtainActive=true; curtainOnAt=now;
latchedCurt=true; overlayApply(); // témoin orange reste allumé
}
void Curtain_update(unsigned long now){
if(curtainActive && now - curtainOnAt >= CURT_PULSE_MS){
RELAY_WRITE(RELAY_CURTAIN, false);
curtainActive=false; curtainDropped=true;
}
}
// ----- Tout couper (sécurité/fin) -----
void Effects_allOff(){
RELAY_WRITE(RELAY_CONFETTI,false);
RELAY_WRITE(RELAY_CURTAIN,false);
Strobe_off();
Fog_off();
Buzzer_off();
}
/*********************************************
* SECTION 11 — startShow() : lance la séquence
*********************************************/
void startShow(){
if(!safetyOK()){
for(int i=0;i<2;i++){ Vis_Fill(255,0,0); delay((unsigned long)(120*SLOW)); Vis_Black(); delay((unsigned long)(120*SLOW)); }
return;
}
// reset témoins
latchedConf=latchedLed=latchedFog=latchedStrobe=latchedBuzzer=latchedCurt=false;
wrongCount = 0; buffer = "";
t0 = millis();
LED_ShowMark(); // témoin “show démarré” (cyan)
Music_playShowTrack(MUSIC_TRACK); // ← lance 001.mp3
Vis_Black();
state = S_SHOW_P1;
}
/**************************************
* SECTION 12 — setup() : initialisation
**************************************/
void setup(){
pinMode(RELAY_FOG, OUTPUT);
pinMode(RELAY_STROBE, OUTPUT);
pinMode(RELAY_CONFETTI, OUTPUT);
pinMode(RELAY_CURTAIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(GO_BTN_PIN, INPUT_PULLUP);
#if BYPASS_SAFETY==0
pinMode(ARM_PIN, INPUT_PULLUP);
pinMode(ESTOP_PIN, INPUT_PULLUP);
#endif
Effects_allOff();
strip.begin();
strip.setBrightness(LED_BRIGHTNESS);
Vis_Black();
// Musique : initialisation (si DFPlayer absent, ça n’empêche pas le show)
Music_begin();
}
/*****************************************
* SECTION 13 — loop() : logique principale
*****************************************/
void loop(){
unsigned long now = millis();
// Mises à jour non bloquantes
Fog_update(now);
Strobe_update(now);
Buzzer_update(now);
// Bouton GO
if (state == S_IDLE && digitalRead(GO_BTN_PIN) == LOW){
startShow();
}
// Clavier
char k = keypad.getKey();
if (k){
if(k=='*'){
// Reset saisie + témoins + musique stop
buffer="";
latchedConf=latchedLed=latchedFog=latchedStrobe=latchedBuzzer=latchedCurt=false;
Music_stop();
Vis_Black();
#if BEEP_ON_KEY
Buzzer_start((unsigned)(40*SLOW));
#endif
} else if(k=='#'){
if(buffer == PASSCODE && state != S_LOCKOUT) {
startShow();
} else {
wrongCount++; buffer="";
for(int i=0;i<3;i++){ Vis_Fill(255,0,0); delay((unsigned long)(90*SLOW)); Vis_Black(); delay((unsigned long)(90*SLOW)); }
#if BEEP_ON_ERROR
for(int j=0;j<3;j++){ Buzzer_start((unsigned)(60*SLOW)); delay((unsigned)(60*SLOW)); }
#endif
if(wrongCount>=3){ t0=now; Music_stop(); state=S_LOCKOUT; } // stop musique en lockout
}
} else if(((k>='0'&&k<='9')||(k>='A'&&k<='D')) && buffer.length()<8){
buffer += k;
Vis_Fill(0,180,60); delay((unsigned)(60*SLOW)); Vis_Black();
#if BEEP_ON_KEY
Buzzer_start((unsigned)(30*SLOW));
#endif
}
}
// Machine d’états
switch(state){
case S_IDLE:{
// fond noir + témoins latchés
}break;
case S_LOCKOUT:{
if(now - t0 < LOCKOUT_MS){
static unsigned long tr=0;
if(now - tr > (unsigned long)(140*SLOW)){ tr=now; Vis_RedBlink(); }
Effects_allOff(); // sécurité
} else {
wrongCount=0; state=S_IDLE; Vis_Black();
}
}break;
case S_SHOW_P1:{ // rouge + bip bref
static bool started=false;
if(!started){ Buzzer_start((unsigned)(600*SLOW)); started=true; latchedBuzzer=true; overlayApply(); }
static unsigned long t1r=0;
if(now - t1r > (unsigned long)(140*SLOW)){ t1r=now; Vis_RedBlink(); }
if(now - t0 > P1_MS){ started=false; Vis_Black(); t0=now; state=S_SHOW_P2; }
}break;
case S_SHOW_P2:{ // fumée + strobe
static unsigned long t2s=0;
if(now - t2s > (unsigned long)(60*SLOW)){ t2s=now; Vis_StrobeBlink(); }
Fog_on(); // fumée ON
Strobe_on(); // strobe ON (continu ou SSR selon STROBE_MODE)
if(now - t0 > P2_MS){
Strobe_off();
Fog_startTail(now); // traîne optionnelle
Vis_Black();
t0=now; state=S_SHOW_P3;
}
}break;
case S_SHOW_P3:{ // pause noire lisible
if(now - t0 > P3_MS){
confettiActive=curtainActive=false; confettiShot=curtainDropped=false;
Vis_Black();
t0 = now; state = S_SHOW_P4;
}
}break;
case S_SHOW_P4:{ // pulses confettis / rideau
if (!confettiShot && !confettiActive && now - t0 >= CONF_START_MS) {
Confetti_start(now);
}
Confetti_update(now);
if (!curtainDropped && !curtainActive && now - t0 >= CURT_START_MS) {
Curtain_start(now);
}
Curtain_update(now);
if(now - t0 > P4_MS){
Effects_allOff();
Music_stop(); // ← stop musique en fin de show
Vis_Black();
state = S_IDLE;
}
}break;
}
}
(click to edit)
(click to edit)
(click to edit)
(click to edit)