{"id":3198,"date":"2024-09-01T19:28:51","date_gmt":"2024-09-01T17:28:51","guid":{"rendered":"https:\/\/domo.rem81.com\/?p=3198"},"modified":"2025-08-27T18:25:18","modified_gmt":"2025-08-27T16:25:18","slug":"ha-gestion-complete-dune-piscine-avec-esp32-et-esphome","status":"publish","type":"post","link":"https:\/\/domo.rem81.com\/index.php\/2024\/09\/01\/ha-gestion-complete-dune-piscine-avec-esp32-et-esphome\/","title":{"rendered":"HA-Gestion Compl\u00e8te d\u2019une Piscine avec ESP32 et ESPHome"},"content":{"rendered":"\n\n\n\n<h1 class=\"wp-block-heading\">Intro<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Dans un article <a href=\"https:\/\/domo.rem81.com\/index.php\/2022\/02\/02\/ha-gestion-piscine-1-filtration-avec-appdaemon-2\/\" target=\"_blank\" rel=\"noreferrer noopener\">pr\u00e9c\u00e9dent<\/a>, je vous pr\u00e9sentais un module de gestion de la filtration et traitement d&rsquo;une piscine d\u00e9velopp\u00e9 sous APP DAEMON. J&rsquo;ai depuis, pour simplifier la mise en oeuvre, migr\u00e9 vers ESP Home. Je vous invite quand m\u00eame \u00e0 le parcourir, tout n&rsquo;est pas \u00e0 jeter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dans ce nouveau projet, j\u2019ai d\u00e9velopp\u00e9 une solution compl\u00e8te pour automatiser la gestion de ma piscine \u00e0 l\u2019aide d\u2019un ESP32 programm\u00e9 avec ESPHome. Ce syst\u00e8me contr\u00f4le la filtration, la r\u00e9gulation du pH et du chlore, le niveau d\u2019eau, le volet roulant, et m\u00eame une protection hors gel, le tout int\u00e9gr\u00e9 \u00e0 Home Assistant. Voici les d\u00e9tails de cette r\u00e9alisation, du mat\u00e9riel au code, pour ceux qui voudraient s\u2019en inspirer !<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Update:<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li>09\/06\/2025:\n<ul class=\"wp-block-list\">\n<li>Suppression des {&lsquo;$device_name} dans les d\u00e9finitions d&rsquo;entit\u00e9s (simplification des noms)<\/li>\n\n\n\n<li>S\u00e9paration du module \u00ab\u00a0mesure_pression\u00a0\u00bb du module principal<\/li>\n\n\n\n<li>Ajout compteur de litres dans ph et chlore<\/li>\n\n\n\n<li>Mise \u00e0 jour du sch\u00e9ma (Brochage ADS1115)<br><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>21\/05\/2025:\n<ul class=\"wp-block-list\">\n<li>refonte du programme: S\u00e9paration des fichiers ph, chlore, Appoint_eau, gestion volet. Cela permet de les installer uniquement si n\u00e9cessaire.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>18\/05\/2025: \n<ul class=\"wp-block-list\">\n<li>Int\u00e9gration d&rsquo;une fonction de conversion d&rsquo;un int en  HH:MM:SS<\/li>\n\n\n\n<li>Refonte de la r\u00e9gulation pH<\/li>\n\n\n\n<li>Refonte de l&rsquo;injection Chlore<\/li>\n\n\n\n<li>Ajout d&rsquo;un script de pilotage de la couverture flottante<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">Objectif du Projet<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">L\u2019id\u00e9e \u00e9tait de centraliser et d\u2019automatiser toutes les fonctions essentielles de ma piscine (volume : 50 m\u00b3) :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Filtration<\/strong> : adapter la dur\u00e9e en fonction de la temp\u00e9rature de l\u2019eau, avec plusieurs modes (Palier, Classique, Abaque, Horaire).<\/li>\n\n\n\n<li><strong>R\u00e9gulation chimique<\/strong> : maintenir le pH et le chlore \u00e0 des niveaux cibles via des pompes doseuses.<\/li>\n\n\n\n<li><strong>Niveau d\u2019eau<\/strong> : g\u00e9rer l\u2019appoint automatiquement avec une \u00e9lectrovanne.<\/li>\n\n\n\n<li><strong>Volet roulant<\/strong> : ouverture\/fermeture via relais.<\/li>\n\n\n\n<li><strong>S\u00e9curit\u00e9<\/strong> : surveiller la pression du filtre, le temps d\u2019usage des galets de chlore, et activer un mode hors gel en hiver.<\/li>\n\n\n\n<li><strong>Monitoring<\/strong> : notifications Telegram et affichage local sur un \u00e9cran LCD.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Le tout repose sur une carte ESP32 et un circuit imprim\u00e9 maison pour simplifier les raccordements.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Mat\u00e9riel Utilis\u00e9<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Carte ESP32<\/strong> : J\u2019ai choisi une carte avec 8 relais int\u00e9gr\u00e9s, disponible sur <a href=\"https:\/\/fr.aliexpress.com\/item\/1005003999413945.html?spm=a2g0o.productlist.main.1.4a111bceOzplYO&amp;algo_pvid=4613c13a-4ea3-4ac3-ac96-385655633748&amp;algo_exp_id=4613c13a-4ea3-4ac3-ac96-385655633748-0&amp;pdp_ext_f=%7B%22order%22%3A%2265%22%2C%22eval%22%3A%221%22%7D&amp;pdp_npi=4%40dis%21EUR%2115.29%2115.29%21%21%2117.20%2117.20%21%40211b876717506132233944049ee6a7%2112000033558811727%21sea%21FR%21833459399%21X&amp;curPageLogUid=ecgq83LwQUCD&amp;utparam-url=scene%3Asearch%7Cquery_from%3A\" data-type=\"link\" data-id=\"https:\/\/fr.aliexpress.com\/item\/1005003999413945.html?spm=a2g0o.productlist.main.1.4a111bceOzplYO&amp;algo_pvid=4613c13a-4ea3-4ac3-ac96-385655633748&amp;algo_exp_id=4613c13a-4ea3-4ac3-ac96-385655633748-0&amp;pdp_ext_f=%7B%22order%22%3A%2265%22%2C%22eval%22%3A%221%22%7D&amp;pdp_npi=4%40dis%21EUR%2115.29%2115.29%21%21%2117.20%2117.20%21%40211b876717506132233944049ee6a7%2112000033558811727%21sea%21FR%21833459399%21X&amp;curPageLogUid=ecgq83LwQUCD&amp;utparam-url=scene%3Asearch%7Cquery_from%3A\" target=\"_blank\" rel=\"noreferrer noopener\">AliExpress<\/a>. Elle offre assez de sorties pour piloter la pompe de filtration, les pompes doseuses, l\u2019\u00e9lectrovanne, et le volet.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"571\" src=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/06\/image.png\" alt=\"\" class=\"wp-image-3200\" srcset=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/06\/image.png 960w, https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/06\/image-300x178.png 300w, https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/06\/image-768x457.png 768w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" \/><\/figure>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Circuit Imprim\u00e9 Personnalis\u00e9<\/strong> : Con\u00e7u pour connecter facilement les capteurs (temp\u00e9rature, pH, pression) et les actionneurs via des borniers.<\/li>\n\n\n\n<li><strong>Capteurs<\/strong> :\n<ul class=\"wp-block-list\">\n<li>Sonde Dallas DS18B20 (temp\u00e9rature eau).<\/li>\n\n\n\n<li>Un module mesure de ph&nbsp;<a href=\"https:\/\/atlas-scientific.com\/embedded-solutions\/ezo-ph-circuit\/\" target=\"_blank\" rel=\"noreferrer noopener\">EZO<\/a><\/li>\n\n\n\n<li>Une sonde pH&nbsp;<a href=\"https:\/\/atlas-scientific.com\/probes\/consumer-grade-ph-probe\/\" target=\"_blank\" rel=\"noreferrer noopener\">EZO<\/a>. J\u2019utilise celle ci mais il existe des mod\u00e8les compatibles industriels beaucoup plus chers et des mod\u00e8les chinois beaucoup moins chers, c\u2019est selon l\u2019importance et le budget que vous accorderez \u00e0 la fiabilit\u00e9 et la p\u00e9rennit\u00e9 de la mesure.<\/li>\n\n\n\n<li>Un module d\u2019isolation galvanique&nbsp;<a href=\"https:\/\/atlas-scientific.com\/carrier-boards\/electrically-isolated-ezo-carrier-board-gen-2\/\" target=\"_blank\" rel=\"noreferrer noopener\">EZO<\/a>.<\/li>\n\n\n\n<li>Sonde ORP <a href=\"https:\/\/atlas-scientific.com\/probes\/consumer-grade-orp-probe\/\" data-type=\"link\" data-id=\"https:\/\/atlas-scientific.com\/probes\/consumer-grade-orp-probe\/\" target=\"_blank\" rel=\"noreferrer noopener\">EZO<\/a> ou <a href=\"https:\/\/fr.aliexpress.com\/item\/1005007575561954.html?spm=a2g0o.order_list.order_list_main.22.5aa75e5ba5DQEF&amp;gatewayAdapt=glo2fra\" data-type=\"link\" data-id=\"https:\/\/fr.aliexpress.com\/item\/1005007575561954.html?spm=a2g0o.order_list.order_list_main.22.5aa75e5ba5DQEF&amp;gatewayAdapt=glo2fra\" target=\"_blank\" rel=\"noreferrer noopener\">Ali Express<\/a>.<\/li>\n\n\n\n<li>Capteur de pression ADS1115 (filtre).<\/li>\n\n\n\n<li>un module <a href=\"https:\/\/www.amazon.fr\/Hopbucan-PZEM-004T-Amp%C3%A8rem%C3%A8tre-num%C3%A9rique-Fr%C3%A9quence\/dp\/B0BFQXZSX8\/ref=sr_1_3?crid=RQCG5I9R76N9&amp;dib=eyJ2IjoiMSJ9.4XspIaX3c4J7xoE56rGimSS7eLB8_Yh14HBKR_Q9l7T3PVHeHO7ql6s9KaSoqXbAG8_Y5RSNxBcVMuj2-AoLVQ-jd__Jfg22PjEQ0KmFSGgBOwGUkepTJr7s82UNQh5Q58Pbr6YlPoykwrwarvaHNs3Roi30Om0NHg3yjxRRp-tNO_AMK6sxjhfxjryx6QHI_7z5jZicYDgDjjii3d_7modpzNIMuhljd5Hr59nCP-Q-8NGJKouU9aowARcZBNkX91oqdir9idgIiwcj8hG6FhLUdaSUmbheTIfKyVO57b7xg9tW1d_jkdmNm54oSffDwEZYQX8Fm3u3IpxRFGAnLjlbbr9ItV4VvWwVmTn8HKZrTVDAQgxxTkNGrdu_U0ULNSIPkQZsdXO9Tec5oQ2paYiGSKJ2qLC38hHDZhAnSaizByLrAh2O89Mntdi66ECR._G9emuWca-TwdJUaJaNOjr-SQlqch1RUNlCM3eSElDc&amp;dib_tag=se&amp;keywords=pzem-004t+v3&amp;qid=1742888346&amp;sprefix=pzem%2Caps%2C125&amp;sr=8-3\" target=\"_blank\" rel=\"noreferrer noopener\">PZEM-004T 100A<\/a> permettant de mesurer des courants de 0-100A sous une tension alternative de 80-260V.<\/li>\n\n\n\n<li>D\u00e9tecteurs de niveau (LSH, LSL via SX1509). Voir article sur <a href=\"https:\/\/domo.rem81.com\/index.php\/2021\/04\/02\/home-assistant-gestion-piscine-1_mise-a-niveau-automatique\/\" data-type=\"link\" data-id=\"https:\/\/domo.rem81.com\/index.php\/2021\/04\/02\/home-assistant-gestion-piscine-1_mise-a-niveau-automatique\/\" target=\"_blank\" rel=\"noreferrer noopener\">la mesure de niveau<\/a><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Actionneurs<\/strong> : Relais pour pompe filtration (12 m\u00b3\/h), pompes doseuses (pH-, chlore), \u00e9lectrovanne eau, et volet roulant.<\/li>\n\n\n\n<li><strong>Afficheur LCD 16&#215;2 I2C<\/strong> : Pour un suivi local (pH, pression, temp\u00e9rature).<\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">Conception du Circuit Imprim\u00e9<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Pour \u00e9viter un c\u00e2blage anarchique, j\u2019ai con\u00e7u un PCB qui regroupe toutes les connexions n\u00e9cessaires. Voici une vue du PCB final, con\u00e7u avec EasyEda et fabriqu\u00e9 par JLCPCB :<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"797\" height=\"341\" src=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2025\/03\/image.png\" alt=\"\" class=\"wp-image-3294\" srcset=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2025\/03\/image.png 797w, https:\/\/domo.rem81.com\/wp-content\/uploads\/2025\/03\/image-300x128.png 300w, https:\/\/domo.rem81.com\/wp-content\/uploads\/2025\/03\/image-768x329.png 768w\" sizes=\"auto, (max-width: 797px) 100vw, 797px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Le PCB inclut :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Connecteurs pour capteurs<\/strong> : Borniers pour la sonde Dallas (temp\u00e9rature eau), les sondes EZO (pH et ORP), l\u2019ADS1115 (pression), et les d\u00e9tecteurs de niveau (LSH, LSL).<\/li>\n\n\n\n<li><strong>Interfaces de communication<\/strong> : I2C pour l\u2019afficheur et les EZO, UART pour le PZEM-004T.<\/li>\n\n\n\n<li><strong>Extension SX1509<\/strong> : Pour g\u00e9rer plus d\u2019entr\u00e9es\/sorties (niveaux, LEDs).<\/li>\n\n\n\n<li><strong>Alimentation<\/strong> : R\u00e9gulateurs 12V\/5V\/3.3V pour alimenter l\u2019ESP32 et les capteurs.<\/li>\n\n\n\n<li><strong>Sorties vers les relais<\/strong> : Connecteurs pour piloter la pompe de filtration, les pompes doseuses, l\u2019\u00e9lectrovanne, et le volet roulant.<\/li>\n\n\n\n<li><strong>Connecteurs suppl\u00e9mentaires<\/strong> : Pour des extensions futures (ex. : capteur ORP).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Le PCB est \u00e9tiquet\u00e9 clairement (ex. : \u00ab\u00a0PZEM-004T\u00a0\u00bb, \u00ab\u00a0ADS1115\u00a0\u00bb, \u00ab\u00a0PH-EZO\u00a0\u00bb) pour faciliter les raccordements. Les borniers \u00e0 vis permettent de brancher\/d\u00e9brancher facilement les capteurs et actionneurs sans soudure. La conception a \u00e9t\u00e9 finalis\u00e9e le 25\/07\/2024, comme indiqu\u00e9 sur le PCB.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Le Gerber et la liste du mat\u00e9riel sont disponibles ici: <\/strong><a href=\"https:\/\/github.com\/remycrochon\/domo.rem81\/tree\/main\/extension_platine-relais_esp32\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/remycrochon\/domo.rem81\/tree\/main\/extension_platine-relais_esp32<\/a><\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Sch\u00e9ma de C\u00e2blage<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Pour mieux comprendre les connexions, voici le sch\u00e9ma de c\u00e2blage r\u00e9alis\u00e9 sous Easyeda :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Au format EasyEDA:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/remycrochon\/domo.rem81\/blob\/main\/Platine%20gestion%20piscine%20juillet%202024\/SCH_Extension-Platine-Relais-ESP32-Veth_V07-2024.json\">https:\/\/github.com\/remycrochon\/domo.rem81\/blob\/main\/Platine%20gestion%20piscine%20juillet%202024\/SCH_Extension-Platine-Relais-ESP32-Veth_V07-2024.json<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Et le PCB au format EasyEDA<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/remycrochon\/domo.rem81\/blob\/main\/Platine%20gestion%20piscine%20juillet%202024\/PCB_PCB-eth-V07-2024.json\">https:\/\/github.com\/remycrochon\/domo.rem81\/blob\/main\/Platine%20gestion%20piscine%20juillet%202024\/PCB_PCB-eth-V07-2024.json<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Au format PNG:<\/p>\n\n\n\n<div class=\"wp-block-file\"><a id=\"wp-block-file--media-f81b6ed3-fe9e-423a-aa1a-981c5a354de5\" href=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/Schematic_Extension-Platine-Relais-ESP32-Veth_2025-06-09.png\">Schematic_Extension-Platine-Relais-ESP32-Veth_2025-06-09<\/a><a href=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/Schematic_Extension-Platine-Relais-ESP32-Veth_2025-06-09.png\" class=\"wp-block-file__button wp-element-button\" download aria-describedby=\"wp-block-file--media-f81b6ed3-fe9e-423a-aa1a-981c5a354de5\">T\u00e9l\u00e9charger<\/a><\/div>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"517\" src=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/image-5-1024x517.png\" alt=\"\" class=\"wp-image-4064\" srcset=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/image-5-1024x517.png 1024w, https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/image-5-300x152.png 300w, https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/image-5-768x388.png 768w, https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/image-5.png 1138w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Le sch\u00e9ma montre :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>ESP32<\/strong> : C\u0153ur du syst\u00e8me, connect\u00e9 via GPIO aux capteurs et actionneurs.<\/li>\n\n\n\n<li><strong>Capteurs<\/strong> :\n<ul class=\"wp-block-list\">\n<li>Sonde Dallas (GPIO16) pour la temp\u00e9rature de l\u2019eau.<\/li>\n\n\n\n<li>EZO pH\/ORP via I2C (SDA GPIO15, SCL GPIO22).<\/li>\n\n\n\n<li>ADS1115 (pression filtre) sur I2C.<\/li>\n\n\n\n<li>PZEM-004T (puissance) via UART (RX GPIO3, TX GPIO1).<\/li>\n\n\n\n<li>D\u00e9tecteurs de niveau (LSH, LSL) via SX1509.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Actionneurs<\/strong> :\n<ul class=\"wp-block-list\">\n<li>Relais pour la pompe de filtration (GPIO32), pompes doseuses (GPIO33, GPIO25), \u00e9lectrovanne (GPIO12), et volet roulant (GPIO27, GPIO14).<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Extensions<\/strong> :\n<ul class=\"wp-block-list\">\n<li>SX1509 pour g\u00e9rer les entr\u00e9es\/sorties suppl\u00e9mentaires (niveaux, LEDs).<\/li>\n\n\n\n<li>Afficheur LCD 16&#215;2 sur I2C.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Alimentation<\/strong> : R\u00e9gulateurs 12V\/5V\/3.3V pour l\u2019ESP32 et les capteurs.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Ce sch\u00e9ma est essentiel pour comprendre comment tout est interconnect\u00e9 et pour reproduire le projet.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Fonctionnement et Code ESPHome<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Le programme ESPHome (ESP178) est structur\u00e9 autour de plusieurs scripts et capteurs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le corps principal du programme g\u00e8re les diff\u00e9rents modes de filtration. Vous pouvez ins\u00e9rer ou pas les sous programmes suivants en commentant la ligne correspondante sous l&rsquo;instruction \u00ab\u00a0Includes\u00a0\u00bb:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>packages:\n<ul class=\"wp-block-list\">\n<li>ph: !include pack_esp178\/ph.yaml<\/li>\n\n\n\n<li>chlore: !include pack_esp178\/chlore.yaml<\/li>\n\n\n\n<li>volet: !include pack_esp178\/couverture_flottante.yaml<\/li>\n\n\n\n<li>appoint_eau: !include pack_esp178\/appoint_eau.yaml<\/li>\n\n\n\n<li>hors_gel: !include pack_esp178\/hors_gel.yaml<\/li>\n\n\n\n<li>mesure_elec: !include pack_esp178\/mesure_elec.yaml <\/li>\n\n\n\n<li>mesure_pression: !include pack_esp178\/mesure_pression.yaml<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<div class=\"wp-block-media-text is-stacked-on-mobile\"><figure class=\"wp-block-media-text__media\"><img loading=\"lazy\" decoding=\"async\" width=\"321\" height=\"178\" src=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/image-4.png\" alt=\"\" class=\"wp-image-4048 size-full\" srcset=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/image-4.png 321w, https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/image-4-300x166.png 300w\" sizes=\"auto, (max-width: 321px) 100vw, 321px\" \/><\/figure><div class=\"wp-block-media-text__content\">\n<p class=\"wp-block-paragraph\">Les fichiers .yaml doivent stock\u00e9s dans un sous_dossier de ESPhome, \u00ab\u00a0pack_esp178\u00a0\u00bb dans mon cas.<\/p>\n<\/div><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Filtration<\/strong> : <\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Quatre modes de fonctionnement:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong><em>Palier<\/em> <\/strong>: Dur\u00e9e fix\u00e9e par paliers.<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>la dur\u00e9e est calcul\u00e9e manuellement par rapport au volume de la piscine: Vol=9*4*1.4=50.4 m3<\/li>\n\n\n\n<li>et le d\u00e9bit de la pompe: Qppe Th\u00e9orique=12m3\/h -&gt; retenu:12m3\/h<\/li>\n\n\n\n<li>donc 1 cycle= 50.4\/12=4.2h<\/li>\n\n\n\n<li>il faut filtrer au minimum:<\/li>\n\n\n\n<li>T\u00b0eau&lt;10\u00b0 = 0 cycle<\/li>\n\n\n\n<li>10\u00b0&lt;T\u00b0eau&lt;15\u00b0 = 1 cycle = 4.2h =&gt; 4h<\/li>\n\n\n\n<li>15\u00b0&lt;T\u00b0eau&lt;20\u00b0 = 2 cycles = 8.4h =&gt; 8h<\/li>\n\n\n\n<li>20\u00b0&lt;T\u00b0eau&lt;25\u00b0 = 3 cycles = 12.6h =&gt; 12h<\/li>\n\n\n\n<li>25\u00b0&lt;T\u00b0eau = 3 ou 4 cycles = 12.6h =&gt; 12h<\/li>\n\n\n\n<li>T\u00b0eau&gt;25\u00b0 =&gt; 14h<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><em><strong>Classique<\/strong><\/em> : Dur\u00e9e = temp\u00e9rature \/ 2 (min 5h, max 23h).<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Solution largement. Exemple : \u00c0 20\u00b0C, dur\u00e9e = 10h.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong><em>Abaque<\/em> <\/strong>: Dur\u00e9e calcul\u00e9e par une formule polynomiale cubique :<\/h3>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"679\" height=\"379\" src=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2025\/03\/image-3.png\" alt=\"\" class=\"wp-image-3312\" srcset=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2025\/03\/image-3.png 679w, https:\/\/domo.rem81.com\/wp-content\/uploads\/2025\/03\/image-3-300x167.png 300w\" sizes=\"auto, (max-width: 679px) 100vw, 679px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><br><em><strong>Horaire<\/strong><\/em> : Plage horaire fixe (ex. : hiver).<\/h3>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>R\u00e9gulation pH<\/strong> :<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Dans ce chapitre, je vous explique comment j\u2019ai mis en place un syst\u00e8me intelligent pour ajuster automatiquement le pH en injectant un produit acidifiant, avec des notifications et une gestion fine des dur\u00e9es. L\u2019objectif est de maintenir un pH optimal (g\u00e9n\u00e9ralement entre 7.2 et 7.6), essentiel pour le confort des baigneurs et l\u2019efficacit\u00e9 du chlore.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Contexte et objectif<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ma piscine est \u00e9quip\u00e9e d\u2019une pompe doseuse pour injecter un produit pH- (acidifiant) lorsque le pH mesur\u00e9 d\u00e9passe la cible d\u00e9finie. Je r\u00e9cup\u00e8re la valeur mesur\u00e9e du pH via <code>id(g_memoire_ph)<\/code> et la cible via <code>id(_ph_cible)<\/code>. L\u2019id\u00e9e est de calculer une dur\u00e9e d\u2019injection proportionnelle \u00e0 l\u2019\u00e9cart entre la mesure et la cible, en utilisant les caract\u00e9ristiques de ma piscine (43,2 m\u00b3) et du produit pH Moins Ultra (0,2 l pour 0,1 unit\u00e9 de pH pour 10 m\u00b3). Le syst\u00e8me s\u2019active uniquement en mode automatique et lorsque la filtration est en marche.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">La configuration ESPHome<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Voici le script que j\u2019ai int\u00e9gr\u00e9 dans mon fichier ESPHome pour r\u00e9guler le pH de mani\u00e8re pr\u00e9cise et automatis\u00e9e.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vous trouverez le code complet fin d\u2019article.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ce script est appel\u00e9 deux fois par jour, \u00e0 10:30 et 15:30.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fonctionnement d\u00e9taill\u00e9<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ce script, ex\u00e9cut\u00e9 en mode <code>single<\/code>, r\u00e9gule le pH de mani\u00e8re intelligente. Voici comment il op\u00e8re \u00e9tape par \u00e9tape :<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Condition d\u2019activation<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Le script se d\u00e9clenche uniquement en mode Auto. Il v\u00e9rifie si le pH mesur\u00e9 (<code>g_memoire_ph<\/code>) d\u00e9passe la cible (<code>_ph_cible<\/code>) de plus de 0,1 unit\u00e9 (d\u00e9finie dans <code>_ph_hysteresis<\/code>) et si la filtration est active.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Calcul de la dur\u00e9e d\u2019injection<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">L\u2019\u00e9cart entre la mesure et la cible est calcul\u00e9, et la dur\u00e9e d\u2019injection est d\u00e9termin\u00e9e directement en fonction de cet \u00e9cart, du volume de la piscine, et du d\u00e9bit de la pompe :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Quantit\u00e9 n\u00e9cessaire<\/strong> : On calcule la quantit\u00e9 de produit \u00e0 injecter pour corriger l\u2019\u00e9cart total, en se basant sur les donn\u00e9es du produit (0,2 l pour 0,1 unit\u00e9 de pH pour 10 m\u00b3). Pour ma piscine de 43,2 m\u00b3, cela repr\u00e9sente 0,864 l pour 0,1 unit\u00e9 de pH.<\/li>\n\n\n\n<li><strong>Dur\u00e9e th\u00e9orique<\/strong> : On d\u00e9termine le temps n\u00e9cessaire pour injecter cette quantit\u00e9, en fonction du d\u00e9bit de la pompe (<code>_debit_ppe_moins<\/code>, par exemple 4,272 l\/h, soit environ 1,187 ml\/s).<\/li>\n\n\n\n<li><strong>Facteur de correction<\/strong> : Pour \u00e9viter une sur-correction, seule une fraction de l\u2019\u00e9cart est corrig\u00e9e par cycle. Ce facteur est ajustable via un param\u00e8tre (<code>facteur_correction_ph<\/code>), que j\u2019ai initialement fix\u00e9 \u00e0 10 %.<\/li>\n\n\n\n<li><strong>Limites<\/strong> : La dur\u00e9e est born\u00e9e entre 1 et 60 secondes pour \u00e9viter une surdose.<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">Conversion et affichage<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Le temps calcul\u00e9 est converti en heures, minutes et secondes, puis affich\u00e9 via un composant <code>datetime<\/code> (<code>duree_injection_ph<\/code>) pour une lecture facile dans Home Assistant.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Gestion de la pompe<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Si une dur\u00e9e positive est calcul\u00e9e, la pompe (<code>cde_ppe_ph_moins<\/code>) s\u2019active pour cette dur\u00e9e. Des notifications Telegram sont envoy\u00e9es au d\u00e9but et \u00e0 la fin de l\u2019injection, avec les d\u00e9tails du pH mesur\u00e9, de la cible et de la dur\u00e9e. Si aucune action n\u2019est requise, la pompe reste \u00e9teinte.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Logs et suivi<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Des messages de log permettent de suivre les valeurs mesur\u00e9es, les quantit\u00e9s calcul\u00e9es, le d\u00e9bit, le facteur de correction et la dur\u00e9e finale, facilitant le d\u00e9bogage.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Exemple pratique<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Supposons que ma cible soit 7.4 et que le pH mesur\u00e9 soit 8.0 (\u00e9cart de 0.6) :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Quantit\u00e9 n\u00e9cessaire : (0,6 \/ 0,1) x 0,2 x (43,2 \/ 10) = 5,184 l (soit 5184 ml).<\/li>\n\n\n\n<li>D\u00e9bit de la pompe : 4,272 l\/h = 1,187 ml\/s.<\/li>\n\n\n\n<li>Dur\u00e9e totale : 5184 \/ 1,187 = 4368 secondes (environ 73 minutes).<\/li>\n\n\n\n<li>Avec un facteur de correction de 10 % : 4368 x 0,1 = 436,8 secondes, mais limit\u00e9 \u00e0 60 secondes (maximum par cycle).<\/li>\n\n\n\n<li>R\u00e9sultat : La pompe s\u2019active pendant 60 secondes, injectant environ 71 ml, et le processus se r\u00e9p\u00e8te au prochain cycle jusqu\u2019\u00e0 atteindre la cible.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Conseils pour adapter cette solution<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Ajustez le facteur de correction<\/strong> : Modifiez <code>facteur_correction_ph<\/code> (par exemple, passez de 0,1 \u00e0 0,2 pour une correction plus rapide) selon la r\u00e9activit\u00e9 de votre produit pH-.<\/li>\n\n\n\n<li><strong>V\u00e9rifiez le d\u00e9bit<\/strong> : Assurez-vous que le d\u00e9bit de la pompe (<code>_debit_ppe_moins<\/code>) est correct et ajustez-le si n\u00e9cessaire.<\/li>\n\n\n\n<li><strong>Validez les mesures<\/strong> : Utilisez un kit d\u2019analyse pour confirmer les ajustements et affinez si besoin.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Un dispositif similaire a \u00e9t\u00e9 d\u00e9crit dans ces articles :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li> <a href=\"https:\/\/domo.rem81.com\/index.php\/2021\/08\/14\/ha-gestion-piscine-5_regulation-du-ph\/\">https:\/\/domo.rem81.com\/index.php\/2021\/08\/14\/ha-gestion-piscine-5_regulation-du-ph\/<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/domo.rem81.com\/index.php\/2021\/05\/11\/home-assistant-gestion-piscine-4_mesure-ph\/\">https:\/\/domo.rem81.com\/index.php\/2021\/05\/11\/home-assistant-gestion-piscine-4_mesure-ph\/<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Injection Chlore<\/strong> :<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&rsquo;objectif est de calculer pr\u00e9cis\u00e9ment le temps d&rsquo;injection de chlore liquide (javel \u00e0 9.6%) en fonction de la concentration cible souhait\u00e9e, tout en utilisant une pompe doseuse connect\u00e9e. Voici comment j&rsquo;ai proc\u00e9d\u00e9.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Contexte et objectif<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ma piscine est \u00e9quip\u00e9e d\u2019une pompe doseuse connect\u00e9e via ESPHome, qui injecte de la javel \u00e0 9.6% de chlore actif (96 g\/L). Je voulais un syst\u00e8me capable de calculer automatiquement le temps d\u2019injection en fonction de la concentration cible de chlore (en ppm), du volume de la piscine, et du d\u00e9bit de la pompe. De plus, j\u2019ai ajout\u00e9 des fonctionnalit\u00e9s pour afficher le temps sous forme lisible (heures, minutes, secondes) et envoyer des notifications via Telegram.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">La configuration ESPHome<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Voici le script que j\u2019ai int\u00e9gr\u00e9 dans mon fichier ESPHome pour g\u00e9rer l\u2019injection de chlore. Ce script fonctionne dans diff\u00e9rents modes (automatique, manuel forc\u00e9, ou arr\u00eat\u00e9 forc\u00e9) et s\u2019active uniquement lorsque la filtration est en marche.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vous trouverez le code complet fin d\u2019article.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"> Ce script est appel\u00e9 deux fois par jour \u00e0 10:00 et \u00e0 15:00.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fonctionnement d\u00e9taill\u00e9<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Le script s\u2019ex\u00e9cute dans un mode single, ce qui garantit qu\u2019il ne se r\u00e9p\u00e8te pas ind\u00e9finiment. Voici comment il fonctionne \u00e9tape par \u00e9tape :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Calcul du dosage<\/strong> :<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"526\" height=\"264\" src=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/image-1.png\" alt=\"\" class=\"wp-image-3938\" srcset=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/image-1.png 526w, https:\/\/domo.rem81.com\/wp-content\/uploads\/2024\/09\/image-1-300x151.png 300w\" sizes=\"auto, (max-width: 526px) 100vw, 526px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Conversion du temps<\/strong> :<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Le temps calcul\u00e9 est converti en heures, minutes, et secondes, puis affich\u00e9 via un composant datetime (duree_injection_chlore). Cela me permet de visualiser facilement la dur\u00e9e dans Home Assistant.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Gestion de la Pompe:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>En mode Auto, si un temps positif est calcul\u00e9 et que la filtration est en marche, la pompe (cde_ppe_chlore) s\u2019active pour la dur\u00e9e calcul\u00e9e, et une notification Telegram est envoy\u00e9e avec le temps d\u2019injection.<\/li>\n\n\n\n<li>Si aucun temps n\u2019est requis ou si la filtration est arr\u00eat\u00e9e, la pompe s\u2019arr\u00eate.<\/li>\n\n\n\n<li>En mode Ma_f (manuel forc\u00e9), la pompe s\u2019active tant que la filtration est en marche, quelle que soit la concentration.<\/li>\n\n\n\n<li>En mode At_f (arr\u00eat forc\u00e9) ou si la filtration est d\u00e9sactiv\u00e9e, la pompe s\u2019arr\u00eate automatiquement.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Logs et d\u00e9bogage<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Des messages de log sont g\u00e9n\u00e9r\u00e9s \u00e0 chaque \u00e9tape (d\u00e9but du script, temps calcul\u00e9, activation\/arr\u00eat de la pompe), avec des d\u00e9tails comme la quantit\u00e9 inject\u00e9e et le temps. Cela me permet de suivre l\u2019op\u00e9ration en temps r\u00e9el.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Mesure de Pression:<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Le capteur de pression est reli\u00e9 \u00e0 l&rsquo;entr\u00e9e N\u00b03 du du convertisseur Analogique ADS1115<\/li>\n\n\n\n<li>Le dispositif a \u00e9t\u00e9 d\u00e9crit dans cet article <a href=\"https:\/\/domo.rem81.com\/index.php\/2022\/05\/13\/ha-gestion-piscine-7_mesure-de-pression\/\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/domo.rem81.com\/index.php\/2022\/05\/13\/ha-gestion-piscine-7_mesure-de-pression\/<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Appoint d\u2019Eau<\/strong> :<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L\u2019objectif ici est d\u2019assurer automatiquement le maintien du niveau d\u2019eau dans la piscine gr\u00e2ce \u00e0 une \u00e9lectrovanne command\u00e9e par un ESP32 sous ESPHome, tout en prenant en compte les diff\u00e9rents niveaux d\u2019eau d\u00e9tect\u00e9s par des sondes de niveau. Le tout fonctionne de mani\u00e8re autonome ou manuelle, avec un suivi des dur\u00e9es d\u2019ouverture et une protection contre les d\u00e9fauts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le dispositif mis en oeuvre a \u00e9t\u00e9 d\u00e9crit dans cet article: <a href=\"https:\/\/domo.rem81.com\/index.php\/2021\/04\/02\/home-assistant-gestion-piscine-1_mise-a-niveau-automatique\/\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/domo.rem81.com\/index.php\/2021\/04\/02\/home-assistant-gestion-piscine-1_mise-a-niveau-automatique\/<\/a><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Contexte et objectif<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ma piscine est \u00e9quip\u00e9e de deux sondes de niveau connect\u00e9es \u00e0 un module d\u2019extension SX1509 (LSH = haut, LSL = bas), permettant de d\u00e9tecter quatre \u00e9tats : niveau haut, interm\u00e9diaire, bas, ou d\u00e9faut (cas incoh\u00e9rent). Une \u00e9lectrovanne pilot\u00e9e par un relais ESP32 se charge d\u2019ajouter de l\u2019eau quand n\u00e9cessaire. L\u2019objectif est de g\u00e9rer l\u2019appoint d\u2019eau de mani\u00e8re fiable, avec une s\u00e9curit\u00e9 anti-d\u00e9bordement et une logique de fonctionnement automatique ou forc\u00e9e.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Trois modes sont propos\u00e9s via une entit\u00e9 <code>select<\/code> dans Home Assistant :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Auto<\/strong> : Appoint automatique uniquement si la couverture est ouverte, sans d\u00e9faut, et en fonction du niveau mesur\u00e9.<\/li>\n\n\n\n<li><strong>Ma_f (manuel forc\u00e9)<\/strong> : Ouverture manuelle de l\u2019\u00e9lectrovanne tant que ce mode est actif.<\/li>\n\n\n\n<li><strong>At_f (arr\u00eat forc\u00e9)<\/strong> : Blocage total de l\u2019appoint d\u2019eau, utile en hiver ou lors de maintenance.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">La configuration ESPHome<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Un script principal <code>_regul_eau<\/code> pilote l\u2019ouverture\/fermeture de l\u2019\u00e9lectrovanne selon les niveaux d\u2019eau d\u00e9tect\u00e9s, les conditions de s\u00e9curit\u00e9, et le mode choisi. Ce script peut \u00eatre d\u00e9clench\u00e9 automatiquement ou via un bouton virtuel dans Home Assistant.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Un second script <code>_calcul_niveau_eau<\/code>, ex\u00e9cut\u00e9 toutes les 5 secondes, analyse l\u2019\u00e9tat des deux sondes pour d\u00e9terminer pr\u00e9cis\u00e9ment le niveau global de la piscine (haut, interm\u00e9diaire, bas ou d\u00e9faut).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Un compteur <code>duty_time<\/code> mesure quotidiennement le temps d\u2019ouverture de l\u2019\u00e9lectrovanne, remis \u00e0 z\u00e9ro chaque nuit \u00e0 minuit gr\u00e2ce \u00e0 une synchronisation SNTP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vous trouverez le code complet fin d\u2019article.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fonctionnement d\u00e9taill\u00e9<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Analyse des niveaux d\u2019eau<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Les deux sondes permettent d\u2019interpr\u00e9ter 4 \u00e9tats logiques :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Niveau haut<\/strong> : LSH = ON, LSL = ON<\/li>\n\n\n\n<li><strong>Niveau interm\u00e9diaire<\/strong> : LSH = OFF, LSL = ON<\/li>\n\n\n\n<li><strong>Niveau bas<\/strong> : LSH = OFF, LSL = OFF<\/li>\n\n\n\n<li><strong>D\u00e9faut<\/strong> : LSH = ON, LSL = OFF (\u00e9tat incoh\u00e9rent)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Chaque \u00e9tat est publi\u00e9 via un <code>binary_sensor<\/code> pour un affichage clair dans Home Assistant.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">R\u00e9gulation de l\u2019eau<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>En mode Auto<\/strong> :\n<ul class=\"wp-block-list\">\n<li>Si le niveau est bas ou interm\u00e9diaire, et que le volet est ouvert, l\u2019\u00e9lectrovanne s\u2019ouvre automatiquement pour 15 minutes (timeout de s\u00e9curit\u00e9).<\/li>\n\n\n\n<li>Si le niveau est haut ou en d\u00e9faut, la vanne est imm\u00e9diatement ferm\u00e9e.<\/li>\n\n\n\n<li>Le tout est logu\u00e9 en d\u00e9tail (mode actif, \u00e9tat de la vanne, etc.).<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>En mode Ma_f (manuel forc\u00e9)<\/strong> :\n<ul class=\"wp-block-list\">\n<li>L\u2019\u00e9lectrovanne s\u2019ouvre d\u00e8s que ce mode est activ\u00e9, quel que soit le niveau, tant que la filtration est en fonctionnement.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>En mode At_f (arr\u00eat forc\u00e9)<\/strong> :\n<ul class=\"wp-block-list\">\n<li>La vanne reste ferm\u00e9e en toute circonstance.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Logs et surveillance<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Chaque activation de la vanne est horodat\u00e9e et consign\u00e9e dans les logs. Le capteur <code>duty_time<\/code> permet de visualiser le temps d\u2019ouverture cumul\u00e9 sur la journ\u00e9e, un bon indicateur de fuite \u00e9ventuelle ou de sur-remplissage.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Enfin, l&rsquo;ensemble de cette logique est compl\u00e8tement int\u00e9gr\u00e9e dans Home Assistant avec affichage des niveaux, du mode actif, du bouton d\u2019appoint manuel, et des dur\u00e9es de fonctionnement.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Fonctionnement Hors Gel : Protection Automatis\u00e9e de la Piscine en Hiver<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Afin de prot\u00e9ger le local technique et le circuit hydraulique de la piscine contre le gel, j\u2019ai mis en place une <strong>fonction de hors gel intelligente<\/strong> dans ESPHome. Cette logique permet d\u2019enclencher automatiquement la pompe de filtration en cas de temp\u00e9rature ext\u00e9rieure n\u00e9gative, selon deux seuils personnalisables.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Objectif<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Lors de temp\u00e9ratures proches ou en dessous de z\u00e9ro, le risque de gel dans les canalisations est r\u00e9el, en particulier si l\u2019eau est stagnante. La circulation de l\u2019eau suffit g\u00e9n\u00e9ralement \u00e0 emp\u00eacher ce ph\u00e9nom\u00e8ne. Plut\u00f4t que de laisser tourner la pompe en continu, j\u2019ai opt\u00e9 pour un fonctionnement ponctuel <strong>conditionn\u00e9 par la temp\u00e9rature ext\u00e9rieure<\/strong>, avec des <strong>alertes Telegram<\/strong> \u00e0 chaque d\u00e9clenchement.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Principe de fonctionnement<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Le fonctionnement repose sur deux seuils configurables depuis Home Assistant :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Seuil 1<\/strong> : en dessous duquel la pompe tourne pendant <strong>15 minutes<\/strong>,<\/li>\n\n\n\n<li><strong>Seuil 2<\/strong> : en dessous duquel elle tourne pendant <strong>30 minutes<\/strong>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">La logique est test\u00e9e toutes les 15 minutes. Un <strong>flag interne<\/strong> emp\u00eache que plusieurs cycles ne soient lanc\u00e9s simultan\u00e9ment.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Int\u00e9gration dans ESPHome<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Vous trouverez le code complet fin d\u2019article.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Notifications et suivi<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u00c0 chaque d\u00e9clenchement ou arr\u00eat d\u2019un cycle hors gel, une <strong>notification Telegram<\/strong> est envoy\u00e9e. Cela me permet de suivre \u00e0 distance l\u2019activit\u00e9 hivernale de la piscine.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Automatisation de la couverture flottante<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Dans ce chapitre je vous explique comment j\u2019ai automatis\u00e9 la gestion de la couverture flottante de ma piscine gr\u00e2ce \u00e0 ESPHome. Cette couverture prot\u00e8ge l\u2019eau des impuret\u00e9s, r\u00e9duit l\u2019\u00e9vaporation et conserve la chaleur, mais l\u2019ouvrir et la fermer manuellement peut \u00eatre fastidieux. J\u2019ai donc cr\u00e9\u00e9 un syst\u00e8me qui pilote automatiquement l\u2019ouverture et la fermeture en fonction de l\u2019heure et du mode de fonctionnement. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pour information ma couverture flottante est un :<a href=\"https:\/\/www.abriblue.com\/solutions\/immax\/\">https:\/\/www.abriblue.com\/solutions\/immax\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">J&rsquo;ai raccord\u00e9 un contact NO des relais de commande d&rsquo;ouverture et de fermeture en parall\u00e8le sur le boitier \u00e0 clef fourni avec le volet.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Attention:<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ce n&rsquo;est pas conforme de fermer un volet sans visuel, mais je le fais en connaissance de cause et je vous conseille de modifier les scripts de fermeture: Vous pouvez automatiser l&rsquo;ouverture et d\u00e9clencher la fermeture manuellement avec un visuel sur le volet<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Contexte et objectif<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ma piscine est \u00e9quip\u00e9e d\u2019une couverture flottante motoris\u00e9e, contr\u00f4l\u00e9e par deux interrupteurs : <code>cde_volet_ouverture<\/code> pour ouvrir et <code>cde_volet_fermeture<\/code> pour fermer. Je voulais automatiser ce processus selon deux modes :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Mode \u00ab\u00a0Horaire\u00a0\u00bb<\/strong> : L\u2019ouverture et la fermeture se font \u00e0 des heures fixes, d\u00e9finies par <code>h_ouv_volet<\/code> et <code>h_ferm_volet<\/code>.<\/li>\n\n\n\n<li><strong>Mode \u00ab\u00a0Auto\u00a0\u00bb<\/strong> : L\u2019ouverture est synchronis\u00e9e avec le d\u00e9marrage de la filtration (selon ses modes : \u00ab\u00a0Horaire\u00a0\u00bb, \u00ab\u00a0Palier\u00a0\u00bb, \u00ab\u00a0Classique\u00a0\u00bb, ou \u00ab\u00a0Abaque\u00a0\u00bb), et la fermeture se fait \u00e0 l\u2019heure d\u00e9finie par <code>h_ferm_volet<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">De plus, j\u2019ai ajout\u00e9 un \u00e9clairage (<code>cde_eclairage<\/code>) qui s\u2019allume pendant la fermeture pour des raisons de s\u00e9curit\u00e9, et un script pour arr\u00eater manuellement le mouvement si n\u00e9cessaire.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">La configuration ESPHome<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Vous trouverez le code complet fin d\u2019article.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fonctionnement d\u00e9taill\u00e9<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ce script pilote la couverture flottante de mani\u00e8re intelligente, en fonction de l\u2019heure et du mode choisi. Voici les \u00e9tapes principales :<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>V\u00e9rification de l\u2019heure<\/strong> :<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Le script utilise <code>sntp_time<\/code> pour r\u00e9cup\u00e9rer l\u2019heure actuelle et s\u2019assure qu\u2019elle est valide. Des logs permettent de comparer l\u2019heure actuelle avec les heures programm\u00e9es pour l\u2019ouverture (<code>h_ouv_volet<\/code>) et la fermeture (<code>h_ferm_volet<\/code>).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mode \u00ab\u00a0Horaire\u00a0\u00bb<\/strong> :<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Si le mode est d\u00e9fini sur \u00ab\u00a0Horaire\u00a0\u00bb, le script v\u00e9rifie si l\u2019heure actuelle correspond \u00e0 l\u2019heure d\u2019ouverture ou de fermeture d\u00e9finie. Si c\u2019est le cas, il ex\u00e9cute respectivement <code>script_ouv_volet<\/code> ou <code>script_ferm_volet<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mode \u00ab\u00a0Auto\u00a0\u00bb<\/strong> :<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>En mode \u00ab\u00a0Auto\u00a0\u00bb, l\u2019ouverture est d\u00e9clench\u00e9e \u00e0 l\u2019heure de d\u00e9marrage de la filtration (<code>h_debut<\/code>), mais uniquement si la filtration est configur\u00e9e dans un mode compatible (\u00ab\u00a0Horaire\u00a0\u00bb, \u00ab\u00a0Palier\u00a0\u00bb, \u00ab\u00a0Classique\u00a0\u00bb, ou \u00ab\u00a0Abaque\u00a0\u00bb).<\/li>\n\n\n\n<li>La fermeture est d\u00e9clench\u00e9e \u00e0 l\u2019heure d\u00e9finie par <code>h_ferm_volet<\/code>, comme en mode \u00ab\u00a0Horaire\u00a0\u00bb.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Scripts d\u2019ouverture et de fermeture<\/strong> :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Ouverture (<code>script_ouv_volet<\/code>)<\/strong> : \u00c9teint la commande de fermeture, attend 2 secondes, active la commande d\u2019ouverture pendant 5 secondes, puis l\u2019\u00e9teint.<\/li>\n\n\n\n<li><strong>Fermeture (<code>script_ferm_volet<\/code>)<\/strong> : \u00c9teint la commande d\u2019ouverture, attend 2 secondes, active la commande de fermeture et l\u2019\u00e9clairage pendant 90 secondes (le temps que la couverture se ferme compl\u00e8tement), puis \u00e9teint les deux.<\/li>\n\n\n\n<li><strong>Arr\u00eat (<code>script_stop_volet<\/code>)<\/strong> : Arr\u00eate les deux moteurs, effectue une petite impulsion de fermeture pour s\u00e9curiser, et \u00e9teint l\u2019\u00e9clairage.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Exemple pratique<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Imaginons que je configure mon syst\u00e8me ainsi :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Mode \u00ab\u00a0Horaire\u00a0\u00bb.<\/li>\n\n\n\n<li>Heure d\u2019ouverture : 8h00 (<code>h_ouv_volet<\/code>).<\/li>\n\n\n\n<li>Heure de fermeture : 20h00 (<code>h_ferm_volet<\/code>).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">\u00c0 8h00 pr\u00e9cises, le script <code>script_ouv_volet<\/code> s\u2019ex\u00e9cute : la couverture s\u2019ouvre, laissant la piscine accessible. \u00c0 20h00, <code>script_ferm_volet<\/code> se d\u00e9clenche : la couverture se ferme en 90 secondes, et l\u2019\u00e9clairage s\u2019allume pendant ce temps pour des raisons de s\u00e9curit\u00e9. Si besoin, je peux arr\u00eater manuellement avec <code>script_stop_volet<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Conseils pour adapter cette solution<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Ajustez les dur\u00e9es<\/strong> : Modifiez les d\u00e9lais (5 secondes pour l\u2019ouverture, 90 secondes pour la fermeture) selon la vitesse de votre moteur.<\/li>\n\n\n\n<li><strong>Ajoutez des notifications<\/strong> : Comme pour mes autres scripts, vous pouvez int\u00e9grer des messages Telegram pour \u00eatre inform\u00e9 de chaque ouverture ou fermeture.<\/li>\n\n\n\n<li><strong>Testez les modes<\/strong> : Essayez le mode \u00ab\u00a0Auto\u00a0\u00bb pour voir s\u2019il convient \u00e0 votre routine de filtration, et ajustez les heures si n\u00e9cessaire.<\/li>\n\n\n\n<li><strong>Ne pas reproduire la fermeture automatique<\/strong><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Monitoring<\/strong> :<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Rapport journalier Telegram \u00e0 23h59 (temps filtration, conso, etc.).<\/li>\n\n\n\n<li>Alerte si pression filtre &gt; 1.5 bar ou galets chlore \u00e9puis\u00e9s<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Mesures \u00c9lectriques : Surveillance de la Consommation de la Piscine<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Afin de surveiller avec pr\u00e9cision la consommation \u00e9lectrique de l\u2019ensemble du local technique piscine (pompe de filtration, \u00e9lectrolyseur, r\u00e9gulation, etc.), j\u2019ai int\u00e9gr\u00e9 un module <strong>PZEM-004T (version TTL 100A)<\/strong> \u00e0 mon ESP32. Ce module permet de mesurer en temps r\u00e9el la tension, le courant, la puissance active, et l\u2019\u00e9nergie consomm\u00e9e.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Objectif et contexte<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Mon objectif \u00e9tait de disposer d\u2019un suivi pr\u00e9cis de la consommation \u00e9lectrique de la piscine pour :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>D\u00e9tecter un fonctionnement anormal (pompe bloqu\u00e9e, consommation excessive&#8230;),<\/li>\n\n\n\n<li>Calculer le co\u00fbt r\u00e9el de fonctionnement,<\/li>\n\n\n\n<li>Optimiser la planification de certaines t\u00e2ches en fonction du tarif horaire de l\u2019\u00e9lectricit\u00e9.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Le PZEM est connect\u00e9 au circuit principal d\u2019alimentation de la piscine, et communique avec l\u2019ESP32 via l\u2019interface <strong>UART (RX\/TX)<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">La configuration ESPHome<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Vous trouverez le code complet fin d\u2019article.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fonctionnement et visualisation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Les valeurs mesur\u00e9es sont mises \u00e0 jour toutes les <strong>30 secondes<\/strong> et affich\u00e9es dans Home Assistant via ESPHome. Cela me permet de :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Visualiser la <strong>puissance instantan\u00e9e<\/strong> consomm\u00e9e,<\/li>\n\n\n\n<li>Acc\u00e9der \u00e0 l\u2019<strong>historique de l\u2019\u00e9nergie<\/strong> consomm\u00e9e chaque jour.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Avantages<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Cette mesure pourrait me permet, par exemple, de d\u00e9tecter rapidement un dysfonctionnement de la pompe de filtration, qui tourne \u00e0 vide suite \u00e0 un d\u00e9samor\u00e7age. Le courant absorb\u00e9 \u00e9tant anormalement bas, une alerte pourrait \u00eatre g\u00e9n\u00e9r\u00e9e, \u00e9vitant un fonctionnement prolong\u00e9 inutile.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ce n&rsquo;est qu&rsquo;une suggestion car inutile dans mon cas.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Code ESPHOME<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Programme Principal:<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pour information, les codes ci-dessous ne sont certainement pas \u00e0  jour, de ce fait vous les trouverez en t\u00e9l\u00e9chargement sur:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La derni\u00e8re version  du programme principal est disponible ici:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/remycrochon\/home-assistant\/blob\/master\/esphome\/esp178-piscine.yaml\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/remycrochon\/home-assistant\/blob\/master\/esphome\/esp178-piscine.yaml<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Et les sous programmes ici:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/remycrochon\/home-assistant\/tree\/master\/esphome\/pack_esp178\">https:\/\/github.com\/remycrochon\/home-assistant\/tree\/master\/esphome\/pack_esp178<\/a><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Mode simulation\n# Sensor simul\u00e9:\n  # temperature eau\n  # ph EZO\n  # Seuil binary sensor \"Marche ppe\"\n  # test si ph_EZO est valide\n  # Cron time pour lancer script regule pH\n\nsubstitutions:\n  device_name: \"esp178_piscine\"\n  friendly_name: esp178\n  adress_ip: \"192.168.0.178\"\n  time_timezone: \"Europe\/Paris\"\n  # Definition des seuils admissibles\n  pu_fonctionnement: \"200\"\n  pression_max: \"15\"\n\npackages:\n  ph: !include pack_esp178\/ph.yaml\n  chlore: !include pack_esp178\/chlore.yaml    \n  volet: !include pack_esp178\/couverture_flottante.yaml\n  appoint_eau: !include pack_esp178\/appoint_eau.yaml\n  hors_gel: !include pack_esp178\/hors_gel.yaml\n  mesure_elec: !include pack_esp178\/mesure_elec.yaml\n  mesure_pression: !include pack_esp178\/mesure_pression.yaml\n\nesphome:\n  name: ${device_name}\n  project:\n    name: \"rem81.esp178-esp32-piscine\"\n    version: \"1.0.0\"\n\n  on_boot:\n    priority: 200\n    then:\n      - lambda: |-\n          #define CONVERT_SECONDS(total_seconds, hh, mm, ss) \\\n          do { \\\n            int total = static_cast&lt;int&gt;(total_seconds); \\\n            hh = total \/ 3600; \\\n            int r = total - hh * 3600; \\\n            mm = r \/ 60; \\\n            ss = r - mm * 60; \\\n            hh = std::min(hh, 23); \\\n            mm = std::min(mm, 59); \\\n            ss = std::min(ss, 59); \\\n          } while (0)\n      # Initialisation des templates Ph\n      # A supprimer si pH non utilis\u00e9\n      - sensor.template.publish:\n          id: _tps_injection_ph_moins\n          state: 0.0\n      - sensor.template.publish:\n          id: _vol_injection_ph_moins\n          state: 0.0\n      # Initialisation du template Chlore\n      # A supprimer si Chlore non utilis\u00e9\n      - sensor.template.publish:\n          id: _tps_injection_chlore\n          state: 0.0\n                    \n      # Messages de Boot\n      - delay: 20s\n      - lambda: |-\n          std::string mess = \"Boot ESP178\";\n          id(telegram_msg_buffer) = mess;\n      - homeassistant.service:\n          service: notify.telegram\n          data:\n            message: !lambda 'return id(telegram_msg_buffer).c_str();'\n        # Message Telegram\n      - lambda: |-                \n          std::string mess= \"ESP178 Boot\";\n          id(_message_telegram)-&gt;execute(mess.c_str());                   \n\nesp32:\n  board: esp32dev\n\nwifi:\n  networks:\n   - ssid: !secret wifi_esp\n     password: !secret mdpwifi_esp\n  reboot_timeout: 5min\n\n#ethernet:\n#  type: W5500\n#  clk_pin: GPIO17\n#  mosi_pin: GPIO19\n#  miso_pin: GPIO18\n#  cs_pin: GPIO21 \n#  interrupt_pin: GPIO4\n#  reset_pin: GPIO5\n#  clock_speed: 15Mhz\n  manual_ip:\n    static_ip: ${adress_ip}\n    gateway: 192.168.0.254\n    subnet: 255.255.255.0\n    dns1: 192.168.0.254\n\n# Utilisez la LED de l'appareil comme LED d'\u00e9tat, qui clignotera s'il y a des avertissements (lent) ou des erreurs (rapide)\nstatus_led:\n  pin:\n    number: GPIO23\n    inverted: true\n\n# Enable logging\nlogger:\n  level: INFO\n  baud_rate: 0\n  \n# Enable Home Assistant API\napi:\n\n\nota:\n  platform: esphome\n\nweb_server:\n  port: 80\n  version: 3 # sinon 2\n\ntime:\n  - platform: sntp\n    id: sntp_time\n    timezone: Europe\/Paris\n    servers:\n      - 0.pool.ntp.org\n      - 1.pool.ntp.org\n      - 2.pool.ntp.org\n    on_time:\n      # reset le compteur de temps \u00e0 minuit\n      - seconds: 0\n        minutes: 0\n        hours: 0\n        then:\n          - sensor.duty_time.reset: _temps_fonctionnement_ppe_piscine_jour\n\n      # Notification du rapport journalier sur Telegram\n      - seconds: 00\n        minutes: 59\n        hours: 23\n        then:\n          - lambda: |-\n              std::string mess = \"ESP178 Temps Fonctionnement filtration\";\n              id(_message_telegram_v2)-&gt;execute(mess,id(_temps_fonctionnement_ppe_piscine_jour).state);\n          - lambda: |-          \n              std::string mess = \"ESP178 Tps Fonct ppe pH\";\n              id(_message_telegram_v2)-&gt;execute(mess,id(_temps_fonctionnement_ppe_ph).state);\n          - lambda: |-          \n              std::string mess = \"ESP178 Tps Fonct ppe Chl\";\n              id(_message_telegram_v2)-&gt;execute(mess,id(_temps_fonctionnement_ppe_chlore).state);\n\n# Connection Bus i2c (Afficheur, EZO,...)\ni2c:\n  sda: 15\n  scl: 22\n  scan: false\n  id: bus_a\n  frequency: 300kHz\n\n# Connection sonde(s) de temp\u00e9rature DS18b20\none_wire:\n  - platform: gpio\n    pin: GPIO16\n\n# Extension E\/S\nsx1509:\n  - id: sx1509_hub1\n    address: 0x3E\n\n# d\u00e9claration des variables \"globals\"\nglobals:\n    # temp\u00e9rature de fonctionnement en d\u00e9but de pompage avant prise en compte de la mesure de temp\u00e9rature\n    # en secondes\n    - id: g_memoire_temp_eau\n      type: float\n      restore_value: yes\n      initial_value: '25'\n\n    - id: flag_tempo_ppe_filtre\n      type: bool\n      restore_value: no\n      initial_value: 'false'\n\n    # m\u00e9morise la dur\u00e9e de filtation dans les diff\u00e9rents modes de fonctionnement\n    - id: g_tps_filtration\n      type: float\n      restore_value: no\n\n    # Limite haute du temps de filtration \"en heure\"\n    - id: g_temps_max_filtration\n      type: float\n      initial_value: '23'\n    # Limite basse du temps de filtration (en heure)\n    - id: g_temps_min_filtration\n      type: float\n      initial_value: '5'\n    # Paliers temperature\/Temps filtration avec le mode \"Palier\"\n    # Seuils Temp\u00e9rature en \u00b0C\n    # la dur\u00e9e est calcul\u00e9e manuellement par rapport au volume de la piscine: Vol=9*4*1.4=50.4 m3\n    # et le d\u00e9bit de la pompe: Qppe Th\u00e9orique=12m3\/h -&gt; retenu:12m3\/h\n    # donc 1 cycle= 50.4\/12=4.2h\n    # il faut filtrer au minimum:\n    # T\u00b0eau&lt;10\u00b0 = 0 cycle\n    # 10\u00b0&lt;T\u00b0eau&lt;15\u00b0 = 1 cycle = 4.2h =&gt; 4h\n    # 15\u00b0&lt;T\u00b0eau&lt;20\u00b0 = 2 cycles = 8.4h =&gt; 8h\n    # 20\u00b0&lt;T\u00b0eau&lt;25\u00b0 = 3 cycles = 12.6h =&gt; 12h\n    # 25\u00b0&lt;T\u00b0eau = 3 ou 4 cycles = 12.6h =&gt; 12h\n    # T\u00b0Eau&gt;25\u00b0 =&gt; 14h\n    # \n    # si beaucoup de baigneurs de jour alors augmenter le coeff\n    # Mode \"Palier\":\n      # Si T\u00b0eau&lt; Seuil Temp1 alors Dur\u00e9e = Tps paliers 1\n      # Sinon\n      # Si T\u00b0eau&gt;= Seuil Temp1 et T\u00b0eau&lt; Seuil Temp2 alors Dur\u00e9e = Tps paliers 2\n      # Sinon\n      # Si T\u00b0eau&gt;= Seuil Temp2 et T\u00b0eau&lt; Seuil Temp3 alors Dur\u00e9e = Tps paliers 3\n      # Sinon\n      # Si T\u00b0eau&gt;= Seuil Temp3 et T\u00b0eau&lt; Seuil Temp4 alors Dur\u00e9e = Tps paliers 4\n      # Sinon\n      # Dur\u00e9e = Tps paliers 5\n    - id: g_temp_palier1\n      type: float\n      initial_value: '10'\n    - id: g_temp_palier2\n      type: float\n      initial_value: '15'\n    - id: g_temp_palier3\n      type: float\n      initial_value: '20'\n    - id: g_temp_palier4\n      type: float\n      initial_value: '25' \n  # nb d'heures de filtration (en h)\n    - id: g_tps_palier1\n      type: float\n      initial_value: '1'      \n    - id: g_tps_palier2\n      type: float\n      initial_value: '4'\n    - id: g_tps_palier3\n      type: float\n      initial_value: '8'\n    - id: g_tps_palier4\n      type: float\n      initial_value: '12'\n    - id: g_tps_palier5\n      type: float\n      initial_value: '14'\n\n    # Constantes utilis\u00e9es dans le mode \"Abaque\"  \n    - id: g_abaque_a\n      type: float\n      initial_value: '0.00335'\n    - id: g_abaque_b\n      type: float\n      initial_value: '-0.14953'\n    - id: g_abaque_c\n      type: float\n      initial_value: '2.43489'\n    - id: g_abaque_d\n      type: float\n      initial_value: '-10.72859'\n\n    # Variables intermediaires utilis\u00e9es dans le calcul \"heure debut et fin\"\n    - id: g_hh\n      type: int\n    - id: g_mm\n      type: int\n    - id: g_ss\n      type: int\n    \n    # stocke temporairement le message \u00e0 envoyer \u00e0 telegram\n    - id: telegram_msg_buffer\n      type: std::string\n      restore_value: no\n      initial_value: '\"\"'\n\n    - id: convert_seconds\n      type: std::function&lt;void(int, int&amp;, int&amp;, int&amp;)&gt;\n\n# d\u00e9claration des modes de fonctionnement dans des \"input select\"\nselect:\n  - platform: template\n    name: \"Mode_Fonctionnement_filtration\"\n    optimistic: true\n    restore_value: true\n    options:\n      - Palier\n      - Classique\n      - Abaque\n      - Horaire\n      - Ma_f\n      - At_f\n    id: _Mode_Fonctionnement_filtration\n    on_value: \n      then:\n        - script.execute: _fonctionnement_filtration        \n        - logger.log:\n            format: \"Mode Fonct Filtration --&gt; %s\"\n            args: &#091; 'id(_Mode_Fonctionnement_filtration).state.c_str()' ]\n            level: INFO\n        - lambda: |-\n            char buf&#091;64];\n            snprintf(buf, sizeof(buf), \"Mode Fonct Filtration --&gt; %s\", id(_Mode_Fonctionnement_filtration).state.c_str());\n            id(_log_message)-&gt;execute(std::string(buf));\n\n\nbutton:\n  # Ce bouton stoppe la filtration pour la journ\u00e9e (cas de mauvais temps par ex)\n  # Dur\u00e9e \"Arret Jour\" en heure on multiplie par 3600 pour avoir des secondes \n  # puis par 1000 pour des millisecondes: unit\u00e9 du delay lambda\n  - platform: template\n    name: \"BP_arret_jour\"\n    id: _arret_jour\n    on_press: \n      then:\n      - switch.template.publish:\n          id: ent_at_force\n          state: ON\n      - logger.log:\n          format: \"D\u00e9but arret jour pour : %.0f h\"\n          args: &#091; 'id(duree_at_jour).state' ]\n          level: INFO         \n      - delay: !lambda \"return id(duree_at_jour).state*3600*1000;\"\n      - switch.template.publish:\n          id: ent_at_force\n          state: OFF\n      - logger.log:\n          format: \"Fin arret jour de : %.0f h\"\n          args: &#091; 'id(duree_at_jour).state' ]\n          level: info \n  \n  # Lance un test\n  - platform: template\n    name: \"BP_Pour_Test\"\n    on_press:\n      - script.execute: _test\n\nbinary_sensor:\n  #Etat de la connection\n  - platform: status\n    name: \"Status\"\n\n  # Pompe en fonctionnement\n  # Remplacer le seuil (threshold) par du n\u00e9gatif pour simuler\n  - platform: analog_threshold\n    name: \"Ppe_en_fonctionnement\"\n    id: ppe_filt_en_fonctionnement\n    sensor_id: puissance\n    threshold: ${pu_fonctionnement} # D\u00e9fini dans Substitution en Watt\n    on_press:\n      - lambda: |-                \n          std::string mess= \"ESP178 Debut Filtration\";\n          id(_message_telegram)-&gt;execute(mess.c_str());           \n    on_release: \n      - lambda: |-                \n          std::string mess= \"ESP178 Fin Filtration\";\n          id(_message_telegram)-&gt;execute(mess.c_str());    \n\n  # Entr\u00e9e logique permettant de lire le BP I00 de la carte\n  - platform: gpio\n    pin:\n      number: GPIO00\n      inverted: True\n      mode:\n        input: true\n        pullup: true\n    name: \"bp1\"\n\n  # GPIO sur module extension SX1509\n  - platform: gpio\n    name: \"E4\"\n    pin:\n      sx1509: sx1509_hub1\n      number: 3\n      mode:\n        input: true\n        pullup: false\n      inverted: false   \n  - platform: gpio\n    name: \"E5\"\n    pin:\n      sx1509: sx1509_hub1\n      number: 4\n      mode:\n        input: true\n        pullup: false\n      inverted: false   \n  - platform: gpio\n    name: \"E6\"\n    pin:\n      sx1509: sx1509_hub1\n      number: 5\n      mode:\n        input: true\n        pullup: false\n      inverted: false   \n\n# D\u00e9finiton des \"Time\"\ndatetime:\n  - platform: template\n    id: heure_pivot\n    type: time\n    name: \"heure_pivot\"\n    optimistic: yes\n    initial_value: \"13:30:00\"\n    restore_value: true\n\n  - platform: template\n    id: h_debut\n    type: time\n    name: \"h_debut\"\n    optimistic: yes\n    initial_value: \"00:00:00\"\n    restore_value: false\n    \n  - platform: template\n    id: h_fin\n    type: time\n    name: \"h_fin\"\n    optimistic: yes\n    initial_value: \"00:00:00\"\n    restore_value: false\n\n  - platform: template\n    id: duree_filtration\n    type: time\n    name: \"duree_filtration\"\n    optimistic: yes\n    initial_value: \"00:00:00\"\n    restore_value: false\n\n  - platform: template\n    id: debut_mode_horaire\n    type: time\n    name: \"debut_mode_horaire\"\n    optimistic: yes\n    restore_value: true\n\n  - platform: template\n    id: duree_mode_horaire\n    type: time\n    name: \"duree_mode_horaire\"\n    optimistic: yes\n    restore_value: true\n\n# Input Number\nnumber:\n  # Simulation Temp eau \n  - platform: template\n    name: \"simule_Temp\"\n    id: simul_temp_eau\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: -10\n    max_value: 50\n    device_class: temperature\n    step: 0.01 \n    on_value: \n      then:\n        - lambda: |-\n            id(g_memoire_temp_eau)=id(temp_eau).state;     \n\n  # Temps de recirculation avant prise en compte mesure de temp\u00e9rature\n  - platform: template\n    name: \"tempo_recirculation\"\n    id: tempo_mesure_temp\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 0\n    max_value: 900\n    unit_of_measurement: \"s\"\n    step: 1\n    icon: mdi:clock\n\n  # Coefficient de filtration\n  - platform: template\n    name: \"coeff_Filtration\"\n    id: coeff_filtration\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 50\n    max_value: 150\n    unit_of_measurement: \"%\"\n    step: 1\n    icon: mdi:percent\n\n  # Dur\u00e9e de l'arret sur la journ\u00e9e en lien avec \"arret_jour\"\n  - platform: template\n    name: \"Dur\u00e9e-Arret_jour\"\n    id: duree_at_jour\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 0\n    max_value: 24\n    unit_of_measurement: \"h\"\n    step: 0.01\n\nsensor:\n  - platform: homeassistant\n    name: \"temperature_exterieure\"\n    entity_id: \"sensor.vp2_temp_out\"\n    id: temp_ext\n  #    entity_id: \"input_number.simule_temp_exterieur\" sert \u00e0 la simulation\n\n  - platform: dallas_temp\n    #address: 0x060321117ae89b28\n    name: \"temperature_eau\"\n    id: temp_eau\n    device_class: temperature\n    state_class: \"measurement\"     \n    filters:\n      - filter_out: 0.0\n        # Etalonn\u00e9 le 26 Aout 2024  avec une PT100       \n      #- calibrate_linear:\n      #  - 0 -&gt; 0\n      #  - 26.6 -&gt; 27.7\n\n  # Calcul du temps de fonctionnement\n  # Pompe piscine\n  - platform: duty_time\n    id: _temps_fonctionnement_ppe_piscine_jour\n    name: 'temps_ma_ppe_piscine_jour'\n    sensor: ppe_filt_en_fonctionnement\n    restore: true\n    filters: \n      - round: 0\n\n# d\u00e9claration des \"text_sensors\"\ntext_sensor:\n  # Affichage des heures de filtration dans Home Assistant\n  - platform: template\n    id: aff_heure_filtration\n    name: \"affich_heure_filtration\"\n    icon: mdi:timer\n\n# D\u00e9claration des switches: cde des relais\nswitch:\n  - platform: gpio\n    name: \"cde_pompe_filtration\"\n    pin: GPIO32\n    id: cde_ppe_filtration\n    on_turn_on:\n      then:\n        - switch.turn_on: led14        \n        - delay: !lambda \"return id(tempo_mesure_temp).state*1000;\" # Dur\u00e9e de fonctionnement de la pompe avant prise en compte de la temp\u00e9rature eau\n        - logger.log:\n            format: \"Set tempo cde ppe\"\n            level: DEBUG\n        - lambda: |-\n            id(flag_tempo_ppe_filtre) = true;\n    on_turn_off: \n      then:\n        - logger.log:\n            format: \"Reset tempo cde ppe\"\n            level: DEBUG\n        - lambda: |-\n            id(flag_tempo_ppe_filtre) = false;\n        - script.stop: _regul_ph\n        - switch.turn_off: cde_ppe_ph_moins\n        - script.stop: _regul_chlore\n        - switch.turn_off: cde_ppe_chlore\n        - switch.turn_off: led14     \n\n  - platform: gpio\n    name: \"cde_eclairage\"\n    pin: GPIO26\n    id: cde_eclairage\n\n  - platform: gpio\n    name: \"relais8\"\n    pin: GPIO13\n    id: relais8\n\n  - platform: gpio\n    name: \"led14\"\n    id: led14\n    pin:\n      sx1509: sx1509_hub1\n      number: 14\n      mode:\n        output: true\n      inverted: false\n\n  - platform: gpio\n    name: \"led15\"\n    id: led15\n    pin:\n      sx1509: sx1509_hub1\n      number: 15\n      mode:\n        output: true\n      inverted: false\n\n  - platform: restart\n    name: \"Restart\"\n\n  # Switch For\u00e7age Arret pompe filtration en mode Auto\n  #\n  - platform: template\n    name: \"ent_arret_forc\u00e9\"\n    id: ent_at_force\n    optimistic: True\n    lambda: |-\n      if (id(ent_at_force).state) {\n        return true;\n      } else {\n        return false;\n      }\n    on_turn_on: \n      then:\n        - script.execute: _fonctionnement_filtration        \n        - switch.turn_off: cde_ppe_filtration\n        - logger.log: \n            format: \"entr\u00e9e_arret_forc\u00e9_ppe_filtration\"\n            level: DEBUG\n        # RAZ de la dur\u00e9e de filtration\n        - datetime.time.set:\n            id: duree_filtration\n            time: !lambda |-\n              return {second: 0, minute: 0, hour: 0};                \n        - lambda: |-\n            std::string mess= \"Ent At Forc\u00e9\";\n            id(aff_heure_filtration).publish_state(mess.c_str());\n        # Message Telegram\n        - lambda: |-                \n            std::string mess= \"ESP178 Debut Arret Forc\u00e9 Filtration\";\n            id(_message_telegram)-&gt;execute(mess.c_str());                 \n\n    on_turn_off: \n      then:\n        # Message Telegram\n        - lambda: |-                \n            std::string mess= \"ESP178 Fin arret Forc\u00e9 Filtration\";\n            id(_message_telegram)-&gt;execute(mess.c_str());              \n        - script.execute: _fonctionnement_filtration\n\n# Gestion de l'afficheur \ndisplay:\n  - platform: lcd_pcf8574\n    dimensions: 16x2\n    address: 0x27\n    update_interval: 10s\n    lambda: |-\n      it.printf(0,0,\"Ph=%.2f\",id(ph_ezo).state);\n      it.printf(0,1,\"ORP=%.2f\",id(orp_ezo).state);\n      it.printf(8,0,\"P=%.3f\",id(pression_filtre).state);\n      it.printf(8,1,\"T=%.1f\",id(g_memoire_temp_eau));\n\n#it.printf(15,1,\"T=%.1s\",id(mode_f).state);\n\n# D\u00e9clenchement des scripts \u00e0 intervalles r\u00e9guliers\ninterval:\n  - interval: 5s\n    then:      \n      - script.execute: _fonctionnement_filtration\n\n  - interval: 5s\n    then: \n      - script.execute: _memorisation_temperature_eau\n\n  #- interval: 20s # Test\n  #  then: \n  #    - script.execute: _test\n\n# D\u00e9claration des \"Scripts\"\nscript:\n  # Script utilis\u00e9 pour tester\", peut etre supprimer si inutilise\n  - id: _test\n    then:\n      - lambda: |-\n          std::string mess = \"ESP178 Temps Fonctionnement filtration\";\n          id(_message_telegram_v2)-&gt;execute(mess,id(_temps_fonctionnement_ppe_piscine_jour).state);\n      - lambda: |-          \n          std::string mess = \"ESP178 Tps Fonct ppe pH\";\n          id(_message_telegram_v2)-&gt;execute(mess,id(_temps_fonctionnement_ppe_ph).state);\n      - lambda: |-          \n          std::string mess = \"ESP178 Tps Fonct ppe Chl\";\n          id(_message_telegram_v2)-&gt;execute(mess,id(_temps_fonctionnement_ppe_chlore).state);\n\n  - id: _testold\n    then:\n      - lambda: |-\n          std::string mess = \"ESP178 Rapport Journalier\\n\";\n\n          CONVERT_SECONDS(id(_temps_fonctionnement_ppe_piscine_jour).state, id(g_hh), id(g_mm), id(g_ss));\n          char buf1&#091;32];\n          snprintf(buf1, sizeof(buf1), \"Tps Filtration: %02d:%02d:%02d\\n\", id(g_hh), id(g_mm), id(g_ss));\n          mess += buf1;\n\n          CONVERT_SECONDS(id(_temps_fonctionnement_ppe_ph).state, id(g_hh), id(g_mm), id(g_ss));\n          snprintf(buf1, sizeof(buf1), \"Tps Ppe pH: %02d:%02d:%02d\\n\", id(g_hh), id(g_mm), id(g_ss));\n          mess += buf1;\n\n          CONVERT_SECONDS(id(_temps_fonctionnement_ppe_chlore).state, id(g_hh), id(g_mm), id(g_ss));\n          snprintf(buf1, sizeof(buf1), \"Tps Ppe Chlore: %02d:%02d:%02d\\n\", id(g_hh), id(g_mm), id(g_ss));\n          mess += buf1;\n\n          id(_message_telegram)-&gt;execute(mess);\n  # Si la pompe tourne depuis au moins \"tempo_recirculation\" on raffraichit la memoire de la temperature eau qui est\n  # prise en compte dans les scripts.\n  # sinon on travaille avec la t\u00e9mp\u00e9rature m\u00e9moris\u00e9e avant l'arret pr\u00e9c\u00e9dent\n  - id: _memorisation_temperature_eau\n    then:\n      - if:\n          condition:\n            lambda: 'return id(flag_tempo_ppe_filtre) == true;'\n          then:\n            - lambda: |-\n                id(g_memoire_temp_eau)=id(temp_eau).state;            \n          else:\n            - lambda: |-\n                id(g_memoire_temp_eau)=id(g_memoire_temp_eau);\n      - logger.log:\n          format: \"Flag Temp: %i \/ Temp Dallas: %.2f \/ Mem Temp: %.2f\"\n          args: &#091; 'id(flag_tempo_ppe_filtre)','id(temp_eau).state','id(g_memoire_temp_eau)' ]\n          level: DEBUG\n\n   # Calcul de la dur\u00e9e de la filtration en fonction du mode de fonctionnement selectionn\u00e9\n  - id: _fonctionnement_filtration\n    then:\n      - logger.log:\n          format: \"Switch at force 2: %i \"\n          args: &#091; 'id(ent_at_force).state' ]\n          level: DEBUG\n\n      # Entr\u00e9e Arret Forc\u00e9 par Binary_sensor \"Arret force\"\n      - if:\n          condition:\n            - lambda: 'return id(ent_at_force).state == true;'\n          then:\n            - switch.turn_off: cde_ppe_filtration\n\n      # Entr\u00e9e Marche Forc\u00e9e HG\n      - if:\n          condition:\n            - lambda: 'return id(g_flag_hg) == true;'\n          then:\n            - switch.turn_on: cde_ppe_filtration\n            - logger.log: \n                format: \"Marche HG Ppe filtration\"\n                level: INFO\n            - logger.log:\n                format: \"Flag HG: %i \/ Temp Ext: %.2f \/ S1: %.2f \/ S2: %.2f\"\n                args: &#091; 'id(g_flag_hg)','id(temp_ext).state','id(s1_temp_hg).state','id(s2_temp_hg).state' ]\n                level: DEBUG                \n            - lambda: |-\n                std::string mess= \"Ma HG\";\n                id(aff_heure_filtration).publish_state(mess.c_str());\n\n            # RAZ de la dur\u00e9e de filtration\n            - datetime.time.set:\n                id: duree_filtration\n                time: !lambda |-\n                  return {second: 0, minute: 0, hour: 0};\n\n      # Mode Arret forc\u00e9 Input Select\n      - if:\n          condition:\n              - lambda: 'return id(_Mode_Fonctionnement_filtration).state == \"At_f\";'\n              - lambda: 'return id(ent_at_force).state == false;'\n              - lambda: 'return id(g_flag_hg) == false;'\n          then:\n            - switch.turn_off: cde_ppe_filtration\n            - logger.log: \n                format: \"arret_forc\u00e9_ppe_filtration\"\n                level: DEBUG\n            - lambda: |-\n                std::string mess=\"At_force\";\n                id(aff_heure_filtration).publish_state(mess.c_str());\n\n      # Mode Marche forc\u00e9e Input Select\n      - if:\n          condition:\n            - lambda: 'return id(_Mode_Fonctionnement_filtration).state == \"Ma_f\";'\n            - lambda: 'return id(ent_at_force).state == false;'\n            - lambda: 'return id(g_flag_hg) == false;'\n          then:\n            - switch.turn_on: cde_ppe_filtration\n            - logger.log: \n                format: \"Marche_forc\u00e9e_ppe_filtration\"\n                level: DEBUG\n            - lambda: |-\n                std::string mess=\"Ma_force\";\n                id(aff_heure_filtration).publish_state(mess.c_str());                \n\n      # Mode \"Palier\":\n      # Si T\u00b0eau&lt; Seuil Temp1 alors Dur\u00e9e = Tps paliers 1\n      # Sinon\n      # Si T\u00b0eau&gt;= Seuil Temp1 et T\u00b0eau&lt; Seuil Temp2 alors Dur\u00e9e = Tps paliers 2\n      # Sinon\n      # Si T\u00b0eau&gt;= Seuil Temp2 et T\u00b0eau&lt; Seuil Temp3 alors Dur\u00e9e = Tps paliers 3\n      # Sinon\n      # Si T\u00b0eau&gt;= Seuil Temp3 et T\u00b0eau&lt; Seuil Temp4 alors Dur\u00e9e = Tps paliers 4\n      # Sinon\n      # Dur\u00e9e = Tps paliers 5\n      - if:\n          condition:\n            - lambda: 'return id(_Mode_Fonctionnement_filtration).state == \"Palier\";'\n            - lambda: 'return id(ent_at_force).state == false;'\n            - lambda: 'return id(g_flag_hg) == false;'\n          then:\n            - lambda: |-\n                if (id(g_memoire_temp_eau)&lt;id(g_temp_palier1)){\n                  id(g_tps_filtration)=id(g_tps_palier1);\n                } else {\n                  if (id(g_memoire_temp_eau)&gt;=id(g_temp_palier1) &amp;&amp; (id(g_memoire_temp_eau)&lt;id(g_temp_palier2))){\n                    id(g_tps_filtration)=id(g_tps_palier2);\n                  } else {\n                    if (id(g_memoire_temp_eau)&gt;=id(g_temp_palier2) &amp;&amp; (id(g_memoire_temp_eau)&lt;id(g_temp_palier3))){\n                    id(g_tps_filtration)=id(g_tps_palier3);\n                    } else {\n                      if (id(g_memoire_temp_eau)&gt;=id(g_temp_palier3) &amp;&amp; (id(g_memoire_temp_eau)&lt;id(g_temp_palier4))){\n                      id(g_tps_filtration)=id(g_tps_palier4);\n                      } else {\n                        id(g_tps_filtration)=id(g_tps_palier5);\n                      }\n                    }\n                  }\n                }\n                \n                id(g_tps_filtration)=id(g_tps_filtration)*id(coeff_filtration).state\/100;\n\n            - logger.log:\n                format: \"Mode: Palier \/ Valeur Mem Temp: %.2f \/ Tps Filtrat: %2f\"\n                args: &#091; 'id(g_memoire_temp_eau)','id(g_tps_filtration)' ]\n                level: DEBUG\n            - script.execute: _calcul_hdebut_hfin\n      \n      # Mode \"Classique\":\n      # La dur\u00e9e de filtration en h est \u00e9gale \u00e0 la temp\u00e9rature de l'eau divis\u00e9e par 2\n      - if:\n          condition:\n            - lambda: 'return id(_Mode_Fonctionnement_filtration).state == \"Classique\";'\n            - lambda: 'return id(ent_at_force).state == false;'     \n            - lambda: 'return id(g_flag_hg) == false;'       \n          then:\n            - lambda: |-\n                id(g_tps_filtration)=id(g_memoire_temp_eau)\/2;\n                id(g_tps_filtration)=min(id(g_temps_max_filtration),id(g_tps_filtration));\n                id(g_tps_filtration)=max(id(g_temps_min_filtration),id(g_tps_filtration));\n                id(g_tps_filtration)=id(g_tps_filtration)*id(coeff_filtration).state\/100;\n            - logger.log:\n                format: \"Mode Classique \/ Valeur Mem Temp: %.2f \/ Tps Filtrat: %2f\"\n                args: &#091; 'id(g_memoire_temp_eau)','id(g_tps_filtration)' ]\n                level: DEBUG\n            - script.execute: _calcul_hdebut_hfin\n      \n      # Mode \"Abacus\"\n      #\n      - if:\n          condition:\n            - lambda: 'return id(_Mode_Fonctionnement_filtration).state == \"Abaque\";'\n            - lambda: 'return id(ent_at_force).state == false;'    \n            - lambda: 'return id(g_flag_hg) == false;'        \n          then:\n            - lambda: |-\n                  id(g_tps_filtration)=id(g_abaque_a)*pow(id(g_memoire_temp_eau),3)+id(g_abaque_b)*pow(id(g_memoire_temp_eau),2)+id(g_abaque_c)*id(g_memoire_temp_eau)+id(g_abaque_d);\n                  id(g_tps_filtration)=id(g_tps_filtration)*id(coeff_filtration).state\/100;\n                  id(g_tps_filtration)=min(id(g_temps_max_filtration),id(g_tps_filtration));\n                  id(g_tps_filtration)=max(id(g_temps_min_filtration),id(g_tps_filtration));\n            - logger.log:\n                format: \"Mode Abaque \/ Valeur Mem Temp: %.2f \/ Tps Filtrat: %2f\"\n                args: &#091; 'id(g_memoire_temp_eau)','id(g_tps_filtration)' ]\n                level: DEBUG\n            - script.execute: _calcul_hdebut_hfin\n\n      # Mode \"Horaire\"\n      # D\u00e9bute \u00e0 l'heure programm\u00e9e pour une dur\u00e9e programm\u00e9e\n      # je m'en sers surtout l'hiver\n      - if:\n          condition:\n            - lambda: 'return id(_Mode_Fonctionnement_filtration).state == \"Horaire\";'\n            - lambda: 'return id(ent_at_force).state == false;'\n            - lambda: 'return id(g_flag_hg) == false;'\n          then:\n            - logger.log:\n                format: \"Mode Abaque \/ Valeur Mem Temp: %.2f\"\n                args: &#091; 'id(g_memoire_temp_eau)' ]\n                level: DEBUG\n\n            - datetime.time.set:\n                id: h_debut\n                time: !lambda |-\n                  return {second: 0, minute: id(debut_mode_horaire).minute, hour: id(debut_mode_horaire).hour};\n\n            - datetime.time.set:\n                id: h_fin\n                time: !lambda |-\n                  return {second: 0, minute: static_cast&lt;uint8_t&gt;(id(h_debut).minute+id(duree_mode_horaire).minute), hour: static_cast&lt;uint8_t&gt;(id(h_debut).hour+id(duree_mode_horaire).hour)};\n                  \/\/ return {second: 0, minute: id(h_debut).minute+id(duree_mode_horaire).minute, hour: id(h_debut).hour+id(duree_mode_horaire).hour};\n            - lambda: |-\n                std::string mess= std::to_string(id(h_debut).hour)+\":\"+std::to_string(id(h_debut).minute)+\"\/\"+std::to_string(id(h_fin).hour)+\":\"+std::to_string(id(h_fin).minute);\n                id(aff_heure_filtration).publish_state(mess.c_str());\n                \n            - script.execute: _calcul_ma_at_ppe_filtration\n\n  # Calcul l'heure de debut et fin de filtration en fonction la dur\u00e9e de filtration et de l'heure pivot\n  # La variable: g_tps_filtration contient la dur\u00e9e en heure\n  - id: _calcul_hdebut_hfin\n    mode: single\n    then:\n      #RAZ des secondes de l'heure pivot\n      - datetime.time.set:\n          id: heure_pivot\n          time: !lambda |-\n            return {second: 0,minute: id(heure_pivot).minute, hour: id(heure_pivot).hour};\n\n      - logger.log:\n          format: \"HPivot %2d:%.2d:%2d\"\n          args: &#091; 'id(heure_pivot).hour', 'id(heure_pivot).minute', 'id(heure_pivot).second' ]\n          level: DEBUG\n\n      # Heure de debut = Heure pivot converti en minutes - temps filtration converti en minutes\n      - lambda: |-\n          static double dt=0;\n          static double hp=0;\n          \n          hp = id(heure_pivot).hour*60+id(heure_pivot).minute;\n          dt = id(heure_pivot).hour*60+id(heure_pivot).minute-((id(g_tps_filtration)\/2)*60);\n\n          id(g_hh)=int(dt\/60);\n          id(g_mm)=dt-id(g_hh)*60;\n\n      - logger.log:\n          format: \"H debut Filtration %2d: %.2d\"\n          args: &#091; 'id(g_hh)', 'id(g_mm)' ]\n          level: DEBUG\n\n      - datetime.time.set:\n          id: h_debut\n          time: !lambda |-\n            return {second: 0, minute: static_cast&lt;uint8_t&gt;(id(g_mm)), hour: static_cast&lt;uint8_t&gt;(id(g_hh))};\n\n      # Heure de fin = Heure pivot converti en minutes + temps filtration converti en minutes\n      - lambda: |-\n          static double dt=0;\n          static double hp=0;\n          \n          hp = id(heure_pivot).hour*60+id(heure_pivot).minute;\n          dt = id(heure_pivot).hour*60+id(heure_pivot).minute+((id(g_tps_filtration)\/2)*60);\n\n          id(g_hh)=int(dt\/60);\n          id(g_mm)=dt-id(g_hh)*60;\n\n      - logger.log:\n          format: \"H debut Filtration %2d:%.2d\"\n          args: &#091; 'id(g_hh)', 'id(g_mm)' ]\n          level: DEBUG\n\n      - datetime.time.set:\n          id: h_fin\n          time: !lambda |-\n            return {second: 0, minute: static_cast&lt;uint8_t&gt;(id(g_mm)), hour: static_cast&lt;uint8_t&gt;(id(g_hh))};\n\n      # Convertion et affichage de la dur\u00e9e de filtration en hh:mm\n      - lambda: |-\n          CONVERT_SECONDS(id(g_tps_filtration)*3600, id(g_hh), id(g_mm), id(g_ss));     \n      - logger.log:\n          format: \"Dur\u00e9e Filtration %2d: %.2d Tps Filtrat: %2f\"\n          args: &#091; 'id(g_hh)', 'id(g_mm)', 'id(g_tps_filtration)'  ]\n          level: INFO     \n      - datetime.time.set:\n          id: duree_filtration\n          time: !lambda |-\n            return {second: 0, minute: static_cast&lt;uint8_t&gt;(id(g_mm)), hour: static_cast&lt;uint8_t&gt;(id(g_hh))};\n      \n      - lambda: |-\n          std::string mess= std::to_string(id(h_debut).hour)+\":\"+std::to_string(id(h_debut).minute)+\"\/\"+std::to_string(id(heure_pivot).hour)+\":\"+std::to_string(id(heure_pivot).minute)+\"\/\"+std::to_string(id(h_fin).hour)+\":\"+std::to_string(id(h_fin).minute);\n          id(aff_heure_filtration).publish_state(mess.c_str());          \n\n      - script.execute: _calcul_ma_at_ppe_filtration\n\n  # Calcul la sortie de commande la pompe de filtration en fonction de l'heure actuelle, de l'heure de d\u00e9but et de l'heure de fin\n  - id: _calcul_ma_at_ppe_filtration\n    mode: single\n    then:\n      - lambda: |-\n          auto time = id(sntp_time).now();\n      - logger.log:\n          format: \"H now: %.2d:%2d:%d\"\n          args: &#091; 'id(sntp_time).now().hour', 'id(sntp_time).now().minute', 'id(heure_pivot).second' ]\n          level: DEBUG\n      - logger.log:\n          format: \"HT: %.2d - HD:%2d - HF:%d\"\n          args: &#091; 'id(sntp_time).now().hour*60+id(sntp_time).now().minute', 'id(h_debut).hour*60+id(h_debut).minute', 'id(h_fin).hour*60+id(h_fin).minute' ]\n          level: DEBUG\n      - if:\n          condition:\n            time.has_time:\n          else:\n            - logger.log:\n                format: \"L'heure n'est ni initialis\u00e9e, ni valid\u00e9e!\"\n                level: INFO\n      - if:\n          condition:\n            - lambda: 'return (id(sntp_time).now().is_valid());'\n            - lambda: 'return (id(sntp_time).now().hour*60+id(sntp_time).now().minute &gt;= id(h_debut).hour*60+id(h_debut).minute &amp;&amp; id(sntp_time).now().hour*60+id(sntp_time).now().minute &lt; id(h_fin).hour*60+id(h_fin).minute);'\n          then:\n            - switch.turn_on: cde_ppe_filtration\n          else:\n            - switch.turn_off: cde_ppe_filtration\n    \n             \n  # Envoi d'un message \u00e0 Telegram via HA, celui ci doit etre operationnel\n  # Message \u00e0 construire au format String avant appel de ce script\n  - id: _message_telegram\n    parameters:\n      mess1: string\n    then:\n      - lambda: |-\n          std::string mess = id(sntp_time).now().strftime(\"%Y-%m-%d %H:%M:%S\").c_str();\n          mess += \"\\n\";\n          mess += mess1;\n          id(telegram_msg_buffer) = mess;\n      - homeassistant.service:\n          service: notify.telegram\n          data:\n            message: !lambda 'return id(telegram_msg_buffer).c_str();'\n      - lambda: |-\n          std::string mess = id(sntp_time).now().strftime(\"%Y-%m-%d %H:%M:%S\").c_str();\n          mess += \",\";\n          mess += mess1;\n          id(telegram_msg_buffer) = mess;\n      - homeassistant.service:\n          service: script.envoyer_message_log_esp178\n          data:\n            message: !lambda 'return id(telegram_msg_buffer).c_str();'\n\n  - id: _message_telegram_v2\n    parameters:\n      mess1: string\n      duree_sec: float\n    then:\n      - lambda: |-\n          int h, m, s;\n          \/\/ id(convert_seconds)(duree_sec, h, m, s);\n          CONVERT_SECONDS(duree_sec, h, m, s);\n          char buf&#091;128];\n          snprintf(buf, sizeof(buf), \n              \"%s\\n\"\n              \"Tps: %02d:%02d:%02d\\n\", mess1.c_str(), h, m, s);\n\n          std::string mess = id(sntp_time).now().strftime(\"%Y-%m-%d %H:%M:%S\").c_str();\n          mess += \"\\n\";\n          mess += buf;\n\n          id(telegram_msg_buffer) = mess;\n      - homeassistant.service:\n          service: notify.telegram\n          data:\n            message: !lambda 'return id(telegram_msg_buffer).c_str();'\n      - lambda: |-\n          int h, m, s;\n          CONVERT_SECONDS(duree_sec, h, m, s);\n\n          char buf&#091;128];\n          snprintf(buf, sizeof(buf), \"%s,%02d:%02d:%02d\", mess1.c_str(), h, m, s);\n\n          std::string mess = id(sntp_time).now().strftime(\"%Y-%m-%d %H:%M:%S\").c_str();\n          mess += \",\";\n          mess += buf;\n\n          id(telegram_msg_buffer) = mess;\n      - homeassistant.service:\n          service: script.envoyer_message_log_esp178\n          data:\n            message: !lambda 'return id(telegram_msg_buffer).c_str();'\n\n  - id: _log_message\n    parameters:\n      mess1: string\n    then:\n      - lambda: |-\n          std::string mess = id(sntp_time).now().strftime(\"%Y-%m-%d %H:%M:%S\").c_str();\n          mess += \",\";\n          mess += mess1;\n          id(telegram_msg_buffer) = mess;\n      - homeassistant.service:\n          service: script.envoyer_message_log_esp178\n          data:\n            message: !lambda 'return id(telegram_msg_buffer).c_str();'\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Sous programme pH:<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>time:\n  - platform: sntp\n    timezone: Europe\/Paris\n    servers:\n      - 0.pool.ntp.org\n      - 1.pool.ntp.org\n      - 2.pool.ntp.org\n    on_time:\n      # D\u00e9clenchement du script de regul pH deux fois par jour\n      - seconds: 0\n        minutes: 00\n        hours: 12\n        then:\n          - script.execute: _regul_ph\n      - seconds: 0\n        minutes: 00\n        hours: 16\n        then:\n          - script.execute: _regul_ph\n      # reset le compteur de temps \u00e0 minuit\n      - seconds: 0\n        minutes: 0\n        hours: 0\n        then:\n          - sensor.duty_time.reset: _temps_fonctionnement_ppe_ph\n          - number.set:\n              id: conso_ph_jour\n              value: 0\n        # Notification du rapport journalier sur Telegram\n      - seconds: 05\n        minutes: 59\n        hours: 23\n        then:\n          - lambda: |-\n              std::string mess = \"ESP178 Rapport Journalier pH\\n\";\n\n              CONVERT_SECONDS(id(_temps_fonctionnement_ppe_ph).state, id(g_hh), id(g_mm), id(g_ss));\n              char buf1&#091;32];\n              snprintf(buf1, sizeof(buf1), \"Tps Ppe pH: %02d:%02d:%02d\\n\", id(g_hh), id(g_mm), id(g_ss));\n              mess += buf1;\n\n              id(_message_telegram)-&gt;execute(mess);\n\nglobals:\n  - id: g_memoire_ph\n    type: float\n    restore_value: yes\n    initial_value: '7.2'\n\n  # temps d'injection ph\n  - id: g_tps_injection_ph_moins\n    type: float\n    restore_value: no\n\n# d\u00e9claration des modes de fonctionnement dans des \"input select\"\nselect:\n  - platform: template\n    name: \"Mode_Fonctionnement_regul_ph\"\n    optimistic: true\n    restore_value: true\n    options:\n      - Auto\n      - Ma_f\n      - At_f\n    id: _Mode_Fonctionnement_regul_ph\n    on_value: \n      then:\n        - logger.log:\n            format: \"Mode Fonct Regul pH --&gt; %s\"\n            args: &#091; 'id(_Mode_Fonctionnement_regul_ph).state.c_str()' ]\n            level: INFO\n        - if:\n            condition:\n              or:\n                - lambda: 'return id(_Mode_Fonctionnement_regul_ph).state == \"Ma_f\";'\n                - lambda: 'return id(_Mode_Fonctionnement_regul_ph).state == \"At_f\";'\n            then:                    \n              - script.stop: _regul_ph   \n              - delay: 1s            \n              - script.execute: _regul_ph       \n\nbutton:\n  - platform: template\n    name: \"BP_Cycle_Regul_pH\"\n    on_press:\n      - script.execute: _regul_ph\n\ndatetime: \n  - platform: template\n    id: duree_injection_ph\n    type: time\n    name: \"duree_injection_pH\"\n    optimistic: yes\n    restore_value: true\n\n# Input Number\nnumber:\n  # Simulation Niveau pH\n  - platform: template\n    name: \"simule_pH\"\n    id: simul_ph_ezo\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 0\n    max_value: 10\n    unit_of_measurement: \"pH\"\n    step: 0.01\n\n  # Cible R\u00e9gulation pH\n  - platform: template\n    name: \"pH_Cible\"\n    id: _ph_cible\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 6\n    max_value: 7.6\n    unit_of_measurement: \"pH\"\n    step: 0.01\n\n  # Hysterisis pH: S'additionne \u00e0 la consigne dans la comparaison avec la cible\n  - platform: template\n    name: \"pH_Hysteris\"\n    id: _ph_hysteresis\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 0\n    max_value: 1\n    unit_of_measurement: \"pH\"\n    step: 0.01\n\n  # Facteur de correction par cycle (fraction de la correction totale)\n  - platform: template\n    name: \"facteur_correction_ph\"\n    id: facteur_correction_ph\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 0.01\n    max_value: 1.0\n    unit_of_measurement: \"\"\n    step: 0.01\n    icon: mdi:percent\n\n  # Debit Pompe pH moins\n  # Etalonnage du 18\/07\/2024: 4.272 l\/h\n  # Etalonnage du 17\/05\/2025: 4.269 l\/h\n  - platform: template\n    name: \"debit_ppe_ph_moins\"\n    id: _debit_ppe_moins\n    optimistic: true\n    initial_value: 4.269\n    mode: box\n    min_value: 0.5\n    max_value: 7.2\n    unit_of_measurement: \"l\/h\"\n    step: 0.001\n\n  # Dur\u00e9e marche pompe ph cycle\n  - platform: template\n    name: \"dur\u00e9e_injection_phmoins\"\n    id: duree_inject_phmoins\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 0\n    max_value: 120\n    unit_of_measurement: \"s\"\n    step: 1\n    icon: mdi:clock\n\n  # Comptabilise le volume de pH inject\u00e9\n  - platform: template\n    name: \"conso_ph_total\"\n    id: conso_ph_total\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 0\n    max_value: 100\n    step: 0.001\n    unit_of_measurement: \"l\"\n    icon: mdi:ph\n\n  - platform: template\n    name: \"conso_ph_jour\"\n    id: conso_ph_jour\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 0\n    max_value: 100\n    step: 0.001\n    unit_of_measurement: \"l\"\n    icon: mdi:ph\n\nsensor:\n  # Mesure du pH\n  # Proc\u00e9dure \u00e9talonnage:\n    # Mettre 1 s dans \"update interval\"\n    # Mettre accuracy_decimals: 3\n    # Decommenter le log\n    # Commenter les 4 lignes calibrate_linear\n    # D\u00e9commenter le LOGI du \"on_value\"  \n    # ainsi on moyenne sur 15 s avec un affichage toutes les 5s \n    # 1-Faire une mesure avec solution \u00e9talon de 4.0\n    # 2-Attendre 2 \u00e0 3 minutes que ca se stabilise\n    # 3-relever la valeur du pH\n    # 4-Toujours bien rincer la sonde \u00e0 l'eau d\u00e9min\u00e9ralis\u00e9e entre deux solutions\n    # Refaire \u00e9tapes 1 \u00e0 4 avec une solution \u00e9talon de 6.86\n    # Puis avec une solution \u00e9talon de 9.18\n    # Saisir les valeurs relev\u00e9s dans \"Calibrate_linear\"\n    # D\u00e9commenter les 4 lignes calibrate_linear    \n    # Remettre 60 s dans \"update interval\"\n    # Mettre accuracy_decimals: 2\n    # Commenter le LOGI du \"on_value\"  \n    # Compiler\n  # Fin proc\u00e9dure \u00e9talonnage\n\n  # Etalonnage 28 juin 2023          \n  #      - 4.547 -&gt; 4.0\n  #      - 7.282 -&gt; 6.86\n  #      - 9.447 -&gt; 9.18\n  # Etalonnage 6 juillet 2022          \n  #      - 4.44 -&gt; 4.0\n  #      - 7.17 -&gt; 6.86\n  #      - 9.41 -&gt; 9.18\n  # Etalonnage 20 juin 2024\n  #      - 4.665 -&gt; 4.0\n  #      - 7.482 -&gt; 6.86\n  #      - 9.505 -&gt; 9.18\n  # Etalonnage 21 mai 2025\n  #      - 4.76 -&gt; 4.0\n  #      - 7.50 -&gt; 6.86\n  #      - 9.49 -&gt; 9.18\n  - platform: ezo\n    id: ph_ezo\n    name: \"ph_ezo\"\n    address: 99\n    unit_of_measurement: \"pH\"\n    accuracy_decimals: 2\n    state_class: measurement\n    update_interval: 60s\n    filters:\n      - sliding_window_moving_average:\n          window_size: 15\n          send_every: 1\n          send_first_at: 1\n      - calibrate_linear:\n          - 4.76 -&gt; 4.0\n          - 7.50 -&gt; 6.86\n          - 9.49 -&gt; 9.18\n    on_value:\n      then:\n        - lambda: |-\n            \/\/ ESP_LOGI(\"ph_ezo\", \"Mesure pH (\u00e9talonnage): %.2f\", id(ph_ezo).state);\n\n  # m\u00e9morise le temps d'injection calcul\u00e9\n  - platform: template\n    name: \"tps_injection_ph_moins\"\n    id: _tps_injection_ph_moins\n    unit_of_measurement: \"s\"\n    state_class: \"measurement\" \n\n  # Affiche le volume de pH moins \u00e0 injecter\n  - platform: template\n    name: \"vol_injection_ph_moins\"\n    id: _vol_injection_ph_moins\n    unit_of_measurement: \"l\"\n    state_class: \"measurement\" \n    \n  # Ppe pH\n  - platform: duty_time\n    id: _temps_fonctionnement_ppe_ph\n    name: 'temps_ma_ppe_ph'\n    lambda: \"return id(cde_ppe_ph_moins).state == true;\"\n    restore: true\n    filters: \n      - round: 0\n\nswitch:\n  - platform: gpio\n    name: \"cde_ppe_ph_moins\"\n    pin: GPIO33\n    id: cde_ppe_ph_moins\n\ninterval:\n  - interval: 5s\n    then: \n      - script.execute: _memorisation_ph\n      - script.execute: _securisation_regul_ph\n\nscript:\n  # Si la pompe tourne depuis au moins \"tempo_recirculation\" on raffraichit la memoire du Ph qui est\n  # prise en compte dans les scripts.\n  # sinon on travaille avec le pH m\u00e9moris\u00e9 avant l'arret pr\u00e9c\u00e9dent\n  - id: _memorisation_ph\n    then:\n      - if:\n          condition:\n            lambda: 'return id(flag_tempo_ppe_filtre) == true;'\n          then:\n            - lambda: |-\n                id(g_memoire_ph)=id(ph_ezo).state;            \n          else:\n            - lambda: |-\n                id(g_memoire_ph)=id(g_memoire_ph);\n      - if:\n          condition:\n            lambda: 'return isnan(id(ph_ezo).state);' # verifie si ph_ezo est valide\n          then:\n            - logger.log:\n                format: \"ph_EZO invalide: %.2f\"\n                args: &#091; 'id(ph_ezo).state']\n                level: DEBUG\n            - lambda: |-\n                id(g_memoire_ph)=id(g_memoire_ph);\n          else: \n            - logger.log:\n                format: \"ph_EZO Valide: %.2f\"\n                args: &#091; 'id(ph_ezo).state']\n                level: DEBUG   \n\n      - logger.log:\n          format: \"Flag Temp: %i \/ Ph EZO: %.2f \/ Mem Ph: %.2f\"\n          args: &#091; 'id(flag_tempo_ppe_filtre)','id(ph_ezo).state','id(g_memoire_ph)' ]\n          level: DEBUG\n  # Script principal pour la r\u00e9gulation du pH\n  - id: _regul_ph\n    mode: single\n    then:\n      - if:\n          condition:\n            - lambda: 'return id(_Mode_Fonctionnement_regul_ph).state == \"Auto\";'\n          then:\n            - logger.log:\n                format: \"Log mem ph: %f \/ pH Cible: %f\"\n                args: &#091; 'id(g_memoire_ph)','id(_ph_cible).state' ]\n                level: debug\n            - if:\n                condition:\n                  and:\n                    - lambda: 'return id(g_memoire_ph) &gt; (id(_ph_cible).state+id(_ph_hysteresis).state);'\n                    - lambda: 'return id(g_memoire_ph) &gt; 0;'\n                    - switch.is_on: cde_ppe_filtration          \n                then:\n                  - lambda: |-\n                      float ecart = abs(id(g_memoire_ph) - id(_ph_cible).state);\n                          \/\/ Volume de la piscine (fixe \u00e0 43,2 m\u00b3)\n                          float volume_piscine = 43.2;\n                          \/\/ Quantit\u00e9 de r\u00e9f\u00e9rence : 0,2 l pour 0,1 unit\u00e9 de pH pour 10 m\u00b3\n                          float quantite_ref_ph = 0.2;\n                          \/\/ Calcul de la quantit\u00e9 n\u00e9cessaire pour corriger l'\u00e9cart total (en litres)\n                          float quantite_necessaire = (ecart \/ 0.1) * quantite_ref_ph * (volume_piscine \/ 10.0);\n                          \/\/ Conversion du d\u00e9bit de l\/h en ml\/s (1 l = 1000 ml, 1 h = 3600 s)\n                          float debit_ml_s = (id(_debit_ppe_moins).state * 1000.0) \/ 3600.0;  \/\/ Exemple : 4,272 l\/h = 1,187 ml\/s\n                          \/\/ Calcul de la dur\u00e9e n\u00e9cessaire pour injecter la quantit\u00e9 (quantit\u00e9 en ml, d\u00e9bit en ml\/s)\n                          float duree = (quantite_necessaire * 1000.0) \/ debit_ml_s;\n                          \/\/ Ajustement pour \u00e9viter des corrections trop brutales : on limite \u00e0 une fraction par cycle\n                          \/\/ Utilisation du facteur de correction d\u00e9fini par l'utilisateur\n                          float facteur_correction = id(facteur_correction_ph).state;\n                          duree = duree * facteur_correction;\n                          \/\/ Limites de s\u00e9curit\u00e9 : entre 1 et 60 secondes\n                          duree = std::max(1.0f, std::min(duree, 60.0f));\n                          \/\/ Log pour d\u00e9bogage\n                          ESP_LOGI(\"regul_ph\", \"\u00c9cart: %.2f, Quantit\u00e9: %.2f ml, D\u00e9bit: %.3f ml\/s, Facteur: %.2f, Dur\u00e9e: %.2f s\", ecart, quantite_necessaire * 1000.0, debit_ml_s, facteur_correction, duree);\n                          id(g_tps_injection_ph_moins) = duree;\n\n                else:\n                  - lambda: |-\n                      id(g_tps_injection_ph_moins)=0;\n\n            - lambda: |-\n                id(_tps_injection_ph_moins).publish_state(id(g_tps_injection_ph_moins));\n\n            # Convertion et affichage de la dur\u00e9e d'injection pH en hh:mm\n            - lambda: |-\n                CONVERT_SECONDS(id(g_tps_injection_ph_moins), id(g_hh), id(g_mm), id(g_ss));                        \n            - datetime.time.set:\n                id: duree_injection_ph\n                time: !lambda |-\n                  return {second: static_cast&lt;uint8_t&gt;(id(g_ss)), minute: static_cast&lt;uint8_t&gt;(id(g_mm)), hour: static_cast&lt;uint8_t&gt;(id(g_hh))};\n\n            - logger.log:\n                format: \"Log tps injection: %f\"\n                args: &#091; 'id(g_tps_injection_ph_moins)' ]\n                level: DEBUG\n\n            - if:\n                condition:\n                  and:\n                    - lambda: 'return id(g_tps_injection_ph_moins) &gt; 0;'\n                then:\n                  - lambda: |-\n                      std::string mess = \"ESP178 Debut injection pH\\n\";\n                      mess += \"Cible pH: \" + std::to_string(id(_ph_cible).state) + \"\\n\";\n                      mess += \"Mesure pH: \" + std::to_string(id(g_memoire_ph));\n                      id(_message_telegram_v2)-&gt;execute(mess,id(g_tps_injection_ph_moins));                      \n                  - logger.log:\n                      format: \"Ph Mesure ph: %f \/ pH Cible: %f\"\n                      args: &#091; 'id(g_memoire_ph)','id(_ph_cible).state' ]\n                      level: INFO                      \n                  - switch.turn_on: cde_ppe_ph_moins\n                  - logger.log: \n                      format: \"Marche ppe Ph moins en Auto\"\n                      level: INFO        \n                  - delay: !lambda \"return id(g_tps_injection_ph_moins)*1000;\"\n                  - switch.turn_off: cde_ppe_ph_moins\n                  - lambda: |-\n                      std::string mess = \"ESP178 Fin Injection pH\";\n                      id(_message_telegram_v2)-&gt;execute(mess,id(g_memoire_ph));\n                  - logger.log: \n                      format: \"Arret ppe Ph moins en Auto\"\n                      level: INFO\n                  # Comptabilise le nombre de litres de pH inject\u00e9\n                  - lambda: |-\n                      float debit = id(_debit_ppe_moins).state;  \/\/ l\/h\n                      float duree = id(_tps_injection_ph_moins).state;  \/\/ s\n                      float volume = debit * duree \/ 3600.0;  \/\/ conversion en litres\n                      \/\/ Mise \u00e0 jour des compteurs\n                      id(conso_ph_jour).publish_state(id(conso_ph_jour).state + volume);\n                      id(conso_ph_total).publish_state(id(conso_ph_total).state + volume);\n\n                else:\n                  - switch.turn_off: cde_ppe_ph_moins\n                  - logger.log: \n                      format: \"Arret ppe Ph moins en Auto\"\n                      level: INFO\n\n      - if:\n          condition:\n            and:\n              - lambda: 'return id(_Mode_Fonctionnement_regul_ph).state == \"Ma_f\";'\n              - switch.is_on: cde_ppe_filtration\n          then:\n            - switch.turn_on: cde_ppe_ph_moins\n            - logger.log: \n                format: \"Marche Forc\u00e9e ppe pH\"\n                level: INFO\n      - if:\n          condition:\n            or:\n              - lambda: 'return id(_Mode_Fonctionnement_regul_ph).state == \"At_f\";'\n              - switch.is_off: cde_ppe_filtration\n          then:\n            - switch.turn_off: cde_ppe_ph_moins\n            - logger.log: \n                format: \"Arret Forc\u00e9 ppe pH\"\n                level: INFO\n  # Stoppe le Script de regule pH quand la mesure devient inferieure \u00e0 la consigne\n  # Cela \u00e9vite de trop injecter de pH Moins\n  - id: _securisation_regul_ph\n    mode: single\n    then:\n      - if:\n          condition:\n            - lambda: 'return id(_Mode_Fonctionnement_regul_ph).state == \"Auto\";'\n            - lambda: 'return id(g_memoire_ph) &lt; id(_ph_cible).state;'\n            - script.is_running: _regul_ph\n\n          then:\n            - logger.log:\n                format: \"Log mem ph: %f \/ pH Cible: %f\"\n                args: &#091; 'id(g_memoire_ph)','id(_ph_cible).state' ]\n                level: DEBUG  \n            - script.stop: _regul_ph                \n            - switch.turn_off: cde_ppe_ph_moins\n            - lambda: |-\n                float Q = id(_temps_fonctionnement_ppe_ph).state * id(_debit_ppe_moins).state \/ 3600;\n                std::string mess = \"ESP178 Arret S\u00e9curis\u00e9 Script Injection pH\\n\";\n                mess += \"Cible pH: \" + std::to_string(id(_ph_cible).state) + \"\\n\";\n                mess += \"Mesure pH: \" + std::to_string(id(g_memoire_ph)) + \"\\n\";\n                CONVERT_SECONDS(id(g_tps_injection_ph_moins), id(g_hh), id(g_mm), id(g_ss));\n                char buf&#091;64];\n                snprintf(buf, sizeof(buf), \"Tps Injec pH: %02d:%02d:%02d\\n\", id(g_hh), id(g_mm), id(g_ss));\n                mess += buf;\n                snprintf(buf, sizeof(buf), \"Volume inj: %.2f L\\n\", Q);\n                mess += buf;\n                CONVERT_SECONDS(id(_temps_fonctionnement_ppe_ph).state, id(g_hh), id(g_mm), id(g_ss));\n                snprintf(buf, sizeof(buf), \"Tps Fonct Ppe pH: %02d:%02d:%02d\\n\", id(g_hh), id(g_mm), id(g_ss));\n                mess += buf;\n                id(_message_telegram)-&gt;execute(mess);               \n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Sous programme Chlore:<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>time:\n  - platform: sntp\n    id: sntp_time1  \n    timezone: Europe\/Paris\n    servers:\n     - 0.pool.ntp.org\n     - 1.pool.ntp.org\n     - 2.pool.ntp.org\n    on_time:\n      # D\u00e9clenchement du script Injection Clhore liquide \n      - seconds: 0\n        minutes: 00\n        hours: 10\n        then:\n          - script.execute: _regul_chlore\n      # reset le compteur de temps \u00e0 minuit\n      - seconds: 0\n        minutes: 0\n        hours: 0\n        then:\n          - sensor.duty_time.reset: _temps_fonctionnement_ppe_chlore\n          - number.set:\n              id: conso_chlore_jour\n              value: 0\n      # Notification du rapport journalier sur Telegram\n      - seconds: 10\n        minutes: 59\n        hours: 23\n        then:\n          - lambda: |-\n              std::string mess = \"ESP178 Rapport Journalier Chlore\\n\";\n              CONVERT_SECONDS(id(_temps_fonctionnement_ppe_chlore).state, id(g_hh), id(g_mm), id(g_ss));\n              char buf1&#091;32];\n              snprintf(buf1, sizeof(buf1), \"Tps Ppe Chlore: %02d:%02d:%02d\\n\", id(g_hh), id(g_mm), id(g_ss));\n              mess += buf1;\n              id(_message_telegram)-&gt;execute(mess);\n\nglobals:\n    # temps d'injection chlore\n    - id: g_tps_injection_chlore\n      type: float\n      restore_value: no\n      initial_value: '0'\n\nselect:\n  - platform: template\n    name: \"Mode_Fonct_regul_chlore\"\n    optimistic: true\n    restore_value: true\n    options:\n      - Auto\n      - Ma_f\n      - At_f\n    id: _Mode_Fonctionnement_regul_chlore\n    on_value: \n      then:\n        - logger.log:\n            format: \"Mode Fonct Regul chlore --&gt; %s\"\n            args: &#091; 'id(_Mode_Fonctionnement_regul_chlore).state.c_str()' ]\n            level: INFO \n        - if:\n            condition:\n              or:\n                - lambda: 'return id(_Mode_Fonctionnement_regul_chlore).state == \"Ma_f\";'\n                - lambda: 'return id(_Mode_Fonctionnement_regul_chlore).state == \"At_f\";'   \n            then:                \n              - script.stop: _regul_chlore\n              - delay: 1s            \n              - script.execute: _regul_chlore\nbutton:\n  - platform: template\n    name: \"BP_Cycle_Regul_chlore\"\n    on_press:\n      - script.execute: _regul_chlore\n  - platform: template\n    name: \"BP_RAZ_Tps_Galet_Chlore\"\n    on_press:\n      - sensor.duty_time.reset: _temps_galet_chlore\n\nbinary_sensor:\n  # Etat galets Chlore\n  # Si Tps &gt; \u00e0 temps max alors = True\n  - platform: template\n    name: \"Etat_Galets_Chlore\"\n    id: _etat_galets_chlore\n    lambda: |-\n      return id(_temps_galet_chlore).state\/3600 &gt; id(_tps_max_galet_chlore).state;\n\ndatetime:\n  - platform: template\n    id: duree_injection_chlore\n    type: time\n    name: \"duree_injection_chlore\"\n    optimistic: yes\n    restore_value: true\n\nnumber:\n  # Cible Niveau Chlore\n  - platform: template\n    name: \"chlore_Cible\"\n    id: _chlore_cible\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 0\n    max_value: 10\n    unit_of_measurement: \"ppm\"\n    step: 0.01\n\n  # Tps Max Galet Chlore\n  - platform: template\n    name: \"tps_Max_galet_Chlore\"\n    id: _tps_max_galet_chlore\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 0\n    max_value: 100\n    unit_of_measurement: \"h\"\n    step: 1\n\n  # Debit Pompe chlore\n  # Etalonnage du 17\/05\/2025: 4.957 l\/h\n  - platform: template\n    name: \"debit_ppe_chlore\"\n    id: _debit_ppe_chlore\n    optimistic: true\n    initial_value: 4.957\n    mode: box\n    min_value: 0.5\n    max_value: 7.2\n    unit_of_measurement: \"l\/h\"\n    step: 0.001\n\n  # Comptabilise le volume de ch inject\u00e9\n  - platform: template\n    name: \"conso_chlore_total\"\n    id: conso_chlore_total\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 0\n    max_value: 100\n    step: 0.001\n    unit_of_measurement: \"l\"\n    icon: mdi:chemical-weapon\n\n  - platform: template\n    name: \"conso_chlore_jour\"\n    id: conso_chlore_jour\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: 0\n    max_value: 100\n    step: 0.001\n    unit_of_measurement: \"l\"\n    icon: mdi:chemical-weapon \n\nsensor:\n  # Mesure de l'ORP\n  # Proc\u00e9dure \u00e9talonnage:\n    # Mettre 1 s dans \"update interval\"\n    # Mettre accuracy_decimals: 3\n    # Decommenter le log\n    # Commenter les 3 lignes calibrate_linear\n    # D\u00e9commenter le LOGI du \"on_value\"  \n    # ainsi on moyenne sur 15 s avec un affichage toutes les 5s \n    # 1-Faire une mesure avec solution \u00e9talon de 256mV\n    # 2-Attendre 2 \u00e0 3 minutes que ca se stabilise\n    # 3-relever la valeur de ORP\n    # D\u00e9commenter les 3 lignes calibrate_linear    \n    # Remettre 60 s dans \"update interval\"\n    # Mettre accuracy_decimals: 2\n    # Commenter le LOGI du \"on_value\"  \n    # Compiler\n  # Fin proc\u00e9dure \u00e9talonnage    \n  - platform: ezo\n    id: orp_ezo\n    name: \"orp_ezo\"\n    address: 98\n    unit_of_measurement: \"mV\"\n    accuracy_decimals: 2\n    device_class: voltage\n    state_class: measurement\n    update_interval: 60s\n    icon: mdi:chemical-weapon \n    # Filtrage en option (d\u00e9commenter si besoin)\n    filters:\n      - sliding_window_moving_average:\n          window_size: 15\n          send_every: 1\n          send_first_at: 1\n      - calibrate_linear:\n          - 150 -&gt; 150\n          - 256 -&gt; 256\n    on_value:\n      then:\n        - lambda: |-\n            \/\/ESP_LOGI(\"ORP_ezo\", \"Mesure ORP (\u00e9talonnage): %.2f\", id(orp_ezo).state);\n\n  # m\u00e9morise le temps d'injection calcul\u00e9\n  - platform: template\n    name: \"tps_injection_chlore\"\n    id: _tps_injection_chlore\n    unit_of_measurement: \"s\"\n    state_class: \"measurement\"\n\n  - platform: duty_time\n    id: _temps_galet_chlore\n    name: 'temps_galet_chlore'\n    sensor: ppe_filt_en_fonctionnement\n    restore: true\n    filters: \n      - round: 0\n    \n  # Ppe Chlore\n  - platform: duty_time\n    id: _temps_fonctionnement_ppe_chlore\n    name: 'temps_ma_ppe_chlore'\n    lambda: \"return id(cde_ppe_chlore).state == true;\"\n    restore: true\n    filters: \n      - round: 0\n  # Estimation du taux de chlore\n  - platform: template\n    name: \"Chlore estim\u00e9 (ppm)\"\n    unit_of_measurement: \"ppm\"\n    accuracy_decimals: 2\n    update_interval: 60s\n    icon: mdi:chemical-weapon \n    lambda: |-\n      const float orp = id(orp_ezo).state;  \/\/ en mV\n      const float ph = id(ph_ezo).state;    \/\/ pH de 0 \u00e0 14\n\n      if (orp &lt;= 0 || ph &lt;= 0) {\n        return NAN;\n      }\n\n      \/\/ Formule empirique bas\u00e9e sur un mod\u00e8le simplifi\u00e9\n      \/\/ Source : adaptation de mod\u00e8les de traitement d'eau et docs industrielles\n      float ppm = 10 * exp((orp - 650.0) \/ 100.0) * pow(10, -(ph - 7.0));\n\n      \/\/ Optionnel : borne les valeurs\n      if (ppm &lt; 0.0) ppm = 0.0;\n      if (ppm &gt; 5.0) ppm = 5.0;\n\n      return ppm;\n\n\nswitch: \n  - platform: gpio\n    name: \"cde_ppe_chlore\"\n    pin: GPIO25\n    id: cde_ppe_chlore\n\ninterval: \n  - interval: 1h # Test temps usage Galets Chlore\n    then: \n      - script.execute: _fonction_galet_chlore\n\nscript: \n  # Injection chlore liquide\n  # Calcul du temps d'injection\n  - id: _regul_chlore\n    mode: single\n    then:\n      - logger.log:\n          format: \"Script Injection Chlore\"\n          level: INFO      \n      - if:\n          condition:\n            and:\n              - switch.is_on: cde_ppe_filtration  \n              - lambda: 'return id(_Mode_Fonctionnement_regul_chlore).state == \"Auto\";'        \n          then:\n            - lambda: |-\n                static float nb = 0;\n                static float mp = 96.0f;  \/\/ Concentration de chlore actif en g\/L (9.6%)\n                static float vb = 50000.0f;  \/\/ Volume de la piscine en L\n                static float de = 0;\n                static float q = 0;\n                nb = id(_chlore_cible).state;  \/\/ Concentration cible en mg\/L\n                de = id(_debit_ppe_chlore).state;  \/\/ D\u00e9bit en L\/h\n                q = (nb * vb) \/ (mp * 1000.0f);  \/\/ Quantit\u00e9 en L\n                if (de &gt; 0) {\n                  id(g_tps_injection_chlore) = std::min((q \/ de) * 3600.0f, 3600.0f);  \/\/ Temps en secondes, limit\u00e9 \u00e0 1 heure\n                } else {\n                  id(g_tps_injection_chlore) = 0;\n                }\n                id(_tps_injection_chlore).publish_state(id(g_tps_injection_chlore));\n                ESP_LOGI(\"Dosage Chlore\", \"Quantit\u00e9: %.2f L, Temps: %.2f s\", q, id(g_tps_injection_chlore));\n          else:\n            - lambda: |-\n                id(g_tps_injection_chlore)=0;\n                id(_tps_injection_chlore).publish_state(0);\n            # Convertion et affichage de la dur\u00e9e d'injection pH en hh:mm\n      - lambda: |-    \n          CONVERT_SECONDS(id(g_tps_injection_chlore), id(g_hh), id(g_mm), id(g_ss));          \n      - datetime.time.set:\n          id: duree_injection_chlore\n          time: !lambda |-\n            return {second: static_cast&lt;uint8_t&gt;(id(g_ss)), minute: static_cast&lt;uint8_t&gt;(id(g_mm)), hour: static_cast&lt;uint8_t&gt;(id(g_hh))};\n          \n      - if:\n          condition:\n            - lambda: 'return id(_Mode_Fonctionnement_regul_chlore).state == \"Auto\";'\n          then:\n            - if:\n                condition:\n                  and:\n                    - lambda: 'return id(_tps_injection_chlore).state &gt; 0;'\n                    - switch.is_on: cde_ppe_filtration\n                then:\n                  - switch.turn_on: cde_ppe_chlore\n                  - logger.log: \n                      format: \"Marche ppe Ph Chlore en Auto\"\n                      level: INFO\n                  - lambda: |-\n                      \/\/ Affichage du temps d'injection Ppe Chlore\n                      std::string mess = \"ESP178 Temps inj Chlore\";\n                      id(_message_telegram_v2)-&gt;execute(mess,id(g_tps_injection_chlore));\n\n                  - delay: !lambda \"return id(g_tps_injection_chlore)*1000;\"\n                  - switch.turn_off: cde_ppe_chlore\n                  - logger.log: \n                      format: \"Arret ppe chlore en Auto\"\n                      level: INFO\n                  - lambda: |-\n                      std::string mess = \"Fin Injection Chlore en Auto\";                                         \n                      id(_message_telegram)-&gt;execute(mess.c_str());                      \n                  # Comptabilise le volume de ch inject\u00e9\n                  - lambda: |-\n                      float debit = id(_debit_ppe_chlore).state;  \/\/ l\/h\n                      float duree = id(_tps_injection_chlore).state;  \/\/ s\n                      float volume = debit * duree \/ 3600.0;  \/\/ conversion en litres\n                      \/\/ Mise \u00e0 jour des compteurs\n                      id(conso_chlore_jour).publish_state(id(conso_chlore_jour).state + volume);\n                      id(conso_chlore_total).publish_state(id(conso_chlore_total).state + volume);\n    \n                else:\n                  - switch.turn_off: cde_ppe_chlore\n                  - lambda: |-\n                      id(_tps_injection_chlore).publish_state(0);\n                  - logger.log: \n                      format: \"Arret ppe chlore en Auto\"\n                      level: INFO\n\n      - if:\n          condition:\n            and:\n              - lambda: 'return id(_Mode_Fonctionnement_regul_chlore).state == \"Ma_f\";'\n              - switch.is_on: cde_ppe_filtration\n          then:\n            - switch.turn_on: cde_ppe_chlore\n            - logger.log: \n                format: \"Marche Forc\u00e9e ppe Chlore\"\n                level: INFO\n      - if:\n          condition:\n            or:\n              - lambda: 'return id(_Mode_Fonctionnement_regul_chlore).state == \"At_f\";'\n              - switch.is_off: cde_ppe_filtration\n          then:\n            - switch.turn_off: cde_ppe_chlore\n            - logger.log: \n                format: \"Arret Forc\u00e9 ppe Chlore\"\n                level: INFO\n\n  # Log \u00e9tat galets Chlore en heure\n  - id: _fonction_galet_chlore\n    then:\n      - if:\n          condition:\n            and:\n              - lambda: 'return id(_Mode_Fonctionnement_regul_chlore).state == \"Auto\";'\n              - lambda: 'return id(_etat_galets_chlore).state == true;'\n          then:\n            - logger.log: \n                format: \"Galets Chlore D\u00e9pass\u00e9\"\n                level: INFO  \n            - lambda: |-\n                std::string mess = \"ESP178 Tps Utilisation Galets Chlore Atteint\";\n                id(_message_telegram)-&gt;execute(mess);\n\n          else:\n            - lambda: \n                id(_etat_galets_chlore).publish_state(false);<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Sous programme Volet:<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>globals:\n  - id: action_ouv_executee\n    type: bool\n    restore_value: false\n    initial_value: 'false'\n  - id: action_ferm_executee\n    type: bool\n    restore_value: false\n    initial_value: 'false'\n\nselect:\n  - platform: template\n    name: \"Mode_Fonctionnement_volet\"\n    optimistic: true\n    restore_value: true\n    options:\n      - Auto\n      - Horaire\n      - At_f\n    id: _Mode_Fonctionnement_volet\n    on_value: \n      then:\n        - logger.log:\n            format: \"Mode Fonct Volet --&gt; %s\"\n            args: &#091; 'id(_Mode_Fonctionnement_volet).state.c_str()' ]\n            level: INFO\n\nbinary_sensor: \n  # GPIO sur module extension SX1509\n\n  - platform: gpio\n    name: \"volet_ferme\"\n    id: volet_ferme\n    pin:\n      sx1509: sx1509_hub1\n      # Use pin number 0 on the SX1509\n      number: 0\n      mode:\n        input: true\n        pullup: false\n      inverted: false\n    filters:\n      - delayed_on_off: 500ms\n\ndatetime:\n  - platform: template\n    id: h_ouv_volet\n    type: time\n    name: \"h_ouv_volet\"\n    optimistic: yes\n    restore_value: true\n    \n  - platform: template\n    id: h_ferm_volet\n    type: time\n    name: \"h_ferm_volet\"\n    optimistic: yes\n    restore_value: true\n\n# D\u00e9claration Volet piscine\ncover:\n  - platform: template\n    name: \"volet_piscine\"\n    lambda: |-\n      if (id(volet_ferme).state) {\n        return COVER_CLOSED;\n      } else {\n        return COVER_OPEN;\n      }\n    open_action:\n      - script.execute: script_ouv_volet\n    close_action:\n      - script.execute: script_ferm_volet\n    stop_action:\n      - script.execute: script_stop_volet\n    optimistic: true\n\nswitch:\n  - platform: gpio\n    name: \"cde_volet_ouverture\"\n    pin: GPIO27\n    id: cde_volet_ouverture\n    interlock: &#091;cde_volet_fermeture]\n    on_turn_on:\n      then:\n        - delay: 120s\n        - script.execute: _regul_eau\n\n  - platform: gpio\n    name: \"cde_volet_fermeture\"\n    pin: GPIO14\n    id: cde_volet_fermeture\n    interlock: &#091;cde_volet_ouverture]\n\ninterval:\n  - interval: 1s\n    then:\n      - script.execute: _calcul_cde_volet\n\nscript: \n  # Scripts Commande Volet\n  # En mode \"Horaire\" La commande ouverture &amp; fermeture volet est asservie \u00e0 l'heure d'ouverture et de l'heure de fermeture du volet\n  # En mode \"Auto\": La commande ouverture volet est asservi \u00e0 l'heure de debut ma pompe en modes Palier\/Classique\/Horaire\/Abaque\n  #               : La commande fermeture volet est asservi \u00e0 l'heure de fermeture du volet\n  - id: _calcul_cde_volet\n    mode: single\n    then:\n      - lambda: |-\n          auto time = id(sntp_time).now();\n      - logger.log:\n          format: \"H now: %.2d:%2d:%d\"\n          args: &#091; 'id(sntp_time).now().hour', 'id(sntp_time).now().minute', 'id(heure_pivot).second' ]\n          level: DEBUG\n      - logger.log:\n          format: \"HT: %.2d - HD:%2d - HF:%d\"\n          args: &#091; 'id(sntp_time).now().hour*60+id(sntp_time).now().minute', 'id(h_ouv_volet).hour*60+id(h_ouv_volet).minute', 'id(h_ferm_volet).hour*60+id(h_ferm_volet).minute' ]\n          level: DEBUG\n      - if:\n          condition:\n            time.has_time:\n          else:\n            - logger.log:\n                format: \"L'heure n'est ni initialis\u00e9e, ni valid\u00e9e!\"\n                level: INFO\n      # En mode Horaire, l'heure ouverture volet est cal\u00e9e sur le l'heure d(ouverture volet\n      # le drapeau id(action_ouv_executee) est mis \u00e0 un \u00e0 la premiere commande d'ouverture et RAZ \u00e0 minuit\n      - if:\n          condition:\n            - lambda: 'return (id(sntp_time).now().is_valid());'\n            - lambda: 'return (id(sntp_time).now().hour*60+id(sntp_time).now().minute == id(h_ouv_volet).hour*60+id(h_ouv_volet).minute);'\n            - lambda: 'return id(_Mode_Fonctionnement_volet).state == \"Horaire\";'\n            - lambda: 'return !id(action_ouv_executee);'\n          then:\n            - script.execute: script_ouv_volet\n            - lambda: 'id(action_ouv_executee) = true;'\n      - if:\n          condition:\n            - lambda: 'return (id(sntp_time).now().is_valid());'\n            - lambda: 'return (id(sntp_time).now().hour*60+id(sntp_time).now().minute == id(h_ferm_volet).hour*60+id(h_ferm_volet).minute);'\n            - lambda: 'return id(_Mode_Fonctionnement_volet).state == \"Horaire\";'\n            - lambda: 'return !id(action_ferm_executee);'\n          then:\n            - script.execute: script_ferm_volet\n            - lambda: 'id(action_ferm_executee) = true;'\n\n      # En mode Auto, l'heure ouverture volet est cal\u00e9e sur le l'heure de debut filtration\n      # le drapeau id(action_ouv_executee) est mis \u00e0 un \u00e0 la premiere commande d'ouverture et RAZ \u00e0 minuit\n      - if:\n          condition:\n            - lambda: 'return (id(sntp_time).now().is_valid());'\n            - lambda: 'return (id(sntp_time).now().hour*60+id(sntp_time).now().minute == id(h_debut).hour*60+id(h_debut).minute );'\n            - lambda: 'return id(_Mode_Fonctionnement_volet).state == \"Auto\";'\n            - lambda: 'return !id(action_ouv_executee);'\n            - or:\n              - lambda: 'return id(_Mode_Fonctionnement_filtration).state == \"Horaire\";'\n              - lambda: 'return id(_Mode_Fonctionnement_filtration).state == \"Palier\";'\n              - lambda: 'return id(_Mode_Fonctionnement_filtration).state == \"Classique\";'\n              - lambda: 'return id(_Mode_Fonctionnement_filtration).state == \"Abaque\";'\n          then:\n            - script.execute: script_ouv_volet\n            - lambda: 'id(action_ouv_executee) = true;'\n\n      # En mode Auto, l'heure fermeturee volet est cal\u00e9e sur le l'heure de fermeture Volet\n      # le drapeau id(action_ferm_executee) est mis \u00e0 un \u00e0 la premiere commande de fermeture et RAZ \u00e0 minuit\n      - if:\n          condition:\n            - lambda: 'return (id(sntp_time).now().is_valid());'\n            - lambda: 'return (id(sntp_time).now().hour*60+id(sntp_time).now().minute == id(h_ferm_volet).hour*60+id(h_ferm_volet).minute);'\n            - lambda: 'return id(_Mode_Fonctionnement_volet).state == \"Auto\";'\n            - lambda: 'return !id(action_ferm_executee);'\n          then:\n            - script.execute: script_ferm_volet\n            - lambda: 'id(action_ferm_executee) = true;'\n\n      # Remise \u00e0 z\u00e9ro des drapeaux \u00e0 minuit\n      - lambda: |-\n          if (id(sntp_time).now().hour == 0 &amp;&amp; id(sntp_time).now().minute == 0) {\n            id(action_ouv_executee) = false;\n            id(action_ferm_executee) = false;\n          }                  \n\n  - id: script_ouv_volet\n    mode: single\n    then:\n      - switch.turn_off: cde_volet_fermeture\n      - delay: 2s\n      - switch.turn_on: cde_volet_ouverture\n      - delay: 5s\n      - switch.turn_off: cde_volet_ouverture\n          \n  - id: script_ferm_volet\n    mode: single\n    then:\n      - switch.turn_off: cde_volet_ouverture\n      - delay: 2s\n      - switch.turn_on: cde_volet_fermeture\n      - switch.turn_on: cde_eclairage\n      - delay: 90s\n      - switch.turn_off: cde_volet_fermeture\n      - switch.turn_off: cde_eclairage\n                    \n  - id: script_stop_volet\n    mode: single\n    then:\n      - switch.turn_off: cde_volet_ouverture\n      - switch.turn_off: cde_volet_fermeture\n      - delay: 2s\n      - switch.turn_on: cde_volet_fermeture\n      - delay: 2s\n      - switch.turn_off: cde_volet_fermeture\n      - switch.turn_off: cde_eclairage\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Sous programme Appoint Eau:<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>time:\n  - platform: sntp\n    timezone: Europe\/Paris\n    servers:\n      - 0.pool.ntp.org\n      - 1.pool.ntp.org\n      - 2.pool.ntp.org\n\n    on_time:\n      # reset le compteur de temps \u00e0 minuit\n      - seconds: 0\n        minutes: 0\n        hours: 0\n        then:\n          - sensor.duty_time.reset: _temps_fonctionnement_ev_eau\n\nselect:\n  - platform: template\n    name: \"Mode_Fonct_appoint_eau\"\n    optimistic: true\n    restore_value: true\n    options:\n      - Auto\n      - Ma_f\n      - At_f\n    id: _Mode_Fonctionnement_regul_eau\n    on_value: \n      then:\n        - logger.log:\n            format: \"Mode Fonct Regul Eau --&gt; %s\"\n            args: &#091; 'id(_Mode_Fonctionnement_regul_eau).state.c_str()' ]\n            level: INFO        \n        - script.stop: _regul_eau\n        - delay: 1s            \n        - script.execute: _regul_eau\n\nbutton:\n  # Lance un Appoint d'Eau\n  - platform: template\n    name: \"BP_Appoint Eau\"\n    on_press:\n      - script.execute: _regul_eau\n\nbinary_sensor: \n  # calcul des niveaux piscine\n  # Si LSL ou LSH recouvert alors True sinon False\n  - platform: template\n    name: \"niv_haut\"\n    id: niv_haut\n      \n  - platform: template\n    name: \"niv_inter\"\n    id: niv_inter\n\n  - platform: template\n    name: \"niv_bas\"\n    id: niv_bas\n\n  - platform: template\n    name: \"niv_defaut\"\n    id: niv_defaut\n\n  # GPIO sur module extension SX1509\n  # Niveau haut \u00e0 1 si decouvert\n  - platform: gpio\n    name: \"tp_plein_lsh\"\n    id: lsh\n    pin:\n      sx1509: sx1509_hub1\n      number: 1\n      mode:\n        input: true\n        pullup: false\n      inverted: true\n    filters:\n      - delayed_on_off: 5s\n    on_press:\n      then:\n        - script.stop: _regul_eau\n        - switch.turn_off: cde_ev_eau\n\n  # Niveau bas \u00e0 1 si decouvert\n  - platform: gpio\n    name: \"tp_plein_lsl\"\n    id: lsl\n    pin:\n      sx1509: sx1509_hub1\n      number: 2\n      mode:\n        input: true\n        pullup: false\n      inverted: true\n    filters:\n      - delayed_on_off: 5s\n\nsensor:\n  # EV Eau\n  - platform: duty_time\n    id: _temps_fonctionnement_ev_eau\n    name: 'temps_ma_ev_eau'\n    lambda: \"return id(cde_ev_eau).state == true;\"\n    restore: true\n    filters: \n      - round: 0\n\nswitch:\n  - platform: gpio\n    name: \"cde_ev_eau\"\n    pin: GPIO12\n    id: cde_ev_eau\n\ninterval:\n  - interval: 5s\n    then:      \n      - script.execute: _calcul_niveau_eau\n  - interval: 60s\n    then:\n      - lambda: |-\n          \/\/ Reset le compteur \u00e0 00:00:00      \n          auto now = id(sntp_time).now();\n          if (now.hour == 0 &amp;&amp; now.minute == 0 &amp;&amp; now.second == 0) {\n            id(_temps_fonctionnement_ev_eau).reset();\n          }          \n\nscript: \n  # Regulation du niveau d'eau piscine\n  # Declench\u00e9 par BP Appoint Eau ou 120s apres la cde ouverture volet apres 120s\n  - id: _regul_eau\n    mode: single  \n    then:\n      - if:\n          condition:\n            and:\n              - lambda: 'return id(_Mode_Fonctionnement_regul_eau).state == \"Auto\";'\n              - binary_sensor.is_off: niv_defaut\n              - binary_sensor.is_off: volet_ferme\n              - or:\n                - binary_sensor.is_on: niv_bas\n                - binary_sensor.is_on: niv_inter\n          then:\n            - logger.log: \n                format: \"Ouverture vanne eau Mode Auto\"\n                level: INFO\n            - switch.turn_on: cde_ev_eau\n            - delay: 15min\n            - logger.log: \n                format: \"Fermeture vanne eau sur Timeout 15 min\"\n                level: WARN\n            - switch.turn_off: cde_ev_eau\n      - if:\n          condition:\n            and:\n              - lambda: 'return id(_Mode_Fonctionnement_regul_eau).state == \"Auto\";'\n              - or:\n                - binary_sensor.is_on: niv_haut\n                - binary_sensor.is_on: niv_defaut\n          then:\n            - logger.log: \n                format: \"Fermeture vanne eau Mode Auto\"\n                level: INFO\n            - switch.turn_off: cde_ev_eau\n\n      - if:\n          condition:\n            and:\n              - lambda: 'return id(_Mode_Fonctionnement_regul_eau).state == \"Ma_f\";'\n          then: \n            - logger.log: \n                format: \"Ouverture vanne eau Mode Ma For\u00e7\u00e9\"\n                level: DEBUG\n            - switch.turn_on: cde_ev_eau\n\n      - if:\n          condition:\n            or:\n              - lambda: 'return id(_Mode_Fonctionnement_regul_eau).state == \"At_f\";'\n          then:\n            - logger.log: \n                format: \"Fermeture vanne eau Mode At For\u00e7\u00e9\"\n                level: DEBUG\n            - switch.turn_off: cde_ev_eau\n\n  # Calcul des niveaux d'eau en fonction des sondes de niveaux\n  # si niveau haut et niveau bas =&gt; niveau haut\n  - id: _calcul_niveau_eau\n    then:\n      - if: \n          condition:\n            and:\n              - binary_sensor.is_on: lsh\n              - binary_sensor.is_on: lsl\n          \n          then:\n            - binary_sensor.template.publish:\n                id: niv_haut\n                state: ON\n          else:\n            - binary_sensor.template.publish:\n                id: niv_haut\n                state: OFF\n      # si pas niveau haut et niveau bas =&gt; defaut interm\u00e9diaire\n      - if: \n          condition:\n            and:\n              - binary_sensor.is_off: lsh\n              - binary_sensor.is_on: lsl\n          \n          then:\n            - binary_sensor.template.publish:\n                id: niv_inter\n                state: ON\n          else:\n            - binary_sensor.template.publish:\n                id: niv_inter\n                state: OFF     \n      # si pas niveau haut et pas niveau bas =&gt; niveau bas\n      - if: \n          condition:\n            and:\n              - binary_sensor.is_off: lsh\n              - binary_sensor.is_off: lsl\n          \n          then:\n            - binary_sensor.template.publish:\n                id: niv_bas\n                state: ON\n          else:\n            - binary_sensor.template.publish:\n                id: niv_bas\n                state: OFF                \n      # si niveau haut et pas niveau bas =&gt; defaut niveau\n      - if: \n          condition:\n            and:\n              - binary_sensor.is_on: lsh\n              - binary_sensor.is_off: lsl\n          then:\n            - binary_sensor.template.publish:\n                id: niv_defaut\n                state: ON\n          else:\n            - binary_sensor.template.publish:\n                id: niv_defaut\n                state: OFF<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Sous programme Hors gel:<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>globals:\n    # flag marche Hors gel activ\u00e9\n    - id: g_flag_hg\n      type: bool\n      restore_value: yes\n\nselect:\n  # d\u00e9fini l'activation du mode Hors Gel\n  - platform: template\n    name: \"Mode_Hors_Gel\"\n    optimistic: true\n    restore_value: true\n    options:\n      - Desactiv\u00e9\n      - Activ\u00e9\n    id: _Mode_Fonctionnement_hg\n    on_value: \n      then:\n        - logger.log:\n            format: \"Mode Fonct Hors Gel --&gt; %s\"\n            args: &#091; 'id(_Mode_Fonctionnement_hg).state.c_str()' ]\n            level: INFO\nnumber:\n  # Seuil 1 Temp Hors gel\n  - platform: template\n    name: \"Seuil1_Temp_HG\"\n    id: s1_temp_hg\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: -5\n    max_value: 0\n    device_class: temperature\n    step: 0.1\n\n  # Seuil 2 Temp Hors gel\n  - platform: template\n    name: \"Seuil2_Temp_HG\"\n    id: s2_temp_hg\n    optimistic: true\n    restore_value: true\n    mode: box\n    min_value: -10\n    max_value: 0\n    device_class: temperature\n    step: 0.1\n\ninterval:\n  - interval: 900s # Test HG toutes les 15 mn (900s)\n    then: \n      - script.execute: _fonction_hors_gel\n\nscript: \n# Fonctionnement Hors Gel\n# Si temp ext\u00e9rieure inferieur \u00e0 seuil1 et sup\u00e9rieur \u00e0 seuil2 alors Ma pompe Filtration pendant 15 mn\n# Si temp ext\u00e9rieure inferieur \u00e0 seuil2 alors Ma pompe Filtration pendant 30 mn\n  - id: _fonction_hors_gel\n    then:\n      # Reset flag HG si temp &gt;S1\n      - if:\n          condition:\n            and:\n              - lambda: 'return id(_Mode_Fonctionnement_hg).state == \"Activ\u00e9\";'\n              - lambda: 'return id(temp_ext).state &gt; id(s1_temp_hg).state;'\n          then:\n            - lambda: |-\n                id(g_flag_hg) = false;\n            - logger.log: \n                format: \"Reset Flag HG\"\n                level: INFO\n       \n      # Activation si S1 &gt;temp &gt;S2\n      - if:\n          condition:\n            and:\n              - lambda: 'return id(_Mode_Fonctionnement_hg).state == \"Activ\u00e9\";'\n              - lambda: 'return id(temp_ext).state &lt; id(s1_temp_hg).state;'\n              - lambda: 'return id(temp_ext).state &gt; id(s2_temp_hg).state;'\n              - lambda: 'return id(g_flag_hg) == false;'\n          then:\n            - lambda: |-\n                id(g_flag_hg) = true;\n            - logger.log: \n                format: \"Set Flag HG Seuil1\"\n                level: INFO\n            - lambda: |-                \n                std::string mess = \"ESP178 Debut Marche HG Seuil1\\n\";\n                mess += \"Temp Ext: \" + std::to_string(id(temp_ext).state) + \"\\n\";\n                mess += \"Seuil1: \"   + std::to_string(id(s1_temp_hg).state) + \"\\n\";\n                id(_message_telegram)-&gt;execute(mess);                 \n            - delay: 900s  #900s\n            - lambda: |-\n                id(g_flag_hg) = false;\n            - logger.log: \n                format: \"Reset Flag HG Seuil1\"\n                level: INFO     \n            - lambda: |-                \n                std::string mess= \"ESP178 Fin Marche HG Seuil1\";\n                mess += \"Temp Ext: \"+ std::to_string(id(temp_ext).state)+\"\\n\";\n                mess += \"Seuil1: \"+ std::to_string(id(s1_temp_hg).state)+\"\\n\";\n                id(_message_telegram)-&gt;execute(mess.c_str());                               \n      # Activation si temp &gt; S2\n      - if:\n          condition:\n            and:\n              - lambda: 'return id(_Mode_Fonctionnement_hg).state == \"Activ\u00e9\";'\n              - lambda: 'return id(temp_ext).state &lt; id(s1_temp_hg).state;'\n              - lambda: 'return id(temp_ext).state &lt; id(s2_temp_hg).state;'\n              - lambda: 'return id(g_flag_hg) == false;'\n          then:\n            - lambda: |-\n                id(g_flag_hg) = true;\n            - logger.log: \n                format: \"Set Flag HG Seuil2\"\n                level: INFO\n            - lambda: |-                \n                std::string mess= \"ESP178 Debut Marche HG Seuil2\";\n                mess += \"Temp Ext: \"+ std::to_string(id(temp_ext).state)+\"\\n\";\n                mess += \"Seuil2: \"+ std::to_string(id(s2_temp_hg).state)+\"\\n\";\n                id(_message_telegram)-&gt;execute(mess.c_str());                   \n            - delay: 1800s  #1800s\n            - lambda: |-\n                id(g_flag_hg) = false;\n            - logger.log: \n                format: \"Reset Flag HG Seuil2\"\n                level: INFO      \n            - lambda: |-                \n                std::string mess= \"ESP178 Fin Marche HG Seuil2\";\n                mess += \"Temp Ext: \"+ std::to_string(id(temp_ext).state)+\"\\n\";\n                mess += \"Seuil2: \"+ std::to_string(id(s2_temp_hg).state)+\"\\n\";\n                id(_message_telegram)-&gt;execute(mess.c_str());   \n\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Sous programme Mesure \u00e9lectrique:<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>    # mesure grandeurs electriques avec un PZEM-004T-100A\n\n# Configuration UART\nuart:\n  rx_pin: GPIO3 #rx\n  tx_pin: GPIO1 #tx\n  baud_rate: 9600\n\n# modbus necessaire au PZEM  \nmodbus:\n\nsensor:\n  - platform: pzemac\n    update_interval: 30s\n    current:\n      name: \"pzem_pisc_courant\"\n      unit_of_measurement: \"A\"\n    voltage:\n      name: \"pzem_pisc_tension\"\n      unit_of_measurement: \"V\"\n    energy:\n      name: \"pzem_pisc_energy\"\n      unit_of_measurement: \"kWh\"\n      filters:\n        - multiply: 0.001\n    power:\n      name: \"pzem_pisc_puissance\"\n      unit_of_measurement: \"W\"\n      id: puissance<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Sous Programme Mesure de pression:<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>\nads1115:\n  - address: 0x48\n\nbinary_sensor:\n  # Etat pression filtre\n  # Si pression superieure \u00e0 p_Max alors = True\n  - platform: analog_threshold\n    name: \"Etat_pression_Filtre\"\n    sensor_id: pression_filtre    \n    id: _etat_pression_filtre\n    threshold: ${pression_max} # D\u00e9fini dans Substitution en bar\n    on_press:\n        - lambda: |-                \n            std::string mess= \"ESP178 Seuil Pression Filtre Atteint\";\n            id(_message_telegram)-&gt;execute(mess.c_str());     \n    on_release: \n        - lambda: |-                \n            std::string mess= \"ESP178 Pression filtre OK\";\n            id(_message_telegram)-&gt;execute(mess.c_str());   \n\nsensor:\n    # Mesure de la pression filtre Entr\u00e9e ANA 3\n  - platform: ads1115\n    multiplexer: 'A3_GND'\n    gain: 6.144\n    name: \"Pression_filtre\"          \n    update_interval: 10s\n    unit_of_measurement: \"bar\"\n    device_class: pressure\n    state_class: \"measurement\"        \n    id: pression_filtre\n    filters:\n    - calibrate_linear:\n        - 0.561 -&gt; 0.0\n        - 2.213 -&gt; 0.81\n    # moyenne sur 30 mn + affichage toutes les 2 mn\n    - sliding_window_moving_average:\n        window_size: 30\n        send_every: 2\n        send_first_at: 1<\/code><\/pre>\n\n\n\n<h1 class=\"wp-block-heading\">Int\u00e9gration dans Home Assistant<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">L\u2019ESP178 s\u2019int\u00e8gre via l\u2019API ESPHome. Dans HA, j\u2019ai cr\u00e9\u00e9 un dashboard avec :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Graphiques : pH, temp\u00e9rature, pression, puissance.<\/li>\n\n\n\n<li>R\u00e9glages : consignes pH\/chlore, modes filtration, seuils hors gel.<\/li>\n\n\n\n<li>Notifications Telegram via un text_sensor d\u00e9di\u00e9.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Notification avec Telegram<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">N&rsquo;ayant pas trouver de programme de notification \u00ab\u00a0T\u00e9l\u00e9gram\u00a0\u00bb fonctionel dans ESP Home, j&rsquo;utilise ma notification telegram fonctionnelle dans HA. Un automatisme surveille un changement de valeur dans le \u00ab\u00a0sensor.esp178_message_notif_telegram\u00a0\u00bb et notifie en cas de chanement de valeur.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>alias: Notification message Telegram de ESP178\ndescription: \" Notifie sur Telegram le message mis \u00e0 jour par l'ESP178 Piscine\"\nmode: single\ntriggers:\n  - entity_id:\n      - sensor.esp178_message_notif_telegram\n    for:\n      hours: 0\n      minutes: 0\n      seconds: 0\n    trigger: state\nconditions:\n  - condition: not\n    conditions:\n      - condition: or\n        conditions:\n          - condition: state\n            entity_id: sensor.esp178_message_notif_telegram\n            state: unavailable\n          - condition: state\n            entity_id: sensor.esp178_message_notif_telegram\n            state: unknown\nactions:\n  - data:\n      message: \"{{states('sensor.esp178_message_notif_telegram')}}\"\n      title: Message ESP178!!!\n    action: notify.telegram\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Exemple de carte HA<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"313\" height=\"601\" src=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2025\/03\/image-4.png\" alt=\"\" class=\"wp-image-3318\" srcset=\"https:\/\/domo.rem81.com\/wp-content\/uploads\/2025\/03\/image-4.png 313w, https:\/\/domo.rem81.com\/wp-content\/uploads\/2025\/03\/image-4-156x300.png 156w\" sizes=\"auto, (max-width: 313px) 100vw, 313px\" \/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>type: custom:pool-monitor-card\ndisplay:\n  compact: false\n  show_names: true\n  show_labels: true\n  show_last_updated: false\n  show_icons: true\n  show_units: true\n  gradient: true\n  language: fr\ncolors:\n  normal_color: \"#00b894\"\n  low_color: \"#fdcb6e\"\n  warn_color: \"#e17055\"\n  cool_color: \"#00BFFF\"\n  marker_color: \"#000000\"\n  hi_low_color: \"#00000099\"\nsensors:\n  temperature:\n    - entity: sensor.esp178_temperature_eau\n      setpoint: 30\n      step: 3\n  ph:\n    - entity: sensor.esp178_ph_ezo\n      setpoint: 7.2\n      step: 0.2\n  free_chlorine:\n    - entity: sensor.my_sampling_point_pl_chlorine_free\n      setpoint: 2\n      chlorine_step: 1\n  total_chlorine:\n    - entity: sensor.my_sampling_point_pl_chlorine_total\n      setpoint: 3\n      step: 1\n  cya:\n    - entity: sensor.my_sampling_point_pl_cyanuric_acid\n      setpoint: 37.5\n      step: 12.5\n  alkalinity:\n    - entity: sensor.my_sampling_point_pl_alkalinity\n      setpoint: 90\n      step: 30\n  pressure:\n    - entity: sensor.esp178_pression_filtre\n      unit: bar\n      setpoint: 1\n      step: 1\n  product_weight:\n    - entity: sensor.esp129_poids_ph_moins<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vous pouvez utiliser \u00ab\u00a0auto-entites\u00a0\u00bb, vous visualisez ainsi toutes les entit\u00e9s contenant \u00ab\u00a0ESP178\u00a0\u00bb dans mon cas (c&rsquo;est le nom de mon ESP).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>type: custom:auto-entities\ncard:\n  type: entities\n  show_header_toggle: false\n  title: ESP 178\nfilter:\n  include:\n    - entity_id: \"*esp178*\"\n      options: {}\n  exclude: &#091;]\nsort:\n  method: entity_id\n<\/code><\/pre>\n\n\n\n<h1 class=\"wp-block-heading\">Am\u00e9liorations Possibles<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Ajouter une sonde ORP calibr\u00e9e pour une r\u00e9gulation chlore plus pr\u00e9cise.<\/li>\n\n\n\n<li>Int\u00e9grer une pr\u00e9vision m\u00e9t\u00e9o pour ajuster la filtration.<\/li>\n\n\n\n<li>Tester le mode Ethernet (comment\u00e9 dans le code) pour plus de stabilit\u00e9.<\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusion<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Ce syst\u00e8me offre une gestion autonome et personnalisable de ma piscine, avec un co\u00fbt mat\u00e9riel raisonnable (&lt; 100\u20ac hors PCB). Le PCB maison et le sch\u00e9ma de c\u00e2blage ont grandement simplifi\u00e9 l\u2019installation et la maintenance. Le code est perfectible, mais il r\u00e9pond d\u00e9j\u00e0 \u00e0 mes besoins. Si vous voulez les fichiers PCB, le sch\u00e9ma EasyEda, ou des pr\u00e9cisions, laissez un commentaire !<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Liste des publications en lien avec cet article:<\/h1>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Filtration avec <a href=\"https:\/\/domo.rem81.com\/index.php\/2025\/03\/21\/ha-gestion-complete-dune-piscine-avec-esp32-et-esphome\/\" data-type=\"link\" data-id=\"https:\/\/domo.rem81.com\/index.php\/2025\/03\/21\/ha-gestion-complete-dune-piscine-avec-esp32-et-esphome\/\" target=\"_blank\" rel=\"noreferrer noopener\">ESPHome et ESP32<\/a><\/li>\n\n\n\n<li>Filtration avec \u00ab\u00a0<a href=\"https:\/\/domo.rem81.com\/index.php\/2022\/02\/02\/ha-gestion-piscine-1-filtration-avec-appdaemon-2\/\" target=\"_blank\" rel=\"noreferrer noopener\">AppDaemon\u00a0\u00bb<\/a><\/li>\n\n\n\n<li>Filtration avec \u00ab\u00a0<a href=\"https:\/\/domo.rem81.com\/index.php\/2021\/01\/20\/home-assistant-gestion-piscine-filtration-poolpump\/\" target=\"_blank\" rel=\"noreferrer noopener\">Pool Pump Manager<\/a>\u00ab\u00a0<\/li>\n\n\n\n<li><a href=\"https:\/\/domo.rem81.com\/index.php\/2021\/02\/24\/home-assistant-gestion-piscine-2_mesure-de-puissance-electrique\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mesure de puissance \u00e9lectrique<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/domo.rem81.com\/index.php\/2021\/04\/02\/home-assistant-gestion-piscine-1_mise-a-niveau-automatique\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mise \u00e0 niveau automatique<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/domo.rem81.com\/index.php\/2021\/05\/11\/home-assistant-gestion-piscine-4_mesure-ph\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mesure du pH<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/domo.rem81.com\/index.php\/2021\/08\/14\/ha-gestion-piscine-5_regulation-du-ph\/\" target=\"_blank\" rel=\"noreferrer noopener\">R\u00e9gulation du Ph<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/domo.rem81.com\/index.php\/2021\/11\/30\/ha-gestion-piscine-6_mode-hors-gel\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mise Hors Gel<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/domo.rem81.com\/index.php\/2022\/05\/13\/ha-gestion-piscine-7_mesure-de-pression\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mesure de pression<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/domo.rem81.com\/index.php\/2025\/04\/04\/ha-mesurer-la-consommation-deau-de-ma-piscine-avec-un-esp8266-et-esphome\/\" data-type=\"link\" data-id=\"https:\/\/domo.rem81.com\/index.php\/2025\/04\/04\/ha-mesurer-la-consommation-deau-de-ma-piscine-avec-un-esp8266-et-esphome\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mesure consommation d&rsquo;eau<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/domo.rem81.com\/index.php\/2025\/04\/11\/ha-panneau-de-commande-pour-piscine-avec-un-esp32-openhasp-et-home-assistant\/\" data-type=\"link\" data-id=\"https:\/\/domo.rem81.com\/index.php\/2025\/04\/11\/ha-panneau-de-commande-pour-piscine-avec-un-esp32-openhasp-et-home-assistant\/\" target=\"_blank\" rel=\"noreferrer noopener\">Panneau de contr\u00f4le avec un ESP32<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/domo.rem81.com\/index.php\/2025\/04\/13\/ha-analyse-de-leau-de-piscine-avec-le-poollab-2-0-et-integration-dans-home-assistant\/\" data-type=\"link\" data-id=\"https:\/\/domo.rem81.com\/index.php\/2025\/04\/13\/ha-analyse-de-leau-de-piscine-avec-le-poollab-2-0-et-integration-dans-home-assistant\/\" target=\"_blank\" rel=\"noreferrer noopener\">Analyse de l&rsquo;eau avec PoolLAB2.0<\/a><\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>Intro Dans un article pr\u00e9c\u00e9dent, je vous pr\u00e9sentais un module de gestion de la filtration et traitement d&rsquo;une piscine d\u00e9velopp\u00e9 sous APP DAEMON. J&rsquo;ai depuis, pour simplifier la mise en &hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[15,33],"class_list":["post-3198","post","type-post","status-publish","format-standard","hentry","category-homeassistant","tag-esp32","tag-piscine"],"_links":{"self":[{"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/posts\/3198","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/comments?post=3198"}],"version-history":[{"count":79,"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/posts\/3198\/revisions"}],"predecessor-version":[{"id":4145,"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/posts\/3198\/revisions\/4145"}],"wp:attachment":[{"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/media?parent=3198"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/categories?post=3198"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/domo.rem81.com\/index.php\/wp-json\/wp\/v2\/tags?post=3198"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}