Installation Esphome sur Waveshare ESP32-S3-POE-ETH-8DI-8RO

oui c’est exactement ça

  • INPUT_PULLUP avec inverted: true = INPUT_PULLDOWN

  • Les deux configurations font que le signal est considéré comme « actif » quand il est à 5V

pour l’écran je n’ai aucune idée d’ou peut venir le problème

  • Problème de connexion des câbles
  • Adresse I2C incorrecte

essaye comme ça !

display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    address: 0x3C
    brightness: 100%
    rotation: 0
    lambda: |-
      it.print("Temp: ");
      it.print(id(temp_eau).state);
      it.print("C");

C’est bon, j’ai trouvé et tout a l’air de parfaitement fonctionné.

J’aurai bien galéré mais le soucis de l’écran provenait du fait que c’est SH1106 (je l’ai pourtant acheté en tant que SSD1306).

Les retours d’états m’ont également l’air d’être impeccable.

esphome:
  name: esp32-piscine
  friendly_name: ESP32 Piscine

esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: esp-idf

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  - platform: esphome

#Configuration wifi (desactivé pour connexion Ethernet)
#wifi:
#  ssid: !secret wifi_ssid
#  password: !secret wifi_password
#
#  # Enable fallback hotspot (captive portal) in case wifi connection fails
#  ap:
#    ssid: "Waveshare-Esp32S3-Eth-8Di-8Ro"
#    password: "WaveshareESP32"

#captive_portal: # uniquement pour connexion wifi

ethernet:
  type: W5500
  mosi_pin: GPIO13
  miso_pin: GPIO14
  clk_pin: GPIO15
  cs_pin: GPIO16
  interrupt_pin: GPIO12
  reset_pin: GPIO39
  manual_ip:
    static_ip: 192.168.1.59
    gateway: 192.168.1.1
    subnet: 255.255.255.0

i2c:
  sda: GPIO42
  scl: GPIO41
  scan: true

pca9554:
  - id: expander
    address: 0x20

# Horloge RTC PCF85063 sur I2C
time:
  - platform: pcf85063
    id: pcf85063_time
    address: 0x51

# Sorties (relais)
switch:
  - platform: gpio
    pin:
      pca9554: expander
      number: 0
    id: relais_filtration
    name: "Filtration"
  - platform: gpio
    pin:
      pca9554: expander
      number: 1
    id: relais_eclairage
    name: "Eclairage Piscine"
  - platform: gpio
    pin:
      pca9554: expander
      number: 2
    id: relais_pac
    name: "PAC Piscine"

# Entrées digitales
binary_sensor:
  - platform: gpio
    name: "Etat Filtration"
    id: etat_filtration
    pin:
      number: GPIO4
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on: 10ms
      - delayed_off: 10ms

  - platform: gpio
    name: "Etat PAC"
    id: etat_pac
    pin:
      number: GPIO5
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on: 10ms
      - delayed_off: 10ms

# Sondes températures
one_wire:
  - platform: gpio
    pin: GPIO21
    id: bus_temp_eau

# Capteur de température
sensor:
  - platform: dallas_temp
    one_wire_id: bus_temp_eau
    id: temp_eau
    name: "Température Eau"
    accuracy_decimals: 1
    update_interval: 30s
    resolution: 12  # Optionnel : résolution maximale pour DS18B20

# Écran OLED
# Configuration des polices
font:
  - file: "gfonts://Roboto"
    id: font_default
    size: 12
  - file: "gfonts://Roboto"
    id: font_small
    size: 10

# Configuration de l'écran OLED SH1106
display:
  - platform: ssd1306_i2c
    model: "SH1106 128x64"
    address: 0x3C
    update_interval: 5s
    contrast: 100%
    external_vcc: NO
    flip_x: YES
    flip_y: YES
    lambda: |-
      it.fill(COLOR_OFF);

      // Ligne 1 - température (centrée)
      if (id(temp_eau).has_state()) {
        it.printf(64, 10, id(font_default), TextAlign::CENTER, "Eau: %.1f°C", id(temp_eau).state);
      } else {
        it.print(64, 10, id(font_default), TextAlign::CENTER, "Eau: --°C");
      }

      // Ligne 2 - état filtration (retour état capteur, pas relais)
      it.printf(0, 24, id(font_default), TextAlign::TOP_LEFT, "Filtration: %s", id(etat_filtration).state ? "ON" : "OFF");

      // Ligne 3 - état éclairage (relais)
      it.printf(0, 38, id(font_default), TextAlign::TOP_LEFT, "Eclairage: %s", id(relais_eclairage).state ? "ON" : "OFF");

      // Ligne 4 - état PAC (retour état capteur, pas relais)
      it.printf(0, 52, id(font_default), TextAlign::TOP_LEFT, "PAC: %s", id(etat_pac).state ? "ON" : "OFF");

