[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 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é. Peut être fixer au mur ou posé sur un meuble.

: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 Mini Development
  • Alimentation : Module AC-DC 220V → 5V (type HLK-PM01).
  • Actionneur : Module relais 5V pour couper/allumer le 220V de la sirène.
  • ** Buzzer :** Sirène d’Alarme Extérieure 220V
  • Signalisation : bandeau de 20 LEDs WS2812B (Neopixel).
  • 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 :

👉 Cliquez pour voir le code documenté complet

/**

  • Projet : ZigbeeSireneMulti par CyrilTech :hammer_and_wrench:
  • Description : Sirène Zigbee avec ruban LED NeoPixel, capteur (Tamper)
  • et mode répétiteur 10 à 20 clients max (Extender).
    */

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

/** Bibliothéque */

#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

/** Configuration des 4 ENDPOINT */
#define ZIGBEE_OUTLET_ENDPOINT 1 // Relais 5v/220v pour buzzer
#define CONTACT_SWITCH_ENDPOINT_NUMBER 2 // Contact pour tamper
#define ARMING_ENDPOINT_NUMBER 3 // Led pour signalisation
#define ZIGBEE_EXTENDER_ENDPOINT 4 // routeur zigbee

/** Déclaration des variables */

uint8_t led = LED_BUILTIN; // led d’association
uint8_t button = BOOT_PIN; // boutton association zigbee > à 3 secondes pour relancer la recherche
uint8_t sensor_pin = 10; // détection tamper
uint8_t ledPin = 14; // RELAIS 5V
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);

/** Crée une tâche lors de l’appel d’identification pour gérer la fonction d’identification */

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;
}

/** Crée une fonction pour les leds en mode alarme */

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

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

/** Crée une fonction pour les leds armement désarmement 3 flash */

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);
}
}

/** Crée une fonction pour le flash des leds orange et vert ici on modifie la couleur */

void onArmingChange(bool value) {
systemArmed = value;
if (systemArmed) {
showConfirmation(pixels.Color(255, 126, 0)); // Flash ORANGE = 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 »);
}

/** Préférences pour stocker l’indicateur ENROLLED afin qu’il persiste après les redémarrages */

Preferences preferences;

//* Setup */

void setup() {

Serial.begin(115200);

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

// Init entrées
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
pinMode(button, INPUT_PULLUP);
pinMode(sensor_pin, INPUT_PULLUP);

// Init ENROOLED
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();

// Set Zigbee device name and model
zbContactSwitch.setManufacturerAndModel(« CyrilTech », « ZigbeeSireneMulti »);
zbContactSwitch.setHardwareVersion(1);
zbExtender.onIdentify(identify);

// Set callback function for power outlet change
zbOutlet.onPowerOutletChange(setLED);
zbArmingSwitch.onPowerOutletChange(onArmingChange);

// Set endpoint
Zigbee.addEndpoint(&zbOutlet);
Zigbee.addEndpoint(&zbContactSwitch);
Zigbee.addEndpoint(&zbArmingSwitch);
Zigbee.addEndpoint(&zbExtender);

// Set config Zigbee max clients à modifier max 20 préconisé entre 10 et 15
#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 »);
}
}

/** Loop */

void loop() {

// Gestion détection tamper
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;
}

// Gestion button reset association
if (digitalRead(button) == LOW) { // Push button pressed
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 peut être modifié par nimporte quelle animation à 20 leds ici clignote en rouge 255.0.0
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).
** fichiers STL dispo sur thingiverse : https://www.thingiverse.com/thing:7304087

2. Zigbee2MQTT / ZHA : L’appareil est reconnu immédiatement. Il est conseillé de le renommer « Sirène Alarme » pour faciliter l’intégration. Vous pouvez modifier les définitions de chaque composant pour rendre le projet plus pratique. le contact temper ce fait sur alarm 1 et alarm 2 en même temps.

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.
9 « J'aime »

J’ai testé le code sur l’Arduino IDE 2.3.7
mais impossible de booter, j’ai ce message en boucle:

