- 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>
321 lines
11 KiB
YAML
321 lines
11 KiB
YAML
# =============================================================================
|
||
# 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.
|