INFO ESPHome 2025.6.3
INFO Reading configuration /config/esphome/esp32-piscine.yaml...
INFO Detected timezone 'Europe/Paris'
INFO Starting log output from 192.168.1.59 using esphome API
INFO Successfully resolved esp32-piscine @ 192.168.1.59 in 0.000s
INFO Successfully connected to esp32-piscine @ 192.168.1.59 in 0.006s
INFO Successful handshake with esp32-piscine @ 192.168.1.59 in 0.015s
[18:55:47][I][app:137]: ESPHome version 2025.6.3 compiled on Jul  9 2025, 18:49:20
[18:55:47][C][pca9554:048]: PCA9554:
[18:55:47][C][pca9554:048]:   I/O Pins: 8
[18:55:47][C][pca9554:052]:   Address: 0x20
[18:55:47][C][logger:211]: Logger:
[18:55:47][C][logger:211]:   Max Level: DEBUG
[18:55:47][C][logger:211]:   Initial Level: DEBUG
[18:55:47][C][logger:217]:   Log Baud Rate: 115200
[18:55:47][C][logger:217]:   Hardware UART: USB_SERIAL_JTAG
[18:55:47][C][logger:224]:   Task Log Buffer Size: 768
[18:55:47][C][i2c.idf:083]: I2C Bus:
[18:55:47][C][i2c.idf:084]:   SDA Pin: GPIO42
[18:55:47][C][i2c.idf:084]:   SCL Pin: GPIO41
[18:55:47][C][i2c.idf:084]:   Frequency: 50000 Hz
[18:55:47][C][i2c.idf:094]:   Recovery: bus successfully recovered
[18:55:47][I][i2c.idf:104]: Results from bus scan:
[18:55:47][I][i2c.idf:110]: Found device at address 0x20
[18:55:47][I][i2c.idf:110]: Found device at address 0x3C
[18:55:47][I][i2c.idf:110]: Found device at address 0x51
[18:55:47][C][gpio.one_wire:022]: GPIO 1-wire bus:
[18:55:47][C][gpio.one_wire:023]:   Pin: GPIO21
[18:55:47][C][gpio.one_wire:084]:   Found devices:
[18:55:47][C][gpio.one_wire:086]:     0xe731f8b10a646128 (DS18B20)
[18:55:47][C][switch.gpio:079]: GPIO Switch 'Filtration'
[18:55:47][C][switch.gpio:079]:   Restore Mode: always OFF
[18:55:47][C][switch.gpio:031]:   Pin: 0 via PCA9554
[18:55:47][C][switch.gpio:079]: GPIO Switch 'Eclairage Piscine'
[18:55:47][C][switch.gpio:079]:   Restore Mode: always OFF
[18:55:47][C][switch.gpio:031]:   Pin: 1 via PCA9554
[18:55:47][C][switch.gpio:079]: GPIO Switch 'PAC Piscine'
[18:55:47][C][switch.gpio:079]:   Restore Mode: always OFF
[18:55:47][C][switch.gpio:031]:   Pin: 2 via PCA9554
[18:55:47][C][gpio.binary_sensor:015]: GPIO Binary Sensor 'Etat Filtration'
[18:55:47][C][gpio.binary_sensor:016]:   Pin: GPIO4
[18:55:47][C][gpio.binary_sensor:015]: GPIO Binary Sensor 'Etat PAC'
[18:55:47][C][gpio.binary_sensor:016]:   Pin: GPIO5
[18:55:47][C][pcf85063:022]: PCF85063:
[18:55:47][C][pcf85063:023]:   Address: 0x51
[18:55:47][C][pcf85063:027]:   Timezone: 'CET-1CEST,M3.5.0,M10.5.0/3'
[18:55:47][C][dallas.temp.sensor:029]: Dallas Temperature Sensor:
[18:55:47][C][dallas.temp.sensor:034]:   Address: 0xe731f8b10a646128 (DS18B20)
[18:55:47][C][dallas.temp.sensor:035]:   Resolution: 12 bits
[18:55:47][C][dallas.temp.sensor:036]:   Update Interval: 30.0s
[18:55:47][C][ssd1306_i2c:023]: I2C SSD1306
[18:55:47][C][ssd1306_i2c:023]:   Rotations: 0 °
[18:55:47][C][ssd1306_i2c:023]:   Dimensions: 128px x 64px
[18:55:47][C][ssd1306_i2c:024]:   Address: 0x3C
[18:55:47][C][ssd1306_i2c:025]:   Model: SH1106 128x64
[18:55:47][C][ssd1306_i2c:027]:   External VCC: NO
[18:55:47][C][ssd1306_i2c:027]:   Flip X: YES
[18:55:47][C][ssd1306_i2c:027]:   Flip Y: YES
[18:55:47][C][ssd1306_i2c:027]:   Offset X: 0
[18:55:47][C][ssd1306_i2c:027]:   Offset Y: 0
[18:55:47][C][ssd1306_i2c:027]:   Inverted Color: NO
[18:55:47][C][ssd1306_i2c:036]:   Update Interval: 5.0s
[18:55:47][C][ethernet:326]: Ethernet:
[18:55:47][C][ethernet:524]:   IP Address: 192.168.1.59
[18:55:47][C][ethernet:524]:   Hostname: 'esp32-piscine'
[18:55:47][C][ethernet:524]:   Subnet: 255.255.255.0
[18:55:47][C][ethernet:524]:   Gateway: 192.168.1.1
[18:55:47][C][ethernet:524]:   DNS1: 0.0.0.0
[18:55:47][C][ethernet:524]:   DNS2: 0.0.0.0
[18:55:47][C][ethernet:545]:   MAC Address: 94:A9:90:23:04:7B
[18:55:47][C][ethernet:545]:   Is Full Duplex: YES
[18:55:47][C][ethernet:545]:   Link Speed: 100
[18:55:47][C][ethernet:329]:   CLK Pin: 15
[18:55:47][C][ethernet:329]:   MISO Pin: 14
[18:55:47][C][ethernet:329]:   MOSI Pin: 13
[18:55:47][C][ethernet:329]:   CS Pin: 16
[18:55:47][C][ethernet:341]:   IRQ Pin: 12
[18:55:47][C][ethernet:343]:   Reset Pin: 39
[18:55:47][C][ethernet:343]:   Clock Speed: 26 MHz
[18:55:47][C][ethernet:357]:   Type: W5500
[18:55:47][C][esphome.ota:073]: Over-The-Air updates:
[18:55:47][C][esphome.ota:073]:   Address: 192.168.1.59:3232
[18:55:47][C][esphome.ota:073]:   Version: 2
[18:55:47][C][safe_mode:018]: Safe Mode:
[18:55:47][C][safe_mode:019]:   Boot considered successful after 60 seconds
[18:55:47][C][safe_mode:019]:   Invoke after 10 boot attempts
[18:55:47][C][safe_mode:019]:   Remain for 300 seconds
[18:55:47][C][api:182]: API Server:
[18:55:47][C][api:182]:   Address: 192.168.1.59:6053
[18:55:47][C][api:192]:   Using noise encryption: NO
[18:55:47][C][mdns:122]: mDNS:
[18:55:47][C][mdns:122]:   Hostname: esp32-piscine
[18:56:15][D][dallas.temp.sensor:054]: 'Température Eau': Got Temperature=25.4°C
[18:56:15][D][sensor:098]: 'Température Eau': Sending state 25.37500 °C with 1 decimals of accuracy
[18:56:45][D][dallas.temp.sensor:054]: 'Température Eau': Got Temperature=25.7°C
[18:56:45][D][sensor:098]: 'Température Eau': Sending state 25.68750 °C with 1 decimals of accuracy