ESP-ROM:esp32h2-20221101
Build:Nov  1 2022
rst:0xc (SW_CPU),boot:0xc (SPI_FAST_FLASH_BOOT)
Saved PC:0x400031b6
SPIWP:0xee
mode:DIO, clock div:1
load:0x408460f0,len:0x1214
load:0x4083c2d0,len:0xe98
load:0x4083efd0,len:0x2f6c
entry 0x4083c2d0
E (372) ZB_ESP_NVRAM: Failed to find zb_storage partition
Zigbee stack assertion failed /builds/thread_zigbee/esp-zboss/components/zboss_port/src/zb_esp_nvram.c:84

abort() was called at PC 0x420154c3 on core 0
Core  0 register dump:
MEPC    : 0x40805f60  RA      : 0x40805f24  SP      : 0x40820750  GP      : 0x4080f6f4  
TP      : 0x40820940  T0      : 0x37363534  T1      : 0x7271706f  T2      : 0x33323130  
S0/FP   : 0x4082077c  S1      : 0x4082077c  A0      : 0x40820788  A1      : 0x4082076a  
A2      : 0x00000000  A3      : 0x408207b5  A4      : 0x00000001  A5      : 0x40817000  
A6      : 0x00000000  A7      : 0x76757473  S2      : 0x40812208  S3      : 0x42096000  
S4      : 0x00000000  S5      : 0x42090000  S6      : 0x42090000  S7      : 0x40812000  
S8      : 0x40812000  S9      : 0x00000000  S10     : 0x00000000  S11     : 0x00000000  
T3      : 0x6e6d6c6b  T4      : 0x6a696867  T5      : 0x66656463  T6      : 0x62613938  
MSTATUS : 0x00001881  MTVEC   : 0x40800001  MCAUSE  : 0x00000002  MTVAL   : 0x00000000  
MHARTID : 0x00000000  

Stack memory:
40820750: 0x00000000 0x00000000 0x40820768 0x4080cbc8 0x42090000 0x42090000 0x00000030 0x40810fb0
40820770: 0x4082077c 0x40810fcc 0x40820768 0x31303234 0x33633435 0x42090000 0x726f6261 0x20292874
40820790: 0x20736177 0x6c6c6163 0x61206465 0x43502074 0x34783020 0x35313032 0x20336334 0x63206e6f
408207b0: 0x2065726f 0x00000030 0x00000000 0x7ed4508c 0x0000002f 0x40814208 0x4209418c 0x420154c6
408207d0: 0x40812208 0x40814208 0x40817000 0x00000054 0x42094100 0x00000169 0x40817000 0x4205a31c
408207f0: 0x42090000 0x00000000 0x40812000 0x42015940 0x4084fa00 0x40850000 0x4084d380 0x40812000
40820810: 0x408208c0 0x00000000 0x408208c0 0x42007572 0x4084d380 0x00000006 0x4084fa08 0x4080b0fc
40820830: 0x40817000 0x00000000 0x40811ce0 0x4200099c 0x00000000 0x00000000 0x00000000 0x00000000
40820850: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820870: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820890: 0x00000000 0x00000000 0x00000000 0x00000000 0x40812000 0x408208c0 0x40811ce0 0x42000ac0
408208b0: 0x40812000 0x40817000 0x40812000 0x42000334 0x00000001 0x00000000 0x00000014 0x00000000
408208d0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
408208f0: 0x00000000 0x00000000 0x00000000 0x4200585e 0x00000000 0x00000000 0x00000000 0x408073ac
40820910: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820930: 0x00000000 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5 0xbaad5678 0x0000348c
40820950: 0xabba1234 0x00003480 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820970: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820990: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
408209b0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
408209d0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
408209f0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820a10: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820a30: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820a50: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820a70: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820a90: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820ab0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820ad0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820af0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820b10: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
40820b30: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000



ELF file SHA256: de9fd9a43

J’ai peut etre un soucis là dedans ?


et si je met un code esphome « vide »

esphome:
  name: h2
  friendly_name: h2

