Voici donc toutes les fonctions que j'ai imaginé pour ce module avec quelques détails techniques pour chacun d'entre eux.
-
Le fonctionnement des volets.
3 touches à effleurement (Merci l'ESP32)
- Sélection du ou des volets à actionner; cycliquement : 1, 2, 3, 4, 1+3+4, 1+2+3+4 (2 étant la porte-fenêtre)
- Montée : appui bref lance ou arrête le mouvement et appui long lance le mouvement mais l'arrête au relâchement.
- Descente : idem montée.
4 LEDs indiquent la sélection du ou des volet(s) à manoeuvrer.
Dit comme ça on peux penser à une usine à gaz mais en fait c'est super intuitif.
8 relais pour commnder tout ça.
-
Un mini écran pour indiquer les températures : extérieur, séjour, piscine.
Une touche à effleurement permet de faire défiler les différentes températures.
-
Un détecteur de présence; pour éventuellement déclencher Alarmo.
-
Une boucle avec en série les 3 contacts des fenêtres et une autre boucle avec le contact de la porte-fenêtre.
C'est aussi pour Alarmo.
-
Un capteur Dallas DS18B20 pour la température intérieure.
Je vais brancher tout ça sur un ESP32-WROOM-32D installé sur un «devkit» de 30 pins. Un petit module 220v Hi-link délivrera du 5v avec une puissance de 5w.
La carte devkit, alimentée en 5v possède un régulateur fournissant du 3,3v.
La carte 8 relais vient de chez Ali. mais je l'ais bien modifié pour ajouter une sécurité : si les relais de montée et de descente sont activés
en même temps une seule sortie est alimentée. J'ai aussi remodelé les connnecteurs : un connecteur 3 fils pour chaque moteur. Il faut 2 relais pour chaque volet.

Au repos le relais 1 envoie le jus sur le relais 2 qui en s'activant alimente B. En activant le relais 1 on alimente A mais on n'alimente plus le relais 2. CQFD

Les borniers sont des modèles débrochables. Le résultat est presque propre !
Les relais sont alimentés entre 5 v (JD-Vcc) et masse grâce à un transistor commandé par un optocoupleur. La broche de commande
arrive sur la cathode d'un optocoupleur (Avec une LED en série). L'anode est reliée à la ligne Vcc. Un shunt permet de relier
JD-Vcc et Vcc, dans ce cas on alimente en 5 v mais il faut que les GPIO de commande acceptent 5 v. Donc bien retenir que c'est un niveau bas qui active le relais.
L'ESP fonctionne en 3,3 v : j'ai donc retiré le shunt et j'alimente la carte avec du 3,3 v et du 5 v. 3 pins
reçoivent : GND, Vcc (3,3 v) et JD-Vcc (5 v).
Le schéma est très simple puisque tous les capteurs et relais sont déportés et connectés grâce à des fiches RJ.
Les fichiers KiCad et PDF sont disponibles sous licence

Les fonctionnalités qui permettent à plusieurs modules ESPhome d'interagir nécessitent de valider une autorisation dans HA sous Appareil --> Options
Reste à écrire le fichier YAML de l'appareil dans ESPHome builder; quelques comentaires avant de plonger dans le code.
- globals:
- Un flag d'arrêt de manoeuvre
- Une variable indiquants la sélection de volet à manoeuvrer
- Un flag d'activation du système
- esp32_touch --> Bouton sensiifs. Les valeurs de "threshold" seront à ajuster sur l'appareil fini.
-
binary_sensor:
- Les 4 boutons sensitifs
- Les 2 boucles d'ouverture des huisseries
- Le détecteur de présence
-
display: et font:
L'affichage des températures
-
i2c: et one_wire
Les interfaces de l'écran et du capteur de température.
-
script:
Ils regroupent l'essentiel de la logique du bidule !
-
sensor: --> dallas_temp
La température intérieure
sensor: --> temperature_piscine
La température provenant d'un autre module
sensor: --> temperature_rue
La température provenant d'un autre module
-
switch: --> Les 8 relais de commande des volets en logique inversée et les 4 LEDs.
esphome:
name: sejour
friendly_name: Séjour
min_version: 2025.9.0
esp32:
variant: esp32
framework:
type: esp-idf
logger:
ota:
- platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Sejour Hotspot"
captive_portal:
api:
globals:
- id: flag_stop
type: bool
initial_value: "false"
- id: groupement
type: int
initial_value: "6"
- id: systeme_actif
type: bool
initial_value: "false"
esp32_touch:
# Dans l'ordre alphabétique des composants
binary_sensor:
# Bouton sélection
- platform: esp32_touch
id: bouton_select
name: "bouton_select"
pin: GPIO12
threshold: 300
filters:
- delayed_on: 20ms
- delayed_off: 20ms
on_press:
then:
- if:
condition:
lambda: 'return ! id(systeme_actif);'
then:
- script.execute:
id: allume_led
else:
- if:
condition:
lambda: 'return id(groupement) == 6;'
then:
- lambda:
id(groupement) = 1;
else:
- lambda:
id(groupement) = id(groupement) + 1;
- script.execute:
id: allume_led
# Bouton monte
- platform: esp32_touch
id: bouton_monte
name: "bouton_monte"
pin: GPIO13
threshold: 300
filters:
- delayed_on: 20ms
- delayed_off: 20ms
on_press:
then:
- if:
condition:
lambda: 'return ! id(systeme_actif);'
then:
- script.execute:
id: allume_led
else:
- if:
condition:
lambda: 'return id(flag_stop);'
then:
- script.execute:
id: stop_relais
else:
- script.execute:
id: monter
- lambda:
id(flag_stop) = true;
- script.execute:
id: bouton_press
on_release:
then:
- if:
condition:
lambda: 'return ! id(flag_stop);'
then:
- script.execute:
id: stop_relais
on_click:
min_length: 50ms
max_length: 400ms
then:
- script.stop:
id: bouton_press
# Bouton descend
- platform: esp32_touch
id: bouton_descend
name: "bouton_descend"
pin: GPIO14
threshold: 300
filters:
- delayed_on: 20ms
- delayed_off: 20ms
on_press:
then:
- if:
condition:
lambda: 'return ! id(systeme_actif);'
then:
- script.execute:
id: allume_led
else:
- if:
condition:
lambda: 'return id(flag_stop);'
then:
- script.execute:
id: stop_relais
else:
- script.execute:
id: descendre
- lambda:
id(flag_stop) = true;
- script.execute:
id: bouton_press
on_release:
then:
- if:
condition:
lambda: 'return ! id(flag_stop);'
then:
- script.execute:
id: stop_relais
on_click:
min_length: 50ms
max_length: 400ms
then:
- script.stop:
id: bouton_press
# Bouton thermomètre
- platform: esp32_touch
id: bouton_thermo
name: "bouton_thermo"
pin: GPIO15
threshold: 300
filters:
- delayed_on: 20ms
- delayed_off: 20ms
on_press:
then:
- display.page.show_next: ecran
- component.update: ecran
# Détecteur d'ouverture fenêtre'
- platform: gpio
id: contact_fenetre
name: "contact_fenetre"
icon: mdi:door-closed
pin:
number: GPIO34
inverted: true
# Détecteur d'ouverture porte'
- platform: gpio
id: contact_porte
name: "contact_porte"
icon: mdi:door-closed
pin:
number: GPIO35
inverted: true
# Détecteur de présence
- platform: gpio
id: presence_sejour
name: "presence_sejour"
device_class: "motion"
pin: GPIO36
display:
- platform: ssd1306_i2c
model: "SSD1306 128x64"
address: 0x3C
id: ecran
update_interval: 2min
pages:
- id: page_interieur
lambda: |-
it.printf(64, 0, id(arial20), TextAlign::TOP_CENTER, "Intérieur");
it.printf(64, 30, id(arial30), TextAlign::TOP_CENTER, "%.1f °", id(temperature_sejour).state);
- id: page_exterieur
lambda: |-
it.printf(64, 0, id(arial20), TextAlign::TOP_CENTER, "Extérieur");
it.printf(64, 30, id(arial30), TextAlign::TOP_CENTER, "%.1f °", id(temperature_rue).state);
- id: page_piscine
lambda: |-
it.printf(64, 0, id(arial20), TextAlign::TOP_CENTER, "Piscine");
it.printf(64, 30, id(arial30), TextAlign::TOP_CENTER, "%.1f °", id(temperature_piscine).state);
font:
- file: "fonts/arialbd.ttf"
id: arial20
size: 20
glyphs: [EIPceéinrstux]
- file: "fonts/arialbd.ttf"
id: arial30
size: 30
glyphs: [0123456789. °-]
i2c:
- id: bus_display
sda: GPIO26
scl: GPIO27
one_wire:
- platform: gpio
pin: GPIO32
script:
- id: bouton_press
then:
- delay: 600ms
- lambda:
id(flag_stop) = false;
- id: monter
then:
- script.execute:
id: allume_led
- if:
condition:
lambda: 'return id(groupement) == 1 || id(groupement) == 5 || id(groupement) == 6;'
then:
- switch.turn_off: relais_D1
- switch.turn_on: relais_M1
- if:
condition:
lambda: 'return id(groupement) == 2 || id(groupement) == 6;'
then:
- switch.turn_off: relais_D2
- switch.turn_on: relais_M2
- if:
condition:
lambda: 'return id(groupement) == 3 || id(groupement) == 5 || id(groupement) == 6;'
then:
- switch.turn_off: relais_D3
- switch.turn_on: relais_M3
- if:
condition:
lambda: 'return id(groupement) == 4 || id(groupement) == 5 || id(groupement) == 6;'
then:
- switch.turn_off: relais_D4
- switch.turn_on: relais_M4
- id: descendre
then:
- script.execute:
id: allume_led
- if:
condition:
lambda: 'return id(groupement) == 1 || id(groupement) == 5 || id(groupement) == 6;'
then:
- switch.turn_off: relais_M1
- switch.turn_on: relais_D1
- if:
condition:
lambda: 'return id(groupement) == 2 || id(groupement) == 6;'
then:
- switch.turn_off: relais_M2
- switch.turn_on: relais_D2
- if:
condition:
lambda: 'return id(groupement) == 3 || id(groupement) == 5 || id(groupement) == 6;'
then:
- switch.turn_off: relais_M3
- switch.turn_on: relais_D3
- if:
condition:
lambda: 'return id(groupement) == 4 || id(groupement) == 5 || id(groupement) == 6;'
then:
- switch.turn_off: relais_M4
- switch.turn_on: relais_D4
- id: allume_led
mode: restart
then:
- lambda:
id(systeme_actif) = true;
- if:
condition:
lambda: 'return id(groupement) == 1 || id(groupement) == 5 || id(groupement) == 6;'
then:
- switch.turn_on: led_1
else:
- switch.turn_off: led_1
- if:
condition:
lambda: 'return id(groupement) == 2 || id(groupement) == 6;'
then:
- switch.turn_on: led_2
else:
- switch.turn_off: led_2
- if:
condition:
lambda: 'return id(groupement) == 3 || id(groupement) == 5 || id(groupement) == 6;'
then:
- switch.turn_on: led_3
else:
- switch.turn_off: led_3
- if:
condition:
lambda: 'return id(groupement) == 4 || id(groupement) == 5 || id(groupement) == 6;'
then:
- switch.turn_on: led_4
else:
- switch.turn_off: led_4
- delay: 30s
- script.execute:
id: stop_relais
- delay: 60s
- switch.turn_off: led_1
- switch.turn_off: led_2
- switch.turn_off: led_3
- switch.turn_off: led_4
- lambda:
id(systeme_actif) = false;
- id: stop_relais
mode: restart
then:
- lambda:
id(flag_stop) = false;
- switch.turn_off: relais_M1
- switch.turn_off: relais_D1
- switch.turn_off: relais_M2
- switch.turn_off: relais_D2
- switch.turn_off: relais_M3
- switch.turn_off: relais_D3
- switch.turn_off: relais_M4
- switch.turn_off: relais_D4
sensor:
# Capteur local Dallas DS18
- platform: dallas_temp
id: temperature_sejour
name: Temperature séjour
update_interval: 10min
# Récupérer température de la piscine
- platform: homeassistant
id: temperature_piscine
entity_id: sensor.thermometre_piscine_temperature
internal: true
unit_of_measurement: "°C"
# Récupérer température extérieure
- platform: homeassistant
id: temperature_rue
entity_id: sensor.portier_temperature_rue
internal: true
unit_of_measurement: "°C"
switch:
# 8 relais des volets
- platform: gpio
id: relais_M1
name: relais_M1
pin:
number: GPIO16
inverted: true
- platform: gpio
id: relais_D1
name: relais_D1
pin:
number: GPIO17
inverted: true
- platform: gpio
id: relais_M2
name: relais_M2
pin:
number: GPIO18
inverted: true
- platform: gpio
id: relais_D2
name: relais_D2
pin:
number: GPIO19
inverted: true
- platform: gpio
id: relais_M3
name: relais_M3
pin:
number: GPIO21
inverted: true
- platform: gpio
id: relais_D3
name: relais_D3
pin:
number: GPIO22
inverted: true
- platform: gpio
id: relais_M4
name: relais_M4
pin:
number: GPIO23
inverted: true
- platform: gpio
id: relais_D4
name: relais_D4
pin:
number: GPIO25
inverted: true
# 4 LEDs d'indication de volet actif
- platform: gpio
id: led_1
name: led_1
pin: GPIO1
- platform: gpio
id: led_2
name: led_2
pin: GPIO2
- platform: gpio
id: led_3
name: led_3
pin: GPIO3
- platform: gpio
id: led_4
name: led_4
pin: GPIO4
Le dessin du C.I.
Le C.I. terminé.
Les pièces 3D et le montage