Alors pas du tout, c’est deux choses differentes
INPUT_PULLUP et INPUT_PULLDOWN ça definit comment est cablé (activée) la résistance de tirage interne de la broche GPIO concernée.
PULLUP = résistance de tirage à VCC
PULLDOWN = résistance de tirage à GND

quant à INVERTED, ça concerne la logique de prise en compte des état (sans aucun rapport avec les résistances de tirage).
quand INVERTED: false
si niveau logique 1 sur l’entrée (VCC) alors le programme envoi l’info comme « vraie »
si niveau logique 0 sur l’entrée (GND) alors le programme envoi l’info comme « fausse »
quand INVERTED: true
si niveau logique 1 sur l’entrée (VCC) alors le programme envoi l’info comme "fausse
si niveau logique 0 sur l’entrée (GND) alors le programme envoi l’info comme « vraie »

Je te remercie de m’avoir repris avec autant de clarté. Ce genre de retour est précieux, et tu fais preuve d’un excellent niveau de compréhension technique.

Dans ma réponse précédente, j’ai effectivement écrit ceci :

« INPUT_PULLUP avec inverted: true = INPUT_PULLDOWN »

Je ne suis pas un électronicien ou automaticien expérimenté, mais dans ma logique de bricoleur :rofl: c’était evident , en tout cas je compte sur toi la prochaine pour me corriger dans ma logique de bricoleur ≠ ingénierie pratique.

inverted: true est purement logiciel

INPUT_PULLUP et INPUT_PULLDOWN concernent uniquement le comportement électrique du GPIO au repos (niveau par défaut)

Moi, ca m’a complètement retourné la tête :smiley:

Actuellement, ca fonctionne très bien lorsque j’envoie du 5v entre GND et la borne DI1.
Ca veut donc dire que ma broche GPIO4 à une résistance de tirage à VCC et que lorsque il y a contact avec cette broche ca envoie l’info fausse (INVERTED: true)
C’est compliqué pour moi :stuck_out_tongue:

Hormis cela, j’ai voulu me simplifier la tache pour les retours d’état en remplaçant mes optocoupleurs 220v/5v qui ont 3 bornes en sortie (gnd, vcc, out) par des transfo 220/5v sur rail din qui ont donc que 2 bornes en sortie (+ et -).
Seulement, lorsqu’ils sont alimentés, ils envoient bien du 5v et retour d’état ok seulement lorsqu’ils ne le sont plus, et bien il y a un sacré délais avant extinction certainement du aux condensateurs.
Une idée pour palier à cela?

Edit: si je remet mes optocoupleurs, je les branchent comment sur mon esp? (3 bornes en sortie)

Une alimentation c’est pas du tout un composant fait pour être utilisé comme interface.
Garde tes optocoupleurs ou si vraiment avoir 3 bornes côté carte ESP te perturbe tant que ça (mais comme ta carte fournit une alimentation et une masse, je vois pas trop le problème ?) utilise des relais.
Mais franchement le coup de l’alim, même si dans le principe ça pourrait fonctionner, c’est une FBI

Oui tu as raison, je vais conserver mes optocoupleurs ou alors j’aurai pu utiliser des relais.
Je branche le moins sur la borne COM des entrées digitale, et 5v récupérer sur un broche 5v des bornes DIN?

Maintenant, je vais rajouter des interrupteurs sur les entrées digitales.

