[PROJECT] Sirène d'Alarme Zigbee DIY (ESP32-H2) : Routeur, Flashs LED & Buzzer 220V

Bonjour à tous !

Je vous présente mon projet de sirène d’alarme Zigbee 100% locale, conçue autour d’un ESP32-H2. Contrairement aux sirènes du commerce souvent limitées, ce module est alimenté en 220V et combine plusieurs fonctions pour renforcer votre réseau Zigbee et votre sécurité.

:rocket: Points forts :

  • Mode Zigbee Router (ZR) : Agit comme un répéteur puissant pour maillage (jusqu’à 20 enfants).
  • Multi-Endpoints :
    1. Sirène (Relais),
    1. Autoprotection (Tamper),
    1. État de l’armement,
    1. Range Extender.
  • Retour Visuel : 20 LEDs WS2812B pour des animations (Armé/Désarmé/Alerte).
  • Puissance : Pilotage d’un Buzzer filaire 220V 120dB via relais.
  • Intégration Alarmo : Entièrement compatible avec l’intégration Alarmo sous Home Assistant.

:hammer_and_wrench: Matériel utilisé :

  • Microcontrôleur : ESP32-H2-DevKitM-1.
  • Alimentation : Module AC-DC 220V → 5V (type HLK-PM01).
  • Actionneur : Module relais 5V pour couper/allumer le 220V de la sirène.
  • Signalisation : bandeau de 20 LEDs WS2812B (Neopixel) en 4 segments de 5 leds.
  • Sécurité : Micro-switch pour la détection d’ouverture du boîtier (Tamper).

:laptop: Le Code (Arduino IDE) :

Le code utilise la bibliothèque officielle ESP32 Zigbee. Il configure l’appareil comme un routeur avec une configuration personnalisée pour maximiser le nombre d’appareils enfants.

Configuration Arduino IDE :

  • Board : ESP32H2 Dev Module
  • Zigbee Mode : Zigbee Router (ZCZR)
👉 Cliquez pour voir le code complet

