Archives mensuelles : décembre 2022

La porte-fenêtre du salon et ses rideaux, la main sur la vanne de l'étagère steampunk

Pourquoi, et surtout comment, connecter ses rideaux

En 2019, l’article sur l’étagère steampunk se terminait par un « reste à faire » : « exploiter la vanne… ». J’avais même passé un câble électrique dans les tuyaux « pour permettre une commande future à partir de la fameuse vanne ».

Trois ans plus tard, voici la commande future : la vanne ouvre et ferme les rideaux du salon.

L'étagère steampunk sur le mur bleu, la vanne rouge sur le tuyau du bas

Ce projet a d’abord été une présentation « fun » devant mes collègues de Lucca, avec un programme ambitieux : la méthode du double diamant, la différence entre les stratégies pull et push, un schéma d’architecture, quelques conseils pour prendre soin de son couple, la sobriété énergétique et, pour finir, quasi sauver le monde. Je reprends ici le même chemin.

Déformation professionnelle oblige, on suit le double diamant : d’abord explorer le problème, ensuite seulement chercher la solution.

Le schéma du double diamant : découvrir, définir, développer, délivrer

Plantons le décor : l’espace du problème

Le salon a une grande porte-fenêtre qui donne sur la rue. Le matin, rideaux fermés, on vit dans le noir :

Le salon dans la pénombre à 8h du matin, rideaux fermés, un filet de lumière entre les deux pans

En journée, une fois les rideaux ouverts, la fenêtre se reflète dans la TV :

La TV éteinte dans laquelle se reflète la porte-fenêtre

Rideaux ouverts ou fermés, la différence se voit tout de suite quand la TV est allumée :

Et le soir, vu de la rue, tout le quartier profite du programme à travers le voilage :

La fenêtre du salon vue de la rue, l'écran de la TV visible à travers le voilage

Le cahier des charges tient en quatre lignes :

  • ouvrir les rideaux au lever du soleil ;
  • les fermer à la tombée de la nuit ;
  • pouvoir les commander avec la vanne de l’étagère ;
  • les fermer quand on allume la TV.

Au départ, rien ne fonctionne :

Les quatre critères (soleil, nuit, vanne, TV), tous barrés d'une croix rouge

Comment connecter ses rideaux : l’espace de la solution

Étape 1 : des robots sur la tringle

Plutôt que de changer de rideaux ou de poser une tringle motorisée, j’ai choisi des SwitchBot Curtain : un petit robot qui s’accroche sur la tringle existante et tire le rideau. Il en faut un par pan de rideau : deux dans le salon TV, et deux dans l’autre salon.

La boîte du SwitchBot Curtain et le robot qui s'accroche à la tringle

Les robots communiquent en Bluetooth avec un petit hub, relié à Internet. L’application SwitchBot gère le lever et le coucher du soleil, et l’intégration Google Home permet de dire « OK Google, ferme les rideaux ».

Étape 1 : les rideaux SwitchBot, leur hub et Internet ; le soleil et la nuit passent au vert

Et surtout, le fonctionnement analogique est conservé : on peut toujours tirer le rideau à la main.

Étape 2 : la vanne

Place à la vanne steampunk, celle qui fonctionne comme un interrupteur. Son contact est relié à un GPIO (une broche d’entrée/sortie) d’un Raspberry Pi. Quand on tourne la vanne, un script Python sur le Raspberry Pi appelle l’API de SwitchBot.

La vanne fonctionne comme un interrupteur va-et-vient : on la tourne, et on obtient l’inverse de l’état actuel. Rideaux fermés, ils s’ouvrent ; rideaux ouverts, ils se ferment. Peu importe que la dernière manœuvre vienne du soleil, de Google ou de la vanne.

Étape 2 : un Raspberry Pi relié à la vanne rejoint l'architecture ; la vanne passe au vert

Côté code, je l’avoue, la version de 2022 était… artisanale : le script de la vanne relançait la commande des rideaux toutes les demi-secondes tant que la vanne restait ouverte ! Voici une version remise au propre pour l’article.

D’abord, un petit module commun qui parle à l’API SwitchBot. Le jeton et la clé secrète se trouvent dans l’application SwitchBot (Profil, Préférences, Options développeur), et chaque appel est signé :

switchbot.pyRaspberry Pi / Python
"""Commande des rideaux SwitchBot par l'API officielle (v1.1)."""
import base64
import hashlib
import hmac
import os
import time
import uuid