Je souhaite automatiser la gestion de la filtration de ma piscine avec l’ESP32 utilisant ESPHome et home assistant, en gardant la possibilité d’un fonctionnement manuel.

  1. Gestion des modes de fonctionnement :
  • Mode Manuel : désactivation complète du contrôle ESP32/Home Assistant. La filtration est alors gérée uniquement par le système d’origine (horloge du coffret piscine classique).
  • Mode Automatique : ESP32 + Home Assistant pilotent la filtration selon des règles (température de l’eau, horaires, marche forcée, arrêt) grâce au Blueprint piscine.
  1. Interrupteurs physiques envisagés :
  • Un interrupteur pour basculer entre mode Manuel et mode Automatique.

  • Un interrupteur à 3 positions pour la filtration : Marche forcée en position haute de l’inter, Mode Auto (en position centrale qui applique la gestion automatique ESP32) et Arrêt en position basse.
    Ceci afin de pouvoir nettoyer le bassin ou encore faire tourner la filtration durant plus longtemps en cas de présence d’algues ou autre…sans que home assistant allumé ou éteint la filtration.

  • Un poussoir (ou interrupteur) pour gérer l’éclairage de la piscine, avec possibilité de commande depuis Home Assistant pour éviter les oublis.

  • En mode Manuel, l’ESP32 ne commande plus la filtration, donc les interrupteurs Marche forcée et Arrêt sont inopérants.

  • En mode Automatique, la filtration est pilotée selon les états des interrupteurs, la température, et les horaires programmées via blueprint piscine.

  • L’éclairage peut être contrôler indépendamment, avec un retour d’état pour éviter les oublis.

À votre avis, est-ce réalisable?

Permettre un contrôle autonome et fiable de la filtration tout en conservant la possibilité d’un contrôle manuel traditionnel sans risque d’interférence.

Oui, ce que tu veux faire est parfaitement réalisable, fiable, et même recommandé dans une installation domotique bien pensée.

  • sécurité : si Home Assistant plante, la filtration ne reste pas bloquée.
  • souplesse : nettoyage rapide avec marche forcée .
  • intégration domotique évolutive (tu pourras ajouter météo, consommation, etc.)

Composants ESPHome impliqués

  • binary_sensor pour les interrupteurs physiques
  • template switch ou output pour le relais de la filtration
  • script: ou number: pour prioriser les commandes selon le mode sélectionné
  • sensor: pour température
  • time: ou automation horaire via HA

Avoir une bonne logique de décision (en YAML ou Blueprint)

Tu peut également ajouter une automatisation directement avec Esphome

  • Automatisme local si HA absent

Tu a aussi pas mal d’info sur le forum ou blog pour la bonne gestion et beaucoup d’informations a ce sujet .

HA-Gestion Complète d’une Piscine avec ESP32 et ESPHome – Domo Rem81

Super si c’est faisable.
Je vais tenter de compléter mon code YAML directement dans esphome tout en restant des plus simple mais efficace si j’y arrive.
J’avais déjà vu la gestion Pool-manager mais c’est bien trop complexe et poussé comme solution surtout pour mon niveau débutant et fraîchement arrivé sur HA.

Je préfère donc me contenter de quelque chose de simple et compréhensible pour moi avec le blueprint piscine.

Mon es32 fonctionne désormais très bien (à priori, pas encore tester les optocoupleurs mais je les ai simulé et ok)

Super, c’est vraiment top d’entendre que tout fonctionne bien ( la simulation est concluante :+1:) !
Tu a raison de rester sur quelque chose de simple et adapté , et tu pourra toujours la faire évoluer dans le temps.

J'aurai bien galéré mais c'est fonctionnel.
J'ai retiré le mode Manuel / Auto car dans tous les cas, vu que l'espace est relier au boitier de filtration d'origine, les deux sont fonctionnel ensemble donc aucun intérêt d'un mode manuel.
J'ai donc mis les modes AUTO, FORCER (Forcé posait un pb avec l'accent sur l'affichage) et Arret (idem pour l'accent)
J'aurai préféré des icônes qui apparaissent / disparaissent pour la filtration etc... mais j'ai pas réussi.

Aider par de nombreuse IA vous vous en doutez mais aucune n'a su faire pour les icônes.

Bref, ca fonctionne bien donc :smiley:

PS: dois-je mettre une pile?

Super boulot :wink:
pour la pile, c’est si tu veux gérer l’heure automatiquement même sans Internet.
pour les icônes regarde ici c’est pas sorcier a faire .

Oui j’avais déjà regardé ce lien mais pas réussi.

Mon YAML actuel:

esphome:
  name: esp32-piscine
  friendly_name: ESP32 Piscine

esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: esp-idf

# Active le journal (logs) pour voir ce qui se passe
logger:

# Permet la communication avec Home Assistant
api:

# Mise a jour OTA (sans fil)
ota:
  - platform: esphome

  #Configuration wifi (desactivé pour connexion Ethernet)
#wifi:
#  ssid: !secret wifi_ssid
#  password: !secret wifi_password
#
#  # Enable fallback hotspot (captive portal) in case wifi connection fails
#  ap:
#    ssid: "Waveshare-Esp32S3-Eth-8Di-8Ro"
#    password: "WaveshareESP32"

#captive_portal: # uniquement pour connexion wifi

# Ethernet W5500 configure avec IP fixe
ethernet:
  type: W5500
  mosi_pin: GPIO13
  miso_pin: GPIO14
  clk_pin: GPIO15
  cs_pin: GPIO16
  interrupt_pin: GPIO12
  reset_pin: GPIO39
  manual_ip:
    static_ip: 192.168.1.59
    gateway: 192.168.1.1
    subnet: 255.255.255.0