esp32:
  board: esp32-h2-devkitm-1
  framework:
    type: esp-idf

# Enable logging
logger:

il boote…

Suite…
avec le parametre
image
c’est mieux !!!
mais le device est en mode inconnu
image

mais…

Ca marche :+1:

Pour ce qui est des paramétrage Arduino IDE pour ESP32-H2 voici les miens

1 « J'aime »

Bonjour @Cyril_Rudler :+1:

Projet intéressant, quelques infos complémentaires pour le chargement du code sur l’ESP32-H2 serait les bienvenues, idem pour la partie câblage de l’ensemble.

Bonjour,
Pas besoin d’héberger une image sur un serveur tiers, tu peux directement la coller dans le message.

Canon ! Bravo et merci pour le partage !
Tu pourrais nous partager ton fichier 3D du boitier stp ? :folded_hands:

Je reviens d’ici fin de semaine avec un model plus compact et finalisé au mieux. je posterais les stl et un peut plus de désciptifs

2 « J'aime »

Et en cas de panne de courant ?

1 « J'aime »

Branché derrière un onduleur ça paaaaasse :grin::wink:
L’idée de base est excellente tout de même. La réalisation est sympa et donne envie.
Juste un reproche, c’est le fait qu’elle (la box) est conçue pour être en pose libre, facilement accessible. C’est très joli, mais du coup rien n’empêche de l’attraper et l’éclater par terre, ou tirer dessus un coup sec pour la débrancher.
Mais ça reste très inspirant ! Ca m’a donné envie d’en concevoir une aussi.

En cas de panne de courant je pense que ton reseau zigbee ne va pas trop fonctionné :rofl:

On est d’accord, donc une sirène pour une alarme qui dépend de la présence du secteur pour fonctionner, c’est moyen non ?
Pour une alarme technique (fumée, inondation…) pourquoi pas, mais pour une alarme intrusion (et comme il y a un contact d’auto-protection ça fait plus penser à ce cas) j’ai comme un doute.

Projet mise à jour et finis.

Super projet que celui ci, mais pourquoi ne pas ajouter une batterie liion avec un petit bms? Ça permettrais justement en cas de coupure de courant de sonner.

Et autre question est ce qu’il serait possible de faire aussi office de carillon avec quelques rtttl en plus dans le code, comme ça ça ferais double utilisation.

En m’inspirant de @Cyril_Rudler, justement, je suis en train de réaliser une sirène extérieure avec batterie et alimentation secourue. Le proto (la partie programmation de l’ESP32-H2) est fonctionnel, j’attends encore quelques pièces (l’alimentation secourue justement, la sirène, et un relais MOSFET pour commander la sirène depuis l’ESP)
J’avais du strip LED WS2812B, l’ESP32-H2, du micro switch (tamper) un abaisseur/régulateur LM2596 et aussi un capteur SHT35 (temp & humidité) ce qui fait que j’ai pu avancer en silence :grinning_face_with_smiling_eyes: (sans sirène) en attendant le matériel. La batterie, que j’ai déjà, est une batterie de type onduleur, une Power Sonic, acide plomb, 12V 17 Ah. (PS-12170B)
Je n’aurai plus qu’a concevoir le boitier. J’ai ce qu’il faut en filament ASA pour ça.
Si ça vous branche je pourrai mettre tout ça en ligne.


Chez moi, j’ai toujours le module qui reste en Non pris en charge: generated dans Z2M

tu as créé un external_converter ?
tu peux nous l’afficher ?

1 « J'aime »

Oui tout à fait, j’ai créé un external converter

const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');

