CODE C++
Composants personnalisés C++ pour ESPHome & Home Assistant
Utilitaires Système
Classes C++ prêtes à l'emploi pour enrichir vos firmwares ESPHome avec des fonctionnalités avancées non disponibles nativement.
⏱️ UptimeCalculator — Durée de fonctionnement lisible
All-in-One#pragma once
#include "esphome.h"
class UptimeCalculator {
public:
static std::string calculate_uptime() {
uint32_t uptime_seconds = millis() / 1000;
uint32_t days = uptime_seconds / (24 * 3600);
uptime_seconds = uptime_seconds % (24 * 3600);
uint32_t hours = uptime_seconds / 3600;
uptime_seconds = uptime_seconds % 3600;
uint32_t minutes = uptime_seconds / 60;
uptime_seconds = uptime_seconds % 60;
char buffer[50];
snprintf(buffer, sizeof(buffer), "%u days %u hours %u min", days, hours, minutes);
return std::string(buffer);
}
};
⚙️ Intégration dans ESPHome (YAML)
ESPHome# Placer uptime_calculator.h dans le même dossier que ce fichier .yaml
# ou dans esphome/custom_components/
esphome:
name: domo30
includes:
- uptime_calculator.h
# Capteur texte exposé dans Home Assistant
text_sensor:
- platform: template
name: "Uptime"
id: uptime_text
icon: "mdi:clock-outline"
update_interval: 60s
lambda: |-
return UptimeCalculator::calculate_uptime();
📖 Fonctionnement & détails techniques
Référence| Élément | Type | Description |
|---|---|---|
millis() |
uint32_t | Temps en ms depuis le démarrage de l'ESP32 (déborde après ~49 jours) |
uptime_seconds |
uint32_t | Secondes totales depuis le boot, converties progressivement en j/h/min |
snprintf |
char[50] | Formatage sécurisé sans allocation dynamique — compatible ESP32 |
| Retour | std::string | Ex : "3 days 14 hours 22 min" |
platform: uptime qui gère le dépassement automatiquement.
Composant I²C Personnalisé
Classe de base pour interfacer n'importe quel capteur ou périphérique I²C via ESPHome : lecture/écriture de registres, masquage de bits, compatible Wire (Arduino) et bus ESPHome natif.
🔌 CustomI2CDevice — Lecture & écriture de registres I²C
All-in-One#include "esphome.h"
#include <Wire.h>
class CustomI2CDevice : public Component {
public:
CustomI2CDevice(uint8_t address) : address_(address) {}
// Lire un registre et le retourner en float
float get_register_as_float(uint8_t reg) {
uint8_t data[2];
if (read_register(reg, data, 2)) {
uint16_t raw_value = (data[0] << 8) | data[1];
return static_cast<float>(raw_value);
}
return NAN;
}
// Modifier les bits d'un registre
void modify_bits(uint8_t reg, uint8_t mask) {
uint8_t data;
if (read_register(reg, &data, 1)) {
data |= mask;
write_register(reg, &data, 1);
}
}
protected:
bool read_register(uint8_t reg, uint8_t* data, uint8_t len) {
Wire.beginTransmission(address_);
Wire.write(reg);
if (Wire.endTransmission(false) != 0) {
return false;
}
Wire.requestFrom(address_, len);
for (uint8_t i = 0; i < len; ++i) {
if (Wire.available()) {
data[i] = Wire.read();
}
}
return true;
}
bool write_register(uint8_t reg, const uint8_t* data, uint8_t len) {
Wire.beginTransmission(address_);
Wire.write(reg);
for (uint8_t i = 0; i < len; ++i) {
Wire.write(data[i]);
}
return Wire.endTransmission() == 0;
}
private:
uint8_t address_;
};
⚙️ Intégration dans ESPHome (YAML)
ESPHome# Placer custom_i2c_device.h dans le même dossier que ce fichier .yaml
esphome:
name: domo30
includes:
- custom_i2c_device.h
# Déclaration du bus I2C (SDA/SCL selon votre câblage)
i2c:
sda: GPIO21
scl: GPIO22
scan: true # Liste les adresses trouvées dans les logs au démarrage
frequency: 400kHz
# Exemple : lecture du registre 0x00 d'un capteur à l'adresse 0x48
sensor:
- platform: custom
lambda: |-
auto device = new CustomI2CDevice(0x48);
App.register_component(device);
return {device};
sensors:
- name: "Valeur Registre I2C"
unit_of_measurement: ""
accuracy_decimals: 0
# Exemple : modification de bits sur le registre de configuration 0x01
# (à appeler depuis une automation ou un script ESPHome)
# device.modify_bits(0x01, 0b00000100);
📖 Référence des méthodes
Référence| Méthode | Paramètres | Description |
|---|---|---|
get_register_as_float(reg) |
uint8_t reg | Lit 2 octets au registre reg, les combine en uint16 et retourne en float. Retourne NAN si erreur. |
modify_bits(reg, mask) |
uint8_t reg, uint8_t mask | Lit le registre, applique un OR avec le masque (met les bits à 1) puis réécrit. Permet de configurer des bits sans écraser les autres. |
read_register(reg, data, len) |
uint8_t*, uint8_t len | Lecture raw de len octets depuis le registre reg. Retourne false si l'adresse I2C ne répond pas. |
write_register(reg, data, len) |
const uint8_t*, uint8_t len | Écriture raw de len octets au registre reg. Retourne false si NACK (périphérique absent ou occupé). |
false (repeated START) évite de libérer le bus entre l'envoi du registre et la lecture des données —
indispensable pour les capteurs qui exigent une transaction atomique (ex. ADS1115, MPU-6050).
Calcul du Point de Rosée
Formule de Magnus–Tetens pour calculer le point de rosée (°C) à partir de la température et de l'humidité relative. Utile avec les capteurs BME280, SHT30, DHT22 pour détecter les risques de condensation.
💧 DewpointCalculator — Point de rosée (formule Magnus)
All-in-One#pragma once
#include "esphome.h"
#include <cmath>
class DewpointCalculator {
public:
static float calculate_dewpoint(float temperature, float humidity) {
float humidity_ratio = humidity / 100.0f;
float numerator = 243.5f * (log(humidity_ratio) +
((17.67f * temperature) / (243.5f + temperature)));
float denominator = 17.67f - log(humidity_ratio) -
((17.67f * temperature) / (243.5f + temperature));
return numerator / denominator;
}
};
⚙️ Intégration dans ESPHome (YAML)
ESPHome# Placer dewpoint_calculator.h dans le même dossier que ce fichier .yaml
esphome:
name: domo30
includes:
- dewpoint_calculator.h
# Capteurs source : température et humidité (ex. BME280)
sensor:
- platform: bme280
temperature:
name: "Température"
id: temp_sensor
humidity:
name: "Humidité"
id: hum_sensor
address: 0x76
update_interval: 30s
# Capteur calculé : point de rosée
- platform: template
name: "Point de Rosée"
id: dewpoint_sensor
unit_of_measurement: "°C"
accuracy_decimals: 1
icon: "mdi:water-thermometer"
update_interval: 30s
lambda: |-
if (isnan(id(temp_sensor).state) || isnan(id(hum_sensor).state))
return NAN;
return DewpointCalculator::calculate_dewpoint(
id(temp_sensor).state,
id(hum_sensor).state
);
📖 Formule & plage de validité
Référence| Paramètre | Valeur / Type | Description |
|---|---|---|
temperature |
float, °C | Température ambiante mesurée. Plage valide : 0 °C à 60 °C |
humidity |
float, %HR | Humidité relative en %. Doit être > 0 (log(0) = −∞) |
Constantes a=17.67, b=243.5 |
Magnus–Tetens | Coefficients empiriques valables de 0 °C à 60 °C (erreur < 0,1 °C) |
| Retour | float, °C | Point de rosée en °C. Ex : 20 °C / 60 %HR → ~12,0 °C |
log(0) est indéfini — le code YAML protège avec un test isnan(),
mais la classe C++ retournera -inf si humidity = 0. Ajouter une garde si ce cas peut se produire.
Filtre de Kalman
Lissage optimal des mesures bruitées (température, pression, courant…) sans délai introduit par une moyenne glissante. Le filtre estime l'état réel en pondérant dynamiquement la mesure et la prédiction selon leur niveau de bruit respectif.
📐 Principe en bref
Théorie
L'incertitude P augmente de Q : le filtre admet que l'état peut avoir dérivé depuis la dernière mesure.
K = P / (P + R) — proche de 1 si le modèle est incertain, proche de 0 si la mesure est bruitée. Il dose la confiance.
L'estimation x se déplace d'un fraction K vers la mesure réelle, et P est réduit en proportion.
Q grand / R petit → le filtre suit la mesure de près, lissage léger, réactivité haute.
📉 KalmanFilter — Lissage 1D scalaire
All-in-One#pragma once
class KalmanFilter {
private:
float x; // Estimation de l'état
float P; // Incertitude de l'état
float Q; // Bruit de processus
float R; // Bruit de mesure
float K; // Gain de Kalman
public:
KalmanFilter(float process_noise, float measurement_noise,
float initial_estimate, float initial_uncertainty) {
Q = process_noise;
R = measurement_noise;
x = initial_estimate;
P = initial_uncertainty;
}
float update(float measurement) {
// Prédiction : l'incertitude augmente
P += Q;
// Gain de Kalman : dose confiance modèle / mesure
K = P / (P + R);
// Correction : on déplace x vers la mesure
x += K * (measurement - x);
// L'incertitude diminue après observation
P *= (1 - K);
return x;
}
};
⚙️ Intégration dans ESPHome (YAML)
ESPHome# Placer kalman_filter.h dans le même dossier que ce fichier .yaml
esphome:
name: domo30
includes:
- kalman_filter.h
# Instance globale : persiste entre les appels de la lambda
globals:
- id: kf_temp
type: KalmanFilter*
restore_value: false
initial_value: "new KalmanFilter(0.1, 2.0, 20.0, 1.0)"
# Q R x0 P0
# Q=0.1 → modèle stable (temp varie lentement)
# R=2.0 → capteur moyennement bruité
# x0=20 → estimation initiale 20 °C
# P0=1 → incertitude initiale faible
sensor:
- platform: bme280
temperature:
name: "Température brute"
id: temp_raw
address: 0x76
update_interval: 10s
# Capteur filtré par Kalman
- platform: template
name: "Température filtrée (Kalman)"
unit_of_measurement: "°C"
accuracy_decimals: 2
icon: "mdi:thermometer"
update_interval: 10s
lambda: |-
return id(kf_temp)->update(id(temp_raw).state);
📋 Réglage des paramètres
Référence| Paramètre | Rôle | Valeurs typiques |
|---|---|---|
Q — bruit processus |
Vitesse de variation attendue de la grandeur | 0.01–0.5 pour température, 1–5 pour courant |
R — bruit mesure |
Niveau de bruit du capteur (datasheet : σ²) | 0.5–5 pour BME280, 10–50 pour ADC 12 bits |
x₀ — estimation initiale |
Valeur de départ (converge rapidement) | Valeur nominale attendue (ex. 20 °C) |
P₀ — incertitude initiale |
Confiance accordée à x₀ | 1.0 si x₀ est plausible, 100 si totalement inconnu |
Documentation ESPHome
Référence complète des composants personnalisés C++ dans ESPHome.
Docs ESPHome