# Bus I2C pour peripheriques (ecran, RTC, etc.)
i2c:
  sda: GPIO42
  scl: GPIO41
  scan: true # scanne les peripheriques I2C au demarrage

# Extension PCA9554 pour sorties supplementaires (via I2C)
pca9554:
  - id: expander
    address: 0x20

# Horloge temps reel PCF85063 sur I2C
time:
  - platform: pcf85063
    id: pcf85063_time
    address: 0x51

# Variable globale pour stocker le mode actuel de filtration
globals:
  - id: mode_filtration
    type: std::string
    initial_value: '"Auto"'

# Definition des relais (sorties)
switch:
  # Relais de filtration (pilote via extension PCA9554)
  - platform: gpio
    pin:
      pca9554: expander
      number: 0
    id: relais_filtration
    internal: true # cache ce relais dans HA, on utilise un template en remplacement

  # Switch template visible dans Home Assistant pour controle relais filtration
  - platform: template
    name: "Filtration"
    id: relais_filtration_lockable
    optimistic: false # etat reel du relais renvoye (pas d’optimisme)
    lambda: |-
      return id(relais_filtration).state;
    turn_on_action:
      - if:
          condition:
            and:
              # Autorise allumage relais uniquement si FORCE et ARRET sont desactives
              - binary_sensor.is_off: inter_filtration_force
              - binary_sensor.is_off: inter_filtration_arret
          then:
            - switch.turn_on: relais_filtration
    turn_off_action:
      - if:
          condition:
            and:
              # Autorise extinction relais uniquement si FORCE et ARRET desactives
              - binary_sensor.is_off: inter_filtration_force
              - binary_sensor.is_off: inter_filtration_arret
          then:
            - switch.turn_off: relais_filtration

  # Relais eclairage piscine
  - platform: gpio
    pin:
      pca9554: expander
      number: 1
    id: relais_eclairage
    name: "Eclairage Piscine"

  # Relais PAC piscine
  - platform: gpio
    pin:
      pca9554: expander
      number: 2
    id: relais_pac
    name: "PAC Piscine"

# Capteurs d’etat (retour d’information)
binary_sensor:
  # Etat reel filtration (lecture GPIO, pas le relais)
  - platform: gpio
    name: "Etat Filtration"
    id: etat_filtration
    pin:
      number: GPIO4
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on: 10ms
      - delayed_off: 10ms

  # Etat PAC (lecture GPIO)
  - platform: gpio
    name: "Etat PAC"
    id: etat_pac
    pin:
      number: GPIO5
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on: 10ms
      - delayed_off: 10ms

  # Interrupteur mode FORCE (position haute = ON)
  - platform: gpio
    name: "Inter Filtration Force"
    id: inter_filtration_force
    pin:
      number: GPIO6
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on: 10ms
      - delayed_off: 10ms
    # Quand l’inter est active, on allume relais filtration
    # Et on met mode_filtration = "Forcer"
    on_state:
      then:
        - if:
            condition:
              binary_sensor.is_on: inter_filtration_force
            then:
              - switch.turn_on: relais_filtration
              - globals.set:
                  id: mode_filtration
                  value: '"Forcer"'
            else:
              # Si FORCER desactive et ARRET aussi, on revient en AUTO
              - if:
                  condition:
                    binary_sensor.is_off: inter_filtration_arret
                  then:
                    - globals.set:
                        id: mode_filtration
                        value: '"Auto"'

  # Interrupteur mode ARRET (position haute = ON)
  - platform: gpio
    name: "Inter Filtration Arret"
    id: inter_filtration_arret
    pin:
      number: GPIO7
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on: 10ms
      - delayed_off: 10ms
    # Quand l’inter ARRET est active, on coupe relais filtration
    # Et on met mode_filtration = "Arret"
    on_state:
      then:
        - if:
            condition:
              binary_sensor.is_on: inter_filtration_arret
            then:
              - switch.turn_off: relais_filtration
              - globals.set:
                  id: mode_filtration
                  value: '"Arret"'
            else:
              # Si ARRET desactive et FORCER aussi, on revient en AUTO
              - if:
                  condition:
                    binary_sensor.is_off: inter_filtration_force
                  then:
                    - globals.set:
                        id: mode_filtration
                        value: '"Auto"'

  # Bouton poussoir eclairage (toggle eclairage)
  - platform: gpio
    name: "Bouton Eclairage"
    id: bouton_eclairage
    pin:
      number: GPIO8
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on: 10ms
      - delayed_off: 10ms
    on_press:
      then:
        - switch.toggle: relais_eclairage

# Bus OneWire pour capteur temperature eau
one_wire:
  - platform: gpio
    pin: GPIO21
    id: bus_temp_eau

# Capteur temperature DS18B20
sensor:
  - platform: dallas_temp
    one_wire_id: bus_temp_eau
    id: temp_eau
    name: "Temperature Eau"
    accuracy_decimals: 1
    update_interval: 30s
    resolution: 12

# Texte affiche dans Home Assistant pour mode filtration
text_sensor:
  - platform: template
    name: "Mode Filtration"
    id: mode_filtration_sensor
    lambda: |-
      return id(mode_filtration);
    update_interval: 5s

# Police pour ecran OLED (Roboto, taille 12)
font:
  - file: "gfonts://Roboto"
    id: font_default
    size: 12

  - file: "gfonts://Roboto"
    id: font_small
    size: 8

  - file: "gfonts://Roboto"
    id: font_large
    size: 14

