Télécommande LED pour chambre d'enfant via Home Assistant
- Package HA : 8 scripts d'effets (calmes et « boum ») et une automatisation webhook local_only avec liste blanche stricte - Page web autonome (HTML/CSS/JS ES5) tenant sur un écran de lecteur MP3 - Mini APK Android (WebView verrouillée) compilé sans Gradle - Modèles + config.local.env non versionné : aucune valeur réelle (IP, ID du webhook, entité, prénom) dans le dépôt ; deploy.sh génère et déploie, android/build.sh injecte la configuration Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
# =============================================================================
|
||||
# Télécommande LED chambre
|
||||
# Ruban : __ENTITE__ (Zigbee2MQTT, driver Iderwel RGB non adressable)
|
||||
#
|
||||
# Généré par deploy.sh : dans le dépôt, ce fichier est un modèle dont les
|
||||
# marqueurs (entre doubles soulignés) sont remplacés depuis config.local.env.
|
||||
#
|
||||
# Point d'entrée unique : webhook local (POST JSON), voir CLAUDE.md
|
||||
# {"action": "off"}
|
||||
# {"action": "couleur", "teinte": 0-359}
|
||||
# {"action": "luminosite", "valeur": 5-100}
|
||||
# {"action": "effet", "nom": "arc_en_ciel|respiration|coucher_de_soleil|ocean|veilleuse
|
||||
# |disco|boum|tourbillon"}
|
||||
#
|
||||
# Règle Zigbee : au moins 2 s entre deux commandes dans les effets calmes,
|
||||
# avec une transition égale au délai. Les effets « boum » (disco, boum,
|
||||
# tourbillon) vont au minimum autorisé : 1 commande par seconde. Pas de
|
||||
# stroboscope (réseau Zigbee, et risque photosensible).
|
||||
# =============================================================================
|
||||
|
||||
script:
|
||||
# --- Arc-en-ciel : tour complet des teintes par pas de 30°, 3 s par pas ---
|
||||
# Ne touche pas à la luminosité : le curseur reste actif pendant l'effet.
|
||||
led_arc_en_ciel:
|
||||
alias: "LED chambre – arc-en-ciel"
|
||||
icon: mdi:looks
|
||||
mode: restart
|
||||
sequence:
|
||||
- repeat:
|
||||
while: "{{ true }}"
|
||||
sequence:
|
||||
- repeat:
|
||||
for_each: [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330]
|
||||
sequence:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: __ENTITE__
|
||||
data:
|
||||
hs_color: ["{{ repeat.item }}", 100]
|
||||
transition: 3
|
||||
- delay: 3
|
||||
|
||||
# --- Respiration : garde la couleur actuelle, luminosité 100 % <-> 10 % ---
|
||||
led_respiration:
|
||||
alias: "LED chambre – respiration"
|
||||
icon: mdi:weather-windy
|
||||
mode: restart
|
||||
sequence:
|
||||
- repeat:
|
||||
while: "{{ true }}"
|
||||
sequence:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: __ENTITE__
|
||||
data:
|
||||
brightness_pct: 10
|
||||
transition: 3
|
||||
- delay: 3
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: __ENTITE__
|
||||
data:
|
||||
brightness_pct: 100
|
||||
transition: 3
|
||||
- delay: 3
|
||||
|
||||
# --- Coucher de soleil : orange -> rose -> rouge -> violet, fondus de 5 s ---
|
||||
led_coucher_de_soleil:
|
||||
alias: "LED chambre – coucher de soleil"
|
||||
icon: mdi:weather-sunset
|
||||
mode: restart
|
||||
sequence:
|
||||
- repeat:
|
||||
while: "{{ true }}"
|
||||
sequence:
|
||||
- repeat:
|
||||
for_each:
|
||||
- { h: 25, s: 100 } # orange
|
||||
- { h: 340, s: 70 } # rose
|
||||
- { h: 0, s: 100 } # rouge
|
||||
- { h: 285, s: 90 } # violet
|
||||
sequence:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: __ENTITE__
|
||||
data:
|
||||
hs_color: ["{{ repeat.item.h }}", "{{ repeat.item.s }}"]
|
||||
transition: 5
|
||||
- delay: 5
|
||||
|
||||
# --- Océan : bleu -> turquoise -> cyan -> bleu profond, fondus de 4 s ---
|
||||
led_ocean:
|
||||
alias: "LED chambre – océan"
|
||||
icon: mdi:waves
|
||||
mode: restart
|
||||
sequence:
|
||||
- repeat:
|
||||
while: "{{ true }}"
|
||||
sequence:
|
||||
- repeat:
|
||||
for_each:
|
||||
- { h: 215, s: 100 } # bleu
|
||||
- { h: 172, s: 100 } # turquoise
|
||||
- { h: 190, s: 80 } # cyan clair
|
||||
- { h: 235, s: 100 } # bleu profond
|
||||
sequence:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: __ENTITE__
|
||||
data:
|
||||
hs_color: ["{{ repeat.item.h }}", "{{ repeat.item.s }}"]
|
||||
transition: 4
|
||||
- delay: 4
|
||||
|
||||
# --- Veilleuse : orange très faible, extinction en fondu après 30 min ---
|
||||
led_veilleuse:
|
||||
alias: "LED chambre – veilleuse"
|
||||
icon: mdi:weather-night
|
||||
mode: restart
|
||||
sequence:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: __ENTITE__
|
||||
data:
|
||||
hs_color: [28, 100]
|
||||
brightness_pct: 5
|
||||
transition: 3
|
||||
- delay: "00:29:00"
|
||||
# Dernière minute : fondu jusqu'à l'extinction (total 30 min)
|
||||
- action: light.turn_off
|
||||
target:
|
||||
entity_id: __ENTITE__
|
||||
data:
|
||||
transition: 60
|
||||
|
||||
# --- Disco : saut de couleur vive chaque seconde ---
|
||||
# Index (n x 7) mod 10 : parcourt les 10 couleurs dans un ordre qui paraît
|
||||
# aléatoire, sans jamais répéter deux fois la même d'affilée.
|
||||
# Ne touche pas à la luminosité.
|
||||
led_disco:
|
||||
alias: "LED chambre – disco"
|
||||
icon: mdi:mirror-rectangle
|
||||
mode: restart
|
||||
# Rouge = 360 et non 0 : une teinte 0 tirée de la palette arrivait vide
|
||||
# dans hs_color et faisait planter le script.
|
||||
variables:
|
||||
palette: [360, 30, 55, 120, 170, 200, 235, 275, 300, 320]
|
||||
sequence:
|
||||
- repeat:
|
||||
while: "{{ true }}"
|
||||
sequence:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: __ENTITE__
|
||||
data:
|
||||
hs_color: ["{{ palette[(repeat.index * 7) % 10] }}", 100]
|
||||
transition: 0
|
||||
- delay: 1
|
||||
|
||||
# --- Boum : flash d'une nouvelle couleur à 100 %, puis fondu à 25 % (2 s) ---
|
||||
led_boum:
|
||||
alias: "LED chambre – boum"
|
||||
icon: mdi:speaker
|
||||
mode: restart
|
||||
variables:
|
||||
palette: [320, 200, 55, 275, 360, 170, 30, 235] # rouge = 360, voir disco
|
||||
sequence:
|
||||
- repeat:
|
||||
while: "{{ true }}"
|
||||
sequence:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: __ENTITE__
|
||||
data:
|
||||
hs_color: ["{{ palette[repeat.index % 8] }}", 100]
|
||||
brightness_pct: 100
|
||||
transition: 0
|
||||
- delay: 1
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: __ENTITE__
|
||||
data:
|
||||
brightness_pct: 25
|
||||
transition: 1
|
||||
- delay: 1
|
||||
|
||||
# --- Tourbillon : arc-en-ciel rapide, pas de 45°, fondus de 1 s ---
|
||||
# Ne touche pas à la luminosité.
|
||||
led_tourbillon:
|
||||
alias: "LED chambre – tourbillon"
|
||||
icon: mdi:tailwind
|
||||
mode: restart
|
||||
sequence:
|
||||
- repeat:
|
||||
while: "{{ true }}"
|
||||
sequence:
|
||||
- repeat:
|
||||
for_each: [0, 45, 90, 135, 180, 225, 270, 315]
|
||||
sequence:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: __ENTITE__
|
||||
data:
|
||||
hs_color: ["{{ repeat.item }}", 100]
|
||||
transition: 1
|
||||
- delay: 1
|
||||
|
||||
automation:
|
||||
- id: led_chambre_webhook
|
||||
alias: "LED chambre – webhook télécommande"
|
||||
description: >-
|
||||
Point d'entrée unique de la page /local/led/. N'agit que sur
|
||||
__ENTITE__ et sur la liste blanche des scripts led_*.
|
||||
# La dernière commande gagne (taps rapides, curseur de luminosité)
|
||||
mode: restart
|
||||
max_exceeded: silent
|
||||
triggers:
|
||||
- trigger: webhook
|
||||
webhook_id: __WEBHOOK_ID__
|
||||
allowed_methods: [POST]
|
||||
local_only: true
|
||||
variables:
|
||||
ruban: __ENTITE__
|
||||
# Liste blanche : nom reçu -> script. Jamais d'entity_id construit depuis le JSON.
|
||||
effets:
|
||||
arc_en_ciel: script.led_arc_en_ciel
|
||||
respiration: script.led_respiration
|
||||
coucher_de_soleil: script.led_coucher_de_soleil
|
||||
ocean: script.led_ocean
|
||||
veilleuse: script.led_veilleuse
|
||||
disco: script.led_disco
|
||||
boum: script.led_boum
|
||||
tourbillon: script.led_tourbillon
|
||||
tous_les_effets:
|
||||
- script.led_arc_en_ciel
|
||||
- script.led_respiration
|
||||
- script.led_coucher_de_soleil
|
||||
- script.led_ocean
|
||||
- script.led_veilleuse
|
||||
- script.led_disco
|
||||
- script.led_boum
|
||||
- script.led_tourbillon
|
||||
corps: "{{ trigger.json if trigger.json is defined and trigger.json is mapping else {} }}"
|
||||
act: "{{ corps.get('action', '') | string }}"
|
||||
nom: "{{ corps.get('nom', '') | string }}"
|
||||
teinte: "{{ ([0, corps.get('teinte', 0) | int(0), 359] | sort)[1] }}"
|
||||
valeur: "{{ ([5, corps.get('valeur', 100) | int(100), 100] | sort)[1] }}"
|
||||
# Remettre 100 % si le ruban est éteint ou sort d'un effet qui
|
||||
# baisse la luminosité (veilleuse 5 %, respiration 10 %, boum 25 %).
|
||||
remettre_lumiere: >-
|
||||
{{ is_state(ruban, 'off')
|
||||
or is_state('script.led_veilleuse', 'on')
|
||||
or is_state('script.led_respiration', 'on')
|
||||
or is_state('script.led_boum', 'on') }}
|
||||
actions:
|
||||
- choose:
|
||||
# --- off : arrête les effets et éteint ---
|
||||
- conditions: "{{ act == 'off' }}"
|
||||
sequence:
|
||||
- action: script.turn_off
|
||||
target:
|
||||
entity_id: "{{ tous_les_effets }}"
|
||||
- action: light.turn_off
|
||||
target:
|
||||
entity_id: "{{ ruban }}"
|
||||
data:
|
||||
transition: 1
|
||||
|
||||
# --- couleur fixe ---
|
||||
- conditions: "{{ act == 'couleur' }}"
|
||||
sequence:
|
||||
- action: script.turn_off
|
||||
target:
|
||||
entity_id: "{{ tous_les_effets }}"
|
||||
- if: "{{ remettre_lumiere }}"
|
||||
then:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: "{{ ruban }}"
|
||||
data:
|
||||
hs_color: ["{{ teinte }}", 100]
|
||||
brightness_pct: 100
|
||||
transition: 1
|
||||
else:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: "{{ ruban }}"
|
||||
data:
|
||||
hs_color: ["{{ teinte }}", 100]
|
||||
transition: 1
|
||||
|
||||
# --- luminosité (ne coupe pas l'effet en cours) ---
|
||||
- conditions: "{{ act == 'luminosite' }}"
|
||||
sequence:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: "{{ ruban }}"
|
||||
data:
|
||||
brightness_pct: "{{ valeur }}"
|
||||
transition: 1
|
||||
|
||||
# --- effet de la liste blanche ---
|
||||
- conditions: "{{ act == 'effet' and nom in effets }}"
|
||||
sequence:
|
||||
- action: script.turn_off
|
||||
target:
|
||||
entity_id: "{{ tous_les_effets }}"
|
||||
# Allume à 100 % avant un effet qui ne règle pas la luminosité
|
||||
- if: "{{ remettre_lumiere and nom not in ['veilleuse', 'respiration', 'boum'] }}"
|
||||
then:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: "{{ ruban }}"
|
||||
data:
|
||||
brightness_pct: 100
|
||||
transition: 1
|
||||
- action: script.turn_on
|
||||
target:
|
||||
entity_id: "{{ effets[nom] }}"
|
||||
# Toute autre requête est ignorée silencieusement.
|
||||
Reference in New Issue
Block a user