`#ifndef ZIGBEE_MODE_ZCZR
#error « Zigbee router mode is not selected in Tools->Zigbee mode »
#endif

#include « Zigbee.h »
#include <Preferences.h>
#include <Adafruit_NeoPixel.h>

// Neopixel leds configuration
#define PIXEL_PIN 12 // Le GPIO pour les WS2812B
#define NUM_PIXELS 20 // Nombre de LEDs
#define BRIGHTNESS 250 // Intensité lumineuse (0 à 255)

Adafruit_NeoPixel pixels(NUM_PIXELS, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

// Variables pour l’animation sans blocage
bool sirenActive = false;
unsigned long lastUpdate = 0;
int animationStep = 0;
const int interval = 250; // Vitesse de clignotement en millisecondes

/* Zigbee configuration */
#define USE_CUSTOM_ZIGBEE_CONFIG 1

#define ZIGBEE_OUTLET_ENDPOINT 1
#define CONTACT_SWITCH_ENDPOINT_NUMBER 2
#define ARMING_ENDPOINT_NUMBER 3
#define ZIGBEE_EXTENDER_ENDPOINT 4

uint8_t led = LED_BUILTIN;
uint8_t button = BOOT_PIN;
uint8_t sensor_pin = 10;
uint8_t ledPin = 14;
bool systemArmed = false; // État de l’armement (Armé/Désarmé)

ZigbeePowerOutlet zbOutlet = ZigbeePowerOutlet(ZIGBEE_OUTLET_ENDPOINT);
ZigbeeContactSwitch zbContactSwitch = ZigbeeContactSwitch(CONTACT_SWITCH_ENDPOINT_NUMBER);
ZigbeePowerOutlet zbArmingSwitch = ZigbeePowerOutlet(ARMING_ENDPOINT_NUMBER);
ZigbeeRangeExtender zbExtender = ZigbeeRangeExtender(ZIGBEE_EXTENDER_ENDPOINT);

// Create a task on identify call to handle the identify function
void identify(uint16_t time) {
static uint8_t blink = 1;
log_d(« Identify called for %d seconds », time);
if (time == 0) {
digitalWrite(led, LOW);
return;
}
digitalWrite(led, blink);
blink = !blink;
}

void setLED(bool value) {
sirenActive = value;
digitalWrite(ledPin, value ? HIGH : LOW);

if (!sirenActive) {
pixels.clear();
pixels.show();
}
}

// fonction validation armement et desarment leds
void showConfirmation(uint32_t color) {
for (int blink = 0; blink < 3; blink++) {
for (int i = 0; i < NUM_PIXELS; i++) {
pixels.setPixelColor(i, color);
}
pixels.show();
delay(150);
pixels.clear();
pixels.show();
delay(150);
}
}

// fonction de retour (Callback) pour l’Armement
void onArmingChange(bool value) {
systemArmed = value;
if (systemArmed) {
showConfirmation(pixels.Color(255, 0, 0)); // Flash ROUGE = Armé
} else {
showConfirmation(pixels.Color(0, 255, 0)); // Flash VERT = Désarmé
// Sécurité : Si on désarme, on coupe aussi la sirène au cas où
sirenActive = false;
digitalWrite(ledPin, LOW);
pixels.clear();
pixels.show();
}
Serial.printf(« Système d’alarme : %s\n », systemArmed ? « ARME » : « DESARME »);
}
/* Preferences for storing ENROLLED flag to persist across reboots */
Preferences preferences;

void setup() {
Serial.begin(115200);

// init leds neopixels
pixels.begin(); // Initialise le ruban
pixels.show(); // Éteint tout par défaut
pixels.setBrightness(BRIGHTNESS);

// Init LED and turn it OFF (if LED_PIN == RGB_BUILTIN, the rgbLedWrite() will be used under the hood)

pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);

preferences.begin(« Zigbee », false); // Save ENROLLED flag in flash so it persists across reboots
bool enrolled = preferences.getBool(« ENROLLED »); // Get ENROLLED flag from preferences
preferences.end();

// Init button + switch
pinMode(button, INPUT_PULLUP);
pinMode(sensor_pin, INPUT_PULLUP);

// Optional: set Zigbee device name and model
zbContactSwitch.setManufacturerAndModel(« Espressif », « ZigbeeSirène »);
zbContactSwitch.setHardwareVersion(1);
zbExtender.onIdentify(identify);

// Set callback function for power outlet change

zbOutlet.onPowerOutletChange(setLED);
zbArmingSwitch.onPowerOutletChange(onArmingChange);

Zigbee.addEndpoint(&zbOutlet);
Zigbee.addEndpoint(&zbContactSwitch);
Zigbee.addEndpoint(&zbArmingSwitch);
Zigbee.addEndpoint(&zbExtender);

#if USE_CUSTOM_ZIGBEE_CONFIG
// Optional: Create a custom Zigbee configuration for Zigbee Extender
esp_zb_cfg_t zigbeeConfig = ZIGBEE_DEFAULT_ROUTER_CONFIG();
zigbeeConfig.nwk_cfg.zczr_cfg.max_children = 20; // 10 is default

// When all EPs are registered, start Zigbee with custom config
if (!Zigbee.begin(&zigbeeConfig)) {
#else
// When all EPs are registered, start Zigbee as ROUTER device
if (!Zigbee.begin(ZIGBEE_ROUTER)) {
#endif
Serial.println(« Zigbee failed to start! »);
Serial.println(« Rebooting… »);
ESP.restart();
}
Serial.println(« Connecting to network »);
while (!Zigbee.connected()) {
Serial.print(« . »);
delay(100);
}
Serial.println();

// Check if device has been enrolled before restarting - if so, restore IAS Zone enroll, otherwise request new IAS Zone enroll
if (enrolled) {
Serial.println(« Device has been enrolled before - restoring IAS Zone enrollment »);
zbContactSwitch.restoreIASZoneEnroll();
} else {
Serial.println(« Device is factory new - first time joining network - requesting new IAS Zone enrollment »);
zbContactSwitch.requestIASZoneEnroll();
}

while (!zbContactSwitch.enrolled()) {
Serial.print(« . »);
delay(100);
}
Serial.println();
Serial.println(« Zigbee enrolled successfully! »);

// Store ENROLLED flag only if this was a new enrollment (previous flag was false)
// Skip writing if we just restored enrollment (flag was already true)
if (!enrolled) {
preferences.begin(« Zigbee », false);
preferences.putBool(« ENROLLED », true); // set ENROLLED flag to true
preferences.end();
Serial.println(« ENROLLED flag saved to preferences »);
}
}

void loop() {
// Checking pin for contact change
static bool contact = false;
if (digitalRead(sensor_pin) == HIGH && !contact) {
// Update contact sensor value
zbContactSwitch.setOpen();
contact = true;
} else if (digitalRead(sensor_pin) == LOW && contact) {
zbContactSwitch.setClosed();
contact = false;
}

// Checking button for factory reset
if (digitalRead(button) == LOW) { // Push button pressed
// Key debounce handling
delay(100);
int startTime = millis();
while (digitalRead(button) == LOW) {
delay(50);
if ((millis() - startTime) > 3000) {
// If key pressed for more than 3secs, factory reset Zigbee and reboot
Serial.println(« Resetting Zigbee to factory and rebooting in 1s. »);
// Clear the ENROLLED flag from preferences
preferences.begin(« Zigbee », false);
preferences.putBool(« ENROLLED », false); // set ENROLLED flag to false
preferences.end();
Serial.println(« ENROLLED flag cleared from preferences »);
delay(1000);
Zigbee.factoryReset();
}
}
}

// Animation des segments —
if (sirenActive) {
unsigned long currentMillis = millis();

if (currentMillis - lastUpdate >= interval) {
  lastUpdate = currentMillis;
  pixels.clear();

  if (animationStep == 0) {
    // Allume segments 1 (0-4) et 3 (10-14)
    for (int i = 0; i < 5; i++) { pixels.setPixelColor(i, pixels.Color(255, 0, 0)); }
    for (int i = 10; i < 15; i++) { pixels.setPixelColor(i, pixels.Color(255, 0, 0)); }
    animationStep = 1;
  } else {
    // Allume segments 2 (5-9) et 4 (15-19)
    for (int i = 5; i < 10; i++) { pixels.setPixelColor(i, pixels.Color(255, 0, 0)); }
    for (int i = 15; i < 20; i++) { pixels.setPixelColor(i, pixels.Color(255, 0, 0)); }
    animationStep = 0;
  }
  pixels.show();
}

}
delay(10);
}


:wrench: Montage & Intégration :

1. PCB & Boîtier : J’ai intégré sur une plaque d’essai les composants et un boîtier sur mesure via Fusion 360 (imprimé en 3D).

2. Zigbee2MQTT / ZHA : L’appareil est reconnu immédiatement. Il est conseillé de le renommer « Sirène Alarme » pour faciliter l’intégration.

3. Configuration Alarmo :

  • Sensors : Ajoutez le Contact Switch (Endpoint 2) comme capteur de sécurité.
  • Actions : Liez l’état « Armed » d’Alarmo à l’Endpoint 3 de la sirène pour le retour visuel.
  • Sirens : Liez l’Endpoint 1 à l’activation de l’alarme.
1 « J'aime »