# Configuration écran OLED SH1106 128x64 I2C

display:
  - platform: ssd1306_i2c
    model: "SH1106 128x64"
    address: 0x3C
    update_interval: 5s
    contrast: 100%
    external_vcc: NO
    flip_x: YES
    flip_y: YES
    lambda: |-
      it.fill(COLOR_OFF);  // Efface l'écran avant affichage

      // Ligne 0 : température de l'eau (centrée avec plus d'espaces, police 14)
      if (id(temp_eau).has_state()) {
        it.printf(0, 0, id(font_large), TextAlign::TOP_LEFT, "          Eau: %.1f°C", id(temp_eau).state);
      } else {
        it.print(0, 0, id(font_large), TextAlign::TOP_LEFT, "          Eau: --°C");
      }

      // Ligne 1 : vide (saut de ligne pour espacement)

      // Ligne 2 (y=26) : Filtration + mode (avec tiret et espaces autour)
      if (id(etat_filtration).state) {
        it.printf(0, 26, id(font_default), TextAlign::TOP_LEFT, "Filtration: on   -   %s", id(mode_filtration).c_str());
      } else {
        it.printf(0, 26, id(font_default), TextAlign::TOP_LEFT, "Filtration:      %s", id(mode_filtration).c_str());
      }

      // Ligne 3 (y=38) : PAC (intervertie avec Eclairage)
      if (id(etat_pac).state) {
        it.print(0, 38, id(font_default), TextAlign::TOP_LEFT, "PAC: on");
      } else {
        it.print(0, 38, id(font_default), TextAlign::TOP_LEFT, "PAC:");
      }

      // Ligne 4 (y=50) : Eclairage (intervertie avec PAC)
      if (id(relais_eclairage).state) {
        it.print(0, 50, id(font_default), TextAlign::TOP_LEFT, "Eclairage: on");
      } else {
        it.print(0, 50, id(font_default), TextAlign::TOP_LEFT, "Eclairage:");
      }

Je suis obligé de créer un template sensor dans home assistant pour récupérer l’icône puis le mettre dans esphome?

Enfin j’ai pas bien compris.

A la base, sur mon écran, j’aurai voulu:
Tout en haut et centré, le Mode: (Arrêt/Forcé/Auto)
Au centre (toujours centré), la température de l’eau.
Tout en bas, à droite l’icône de la PAC, au centre la filtration et à gauche l’éclairage qui apparaissent que quand c’est actif.

Mais j’ai pas réussi.

pour des icônes, la doc :

tu peux rechercher les icônes sur ce site Material Design Icons - Icon Library - Pictogrammers

un exemple, que j'utilise :

image:
  - file: mdi:weather-pouring
    id: pouring
    type: BINARY  
    resize: 70x70
  - file: mdi:weather-partly-cloudy
    id: partly_cloudy
    type: BINARY
    resize: 70x70
  - file: mdi:weather-sunny
    id: sunny
    type: BINARY
    resize: 70x70
  - file: mdi:close
    id: croix
    type: BINARY
    resize: 70x70
  - file: mdi:battery-high
    id: battery
    type: BINARY
    resize: 30x30
  - file: mdi:wifi
    id: wifi_icon
    type: BINARY
    resize: 30x30

color:
  - id: my_white
    red: 100%
    green: 100%
    blue: 100%
  - id: my_blue
    red: 0%
    green: 0%
    blue: 100%
  - id: my_red
    red: 100%
    green: 0%
    blue: 0%
  - id: my_green
    red: 0%
    green: 100%
    blue: 0%
  - id: my_yellow
    red: 100%
    green: 100%
    blue: 0%
  - id: my_orange
    red: 100%
    green: 50%
    blue: 0%

# 1.14 inch, 135*240 Colorful TFT LCD, ST7789v2
display:
  - platform: ili9xxx
    model: st7789v
    cs_pin: GPIO5
    dc_pin: GPIO14
    reset_pin: GPIO12
    rotation: 90
    color_palette: NONE
    dimensions:
      height: 240
      width: 135
      offset_height: 40
      offset_width: 52
    # Required or the colors are all inverted, and Black screen is White
    invert_colors: true
    lambda: |-
      it.strftime(30, 2, id(roboto), id(my_blue), "%H:%M:%S %d/%m/%y", id(my_time).now());     
      if ((id(pression_state).state >= 960) && (id(pression_state).state < 990)) {it.image(14, 30, id(pouring), id(my_red));}
      else if ((id(pression_state).state >= 990) && (id(pression_state).state < 1020)) {it.image(14, 30, id(partly_cloudy), id(my_yellow));}
      else if ((id(pression_state).state >= 1020) && (id(pression_state).state < 1060)) {it.image(14, 30, id(sunny), id(my_green));}
      else {it.image(14, 30, id(croix), id(my_red));}
      it.printf(94, 40, id(roboto), id(my_green), "%.0fhpa", id(pression_state).state);
      it.printf(94, 66, id(roboto), id(my_blue), "%.1f°C", id(temp_state).state);
      it.printf(173, 66, id(roboto), id(my_blue), "%.0f%%", id(hum_state).state);
      if (id(battery_percent).state < 11) { it.image(20, 102, id(battery), id(my_red));}
      else {it.image(20, 102, id(battery), id(my_green));}
      it.printf(54, 106, id(roboto), id(my_white), "%.0f%%", id(battery_percent).state);
      if ((id(wifi_signal_pct).state >= 1) && (id(wifi_signal_pct).state < 25)) {it.image(123, 102, id(wifi_icon), id(my_red));}
      else if ((id(wifi_signal_pct).state >= 25) && (id(wifi_signal_pct).state < 50)) {it.image(123, 102, id(wifi_icon), id(my_orange));}
      else if ((id(wifi_signal_pct).state >= 50) && (id(wifi_signal_pct).state < 75)) {it.image(123, 102, id(wifi_icon), id(my_yellow));}
      else {it.image(123, 102, id(wifi_icon), id(my_green));}
      it.printf(161, 106, id(roboto), id(my_white), "%.0f%%", id(wifi_signal_pct).state);