// ─── Convertisseur custom : décode IAS Zone status (tamper + alarm) ──────────
const fzCustom = {
    ias_zone_status_custom: {
        cluster: 'ssIasZone',
        type: ['commandStatusChangeNotification', 'attributeReport', 'readResponse'],
        convert: (model, msg, publish, options, meta) => {
            const zoneStatus = msg.data.zoneStatus ?? msg.data.zonestatus ?? 0;
            return {
                tamper:  (zoneStatus & (1 << 2)) > 0,
                alarm_1: (zoneStatus & (1 << 0)) > 0,
            };
        },
    },

    // EP3 température : 0x8000 = invalide ZCL → on ignore
    temperature_ep3: {
        cluster: 'msTemperatureMeasurement',
        type: ['attributeReport', 'readResponse'],
        convert: (model, msg, publish, options, meta) => {
            if (msg.endpoint.ID !== 3) return;
            const raw = msg.data['measuredValue'];
            if (raw === undefined || raw === null || raw === -32768 || raw === 0x8000) return;
            return { temperature: parseFloat((raw / 100).toFixed(2)) };
        },
    },

    // EP3 humidité : 0xFFFF = invalide ZCL → on ignore
    humidity_ep3: {
        cluster: 'msRelativeHumidity',
        type: ['attributeReport', 'readResponse'],
        convert: (model, msg, publish, options, meta) => {
            if (msg.endpoint.ID !== 3) return;
            const raw = msg.data['measuredValue'];
            if (raw === undefined || raw === null || raw === 0xFFFF) return;
            return { humidity: parseFloat((raw / 100).toFixed(2)) };
        },
    },
};

// ─── Convertisseur custom : force OnOff sur EP2 (armement) ──────────────────
const tzCustom = {
    warning_ep1: {
        key: ['warning'],
        convertSet: async (entity, key, value, meta) => {
            const endpoint = meta.device.getEndpoint(1);
            await tz.warning.convertSet(endpoint, key, value, meta);
        },
    },
    on_off_ep2: {
        key: ['state'],
        convertSet: async (entity, key, value, meta) => {
            const endpoint = meta.device.getEndpoint(2);
            await tz.on_off.convertSet(endpoint, key, value, meta);
        },
        convertGet: async (entity, key, meta) => {
            const endpoint = meta.device.getEndpoint(2);
            await tz.on_off.convertGet(endpoint, key, meta);
        },
    },
};

// ─── Définition de l'appareil ────────────────────────────────────────────────
const definition = {
    zigbeeModel: ['Sirene'],
    model: 'Sirene',
    vendor: 'ESP32-H2',
    description: 'Sirene IAS DIY ESP32-H2',
    fromZigbee: [
        fz.on_off,
        fz.ias_wd,
        fzCustom.ias_zone_status_custom,
        fzCustom.temperature_ep3,
        fzCustom.humidity_ep3,
        // NE PAS inclure fz.temperature ni fz.humidity → évite temperature_sensor / humidity_sensor
    ],
    toZigbee: [tzCustom.on_off_ep2, tzCustom.warning_ep1],
    exposes: [
        {
            type: 'composite', name: 'warning', label: 'Warning', property: 'warning', access: 2,
            description: 'Control the warning device',
            features: [
                { type: 'enum',    name: 'mode',     label: 'Mode',     property: 'mode',     access: 2, description: 'Mode of the warning (sound effect)', values: ['stop', 'burglar', 'fire', 'emergency', 'police_panic', 'fire_panic', 'emergency_panic'] },
                { type: 'enum',    name: 'level',    label: 'Level',    property: 'level',    access: 2, description: 'Sound level', values: ['low', 'medium', 'high', 'very_high'] },
                { type: 'binary',  name: 'strobe',   label: 'Strobe',   property: 'strobe',   access: 2, description: 'Turn on/off the strobe (light) during warning', value_on: true, value_off: false },
                { type: 'numeric', name: 'duration', label: 'Duration', property: 'duration', access: 2, description: 'Duration in seconds of the alarm', unit: 's' },
            ],
        },
        {
            type: 'switch', name: 'state', label: 'State (arm)', property: 'state', access: 7, endpoint: 'arm',
            description: 'Arm / disarm the siren', value_on: 'ON', value_off: 'OFF', value_toggle: 'TOGGLE',
            features: [{ type: 'binary', name: 'state', label: 'State', property: 'state', access: 7, value_on: 'ON', value_off: 'OFF', value_toggle: 'TOGGLE' }],
        },
        { type: 'binary',  name: 'tamper',      label: 'Tamper',     property: 'tamper',      access: 1, description: 'Indicates whether the device is tampered (chassis opened)', value_on: true, value_off: false },
        { type: 'binary',  name: 'alarm_1',     label: 'Alarm 1',    property: 'alarm_1',     access: 1, description: 'Indicates whether IAS Zone alarm 1 is active', value_on: true, value_off: false },
        { type: 'numeric', name: 'temperature', label: 'Temperature', property: 'temperature', access: 1, description: 'Température interne du boîtier (SHT35)', unit: '°C' },
        { type: 'numeric', name: 'humidity',    label: 'Humidity',    property: 'humidity',    access: 1, description: 'Humidité relative interne du boîtier (SHT35)', unit: '%' },
    ],
    endpoint: (device) => ({ warning: 1, arm: 2, sensor: 3 }),
    meta: { multiEndpoint: true },

    onEvent: async (type, data, device) => {
        if (type === 'stop') return;
        if (['deviceAnnounce', 'deviceJoined', 'deviceInterview', 'start'].includes(type)) {
            const ep3 = device.getEndpoint(3);
            if (ep3) {
                try {
                    await ep3.read('msTemperatureMeasurement', ['measuredValue']);
                    await ep3.read('msRelativeHumidity',       ['measuredValue']);
                } catch (e) {}
            }
        }
    },

    configure: async (device, coordinatorEndpoint) => {
        const ep3 = device.getEndpoint(3);
        await ep3.bind('msTemperatureMeasurement', coordinatorEndpoint);
        await ep3.bind('msRelativeHumidity',       coordinatorEndpoint);
        await ep3.configureReporting('msTemperatureMeasurement', [{
            attribute: 'measuredValue', minimumReportInterval: 30, maximumReportInterval: 120, reportableChange: 50,
        }]);
        await ep3.configureReporting('msRelativeHumidity', [{
            attribute: 'measuredValue', minimumReportInterval: 30, maximumReportInterval: 120, reportableChange: 100,
        }]);
    },
};