import requests

API = "https://api.switch-bot.com/v1.1"
# Jeton et clé secrète : appli SwitchBot > Profil > Préférences > Options développeur
TOKEN = os.environ["SWITCHBOT_TOKEN"]
SECRET = os.environ["SWITCHBOT_SECRET"]
# Identifiants des robots du salon, séparés par des virgules
RIDEAUX = os.environ["SWITCHBOT_RIDEAUX"].split(",")


def entetes():
    """Chaque appel est signé : HMAC-SHA256 du jeton, de l'heure et d'un nombre aléatoire."""
    t = str(int(time.time() * 1000))
    nonce = uuid.uuid4().hex
    message = f"{TOKEN}{t}{nonce}".encode()
    signature = hmac.new(SECRET.encode(), message, hashlib.sha256).digest()
    return {"Authorization": TOKEN, "sign": base64.b64encode(signature).decode(),
            "t": t, "nonce": nonce, "Content-Type": "application/json; charset=utf8"}


def appel(methode, chemin, **options):
    r = requests.request(methode, API + chemin, headers=entetes(), timeout=10, **options)
    r.raise_for_status()
    reponse = r.json()
    if reponse.get("statusCode") != 100:  # 100 = succès pour SwitchBot
        raise RuntimeError(f"SwitchBot a refusé : {reponse}")
    return reponse.get("body", {})


def position():
    """Position du premier rideau : 0 = ouvert, 100 = fermé."""
    return appel("GET", f"/devices/{RIDEAUX[0]}/status")["slidePosition"]


def commande(ordre):
    for rideau in RIDEAUX:
        appel("POST", f"/devices/{rideau}/commands",
              json={"command": ordre, "parameter": "default", "commandType": "command"})


def ouvrir():
    commande("turnOn")


def fermer():
    commande("turnOff")


def basculer():
    """L'inverse de l'état actuel : plutôt fermé, on ouvre ; plutôt ouvert, on ferme."""
    if position() > 50:
        ouvrir()
    else:
        fermer()

Puis la lecture de la vanne. Le script ne réagit qu’au changement de position, une seule fois, et ignore les rebonds du contact (un contact mécanique « rebondit » quelques millisecondes en s’ouvrant ou en se fermant) :

vanne.pyRaspberry Pi / Python
"""La vanne de l'étagère steampunk : chaque manœuvre inverse l'état des rideaux."""
import time

import RPi.GPIO as GPIO

import switchbot

BROCHE = 4  # GPIO4 : le contact de la vanne le relie au 3,3 V

GPIO.setmode(GPIO.BCM)
GPIO.setup(BROCHE, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)  # au repos, l'entrée est tirée à 0


def lecture_stable():
    """Deux lectures identiques à 50 ms d'écart : on ignore les rebonds du contact."""
    while True:
        valeur = GPIO.input(BROCHE)
        time.sleep(0.05)
        if GPIO.input(BROCHE) == valeur:
            return valeur


try:
    etat = lecture_stable()
    while True:
        nouvel_etat = lecture_stable()
        if nouvel_etat != etat:  # la vanne a bougé, peu importe dans quel sens
            etat = nouvel_etat
            try:
                switchbot.basculer()
            except Exception as erreur:  # réseau ou API indisponible : on réessaiera
                print("Échec de la commande :", erreur)
        time.sleep(0.1)
finally:
    GPIO.cleanup()

Étape 3 : la TV

Dernier critère, la TV. Quand on l’allume, les rideaux se ferment : plus de reflet dans l’écran, et plus de spectacle pour les passants.

Pour savoir si la TV est allumée, un second script Python interroge en permanence son adresse IP, plus précisément sa page de description réseau, qui ne répond que lorsque la TV est allumée. Tant qu’elle ne répond pas, la TV est éteinte ; dès qu’elle répond, on ferme. C’est ce qu’on appelle du polling : le script va chercher l’information lui-même, en boucle (une stratégie « pull »). L’inverse, une stratégie push, serait que la TV prévienne elle-même qu’elle vient de s’allumer. Mais à ce moment-là, la TV ne proposait tout simplement pas cette possibilité : le polling était la seule option.

Étape 3 : la Freebox et la TV complètent le schéma ; les quatre critères sont au vert

Deux démonstrations, la TV qu’on allume et les rideaux qui se ferment :

Le script de la TV, lui aussi remis au propre. Il ne ferme les rideaux qu’au moment où la TV passe d’éteinte à allumée, et attend deux réponses identiques avant de conclure, pour éviter les fausses alertes :