résultat :

Ah oui, ça c’est top!

Cependant, ça commence à devenir compliqué pour moi.
Je vais m’y pencher et surtout de tacher de comprendre.
Merci encore pour le tuyau.

Je reviens sur le branchement de l’optcoupleur.

J’ai réussi à le faire fonctionner mais en y branchant 4 fils, sinon ça fonctionne pas.
Je l’ai alimenté (gnd, 5v sur les pin de la carte), la sortie sur une des entrées digitales mais j’ai également du y relier le 5v a DGND car sinon ça fonctionnait pas sur la borne GND.
Pas moyen de le brancher comme sur tout Esp32 ou Arduino directement sur une entrée digitale uniquement sans avoir a également y tirer un second câble?

Normal vu que les entrées sont protégées par des optocoupleurs. Il n’y a aucune liaison électrique entre les entrées et le reste de la carte. Si tu utilise le 5V de la carte, il faut donc qu’il y ai un commun entre les deux. D’où la nécessité de faire ce pont.

Je comprend mieux, merci.

Pour avoir une carte avec le bouton et retour d’état tout en un (on clique et l’icône change d’état en fonction du retour d’état et non du relais), il faut créer une carte avec YAML, le faire avec esphome ou créer une nouvelle entrée/entitée?
Si tous valables, le choix le plus judicieux.

Merci, grâce à ton aide, j'ai enfin réussi:



esphome:
  name: esp32-piscine
  friendly_name: ESP32 Piscine

esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: esp-idf

# Active le journal (logs) pour voir ce qui se passe
logger:

# Permet la communication avec Home Assistant
api:

# Mise a jour OTA (sans fil)
ota:
  - platform: esphome

  #Configuration wifi (desactivé pour connexion Ethernet)
#wifi:
#  ssid: !secret wifi_ssid
#  password: !secret wifi_password
#
#  # Enable fallback hotspot (captive portal) in case wifi connection fails
#  ap:
#    ssid: "Waveshare-Esp32S3-Eth-8Di-8Ro"
#    password: "WaveshareESP32"

#captive_portal: # uniquement pour connexion wifi

# Ethernet W5500 configure avec IP fixe
ethernet:
  type: W5500
  mosi_pin: GPIO13
  miso_pin: GPIO14
  clk_pin: GPIO15
  cs_pin: GPIO16
  interrupt_pin: GPIO12
  reset_pin: GPIO39
  manual_ip:
    static_ip: 192.168.1.59
    gateway: 192.168.1.1
    subnet: 255.255.255.0

# Bus I2C pour peripheriques (ecran, RTC, etc.)
i2c:
  sda: GPIO42
  scl: GPIO41
  scan: true # scanne les peripheriques I2C au demarrage

# Extension PCA9554 pour sorties supplementaires (via I2C)
pca9554:
  - id: expander
    address: 0x20

# Horloge temps reel PCF85063 sur I2C
time:
  - platform: pcf85063
    id: pcf85063_time
    address: 0x51

# Variable globale pour stocker le mode actuel de filtration
globals:
  - id: mode_filtration
    type: std::string
    initial_value: '"Auto"'

# Definition des relais (sorties)
switch:
  # Relais de filtration (pilote via extension PCA9554)
  - platform: gpio
    pin:
      pca9554: expander
      number: 0
    id: relais_filtration
    internal: true # cache ce relais dans HA, on utilise un template en remplacement

  # Switch template visible dans Home Assistant pour controle relais filtration
  - platform: template
    name: "Filtration"
    id: relais_filtration_lockable
    optimistic: false # etat reel du relais renvoye (pas d'optimisme)
    lambda: |-
      return id(relais_filtration).state;
    turn_on_action:
      - if:
          condition:
            and:
              # Autorise allumage relais uniquement si FORCE et ARRET sont desactives
              - binary_sensor.is_off: inter_filtration_force
              - binary_sensor.is_off: inter_filtration_arret
          then:
            - switch.turn_on: relais_filtration
    turn_off_action:
      - if:
          condition:
            and:
              # Autorise extinction relais uniquement si FORCE et ARRET desactives
              - binary_sensor.is_off: inter_filtration_force
              - binary_sensor.is_off: inter_filtration_arret
          then:
            - switch.turn_off: relais_filtration

  # Relais eclairage piscine
  - platform: gpio
    pin:
      pca9554: expander
      number: 1
    id: relais_eclairage
    name: "Eclairage Piscine"

  # Relais PAC piscine
  - platform: gpio
    pin:
      pca9554: expander
      number: 2
    id: relais_pac
    name: "PAC Piscine"