module.exports = definition;

Mais il correspond à mon code et ma config…

Ah oui, ca ne s’invente pas !!!

juste une question, comment z2m va choisir ton fichier.js pour ton esp32-H2 … et pas un autre esp… ou un autre fichier ???
je verrais bien dans le paragraphe // ─── Définition de l'appareil ─────
mais où exactement ?

1 « J'aime »

Il fait la correspondance tout seul, avec le champ zigbeeModel: ['Sirene'], (dans la catégorie Définition de l’appareil oui, bien vu :wink:)
Quand un device Zigbee se connecte, Z2M lui envoie une requête ZCL pour lire l’attribut Model Identifier (cluster Basic, attribut 0x0005). Le device répond avec la chaîne définie dans mon firmware :

static char mod_id[] = "\x06" "Sirene";

Quand Z2M démarre, on le voit dans le log :

Z2M reçoit "Sirene", cherche dans tous ses convertisseurs (natifs + externes) lequel déclare ce zigbeeModel, trouve sirene.js, et l’utilise pour ce device.

Je mettrai mon firmware à dispo. Si tu veux. Peut-être pas ici, je ne voulais pas pourrir le projet de Cyril, ce n’était pas du tout le but. C’était simplement pour témoigner que son idée m’avait beaucoup plu et inspiré. Je ferai un tuto.

1 « J'aime »

Salut david. Je suis ravis que tu reprenne ce projets je vois qu’il t as donné des idées. j’ai hâte de voir le résultat final.

1 « J'aime »

C’est un beau projet DIY loisirs, et je rejoins @Forhorse : attention à l’illusion de sécurité si vous voulez utiliser cette sirène dans le cadre d’une alarme anti intrusion.

La sécurité n’est pas à prendre à la légère, et entre les coupures de courant (matériel HS), le brouillage (matériel incapable de communiquer), et le piratage (matériel piloté par un intrus), il y a beaucoup de risques à gérer.

Amusez vous bien !