tv.pyRaspberry Pi / Python
"""Ferme les rideaux quand la TV s'allume, par polling de son adresse IP."""
import time

import requests

import switchbot

# Adresse IP de la TV : sa page de description (UPnP) ne répond que TV allumée
URL_TV = "http://192.168.1.6:54243/device.xml"
INTERVALLE = 2     # secondes entre deux interrogations
CONFIRMATIONS = 2  # réponses identiques à la suite avant de changer d'avis


def tv_allumee(session):
    try:
        return session.get(URL_TV, timeout=2).status_code == 200
    except requests.RequestException:
        return False


session = requests.Session()
allumee = tv_allumee(session)
compteur = 0
while True:
    mesure = tv_allumee(session)
    compteur = compteur + 1 if mesure != allumee else 0
    if compteur >= CONFIRMATIONS:  # changement d'état confirmé
        allumee, compteur = mesure, 0
        print("TV allumée" if allumee else "TV éteinte")
        if allumee:
            try:
                switchbot.fermer()
            except Exception as erreur:
                print("Échec de la commande :", erreur)
    time.sleep(INTERVALLE)

Enfin, pour que les deux scripts démarrent avec le Raspberry Pi et redémarrent en cas de plantage, un service systemd chacun (à la place du cron et du tmux de l’époque). Le jeton et la clé SwitchBot sont rangés dans un fichier switchbot.env à part, qu’on ne publie jamais :

rideaux-vanne.servicesystemd
# /etc/systemd/system/rideaux-vanne.service (idem rideaux-tv.service avec tv.py)
[Unit]
Description=Rideaux : lecture de la vanne
After=network-online.target
Wants=network-online.target

[Service]
WorkingDirectory=/home/pi/rideaux
EnvironmentFile=/home/pi/rideaux/switchbot.env
ExecStart=/usr/bin/python3 vanne.py
Restart=always

[Install]
WantedBy=multi-user.target

Puis sudo systemctl enable --now rideaux-vanne rideaux-tv, et c’est parti.

Bingo !

Le programme est rempli :

  • la méthode du double diamant : c’est fait ;
  • la différence entre pull et push : le polling de la TV ;
  • un schéma d’architecture : il est juste au-dessus ;
  • un conseil pour prendre soin de son couple : garder des boutons, mécaniques ou analogiques. Tout le monde n’a pas envie de sortir son téléphone ou de parler à une enceinte pour ouvrir un rideau. Tirer le rideau à la main, ça marche toujours ;
  • la sobriété énergétique : des rideaux fermés au coucher du soleil gardent la chaleur l’hiver, et protègent du soleil l’été. Les plantes, elles, profitent de la lumière dès le lever du jour ;
  • quasi sauver le monde : on n’en est pas loin, non ?

[Mise à jour 2026] Les rideaux connectés fonctionnent toujours ! À l’époque, ni Home Assistant ni SmartThings ne tournaient à la maison : tout reposait sur le script Python. Depuis, le déclenchement par la TV est d’abord passé par SmartThings, l’application de la TV, avec une routine « Fermer rideaux TV » : si la TV du salon s’allume entre le lever et le coucher du soleil, alors on ferme les rideaux. La nuit, inutile : ils sont déjà fermés depuis le coucher du soleil.

La routine SmartThings « Fermer rideaux TV » : si la TV 65 pouces OLED du salon est allumée entre le lever et le coucher du soleil, alors fermer le store Salon TV

Puis l’ensemble a rejoint Home Assistant. Aujourd’hui, tout tient dans une seule automatisation, « Rideaux salon TV » : quatre déclencheurs à gauche (le lever du soleil, le coucher du soleil, l’allumage de la TV et son extinction), un aiguillage, puis quatre branches qui ouvrent ou ferment les rideaux. Voici sa trace d’exécution du 5 octobre 2026, avec en surbrillance le chemin suivi jusqu’à l’action « Fermer l’ouvrant » :

Trace d'exécution de l'automatisation Home Assistant « Rideaux salon TV » : quatre déclencheurs, un aiguillage, et l'action Fermer l'ouvrant sur le salon TV

Dans l’autre salon, c’est le même principe avec le vidéoprojecteur : on l’allume, les rideaux se ferment… et en prime, le ruban LED caché dans la corniche s’éteint. Mais ça, c’est une autre histoire, et ce sera le sujet du prochain article !