# Capteurs d'etat (retour d'information)
binary_sensor:
  # Etat reel filtration (lecture GPIO, pas le relais)
  - platform: gpio
    name: "Etat Filtration"
    id: etat_filtration
    pin:
      number: GPIO4
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on: 10ms
      - delayed_off: 10ms

  # Etat PAC (lecture GPIO)
  - platform: gpio
    name: "Etat PAC"
    id: etat_pac
    pin:
      number: GPIO5
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on: 10ms
      - delayed_off: 10ms

  # Interrupteur mode FORCE (position haute = ON)
  - platform: gpio
    name: "Inter Filtration Force"
    id: inter_filtration_force
    pin:
      number: GPIO6
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on: 10ms
      - delayed_off: 10ms
    # Quand l'inter est active, on allume relais filtration
    # Et on met mode_filtration = "Forcer"
    on_state:
      then:
        - if:
            condition:
              binary_sensor.is_on: inter_filtration_force
            then:
              - switch.turn_on: relais_filtration
              - globals.set:
                  id: mode_filtration
                  value: '"Forcer"'
            else:
              # Si FORCER desactive et ARRET aussi, on revient en AUTO
              - if:
                  condition:
                    binary_sensor.is_off: inter_filtration_arret
                  then:
                    - globals.set:
                        id: mode_filtration
                        value: '"Auto"'

  # Interrupteur mode ARRET (position haute = ON)
  - platform: gpio
    name: "Inter Filtration Arret"
    id: inter_filtration_arret
    pin:
      number: GPIO7
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on: 10ms
      - delayed_off: 10ms
    # Quand l'inter ARRET est active, on coupe relais filtration
    # Et on met mode_filtration = "Arret"
    on_state:
      then:
        - if:
            condition:
              binary_sensor.is_on: inter_filtration_arret
            then:
              - switch.turn_off: relais_filtration
              - globals.set:
                  id: mode_filtration
                  value: '"Arret"'
            else:
              # Si ARRET desactive et FORCER aussi, on revient en AUTO
              - if:
                  condition:
                    binary_sensor.is_off: inter_filtration_force
                  then:
                    - globals.set:
                        id: mode_filtration
                        value: '"Auto"'

  # Bouton poussoir eclairage (toggle eclairage)
  - platform: gpio
    name: "Bouton Eclairage"
    id: bouton_eclairage
    pin:
      number: GPIO8
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on: 10ms
      - delayed_off: 10ms
    on_press:
      then:
        - switch.toggle: relais_eclairage

# Bus OneWire pour capteur temperature eau
one_wire:
  - platform: gpio
    pin: GPIO21
    id: bus_temp_eau

# Capteur temperature DS18B20
sensor:
  - platform: dallas_temp
    one_wire_id: bus_temp_eau
    id: temp_eau
    name: "Temperature Eau"
    accuracy_decimals: 1
    update_interval: 30s
    resolution: 12

# Texte affiche dans Home Assistant pour mode filtration
text_sensor:
  - platform: template
    name: "Mode Filtration"
    id: mode_filtration_sensor
    lambda: |-
      return id(mode_filtration);
    update_interval: 3s

# Police pour ecran OLED (Roboto, taille 12)
font:
  - file: "gfonts://Roboto"
    id: font_default
    size: 12

  - file: "gfonts://Roboto"
    id: font_small
    size: 8

  - file: "gfonts://Roboto"
    id: font_large
    size: 14

  - file: "gfonts://Roboto"
    id: font_xlarge
    size: 18

  - file: "gfonts://Roboto"
    id: font_medium
    size: 14

# Images/icônes pour l'écran OLED
image:
  - file: mdi:lightbulb-on-10
    id: lightbulb_icon
    type: BINARY
    resize: 20x20
  - file: mdi:fan
    id: fan_icon
    type: BINARY
    resize: 20x20
  - file: mdi:heat-pump-outline
    id: heat_pump_icon
    type: BINARY
    resize: 20x20

# Configuration écran OLED SH1106 128x64 I2C
display:
  - platform: ssd1306_i2c
    model: "SH1106 128x64"
    address: 0x3C
    update_interval: 3s
    contrast: 100%
    external_vcc: NO
    flip_x: NO
    flip_y: NO
    lambda: |-
      it.fill(COLOR_OFF);  // Efface l'écran avant affichage

      // Ligne 0 : température de l'eau (centrée, police 18)
      if (id(temp_eau).has_state()) {
        it.printf(64, 0, id(font_xlarge), TextAlign::TOP_CENTER, "Eau: %.1f°C", id(temp_eau).state);
      } else {
        it.print(64, 0, id(font_xlarge), TextAlign::TOP_CENTER, "Eau: --°C");
      }

      // Centre : Mode filtration (centré, police 14)
      it.printf(64, 29, id(font_medium), TextAlign::CENTER, "Mode: %s", id(mode_filtration).c_str());

      // Bas : Icônes conditionnelles (à Y=44 pour rester dans l'écran)
      // Icône éclairage (bas gauche) - affiché seulement si ON
      if (id(relais_eclairage).state) {
        it.image(10, 44, id(lightbulb_icon));
      }

      // Icône filtration (bas centre) - affiché seulement si ON
      if (id(etat_filtration).state) {
        it.image(54, 44, id(fan_icon));
      }

      // Icône PAC (bas droite) - affiché seulement si ON
      if (id(etat_pac).state) {
        it.image(98, 44, id(heat_pump_icon));
      }

Est-il possible que en cas de coupure de courant, les relais reprennent leurs état d'avant la coupure?