Merci c’est inspirant et ça motive de travailler dessus
salut à tous,
J’ai besoin d’un petit coup de main. j’ai écrit un code avec ardiuno-esp32 et esp32-H2 qui permet de récupérer 3 valeurs analogiques.
Le code fonctionne bien, je suppose.
Présentation
Présentation des endpoint
Si j’envoie les valeurs par la console dev, donc endpoint/genAnalogOutput/presentValue
Chacune des valeurs est sur le bon endpoint donc exploitable.
par contre si je les envoie par « expose » ils arrivent tous de l’endpoint 1
Wireshark
Console série de l’ide.
en jaune envoie par la console de dev de z2m, en rouge par expose.
je pense que le problème vient de me convertisseur et normalement c’est la que vous intervenez.
Merci d’avance.
mon external_converter
const f = require('zigbee-herdsman-converters/converters/fromZigbee');
const t = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const e = exposes.presets;
const ea = exposes.access;
const modernExtend = require('zigbee-herdsman-converters/lib/modernExtend');
console.log("✅ Mon convertisseur personnalisé est chargé !");
module.exports = {
// C'est ici que le modèle Zigbee de votre appareil doit être défini.
// Il doit correspondre à ce que votre appareil rapporte.
zigbeeModel: ['Zigbee_Analog_Tempo'],
model: '3_tempo',
vendor: 'Orefie_HAfr',
description: 'Module relais Zigbee avec configuration de tempo',
extend: [
modernExtend.deviceEndpoints({
endpoints: {
tempo_1: 1,
tempo_2: 2,
tempo_3: 3,
}
}),
modernExtend.numeric({
name: 'tempo_on_relay_1',
cluster: 'genAnalogOutput',
attribute: 'presentValue',
endpoint: 'tempo_1',
unit: 'ms',
description: 'Durée d\'activation pour le Relais 1 (ms)',
valueMin: 0,
valueMax: 360000000,
access: 'ALL',
}),
modernExtend.numeric({
name: 'tempo_on_relay_2',
cluster: 'genAnalogOutput',
attribute: 'presentValue',
endpoint: 'tempo_2',
unit: 'ms',
description: 'Durée d\'activation pour le Relais 2 (ms)',
valueMin: 0,
valueMax: 360000000,
access: 'ALL',
}),
modernExtend.numeric({
name: 'tempo_on_relay_3',
cluster: 'genAnalogOutput',
attribute: 'presentValue',
endpoint: 'tempo_3',
unit: 'ms',
description: 'Durée d\'activation pour le Relais 3 (ms)',
valueMin: 0,
valueMax: 360000000,
access: 'ALL',
}),
],
meta: {
multiEndpoint: true,
},
};
Bonjour, je suis comme toi, dès que je fais des clusters personnalisés, je n’arrive pas à faire remonter dans zigbee2mqtt correctement.
J’essaie une approche pas très propre mais qui devrait fonctionner, j’utilise des clusters existant et reconnu par zigbee2mqtt.
Par exemple, on peut envoyer des données sur un cluster de température, qui est bien reconnu par zigbee2mqtt en °C, puis utiliser un template dans ha pour changer l’unité.
Il existe cette bibliothèque qui fonc pas mal :
En repartant de ces exemples, on devrait arriver à transmettre les données de façon détourner.
C’est pas top mais ça devrait fonctionner
Merci de ton retour, c’est celle que j’utilise
Tu l’as compilé à la main à partir du github ou tu as pris la dernière release?
J’ai téléchargé depuis github puis j’insère le code dans Arduino, je configure tool comme indiqué sur github.
Je modifie les capteurs et pin.
Mais bon ce n’est pas encore tout à fait fonctionnel. J’arrive à avoir la valeur dans zigbee2mqtt, mais elle ne se met pas à jour automatiquement.
Je cherche encore.
Si j’y arrive, je mettrai un tuto précis sur le forum. Mais bon c’est pas gagné encore
@Orefie, lorsque tu utilises modernExtend.numeric
trois fois de suite pour le même cluster (genAnalogOutput
) et le même attribut (presentValue
), même en spécifiant des endpoints différents, Zigbee2MQTT a tendance à n’enregistrer correctement que le premier convertisseur pour l’écriture (ToZigbee). Les suivants sont ignorés ou écrasés.
Il vaut mieux définir plus explicitement les convertisseurs fromZigbee
et toZigbee
afin de gérer correctement la répartition sur les différents endpoints.
La modif proposée par Gemini:
const { fromZigbee, toZigbee, exposes } = require('zigbee-herdsman-converters/lib/modernExtend');
const e = exposes.presets;
const ea = exposes.access;
const definition = {
// Le zigbeeModel doit correspondre exactement à celui de votre appareil
zigbeeModel: ['Zigbee_Analog_Tempo'],
model: '3_tempo',
vendor: 'Orefie_HAfr',
description: 'Module 3 valeurs analogiques sur endpoints séparés',
exposes: [
e.numeric('tempo_on_relay_1', ea.ALL).withEndpoint('tempo_1').withUnit('ms')
.withDescription("Durée d'activation pour le Relais 1 (ms)")
.withValueMin(0).withValueMax(360000000),
e.numeric('tempo_on_relay_2', ea.ALL).withEndpoint('tempo_2').withUnit('ms')
.withDescription("Durée d'activation pour le Relais 2 (ms)")
.withValueMin(0).withValueMax(360000000),
e.numeric('tempo_on_relay_3', ea.ALL).withEndpoint('tempo_3').withUnit('ms')
.withDescription("Durée d'activation pour le Relais 3 (ms)")
.withValueMin(0).withValueMax(360000000),
],
fromZigbee: [
{
cluster: 'genAnalogOutput',
type: ['attributeReport', 'readResponse'],
// La fonction 'convert' transforme le message Zigbee brut en un objet JSON compréhensible
convert: (model, msg, publish, options, meta) => {
const endpointName = `tempo_on_relay_${msg.endpoint.ID}`;
if (msg.data.hasOwnProperty('presentValue')) {
// Crée dynamiquement la bonne propriété (ex: { "tempo_on_relay_2": 1500 })
return { [endpointName]: msg.data['presentValue'] };
}
},
}
],
toZigbee: [
{
// 'key' est la liste des propriétés que ce convertisseur peut gérer
key: ['tempo_on_relay_1', 'tempo_on_relay_2', 'tempo_on_relay_3'],
// 'convertSet' est appelé lors d'une commande "set" (ex: changer une valeur)
convertSet: async (entity, key, value, meta) => {
// Extrait le numéro de l'endpoint à partir du nom de la clé (ex: "1" depuis "tempo_on_relay_1")
const endpointID = parseInt(key.split('_')[3], 10);
// Envoie la commande d'écriture au bon endpoint et au bon cluster
await meta.device.getEndpoint(endpointID).write('genAnalogOutput', { presentValue: value });
// Retourne l'état pour une mise à jour immédiate de l'interface (optimistic update)
return { state: { [key]: value } };
},
// 'convertGet' est appelé lorsque l'interface demande à rafraîchir la valeur
convertGet: async (entity, key, meta) => {
const endpointID = parseInt(key.split('_')[3], 10);
await meta.device.getEndpoint(endpointID).read('genAnalogOutput', ['presentValue']);
},
}
],
// Indique à Zigbee2MQTT que cet appareil utilise plusieurs endpoints
meta: {
multiEndpoint: true,
},
// Mappe les noms des endpoints à leurs numéros respectifs
// Redondant avec .withEndpoint() mais c'est une bonne pratique de le garder
endpoint: (device) => {
return {
'tempo_1': 1,
'tempo_2': 2,
'tempo_3': 3,
};
},
};
module.exports = definition;
salut @KipK ,
Tu es un chef
J’ai essayé cette approche sans jamais trouver la bonne syntaxe.
Le détail que j’ai du modifier dans ton convertisseur est la déclaration de « expose » sur 2 lignes.
Voici un réccap complet.
le prog
#ifndef ZIGBEE_MODE_ZCZR
#error "Zigbee coordinator/router device mode is not selected in Tools->Zigbee mode"
#endif
#include "Zigbee.h"
// definition du début des endpoint
#define ANALOG_DEVICE_ENDPOINT_NUMBER 1
uint8_t button = BOOT_PIN;
// définition des endpoints
ZigbeeAnalog zbAnalog_tempo_1 = ZigbeeAnalog(ANALOG_DEVICE_ENDPOINT_NUMBER);
ZigbeeAnalog zbAnalog_tempo_2 = ZigbeeAnalog(ANALOG_DEVICE_ENDPOINT_NUMBER + 1);
ZigbeeAnalog zbAnalog_tempo_3 = ZigbeeAnalog(ANALOG_DEVICE_ENDPOINT_NUMBER + 2);
// fonction de reccuperation de "presentvalue" pour chaque endpoint
void onAnalogOutputChangeTempo1(float analog_output) {
Serial.printf("Received analog output Endpoint 1 : %.1f\r\n", analog_output);
}
void onAnalogOutputChangeTempo2(float analog_output) {
Serial.printf("Received analog output Endpoint 2 : %.1f\r\n", analog_output);
}
void onAnalogOutputChangeTempo3(float analog_output) {
Serial.printf("Received analog output Endpoint 3 : %.1f\r\n", analog_output);
}
void setup() {
Serial.begin(115200);
Serial.println("Starting...");
// Init du bouton de la carte
pinMode(button, INPUT_PULLUP);
// Zigbee device name and model sur le premier endpoint
zbAnalog_tempo_1.setManufacturerAndModel("Espressif", "ZigbeeAnalogDevice");
// parametre de la premiere tempo
zbAnalog_tempo_1.addAnalogOutput(); // type de cluster
zbAnalog_tempo_1.setAnalogOutputApplication(ESP_ZB_ZCL_AO_APP_TYPE_OTHER); // Check esp_zigbee_zcl_analog_output.h for application type values https://docs.espressif.com/projects/esp-zigbee-sdk/en/latest/esp32/api-reference/zcl/esp_zigbee_zcl_analog_output.html
zbAnalog_tempo_1.setAnalogOutputDescription("Tempo_1"); // description
zbAnalog_tempo_1.setAnalogOutputResolution(1); // resolution
// appel de la fonction si la valeur change sur tempo_1
zbAnalog_tempo_1.onAnalogOutputChange(onAnalogOutputChangeTempo1);
// repetition pour tempo 2 et 3
zbAnalog_tempo_2.addAnalogOutput();
zbAnalog_tempo_2.setAnalogOutputApplication(ESP_ZB_ZCL_AO_APP_TYPE_OTHER);
zbAnalog_tempo_2.setAnalogOutputDescription("Tempo_2");
zbAnalog_tempo_2.setAnalogOutputResolution(1);
zbAnalog_tempo_2.onAnalogOutputChange(onAnalogOutputChangeTempo2);
zbAnalog_tempo_3.addAnalogOutput();
zbAnalog_tempo_3.setAnalogOutputApplication(ESP_ZB_ZCL_AO_APP_TYPE_OTHER);
zbAnalog_tempo_3.setAnalogOutputDescription("Tempo_3");
zbAnalog_tempo_3.setAnalogOutputResolution(1);
zbAnalog_tempo_3.onAnalogOutputChange(onAnalogOutputChangeTempo3);
// ajout des endpoints
Zigbee.addEndpoint(&zbAnalog_tempo_1);
Zigbee.addEndpoint(&zbAnalog_tempo_2);
Zigbee.addEndpoint(&zbAnalog_tempo_3);
// connexion au reseau
Serial.println("Starting Zigbee...");
if (!Zigbee.begin(ZIGBEE_ROUTER)) {
Serial.println("Zigbee failed to start!");
Serial.println("Rebooting...");
ESP.restart();
} else {
Serial.println("Zigbee started successfully!");
}
Serial.println("Connecting to network");
while (!Zigbee.connected()) {
Serial.print(".");
delay(100);
}
Serial.println("Connected");
}
void loop() {
// Checking button for factory reset and reporting
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.");
delay(1000);
Zigbee.factoryReset();
}
}
}
delay(100);
}
l’external-converter de @KipK
// external-converter ZigbeeAnalogDevice.js
const { fromZigbee, toZigbee } = require('zigbee-herdsman-converters/lib/modernExtend');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const e = exposes.presets;
const ea = exposes.access;
const definition = {
// Le zigbeeModel doit correspondre exactement à celui de votre appareil
zigbeeModel: ['ZigbeeAnalogDevice'],
model: 'ZigbeeAnalogDevice',
vendor: 'Orefie_HAfr',
description: 'Module 3 valeurs analogiques sur endpoints séparés',
exposes: [
e.numeric('tempo_on_relay_1', ea.ALL).withEndpoint('tempo_1').withUnit('ms')
.withDescription("Durée d'activation pour le Relais 1 (ms)")
.withValueMin(0).withValueMax(360000000),
e.numeric('tempo_on_relay_2', ea.ALL).withEndpoint('tempo_2').withUnit('ms')
.withDescription("Durée d'activation pour le Relais 2 (ms)")
.withValueMin(0).withValueMax(360000000),
e.numeric('tempo_on_relay_3', ea.ALL).withEndpoint('tempo_3').withUnit('ms')
.withDescription("Durée d'activation pour le Relais 3 (ms)")
.withValueMin(0).withValueMax(360000000),
],
fromZigbee: [
{
cluster: 'genAnalogOutput',
type: ['attributeReport', 'readResponse'],
// La fonction 'convert' transforme le message Zigbee brut en un objet JSON compréhensible
convert: (model, msg, publish, options, meta) => {
const endpointName = `tempo_on_relay_${msg.endpoint.ID}`;
if (msg.data.hasOwnProperty('presentValue')) {
// Crée dynamiquement la bonne propriété (ex: { "tempo_on_relay_2": 1500 })
return { [endpointName]: msg.data['presentValue'] };
}
},
}
],
toZigbee: [
{
// 'key' est la liste des propriétés que ce convertisseur peut gérer
key: ['tempo_on_relay_1', 'tempo_on_relay_2', 'tempo_on_relay_3'],
// 'convertSet' est appelé lors d'une commande "set" (ex: changer une valeur)
convertSet: async (entity, key, value, meta) => {
// Extrait le numéro de l'endpoint à partir du nom de la clé (ex: "1" depuis "tempo_on_relay_1")
const endpointID = parseInt(key.split('_')[3], 10);
// Envoie la commande d'écriture au bon endpoint et au bon cluster
await meta.device.getEndpoint(endpointID).write('genAnalogOutput', { presentValue: value });
// Retourne l'état pour une mise à jour immédiate de l'interface (optimistic update)
return { state: { [key]: value } };
},
// 'convertGet' est appelé lorsque l'interface demande à rafraîchir la valeur
convertGet: async (entity, key, meta) => {
const endpointID = parseInt(key.split('_')[3], 10);
await meta.device.getEndpoint(endpointID).read('genAnalogOutput', ['presentValue']);
},
}
],
// Indique à Zigbee2MQTT que cet appareil utilise plusieurs endpoints
meta: {
multiEndpoint: true,
},
// Mappe les noms des endpoints à leurs numéros respectifs
// Redondant avec .withEndpoint() mais c'est une bonne pratique de le garder
endpoint: (device) => {
return {
'tempo_1': 1,
'tempo_2': 2,
'tempo_3': 3,
};
},
};
module.exports = definition;
la console série de l’ide
Tout fonctionne parfaitement.
les étapes que j’ai suivie.
Utilisation de l’ide 1.8.19, (je ne suis pas parvenu à installer la lib sur la version 2 )
Compilation de la lib du github de P.R.O.C.H.Y manuellement pour avoir les dernières maj (voir la doc espressif), avec la release il y avait des erreurs de compilation.
Encore merci @KipK , je vais enfin pouvoir avancer, j’avoue que pour moi c’est mystique ces convertisseurs.
Une version plus optimisée pour rendre dynamique le nombre de endpoints et éviter la répétition de code.
#ifndef ZIGBEE_MODE_ZCZR
#error "Zigbee coordinator/router device mode is not selected in Tools->Zigbee mode"
#endif
#include "Zigbee.h"
// Définition du début des endpoint
#define ANALOG_DEVICE_ENDPOINT_NUMBER 1
// Nombre d'endpoints analogiques à créer
#define NUM_ANALOG_ENDPOINTS 3
uint8_t button = BOOT_PIN;
// Utilisation d'un tableau pour les objets ZigbeeAnalog
ZigbeeAnalog* zbAnalogs[NUM_ANALOG_ENDPOINTS];
// Fonction de callback générique pour tous les endpoints
void onAnalogOutputChange(float analog_output, uint8_t endpointId) {
Serial.printf("Received analog output Endpoint %d : %.1f\r\n", endpointId, analog_output);
}
void setup() {
Serial.begin(115200);
Serial.println("Starting Zigbee device...");
// Init du bouton de la carte
pinMode(button, INPUT_PULLUP);
// Initialisation et configuration des endpoints via une boucle
for (int i = 0; i < NUM_ANALOG_ENDPOINTS; i++) {
uint8_t currentEndpoint = ANALOG_DEVICE_ENDPOINT_NUMBER + i;
zbAnalogs[i] = new ZigbeeAnalog(currentEndpoint);
// Zigbee device name and model sur le premier endpoint
if (i == 0) {
zbAnalogs[i]->setManufacturerAndModel("Espressif", "ZigbeeAnalogDevice");
}
zbAnalogs[i]->addAnalogOutput();
zbAnalogs[i]->setAnalogOutputApplication(ESP_ZB_ZCL_AO_APP_TYPE_OTHER);
// Utilisation de snprintf pour nommer dynamiquement les descriptions
char description[10]; // Assez grand pour "Tempo_X\0"
snprintf(description, sizeof(description), "Tempo_%d", i + 1);
zbAnalogs[i]->setAnalogOutputDescription(description);
zbAnalogs[i]->setAnalogOutputResolution(1);
// Utilisation d'une lambda pour capturer la variable i et appeler la fonction générique
zbAnalogs[i]->onAnalogOutputChange([currentEndpoint](float analog_output) {
onAnalogOutputChange(analog_output, currentEndpoint);
});
// Ajout de l'endpoint
Zigbee.addEndpoint(zbAnalogs[i]);
}
// Connexion au réseau
Serial.println("Starting Zigbee stack...");
if (!Zigbee.begin(ZIGBEE_ROUTER)) {
Serial.println("Zigbee failed to start! Rebooting...");
ESP.restart();
} else {
Serial.println("Zigbee started successfully!");
}
Serial.println("Connecting to network...");
unsigned long connectionStartTime = millis();
while (!Zigbee.connected()) {
Serial.print(".");
// Ajouter un timeout pour éviter un blocage infini en cas de problème réseau
if (millis() - connectionStartTime > 30000) { // 30 secondes de timeout
Serial.println("\nConnection timed out. Rebooting...");
ESP.restart();
}
delay(200); // Augmenter le délai pour réduire la fréquence d'impression des points
}
Serial.println("\nConnected to Zigbee network.");
}
void loop() {
// Checking button for factory reset
if (digitalRead(button) == LOW) { // Bouton enfoncé
unsigned long pressStartTime = millis();
Serial.println("Button pressed. Holding for factory reset...");
// Attendre que le bouton soit relâché ou que le délai de reset soit atteint
while (digitalRead(button) == LOW) {
if (millis() - pressStartTime > 3000) { // Plus de 3 secondes
Serial.println("Factory reset initiated. Rebooting in 1 second.");
Zigbee.factoryReset();
delay(1000); // Attendre un instant avant le reboot
ESP.restart();
}
delay(50); // Débounce
}
Serial.println("Button released.");
}
}
Bonjour,
Voici un petit tuto pour les débutants comme moi qui souhaiteraient faire remonter des informations via zigbee d’un esp32-h2.
J’ai pas mal galéré et je ne maitrise pas encore toutes les subtilités pour faire communiquer correctement l’esp32-h2 avec zigbee2mqtt.
J’ai donc pensé à détourner une configuration existante pour envoyé des données.
Ce n’est pas tout à fait propre, car j’utilise un cluster qui n’a rien à voir avec la bonne unité, mais bon cela fonctionne en attendant que j’arrive à créer mes propres cluster et fichier converter.
Voici l’idée : Je souhaitais équiper ma cuve d’un capteur ultrason de type hc-sr04 raccordé à un esp32-h2 pour envoyer les données de distances via zigbee à HA.
La cuve étant trop loin de la maison, l’alimentation de l’esp32-h2 est assurée par batterie 12V et petit panneau solaire.
Voici les différentes étapes :
1 - le montage :
Connexion de l’HC-SR04 à l’esp32-H2 :
VCC HC-SR04 sur le 5V de l’ESP32-H2
GND HC-SR04 sur GNDde l’ESP32-H2.
TRIG HC-SR04 sur GPIO 4 de l’ESP32-H2
ECHO HC-SR04 rsur GPIO 5 de l’ESP32-H2
J’ai fait également un pont diviseur avec deux résistances, 1kΩ et 2kΩ) sur la broche ECHO pour abaisser le signal à 3.3V pour ne pas endommager l’ESP32-H2.
2 - rechercher les informations existantes sur ce matériel et le zigbee.
Le github suivant permet de récupérer pas mal de configurations existantes :
Télécharger le zip et décompresser le pour pouvoir accéder aux exemples.
3 - installation et configuration d’Arduino : j’ai la version 2.3.6 sur windows.
Ouvrir un fichier .ino de la bibliothèque issu de Github.
Ici pour faire simple je suis aller dans la bibliothèque décompressé et j’ai ouvert le fichier .ino du dossier Zigbee_On_Off_Light.
J’ai fait une copie du dossier avant de travailler dedans, comme cela il y avait déjà toute la structure de faite.
Puis j’ai configuré :
a - Aller dans fichier/préférences et indiquer l’url de gestionnaire de cartes supplémentaires suivantes : https://espressif.github.io/arduino-esp32/package_esp32_index.json
b - Aller dans / Outils / gestionnaire de cartes : installer : Adruino ESP32 Boards et ESP32 par Espressif systems (j’ai la version 3.2.0)
c - Aller dans Outils et mettre la configuration suivante :
- Carte : ESP32H2 Dev Module
- Port : sélectionner votre port usb de votre PC (possible de le trouver dans le gestionnaire des périphérique de windows)
- USB CDC On Boot : Enable (permet de regarder par la suite le moniteur serie et les logs)
- Core Débug Level : None (j’ai pas utilisé cet outil)
- Erase All Flash Béfore Sketech Upload : Disabled (permet de faire un hard reset, pour plus d’info voir le github où c’est bien expliqué)
- Flash Frequency : 64Mhz
- Flash Mode: QIO
- Flash Size: 4MB (32Mb)
- JTAG Adapter : Disabled
- Partition Scheme: Zigbee 4MB with spiffs
- Upload Speed: 921600
- Zigbee Mode : Zigbee ED (end Device)
4- J’ai remplacé le code existant par mon code *.ino :
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// ...
/**
* @brief Zigbee distance sensor using HC-SR04 - Simplified version
*
* Measures distance only, all calculations done in Home Assistant
* HC-SR04 ultrasonic sensor connected to:
* - Trigger: GPIO4
* - Echo: GPIO5
*/
#ifndef ZIGBEE_MODE_ED
#error "Zigbee end device mode is not selected in Tools->Zigbee mode"
#endif
#include "Zigbee.h"
#include <cmath>
/* Zigbee configuration */
#define TEMP_SENSOR_ENDPOINT_NUMBER 10
uint8_t button = BOOT_PIN;
// HC-SR04 pins
#define TRIGGER_PIN 4
#define ECHO_PIN 5
// Measurement limits (in cm)
#define MIN_DISTANCE_CM 5
#define MAX_DISTANCE_CM 200
// Reporting configuration
#define MEASURE_INTERVAL_MS 60000 // Mesure toutes les 60 secondes
#define MIN_CHANGE_CM 1.0 // Écart minimal pour rapporter
#define MAX_REPORT_INTERVAL_MS 300000 // Envoi forcé toutes les 5 minutes
ZigbeeTempSensor zbDistanceSensor = ZigbeeTempSensor(TEMP_SENSOR_ENDPOINT_NUMBER);
// Variables de suivi
float lastReportedDistance = -1;
unsigned long lastReportTime = 0;
/************************ HC-SR04 Functions *****************************/
float measureDistance() {
digitalWrite(TRIGGER_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIGGER_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIGGER_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH, 30000); // timeout 30 ms max
if (duration == 0) {
Serial.println("HC-SR04: No echo received");
return -1;
}
float distance = (duration * 0.0343) / 2.0;
if (distance < MIN_DISTANCE_CM || distance > MAX_DISTANCE_CM) {
Serial.printf("HC-SR04: Out of range: %.2f cm\n", distance);
return -1;
}
return distance;
}
bool shouldReport(float currentDistance) {
unsigned long currentTime = millis();
if (currentTime - lastReportTime > MAX_REPORT_INTERVAL_MS) {
return true;
}
if (lastReportedDistance < 0) {
return true;
}
if (fabs(currentDistance - lastReportedDistance) >= MIN_CHANGE_CM) {
return true;
}
return false;
}
/************************ Sensor task *****************************/
static void sensor_task(void *arg) {
for (;;) {
float distance = measureDistance();
if (distance > 0) {
Serial.printf("Distance: %.2f cm\n", distance);
if (shouldReport(distance)) {
Serial.printf("Reporting: %.2f cm (change: %.2f)\n",
distance, fabs(distance - lastReportedDistance));
zbDistanceSensor.setTemperature(distance);
lastReportedDistance = distance;
lastReportTime = millis();
}
} else {
Serial.println("Measurement error");
if (lastReportedDistance >= 0) {
zbDistanceSensor.setTemperature(-1);
lastReportedDistance = -1;
lastReportTime = millis();
}
}
delay(MEASURE_INTERVAL_MS);
}
}
/********************* Arduino functions **************************/
void setup() {
Serial.begin(115200);
pinMode(button, INPUT_PULLUP);
pinMode(TRIGGER_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
digitalWrite(TRIGGER_PIN, LOW);
Serial.println("HC-SR04 Distance Sensor - Simplified Version");
Serial.printf("Measurement range: %d-%d cm\n", MIN_DISTANCE_CM, MAX_DISTANCE_CM);
Serial.printf("Reporting threshold: %.1f cm change\n", MIN_CHANGE_CM);
zbDistanceSensor.setManufacturerAndModel("Espressif", "HC-SR04-Simple");
zbDistanceSensor.setMinMaxValue(MIN_DISTANCE_CM, MAX_DISTANCE_CM);
zbDistanceSensor.setTolerance(0.5);
Zigbee.addEndpoint(&zbDistanceSensor);
Zigbee.setRebootOpenNetwork(60);
Serial.println("Starting Zigbee...");
if (!Zigbee.begin()) {
Serial.println("Zigbee failed to start!");
ESP.restart();
}
Serial.println("Connecting to Zigbee network...");
while (!Zigbee.connected()) {
Serial.print(".");
delay(100);
}
Serial.println("\nConnected to Zigbee network!");
zbDistanceSensor.setReporting(60, 300, 1); // min 60s, max 300s, delta 1cm
xTaskCreate(sensor_task, "sensor", 4096, NULL, 10, NULL);
Serial.println("Distance sensor ready!");
}
void loop() {
if (digitalRead(button) == LOW) {
delay(100);
int startTime = millis();
while (digitalRead(button) == LOW) {
delay(50);
if ((millis() - startTime) > 3000) {
Serial.println("Factory reset in 1s...");
delay(1000);
Zigbee.factoryReset();
}
}
Serial.println("Manual report triggered");
zbDistanceSensor.reportTemperature();
}
delay(100);
}
On voit dans ce code que :
- on va chercher les librairie zigbee.h (issu du github) et cmath
- J’ai défini le endpoint sur 10
- J’ai défini les Pin du HC-SR4 comme sur le montage réalisé.
- Ensuite j’ai créer une validation des données dans une limite, pas besoin de surcharger l’esp avec des mesures fausses. Ma cuve fait 1000 l et cubique, j’ai limité la mesure entre 5cm et 2 m, j’aurais pu réduire à 1m le max.
- Je déclare la variable : ZigbeeTempSensor zbDistanceSensor = ZigbeeTempSensor, c’est là que nous détournons le capteur de température pour envoyer une distance.
- après j’ai configuré le capteur ultrason, comme dans les docs
- configuration du sensor avec une vérification si la distance à changer de plus de 1cm.
- Dans la partie Arduino fonction :
On défini ce qui est envoyé en zigbee et la fréquence d’envoi : 60 secondes et un envoi forcé toutes les 5 minutes.
J’ai ajouté : Zigbee.setRebootOpenNetwork(60) afin qu’à chaque boot, il ouvre la connexion pour faciliter l’appareillage.
Pour finir, j’ai laissé la fonction de reset sur un appui de plus de 3 secondes sur le bouton de l’esp.
5 - J’ai compilé et téléversé à mon Esp32-h2 connecté au COM6 de mon pc en usb.
A la fin j’ai débranché l’usb.
6 - Je suis allé dans home assistant et dans l’onglet zigbee2mqtt.
J’ai lancé l’appareillage, puis j’ai connecté l’usb pour alimenter mon esp32-h2.
Zigbee2mqtt à reconnu directement l’objet et j’ai vérifier dans expose qu’il y avait bien une remonté d’information.
J’ai aussi vérifier dans la fenetre Moniteur serie d’Arduino ce qui était emis.
Une fois tout bien connecté, nous avons donc une mesure de distance en cm qui est envoyé à zigbee2mqtt comme une température en °C.
7 - J’ai créé un template dans la configuration de home assistant :
template:
- sensor:
# ---- Mesure cuve Zigbee ----
- name: "Hauteur d'eau cuve"
unit_of_measurement: "cm"
state: "{{ states('sensor.0x744dbdfffe63e790_temperature') }}"
- name: "Volume d'eau cuve"
unit_of_measurement: "L"
state: >
{% set hauteur = states('sensor.0x744dbdfffe63e790_temperature') | float(0) %}
{% set hauteur_max = 94 %}
{% set volume_max = 930.6 %}
{% if hauteur > 0 and hauteur <= hauteur_max %}
{{ (volume_max * (hauteur / hauteur_max)) | round(1) }}
{% else %}
0
{% endif %}
- name: "Niveau d'eau cuve"
unit_of_measurement: "%"
state: >
{% set hauteur = states('sensor.0x744dbdfffe63e790_temperature') | float(0) %}
{% set hauteur_max = 94 %}
{% if hauteur > 0 and hauteur <= hauteur_max %}
{{ ((hauteur / hauteur_max) * 100) | round(1) }}
{% else %}
0
{% endif %}
Cela permet d’avoir 3 sensors :
- Hauteur d’eau cuve : récupère l’information de mqtt et zigbee2mqtt et transforme le °C en cm
- Volume d’eau en L, converti la distance en l avec les dimensions de la cuve
- Niveau d’eau cuve : permet de connaitre le pourcentage de remplissage.
8 - J’ai créer une card lovelace :
type: glance
title: Cuve d'eau
entities:
- entity: sensor.hauteur_d_eau_cuve
name: Hauteur (cm)
icon: mdi:water
- entity: sensor.volume_d_eau_cuve
name: Volume (L)
icon: mdi:cup-water
- entity: sensor.niveau_d_eau_cuve
name: Niveau (%)
icon: mdi:gauge
Ce qui permet d’afficher les informations sur un tableau de bord :
J’espère que cela vous aidera.
Maintenant je vais essayer de faire un fichier ino propre sans détourner un cluster.
Je vous tiendrais au courant.
Bien joué, ça avance.
pour les clusters regarde ici
un exemple de def
zbAnalog[i].addAnalogOutput(); // type de cluster
zbAnalog[i].setAnalogOutputApplication(ESP_ZB_ZCL_AO_APP_TYPE_OTHER); //le type time other
zbAnalog[i].setAnalogOutputDescription("Tempo ");
zbAnalog[i].setAnalogOutputResolution(1); // résolution