Contents
Intro
Dans un article précédent, je décrivais une deuxieme version de routeur PV développé sous ESP Home donc complètement intégré à HA.
Dans cette version le programme a été totalement refondu avec un principe de régulation inédit.
Vous pouvez que aussi consulter cet article décrivant une première version de routeur solaire, vous y trouverez le matériel utilisé.
Upgrade:
- 15/10/2025: Refonte d’une partie du code avec intégration des notifications Telegram et séparation de la table étalonnage.
Pourquoi un Routeur Solaire ?
Depuis l’installation de mes panneaux photovoltaïques, je cherchais une solution pour maximiser l’utilisation de l’énergie produite, plutôt que de l’injecter dans le réseau à un tarif dérisoire (environ 0,10 €/kWh en France). Mon objectif : rediriger le surplus d’énergie vers mon chauffe-eau électrique pour chauffer l’eau gratuitement. C’est ainsi que j’ai conçu un routeur solaire photovoltaïque basé sur ESPHome, un ESP32, et un gradateur AC, qui ajuste précisément la puissance envoyée à l’ECS (eau chaude sanitaire). Dans cet article, je vous détaille la configuration de mon système solaire, le fonctionnement du programme ESPHome, les quatre modes de régulation, le calcul de la puissance disponible, et comment l’ECS est alimenté en amont sur le grid de l’onduleur.
Un routeur solaire est un dispositif intelligent qui détecte l’excédent d’énergie produit par vos panneaux photovoltaïques et le redirige vers un appareil consommateur, comme un chauffe-eau, pour optimiser l’autoconsommation. Avec des tarifs de rachat d’électricité solaire peu attractifs, utiliser directement l’énergie produite est bien plus rentable.
Mon routeur solaire, surnommé esp176_routeur, utilise un ESP32 programmé avec ESPHome pour :
- Monitorer la production solaire, la consommation domestique, et l’état des batteries via MQTT.
- Réguler la puissance envoyée au chauffe-eau en fonction du surplus disponible.
- Garantir la sécurité avec des contrôles de température, de production minimale, et d’état de charge.
- S’intégrer à Home Assistant pour un suivi en temps réel, des réglages personnalisés, et des notifications Telegram.
Configuration Solaire
Mon installation photovoltaïque est conçue pour couvrir une partie de ma consommation domestique tout en stockant l’énergie dans des batteries pour une utilisation ultérieure. Voici les détails :
- Panneaux solaires : Puissance crête totale de 7.2 kWc.
- Onduleur : Un Victron MultiPlus II 48/5000, qui gère la conversion DC/AC, la charge des batteries, et l’injection/soutirage sur le réseau. L’onduleur est connecté à un Cerbo GX pour le monitoring et la publication des données via MQTT.
- Batteries : D’une capacité totale de 12 kWh, elles permettent de stocker l’excédent pour les périodes sans soleil.
- Chauffe-eau (ECS) : Un chauffe-eau électrique de 300 L avec une résistance de 3 kW, alimenté en amont sur le grid de l’onduleur (côté réseau AC, avant l’onduleur). Cela signifie que l’ECS consomme soit l’énergie solaire excédentaire, soit l’énergie du réseau si nécessaire, sous le contrôle du routeur.
Le Cerbo GX publie les données de l’onduleur (production, consommation, état des batteries) sur un broker MQTT, que l’ESP32 récupère pour calculer la puissance à injecter dans l’ECS.
Matériel du Routeur Solaire
Le routeur est construit autour d’un ESP32 DevKit, choisi pour sa puissance, ses multiples GPIO, et sa compatibilité avec ESPHome. Voici les composants principaux :
- Gradateur AC (dimmer) : Piloté via les broches GPIO33 (gate) et GPIO34 (zero-cross), il ajuste la puissance envoyée à l’ECS de 0 à 3 kW.
- Sonde de température Dallas DS18B20 : Connectée à GPIO27, elle mesure la température du radiateur pour éviter la surchauffe.
- Écran LCD 20×4 (PCF8574) : Relié via I2C (GPIO21/SDA, GPIO22/SCL), il affiche la puissance réseau, la sortie triac, la température, et le mode.
- Relais : Connecté à GPIO5, il s’active en cas de surplus important pour alimenter un autre appareil (par exemple, une pompe à chaleur).
- LEDs : Une LED jaune (GPIO26) indique l’état du routeur, une LED rouge (GPIO25) signale une injection réseau.
- Alimentation 5V : Alimente l’ESP32 et les composants.
Le tout est installé dans un boîtier DIN dans mon tableau électrique, avec une connexion Wi-Fi pour l’intégration à Home Assistant.
Programme ESPHome : Une Régulation Intelligente
Le cœur du routeur est un programme ESPHome qui gère la régulation, l’affichage, les logs, et l’intégration domotique. Voici une explication détaillée des éléments clés.
Les Quatre Modes de Fonctionnement
Le routeur propose quatre modes, sélectionnables via un sélecteur dans Home Assistant (select.esp176_mode_fonctionnement_routeur) :
- Mode Auto :
- Description : Le routeur régule automatiquement la puissance envoyée à l’ECS en fonction du surplus solaire, tout en respectant des conditions de sécurité :
- Production solaire supérieure au seuil (
seuil_prod, par exemple 100 W). - État de charge des batteries (SOC) supérieur au seuil (
seuil_soc, par exemple 50 %). - Température du radiateur inférieure au maximum (
tmax, par exemple 75 °C). - Routeur validé (
switch.esp176_valid_routeuractivé).
- Production solaire supérieure au seuil (
- Comportement : Si toutes les conditions sont remplies, le script
regulation_interpolationcalcule la puissance disponible (p_dispo) et ajuste la sortie du triac (striac) via le gradateur. Sinon, le triac est désactivé (striac = 0).
- Description : Le routeur régule automatiquement la puissance envoyée à l’ECS en fonction du surplus solaire, tout en respectant des conditions de sécurité :
- Mode Manuel :
- Description : Permet de fixer manuellement la sortie du triac (de 0 à 100 %) via un réglage dans Home Assistant (
number.esp176_consigne_triac_en_manu). - Comportement : Le triac prend la valeur définie, indépendamment de la production ou de l’état des batteries. Utile pour des tests ou pour forcer une chauffe. Le routeur reste sécurisé par la validation (
validrouteur).
- Description : Permet de fixer manuellement la sortie du triac (de 0 à 100 %) via un réglage dans Home Assistant (
- Mode Arrêt :
- Description : Désactive complètement le routeur.
- Comportement : Le triac est mis à 0, le gradateur est éteint, et aucune puissance n’est envoyée à l’ECS. Le mode est publié comme « OFF » (
text_sensor.esp176_mode_regulation).
- Mode Étalonnage :
- Description : Utilisé pour établir une table de correspondance entre la sortie du triac (0 à 100 %) et la puissance consommée par l’ECS (
puecs). - Comportement : Le script
etalonnage_striacincrémente la sortie du triac par pas de 1 % toutes les 10 secondes, mesure la puissance ECS, et enregistre les données via l’intégration « files » de HA. Ces données alimenteront la table table_puissance pour une régulation précise.
- Description : Utilisé pour établir une table de correspondance entre la sortie du triac (0 à 100 %) et la puissance consommée par l’ECS (
Calcul de la Puissance Disponible
Le calcul de la puissance disponible (table_puissance) dépend de l’état de charge des batteries, déterminé par l’état du bus VE (etatbus_ve, publié via MQTT) :
- Mode Bulk (etatbus_ve = 3) :
- Les batteries sont en charge rapide, et une partie du surplus doit leur être réservée.
- Formule :
p_dispo = pu_prod - conso_maison - res_pubattpu_prod: Puissance produite par les panneaux solaires (par exemple, 2000 W).conso_maison: Consommation domestique (par exemple, 800 W).res_pubatt: Réserve pour les batteries, configurable dans Home Assistant (number.esp176_reserve_charge_batteries, par exemple 500 W).
- Exemple : Si
pu_prod = 2000 W,conso_maison = 800 W,res_pubatt = 500 W, alorsp_dispo = 2000 - 800 - 500 = 700 W. - La réserve (
res_pubatt) est publiée comme consigne de charge (sensor.esp176_cons_batt_en_cours).
- Mode Absorption (etatbus_ve = 4) ou Float (etatbus_ve = 5) :
- Les batteries sont presque pleines ou pleines, et la puissance des batteries (
pu_batteries) peut être négative (décharge) ou positive (charge lente). - Formule :
p_dispo = pu_prod - conso_maison + pu_batteriespu_batteries: Puissance des batteries, négative en décharge (par exemple, -200 W si les batteries soutiennent la maison).
- Exemple : Si
pu_prod = 2000 W,conso_maison = 800 W,pu_batteries = -200 W, alorsp_dispo = 2000 - 800 + (-200) = 1000 W. - La puissance des batteries (négative) est publiée comme
sensor.esp176_cons_batt_en_cours.
- Les batteries sont presque pleines ou pleines, et la puissance des batteries (
- Autres états :
- Si
etatbus_ven’est ni 3, 4, ni 5, alorsp_dispo = 0, et aucune puissance n’est envoyée à l’ECS.
- Si
La puissance disponible est limitée par la puissance maximale du triac (pmax, par exemple 3000 W) et convertie en pourcentage de sortie triac (striac) via une table de correspondance établie en mode Étalonnage. Une interpolation linéaire entre les valeurs de la table (p_dispo_table) garantit une régulation précise.
Sécurité et Contrôles
Le routeur inclut plusieurs sécurités :
- Température : La sonde Dallas surveille la température du radiateur. Si elle dépasse
tmax(par exemple, 75 °C), le triac est désactivé (temperatureok = false). - Production minimale : Le routeur ne s’active que si
pu_prod > seuil_prod(par exemple, 100 W). - État de charge (SOC) : Un seuil minimal (
seuil_soc, par exemple 50 %) avec une hystérésis de 2 % protège les batteries. - Relais de surproduction : Si le triac est à 90 % et que l’ECS consomme moins de 100 W pendant 5 minutes, un relais s’active pour alimenter un autre appareil.
- Validation : Le routeur ne fonctionne que si
validrouteurest activé.
Affichage LCD
Un écran LCD 20×4 affiche :
- Ligne 1 : Puissance réseau (
pureseau1) et ECS (puecs). - Ligne 2 : Sortie triac (
striac) et état du routeur (validrouteur: OK/NOK). - Ligne 3 : Température du radiateur (
temp_triac) et état de la température (temperatureok: OK/NOK). - Ligne 4 : Mode de fonctionnement (
Auto,Manu,Arret,Etalonnage).
Intégration Home Assistant
Les entités sont exposées dans Home Assistant via l’API ESPHome :
- Capteurs :
sensor.esp176_pu_disponible,sensor.esp176_sortie_triac,sensor.esp176_pu_reseau. - Sélecteurs :
select.esp176_mode_fonctionnement_routeur. - Nombres :
number.esp176_puissance_max_triac,number.esp176_seuil_soc. - Interrupteurs :
switch.esp176_valid_routeur,switch.esp176_relais.
Configuration du fichier log:
Il faut créer une entrée dans l’intégration « file », par exemple »/config/fichiers/etalonnage_routeur.csv »

Vous renommer son id « notify.etalonnage_routeur »

Apres etalonnage vous pourrez visualiser le résultat dans le fichier « etalonnage_routeur.csv » et le copier/coller dans le fichier « table_pu.yaml ».

Vue Synoptique de régulation

Code:
type: picture-elements
elements:
- entity: sensor.esp176_esp32_routeur_1r_mode_regulation
prefix: "Regul "
style:
background: null
color: white
font-size: 120%
left: 5%
top: 5%
transform: none
type: state-label
- entity: sensor.mp2_affichage_status_bus_ve
prefix: "Bus Ve= "
style:
background: null
color: white
font-size: 120%
left: 60%
top: 5%
transform: none
type: state-label
- entity: sensor.esp176_esp32_routeur_1r_pu_disponible
style:
background: none
color: white
font-size: 100%
left: 47%
top: 31%
transform: none
type: state-label
prefix: "P= "
- entity: sensor.esp176_esp32_routeur_1r_cons_batt_en_cours
prefix: "Bat= "
style:
background: null
color: white
font-size: 100%
left: 0%
top: 46%
transform: none
type: state-label
- entity: sensor.esp176_routeur_sortie_triac
prefix: ""
style:
background: null
color: white
font-size: 100%
left: 85%
top: 31%
transform: none
type: state-label
- entity: sensor.esp176_esp32_routeur_1r_p_ecs_jsymk
prefix: "ECS: "
type: state-label
style:
background: null
color: white
font-size: 100%
left: 47%
top: 48%
transform: none
- entity: sensor.mp2_prod_totale_mqtt
prefix: "Prod= "
type: state-label
style:
background: null
color: white
font-size: 100%
left: 0%
top: 26%
transform: none
- entity: sensor.mp2_conso_out1_mqtt
prefix: "Mais= "
type: state-label
style:
background: null
color: white
font-size: 100%
left: 0%
top: 36%
transform: none
- entity: sensor.esp176_routeur_temp_triac
prefix: "T° Triac= "
type: state-label
style:
background: null
color: white
font-size: 100%
left: 70%
top: 60%
transform: none
- entity: sensor.esp126_temp_ecs
prefix: "T° ECS= "
type: state-label
style:
background: null
color: white
font-size: 100%
left: 70%
top: 70%
transform: none
image: /local/images/pid_routeur_v4.png
Fichier image: https://github.com/remycrochon/home-assistant/tree/master/www/images
Alimentation de l’ECS
L’ECS est alimenté en amont sur le grid de l’onduleur, c’est-à-dire sur le réseau AC avant l’entrée de l’onduleur MultiPlus II. Cela signifie que :
- L’ECS peut consommer l’énergie solaire excédentaire gérée par le routeur.
- Si le surplus solaire est insuffisant, l’ECS tire l’énergie du réseau public.
- Le routeur ajuste la puissance via le gradateur pour minimiser le soutirage réseau, en s’appuyant sur les données de l’onduleur (publiées via MQTT).
Cette configuration garantit une intégration fluide avec l’onduleur, qui priorise l’alimentation de la maison et des batteries avant de laisser le surplus à l’ECS.
Installation et Mise en Route
- Assemblage : Connectez l’ESP32, le gradateur, la sonde Dallas, l’écran LCD, et le relais dans un boîtier DIN.
- Câblage : Branchez le gradateur à l’ECS, la sonde au radiateur, et le relais à un consommateur secondaire. Assurez-vous que l’ECS est connectée en amont de l’onduleur.
- Firmware : Flashez l’ESP32 avec le YAML ESPHome, en ajustant les secrets (Wi-Fi, MQTT) et l’IP statique (par ex 192.168.0.176).
- Configuration Home Assistant : Ajoutez l’appareil ESPHome et réglez les seuils (
pmax,tmax,seuil_soc,seuil_prod,res_pubatt). - Étalonnage : Lancez le mode Étalonnage pour générer la table de correspondance, puis passez en mode Auto.
Résultats et Perspectives
Depuis son installation, le routeur a boosté mon autoconsommation. Par une belle journée, je redirige jusqu’à 3 kW de surplus vers l’ECS, réduisant l’injection réseau à presque zéro. Le relais de surproduction s’active occasionnellement il n’est pas utilisé actuellement.
Voici une vue Graphana, vous pouvez constater la réactivité du routeur: la puissance ECS suit de très prêt la puissance disponible!

Quelques idées d’amélioration :
- Ajouter un capteur de température d’eau pour arrêter le routeur quand l’ECS est chaude.
- Intégrer des prévisions météo pour anticiper la production.
- Ajouter un second gradateur pour un autre appareil.
Conclusion
Ce routeur solaire DIY, basé sur ESPHome et un ESP32, est une solution puissante pour maximiser l’autoconsommation photovoltaïque. Avec ses quatre modes de fonctionnement, ses sécurités, et son intégration à Home Assistant, il offre une flexibilité et une efficacité remarquables. Si vous avez une installation solaire avec un onduleur Victron et un chauffe-eau, ce projet est parfait pour vous !
Partagez vos retours ou vos propres projets dans les commentaires ou sur le forum Home Assistant.
Annexes :
Codes ESPHome:
Le dernier code à jour est disponibles ici: https://github.com/remycrochon/home-assistant/blob/master/esphome/esp176-routeur.yaml
substitutions:
device_name: "esp176_routeur"
friendly_name: esp176
adress_ip: "192.168.0.176"
time_timezone: "Europe/Paris"
# Affectation des GPIO
GPIO_onewire: "GPIO27"
GPIO_sda: "GPIO21"
GPIO_scl: "GPIO22"
GPIO_tx: "GPIO17"
GPIO_rx: "GPIO16"
GPIO_Led_jaune: "GPIO26"
GPIO_Led_rouge: "GPIO25"
GPIO_Led_status: "GPIO32"
GPIO_Relais: "GPIO5"
# Dimmer
GPIO_Gate_pin: "GPIO33"
GPIO_ZC_pin: "GPIO34"
packages:
jsk: !include pack_esp176/jsk.yaml
table_pu: !include pack_esp176/table_pu.yaml
esphome:
name: ${device_name}
on_boot:
priority: -100
# Force mode auto et tempok au demarrage
then:
- binary_sensor.template.publish:
id: temperatureok
state: ON
esp32:
board: esp32dev
framework:
type: arduino
wifi:
networks:
- ssid: !secret wifi_esp
password: !secret mdpwifi_esp
reboot_timeout: 5min
manual_ip:
static_ip: ${adress_ip}
gateway: 192.168.0.254
subnet: 255.255.255.0
dns1: !secret dns1
dns2: !secret dns2
# Utilisez la LED bleue de l'appareil comme LED d'état, qui clignotera s'il y a des avertissements (lent) ou des erreurs (rapide)
status_led:
pin:
number: ${GPIO_Led_status} # led jaune
inverted: true
# Enable logging
logger:
baud_rate: 0
level: info
# modbus.component: INFO
# Enable Home Assistant API
api:
ota:
platform: esphome
web_server:
port: 80
version: 3
time:
- platform: sntp
id: sntp_time
timezone: Europe/Paris
servers:
- 0.pool.ntp.org
- 1.pool.ntp.org
- 2.pool.ntp.org
# Protocole I2C
i2c:
sda: ${GPIO_sda}
scl: ${GPIO_scl}
scan: True
id: bus_a
frequency: 400kHz
# Mosquitto Proxmox
mqtt:
broker: 192.168.0.37
username: !secret mqtt_ha_name
password: !secret mqtt_ha_pw
#internal_mqtt_default: internal
globals:
- id: p_dispo
type: float
restore_value: no
initial_value: '0'
- id: regul
type: std::string
restore_value: no
initial_value: '"Pas de régulation"'
- id: striac
type: float
restore_value: no
initial_value: '0'
# stocke temporairement le message à envoyer à telegram
- id: telegram_msg_buffer
type: std::string
restore_value: no
initial_value: '""'
# Sonde Temperature Dallas
one_wire:
- platform: gpio
pin: ${GPIO_onewire}
# déclaration des modes de fonctionnement dans des "input select"
select:
- platform: template
name: "Mode_Fonctionnement_routeur"
optimistic: true
restore_value: true
options:
- Auto
- Manu
- Arret
- Etalonnage
id: _Mode_Fonctionnement_routeur
on_value:
then:
- lambda: |-
char mess[128];
snprintf(mess, sizeof(mess), "Mode Fonctionnement Routeur: %s", id(_Mode_Fonctionnement_routeur).state.c_str());
ESP_LOGI("fichier", "Message: %s", mess);
id(_log_fichier).execute(mess); // Appelle le script _log_fichier avec le paramètre mess
# Passage en mode étalonnage
- if:
condition:
- lambda: 'return id(_Mode_Fonctionnement_routeur).state == "Etalonnage";'
then:
- script.execute: etalonnage_striac
# Passage en mode Manu on remet à Zero le valid Routeur et la consigne Manu
- if:
condition:
- lambda: 'return id(_Mode_Fonctionnement_routeur).state == "Manu";'
then:
- lambda: |-
id(ctriac_manu).publish_state(0);
- switch.turn_off: validrouteur
# Passage dans tous les modes on met à zéro le triac
- lambda: |-
id(striac) = 0;
- light.turn_off:
id: gradateur
- script.execute: calcul_injection
binary_sensor:
#Etat de la connection
- platform: status
name: "Status"
- platform: template
name: "Temp Ok"
id: temperatureok
- platform: template
name: "Seuil Prod Ok"
id: seuil_prod_ok
- platform: template
name: "Seuil SOC Ok"
id: seuil_soc_ok
# Input Number
number:
# seuil SOC validation routeur
- platform: template
name: "Consigne Triac en manu"
id: ctriac_manu
optimistic: true
restore_value: true
mode: box
min_value: 0
max_value: 100
unit_of_measurement: "%"
step: 1
icon: mdi:arrow-collapse-vertical
# Max sortie triac
- platform: template
name: "Puissance Max Triac"
id: pmax
optimistic: true
restore_value: true
mode: box
min_value: 10
max_value: 3000
unit_of_measurement: "W"
step: 1
icon: mdi:arrow-collapse-vertical
# Seuil MAX temperature
- platform: template
name: "T Max"
id: tmax
optimistic: true
restore_value: true
mode: box
min_value: 0
max_value: 75
unit_of_measurement: "C°"
step: 0.1
icon: mdi:arrow-collapse-vertical
# Consigne Régul sur Puissance Batteries en mode Bulk
- platform: template
name: "Reserve Charge Batteries"
id: res_pubatt
optimistic: true
restore_value: true
mode: box
min_value: 0
max_value: 2500
unit_of_measurement: "W"
step: 1
icon: mdi:arrow-collapse-vertical
# seuil SOC validation routeur
- platform: template
name: "Seuil SOC"
id: seuil_soc
optimistic: true
restore_value: true
mode: box
min_value: 0
max_value: 100
unit_of_measurement: "%"
step: 1
icon: mdi:arrow-collapse-vertical
# seuil Production Photovoltaique de validation routeur
- platform: template
name: "Seuil Production Val Routeur"
id: seuil_prod
optimistic: true
restore_value: true
mode: box
min_value: 100
max_value: 3000
unit_of_measurement: "W"
step: 1
icon: mdi:arrow-collapse-vertical
sensor:
- platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
name: "WiFi Signal dB"
id: wifi_signal_db
update_interval: 60s
entity_category: "diagnostic"
- platform: copy # Reports the WiFi signal strength in %
source_id: wifi_signal_db
name: "WiFi Signal Percent"
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
unit_of_measurement: "Signal %"
entity_category: "diagnostic"
device_class: ""
############### TEMPLATE ######################"
# Affichage dans HA et sur l'afficheur
# Puissance lue par le JSk- Négative en injection/Positive en soutirage
- platform: template
name: "Pu Reseau"
id: pureseau1
unit_of_measurement: "W"
state_class: "measurement"
accuracy_decimals: 0
# Sortie triac de 0à100%
- platform: template
name: "Sortie Triac"
id: afstriac
unit_of_measurement: "%"
state_class: "measurement"
accuracy_decimals: 2
# Pu disponible
- platform: template
name: "Pu Disponible"
id: afpdispo
unit_of_measurement: "W"
state_class: "measurement"
accuracy_decimals: 0
# Sensor Intermediaire pour synoptique
- platform: template
name: "Cons batt en Cours"
id: cons_batt_cours
state_class: "measurement"
unit_of_measurement: "W"
accuracy_decimals: 0
# Les MQTT sont déclarés dans le Node Red du Cerbo GX
# https://venus.local:1881/#flow/dbd727f16cbe7b5f
- platform: mqtt_subscribe
name: "Conso Maison"
id: conso_maison
topic: mp2/multiplus2/conso_out1
unit_of_measurement: "W"
state_class: "measurement"
accuracy_decimals: 2
filters:
- sliding_window_moving_average:
window_size: 10
send_every: 1
- platform: mqtt_subscribe
name: "Pu batterie"
id: pu_batteries
topic: mp2/batteries/puissance
unit_of_measurement: "W"
state_class: "measurement"
accuracy_decimals: 2
filters:
- sliding_window_moving_average:
window_size: 10
send_every: 1
- platform: mqtt_subscribe
name: "Pu Produite"
id: pu_prod
topic: mp2/multiplus2/prod_totale
unit_of_measurement: "W"
state_class: "measurement"
accuracy_decimals: 2
filters:
- sliding_window_moving_average:
window_size: 10
send_every: 1
- platform: mqtt_subscribe
name: "Soc"
id: soc
topic: mp2/batteries/soc
unit_of_measurement: "%"
state_class: "measurement"
accuracy_decimals: 2
filters:
- sliding_window_moving_average:
window_size: 10
send_every: 1
- platform: mqtt_subscribe
name: "Etat Bus VE"
id: etatbus_ve
topic: mp2/multiplus2/etatbusve
accuracy_decimals: 0
# lecture sensor Home Assistant
- platform: homeassistant
name: "Status Bus VE"
entity_id: sensor.mp2_status_bus_ve
id: statusbusve
- platform: homeassistant
name: "Tarif Num"
entity_id: sensor.linky_n_tarif
id: hc
- platform: homeassistant
name: "Pu Clim"
entity_id: sensor.ecocompteur_clim
id: pu_clim
# Sonde Temperature radiateur
- platform: dallas_temp
address: 0xeb012112e461b128
name: "Temp triac"
id: temp_triac
update_interval: 60s
filters:
- filter_out: NAN
# déclaration des "text_sensors"
text_sensor:
- platform: template
name: "Mode Regulation"
id: moderegul
switch:
- platform: gpio
name: "Relais"
pin: ${GPIO_Relais}
id: relais
- platform: template
name: "Valid Routeur"
id: validrouteur
optimistic: true
restore_mode: always_on
- platform: restart
name: "Restart"
output:
#LEDS --------------------------------------
- id: led_jaune
platform: gpio
pin: ${GPIO_Led_jaune}
- id: led_rouge
platform: gpio
pin: ${GPIO_Led_rouge}
# Pilotage du Dimmer
- platform: ac_dimmer
id: ecs
gate_pin: ${GPIO_Gate_pin}
method: leading
zero_cross_pin:
number: ${GPIO_ZC_pin}
mode:
input: true
inverted: yes
min_power: 5%
light:
- platform: monochromatic
name: "STriac"
output: ecs
id: gradateur
default_transition_length: 50ms
# Affichage
display:
- platform: lcd_pcf8574
dimensions: 20x4
address: 0x27
update_interval: 20s # Plus espacé pour alléger la charge CPU
lambda: |-
char ligne0[21];
char ligne1[21];
char ligne2[21];
char ligne3[21];
snprintf(ligne0, sizeof(ligne0), "Pr=%0.0fW Pe=%0.0fW", id(pureseau1).state, id(puecs).state);
snprintf(ligne1, sizeof(ligne1), "Tr=%0.1f%% V:%s", id(striac), id(validrouteur).state ? "OK" : "NOK");
snprintf(ligne2, sizeof(ligne2), "Tp=%0.1fc E:%s", id(temp_triac), id(temperatureok).state ? "OK" : "NOK");
snprintf(ligne3, sizeof(ligne3), "Mode:%s", id(_Mode_Fonctionnement_routeur).state.c_str());
it.print(0, 0, ligne0);
it.print(0, 1, ligne1);
it.print(0, 2, ligne2);
it.print(0, 3, ligne3);
interval:
- interval: 1s
then:
- script.execute: calcul_injection
- interval: 5s
then:
- script.execute: etat_production
- script.execute: calcul_relais_surprod
########################################################################""
script:
# Calcul du niveau de puissance à injecter dans le triac pilotant l'ECS
# En Auto: Conditions Initales de Démarrage:
# Mode de Fct=Auto
# Seuil Prod Ok
# Seuil SOC Ok
# temp Triac Ok
# Routeur Validé
- id: calcul_injection
mode: single
then:
- lambda: |-
// Pu production > Seuil de production
id(seuil_prod_ok).publish_state(id(pu_prod).state > id(seuil_prod).state);
// # Seuil de SOC (avec hysteresis de 2 %)
if (id(soc).state >= id(seuil_soc).state) {
id(seuil_soc_ok).publish_state(true);
} else if (id(soc).state < (id(seuil_soc).state - 2)) {
id(seuil_soc_ok).publish_state(false);
}
// Surveille température triac
if (id(temp_triac).state < (id(tmax).state - 2)) {
id(temperatureok).publish_state(true);
} else if (id(temp_triac).state >= id(tmax).state) {
id(temperatureok).publish_state(false);
}
// Log de débug
// ESP_LOGI("regulp", "P Prod; %.0f Seuil: %.2f",id(pu_prod).state,id(seuil_prod).state);
# Si conditions non Ok alors RAZ du Triac
- if:
condition:
or:
# Cas 1 : Mode Arret ==> OFF
- lambda: 'return id(_Mode_Fonctionnement_routeur).state == "Arret";'
# Cas 2 : Mode auto avec conditions NOK => OFF
- and:
- lambda: 'return id(_Mode_Fonctionnement_routeur).state == "Auto";'
- or:
- switch.is_off: validrouteur
- binary_sensor.is_off: temperatureok
- binary_sensor.is_off: seuil_prod_ok
- binary_sensor.is_off: seuil_soc_ok
then:
- lambda: |-
id(striac) = 0;
id(moderegul).publish_state("OFF");
id(afpdispo).publish_state(0);
id(cons_batt_cours).publish_state(0);
- light.turn_off: gradateur
- logger.log:
format: "Régulation OFF - Mode: %s - Striac: %.1f"
args: ['id(moderegul).state.c_str()', 'id(striac)']
level: DEBUG
# Si toutes les conditions OK alors on calcule la S triac
- if:
condition:
and:
# Cas 3 : Mode auto + toutes les conditions OK => PID actif
- lambda: 'return id(_Mode_Fonctionnement_routeur).state == "Auto";'
- switch.is_on: validrouteur
- binary_sensor.is_on: seuil_prod_ok
- binary_sensor.is_on: temperatureok
- binary_sensor.is_on: seuil_soc_ok
then:
# Vers script de régulation
- script.execute: regulation_interpolation
- light.turn_on:
id: gradateur
brightness: !lambda |-
return id(striac) / 100;
# Mode Manuel
- if:
condition:
and:
- lambda: 'return id(_Mode_Fonctionnement_routeur).state == "Manu";'
- switch.is_on: validrouteur
then:
- lambda: |-
// Application de striac avec sécurité
id(striac) = id(ctriac_manu).state;
// Publication des états
id(afpdispo).publish_state(0);
id(moderegul).publish_state("Manu");
id(cons_batt_cours).publish_state(0);
- light.turn_on:
id: gradateur
brightness: !lambda |-
return id(striac) / 100;
# Affichage STriac
- lambda: |-
id(afstriac).publish_state( id(striac)) ;
########################################################################""
# Principe de la régulation:
# Si Bus Ve en Bulk:, on partage le surplus entre la batterie (cons_batt) et l'ECS
# P Dispo= Prod-Conso Maison-Reserve batteries
# Si Bus Ve en Absortion ou Float, cela signifie que la batterie est chargée, alors:
# P Dispo= Prod-Conso Maison-Pu batteries (Negative en décharge)
# Sinon:
# P Dispo = 0
# La sortie triac est calculée en fonction de la P_Dispo en recherchant sa valeur dans la table de correspondance établie en mode étalonnage
- id: regulation_interpolation
mode: single
then:
- lambda: |-
float result = 0.0;
// Sélection de Consigne et Mesure
if (id(etatbus_ve).state == 3) {
id(p_dispo) = id(pu_prod).state - id(conso_maison).state - id(pu_clim).state - id(res_pubatt).state;
if (id(p_dispo) < 0.0) id(p_dispo) = 0.0;
id(cons_batt_cours).publish_state(id(res_pubatt).state);
id(regul) = "Sur Pu batteries Bulk";
} else if ((id(etatbus_ve).state == 4) || (id(etatbus_ve).state == 5)) {
id(p_dispo) = id(pu_prod).state - id(conso_maison).state - id(pu_clim).state + id(pu_batteries).state;
if (id(p_dispo) < 0.0) id(p_dispo) = 0.0;
id(cons_batt_cours).publish_state(id(pu_batteries).state * -1);
id(regul) = "Sur P Batt Absord/Float";
} else {
id(regul) = "Pas de régulation";
id(cons_batt_cours).publish_state(0);
id(p_dispo) = 0;
}
// Limite P dispo
id(p_dispo) = constrain(id(p_dispo), 0.0f, id(pmax).state);
// Interpolation dans la table
float striac_f = 0.0f;
for (size_t i = 1; i < id(table_puissance).size(); i++) {
float x0 = id(table_puissance)[i-1].first; // % Triac
float y0 = id(table_puissance)[i-1].second; // Puissance
float x1 = id(table_puissance)[i].first;
float y1 = id(table_puissance)[i].second;
if (id(p_dispo) <= y1) {
striac_f = x0 + (id(p_dispo) - y0) * (x1 - x0) / (y1 - y0);
break;
}
striac_f = x1;
}
// Sécurité
if (isnan(striac_f)) striac_f = 0.0;
id(striac) = constrain(striac_f, 0.0f, 100.0f);
// Publication
id(afpdispo).publish_state(id(p_dispo));
id(moderegul).publish_state(id(regul));
ESP_LOGI("regul", "p_dispo: %.2f, pu_prod: %.2f, conso_maison: %.2f, pu_clim: %.2f, pu_batt: %.2f, STriac: %.2f",
id(p_dispo), id(pu_prod).state, id(conso_maison).state, id(pu_clim).state, id(cons_batt_cours).state, id(striac));
########################################################################""
# Mode Etalonnage Increment S Triac toutes les 20 s pour laisser du temps à la puissance pour se stabiliser
- id: etalonnage_striac
mode: restart
then:
- lambda: |-
id(striac) = 0.0;
- while:
condition:
lambda: 'return id(striac) < 100.0;' # S'arrête après striac = 100
then:
- lambda: |-
id(striac) += 1.0; // Incrémente striac
ESP_LOGI("striac", "Valeur striac: %.2f", id(striac));
- light.turn_on:
id: gradateur
brightness: !lambda 'return id(striac) / 100.0;' # Normalise entre 0.0 et 1.0
- delay: 20s # Temporisation
- lambda: |-
ESP_LOGI("striac", "Valeur striac: %.2f Pu ECS %.0f", id(striac), id(puecs).state);
- lambda: |-
std::string mess = "{";
mess += std::to_string(id(striac)) + ",";
mess += std::to_string(id(puecs).state)+"}";
ESP_LOGI("fichier", "Message: %s", mess.c_str());
id(_log_etalonnage).execute(mess); // Appelle le script _log_message avec le paramètre mess
- lambda: |-
ESP_LOGI("striac", "Fin de l'étalonnage, striac = %.2f", id(striac));
########################################################################""
# ------------ Pilotage led
- id: etat_production
mode: single
then:
- if:
condition:
sensor.in_range:
id: pureseau1
below: -50
then:
- output.turn_on: led_rouge
else:
- output.turn_off: led_rouge
- if:
condition:
switch.is_on: validrouteur
then:
- output.turn_on: led_jaune
else:
- output.turn_off: led_jaune
########################################################################""
- id: calcul_relais_surprod
mode: single
then:
# Si sortie triac > 20 et Pu ECS >10, signifie que le triac est sans effet, pendant plus de 60s et que
# la température de l'ECS a atteint son max (Thermostat de l'ECS déclenché)
# alors on active le relais
# si Pu ECS > 10 alors on desactive le relais
- if:
condition:
- lambda: 'return (id(striac)>=90 && id(puecs).state<10);'
then:
- delay: 300s
- switch.turn_on: relais
- logger.log: "Relais Activé"
- if:
condition:
- lambda: 'return id(puecs).state >= 10;'
then:
- switch.turn_off: relais
- logger.log: "Relais Désactivé"
########################################################################""
- id: _log_fichier
parameters:
mess1: std::string # Type explicite pour ESPHome
then:
- lambda: |-
std::string mess = mess1; // Utilise directement mess1 sans horodatage
id(telegram_msg_buffer) = mess;
ESP_LOGI("log_message", "Telegram buffer: %s", id(telegram_msg_buffer).c_str());
- homeassistant.service:
action: notify.send_message
data:
entity_id: notify.log_esp176
message: !lambda 'return id(telegram_msg_buffer).c_str();'
########################################################################""
- id: _log_etalonnage
parameters:
mess1: std::string # Type explicite pour ESPHome
then:
- lambda: |-
std::string mess = mess1; // Utilise directement mess1 sans horodatage
id(telegram_msg_buffer) = mess;
ESP_LOGI("log_message", "Telegram buffer: %s", id(telegram_msg_buffer).c_str());
- homeassistant.service:
action: notify.send_message
data:
entity_id: notify.etalonnage_routeur
message: !lambda 'return id(telegram_msg_buffer).c_str();'
Code du fichier « jsk.yaml »
# Protocole du JSK
uart:
id: mod_bus
tx_pin: ${GPIO_tx} #17
rx_pin: ${GPIO_rx} #16
baud_rate: 38400
stop_bits: 1
# debug:
# direction: BOTH
# dummy_receiver: false
# after:
# timeout: 150ms
# sequence:
# - lambda: |-
# UARTDebug::log_string(direction, bytes);
modbus:
#flow_control_pin: 5
#send_wait_time: 200ms
id: modbus1
modbus_controller:
- id: jsymk
## the Modbus device addr
address: 0x1
modbus_id: modbus1
update_interval: 0.75s
command_throttle: 50ms
# setup_priority: -10
sensor:
# tension de l'alimentation
- platform: modbus_controller
modbus_controller_id: jsymk
id: Tension
#name: "Tension JSYMK"
address: 0x0048
unit_of_measurement: "V"
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.0001
register_count: 1
response_size: 4
# Intensité traversant le tore
- platform: modbus_controller
modbus_controller_id: jsymk
id: Itore
name: "I_ECS JSYMK"
address: 0x0049
unit_of_measurement: "A"
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.0001
register_count: 1
response_size: 4
state_class: measurement
# Puissance traversant le tore
- platform: modbus_controller
modbus_controller_id: jsymk
id: puecs
name: "P_ECS JSYMK"
address: 0x004A
unit_of_measurement: "W"
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.0001
- sliding_window_moving_average:
window_size: 10
send_every: 1
register_count: 1
response_size: 4
state_class: measurement
# Energie lue dans le tore
- platform: modbus_controller
modbus_controller_id: jsymk
id: energietore
name: "Energie ECS JSYMK"
address: 0x004B
unit_of_measurement: "kWh"
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.0001
register_count: 1
response_size: 4
state_class: total
# Energie lue dans le tore
- platform: modbus_controller
modbus_controller_id: jsymk
id: fptore
#name: "FP Tore JSYMK"
address: 0x004C
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.0001
register_count: 1
response_size: 4
# Energie NEG lue dans le tore
- platform: modbus_controller
modbus_controller_id: jsymk
id: energietoren
name: "Energie ECS Neg JSYMK"
address: 0x004D
unit_of_measurement: "kWh"
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.0001
register_count: 1
response_size: 4
state_class: total
# Sens du courant dans la pince
- platform: modbus_controller
modbus_controller_id: jsymk
id: senspince
#name: "Sens_Pince JSYMK"
address: 0x004E
register_type: holding
value_type: U_DWORD
bitmask: 0X00010000
filters:
- multiply: 1
register_count: 1
response_size: 4
# Sens du courant dans le tore
- platform: modbus_controller
modbus_controller_id: jsymk
id: senstor
#name: "Sens_Tore JSYMK"
address: 0x004E
register_type: holding
value_type: U_DWORD
accuracy_decimals: 0
bitmask: 0X01000000
filters:
- multiply: 1
register_count: 1
response_size: 4
# Fréquence de l'alimentation
- platform: modbus_controller
modbus_controller_id: jsymk
id: frequence
#name: "Frequence JSYMK"
address: 0x004F
unit_of_measurement: "hz"
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.01
register_count: 1
response_size: 4
# tension de l'alimentation
- platform: modbus_controller
modbus_controller_id: jsymk
id: Tension2
#name: "U_Reseau JSYMK"
address: 0x0050
unit_of_measurement: "V"
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.0001
register_count: 1
response_size: 4
# Intensité lue dans la pince
- platform: modbus_controller
modbus_controller_id: jsymk
id: Ireseau
#name: "I_Reseau JSYMK"
address: 0x0051
unit_of_measurement: "A"
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.0001
register_count: 1
response_size: 4
# puissance lue dans la pince
- platform: modbus_controller
modbus_controller_id: jsymk
id: pureseau
#name: "P_Reseau JSYMK"
address: 0x0052
unit_of_measurement: "W"
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.0001
register_count: 1
response_size: 4
on_value:
then:
- lambda: |-
if ( id(senspince).state == 1 ) {
id(pureseau1).publish_state( id(pureseau).state *-1);
} else {
id(pureseau1).publish_state( id(pureseau).state );
}
# Energie lue dans la pince
- platform: modbus_controller
modbus_controller_id: jsymk
id: energiepince
#name: "Energie Reseau JSYMK"
address: 0x0053
unit_of_measurement: "kWh"
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.0001
register_count: 1
response_size: 4
# Energie lue dans le tore
- platform: modbus_controller
modbus_controller_id: jsymk
id: fppince
#name: "FP Pince JSYMK"
address: 0x0054
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.0001
register_count: 1
response_size: 4
# Energie NEG lue dans le tore
- platform: modbus_controller
modbus_controller_id: jsymk
id: energienegpince
#name: "Energie ECS Neg JSYMK"
address: 0x0055
unit_of_measurement: "kWh"
register_type: holding
value_type: U_DWORD
accuracy_decimals: 1
filters:
- multiply: 0.0001
register_count: 1
response_size: 4
Code du fichier « table_pu.yaml »
Contient le résultat de votre etalonnage
globals:
- id: table_puissance
type: std::vector<std::pair<float, float>>
restore_value: no
initial_value: |-
{{
{0.0, 5.0},
{1.0, 14.661659},
{2.0, 15.560880},
{3.0, 15.927020},
{4.0, 16.568291},
{5.0, 18.303759},
{6.0, 19.157148},
{7.0, 20.944599},
{8.0, 22.596100},
{9.0, 23.025951},
{10.0, 25.903030},
{11.0, 28.434729},
{12.0, 30.524710},
{13.0, 31.625759},
{14.0, 34.235725},
{15.0, 36.901440},
{16.0, 40.163738},
{17.0, 44.019341},
{18.0, 44.768658},
{19.0, 49.555248},
{20.0, 56.278118},
{21.0, 59.905346},
{22.0, 64.042496},
{23.0, 69.965248},
{24.0, 77.182022},
{25.0, 81.073914},
{26.0, 89.339218},
{27.0, 96.296753},
{28.0, 100.722771},
{29.0, 108.025780},
{30.0, 115.575485},
{31.0, 122.833633},
{32.0, 133.766602},
{33.0, 144.135086},
{34.0, 159.488968},
{35.0, 166.558502},
{36.0, 178.282349},
{37.0, 189.092865},
{38.0, 202.196732},
{39.0, 212.861603},
{40.0, 234.333084},
{41.0, 248.347321},
{42.0, 262.094543},
{43.0, 283.565796},
{44.0, 301.493713},
{45.0, 322.728455},
{46.0, 344.373596},
{47.0, 365.494080},
{48.0, 381.928162},
{49.0, 415.564850},
{50.0, 441.482819},
{51.0, 468.173431},
{52.0, 501.893982},
{53.0, 521.712341},
{54.0, 550.043701},
{55.0, 586.746399},
{56.0, 627.726074},
{57.0, 661.966309},
{58.0, 703.051208},
{59.0, 730.505554},
{60.0, 761.137329},
{61.0, 804.480469},
{62.0, 847.356140},
{63.0, 878.056946},
{64.0, 941.720215},
{65.0, 993.722656},
{66.0, 1061.685303},
{67.0, 1101.080322},
{68.0, 1135.102417},
{69.0, 1196.311035},
{70.0, 1233.961548},
{71.0, 1294.063599},
{72.0, 1352.521729},
{73.0, 1392.430054},
{74.0, 1460.270386},
{75.0, 1512.882202},
{76.0, 1609.747437},
{77.0, 1674.149170},
{78.0, 1696.517944},
{79.0, 1770.225342},
{80.0, 1833.700928},
{81.0, 1901.670288},
{82.0, 1970.877197},
{83.0, 2023.935547},
{84.0, 2094.105469},
{85.0, 2165.517334},
{86.0, 2248.448242},
{87.0, 2357.001465},
{88.0, 2456.213135},
{89.0, 2539.881348},
{90.0, 2570.754395},
{91.0, 2631.065918},
{92.0, 2691.585938},
{93.0, 2750.663086},
{94.0, 2817.201172},
{95.0, 2895.280762},
{96.0, 2941.024902},
{97.0, 3038.455322},
{98.0, 3106.765137},
{99.0, 3144.267822},
{100.0, 3183.569824}
}}

Bonjour, je vais essayer mon routeur sur la résistance de mon chauffe eau thermodynamique, je vais ajouter une sonde de température au niveau de la sonde d’origine du ballon pour ne pas qu’il monte trop en température, si je ne peux pas en rajouter je vais voir a faire l’esp de aquamqtt.
Je viens de commander le matériel je vous tiens informé
Bonjour, merci d’avance pour votre retour d’expérience. Bonne continuation.
Bonjour,
Je vais installer votre routeur sur la résistance de mon thermodynamique, il va falloir que je rajoute une sonde de température dans mon ballon, je vais bien réussir à trouver comment et vu que j’ai tout installer moi même la question de garantie ne se pose pas. Je suis responsable d’économie d’énergie courant fort et faible d’un gros site indus donc des idées à la con j’en ai pas mal😅
Mon principe sera simple en cas de surproduction solaire la résistance de 1800w sera sur le routeur avec pour consigne environ 62°, le groupe extérieur continuera à être en automatique en heure creuse avec pour consigne 52°.
Suivant l’évolution de mon installation ecs je verrai a installer un bouclage pour les pièces les plus éloignés.
N’hésitez pas à m’envoyer un petit mail, pour plus de détails
Bonjour, votre expérience sur le routage de ballon thermodynamique est intéressante, merci d’avance
Bonjour,
J’ai un peu de mal a mettre le code y a t’il moyen d’avoir un d’aide par mail?