- 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>
225 lines
11 KiB
Markdown
225 lines
11 KiB
Markdown
# Ma lumière — télécommande LED pour une chambre d'enfant
|
||
|
||
Une appli Android sur le lecteur MP3 d'un enfant pilote le ruban LED de sa chambre via Home Assistant. L'enfant n'a accès à rien d'autre dans HA.
|
||
|
||
```
|
||
[Lecteur MP3 Android] [Home Assistant sur le LAN]
|
||
appli « Ma lumière »
|
||
(WebView plein écran) --GET--> /local/led/index.html (page servie par HA)
|
||
--POST--> /api/webhook/<id> (local_only, POST)
|
||
│
|
||
▼
|
||
automatisation (liste blanche)
|
||
│
|
||
▼
|
||
light.<ruban> / script.led_* (8 effets)
|
||
```
|
||
|
||
Ce document décrit l'installation et récapitule les interventions réalisées le 10 octobre 2026.
|
||
|
||
---
|
||
|
||
## 1. Installation
|
||
|
||
Ce dépôt est **public** : il ne contient aucune valeur réelle. Les fichiers versionnés sont des **modèles** avec des marqueurs, remplacés au déploiement par les valeurs de `config.local.env`.
|
||
|
||
| Marqueur | Variable | Exemple |
|
||
|---|---|---|
|
||
| `__HA_IP__` / `__HA_PORT__` | adresse de HA sur le LAN | `192.168.1.10` / `8123` |
|
||
| `__WEBHOOK_ID__` | ID du webhook, long et aléatoire | `led-chambre-$(openssl rand -hex 16)` |
|
||
| `__ENTITE__` | entité du ruban | `light.ruban_led` |
|
||
| `__PRENOM__` | prénom affiché dans la page | `Camille` |
|
||
| — | `SSH_HOST` : alias SSH vers HA | `homeassistant` |
|
||
| — | `APP_ID` : identifiant Android de l'appli | `fr.exemple.lumiere` |
|
||
|
||
**Étapes :**
|
||
1. Copier `config.example.env` en `config.local.env` et le remplir.
|
||
2. Une seule fois, dans `configuration.yaml` de HA, ajouter `packages: !include_dir_named packages` sous `homeassistant:`, puis redémarrer HA.
|
||
3. Lancer `./deploy.sh`. Il génère `dist/`, copie le package et la page sur HA via SSH, lance `ha core check`, puis recharge les scripts et les automatisations. Le rechargement utilise un jeton HA placé dans `.env` (`HA_TOKEN=…`), optionnel.
|
||
4. Compiler l'appli avec `android/build.sh` (il faut un SDK Android avec build-tools 30.0.2 et la plateforme 25), puis l'installer avec `adb install -r android/build/ma-lumiere.apk`.
|
||
|
||
**Fichiers jamais versionnés** (voir `.gitignore`) : `config.local.env`, `.env`, `dist/`, `android/build/`.
|
||
|
||
---
|
||
|
||
## 2. Contexte technique
|
||
|
||
| Élément | Valeur |
|
||
|---|---|
|
||
| Home Assistant | HAOS 2026.6 sur Odroid N2, en HTTP sur le LAN, accès distant uniquement via VPN WireGuard |
|
||
| Ruban | RGB non adressable, driver Zigbee Iderwel via **Zigbee2MQTT**, mode couleur `xy` |
|
||
| Lecteur MP3 | Fanvace M36, Android 13, écran 640×1136 à 300 dpi (≈ 341 × 530 px utiles), WebView 108, **navigateur bloqué par l'app lock** |
|
||
|
||
**Constats faits pendant les tests sur le ruban :**
|
||
- Couleurs fixes, luminosité et `transition` (fondus) fonctionnent bien.
|
||
- Les **effets natifs du driver** (`colorloop`, `breathe`…) **ne sont pas fiables** : ils ont été abandonnés au profit de scripts HA.
|
||
- Le driver ne remonte pas l'effet en cours : l'attribut `effect` vaut toujours `None` dans HA.
|
||
|
||
---
|
||
|
||
## 3. Interventions sur Home Assistant
|
||
|
||
### 3.1 Fichiers modifiés ou créés
|
||
|
||
| Fichier sur HA | Action |
|
||
|---|---|
|
||
| `/config/configuration.yaml` | **1 ligne ajoutée** sous `homeassistant:` : `packages: !include_dir_named packages` |
|
||
| `/config/packages/led_chambre.yaml` | **créé** : 8 scripts d'effets et l'automatisation webhook |
|
||
| `/config/www/led/` | **créé** : `index.html`, `manifest.json`, `icon.svg`, `icon-180/192/512.png` |
|
||
|
||
Aucun autre fichier existant n'a été modifié. En particulier, `scripts.yaml` et `automations.yaml` n'ont pas été touchés.
|
||
|
||
### 3.2 Sauvegardes laissées sur HA
|
||
|
||
| Sauvegarde | Contenu |
|
||
|---|---|
|
||
| `/config/configuration.yaml.bak-led-20261010` | `configuration.yaml` d'origine, avant l'ajout de `packages:` |
|
||
| `/config/led_chambre.yaml.bak-20261010-boum` | package avant l'ajout des effets disco/boum/tourbillon |
|
||
| `/config/led_chambre.yaml.bak-20261010-disco` | package avant la correction du bug disco |
|
||
|
||
Ces sauvegardes sont volontairement placées **hors** de `packages/`, pour que HA ne les charge pas.
|
||
|
||
### 3.3 Opérations effectuées
|
||
|
||
1. Vérification de la configuration avec `ha core check` avant chaque mise en service.
|
||
2. **Un redémarrage de HA**, pour activer `packages:`.
|
||
3. Ensuite, uniquement des rechargements à chaud (`script.reload`, `automation.reload`), sans redémarrage.
|
||
|
||
### 3.4 Contenu du package `led_chambre.yaml`
|
||
|
||
**Scripts d'effets**, tous en `mode: restart` :
|
||
|
||
| Script | Comportement | Rythme Zigbee |
|
||
|---|---|---|
|
||
| `led_arc_en_ciel` | tour des teintes par pas de 30° | 1 commande / 3 s |
|
||
| `led_respiration` | garde la couleur, luminosité 10 % ↔ 100 % | 1 / 3 s |
|
||
| `led_coucher_de_soleil` | orange → rose → rouge → violet | 1 / 5 s |
|
||
| `led_ocean` | bleu → turquoise → cyan → bleu profond | 1 / 4 s |
|
||
| `led_veilleuse` | orange à 5 %, puis extinction en fondu au bout de 30 min | — |
|
||
| `led_disco` | saut de couleur vive chaque seconde | 1 / s |
|
||
| `led_boum` | flash d'une couleur à 100 %, puis fondu à 25 % | 1 / s |
|
||
| `led_tourbillon` | arc-en-ciel rapide, pas de 45° | 1 / s |
|
||
|
||
Il n'y a pas de stroboscope : le réseau Zigbee ne le permet pas, et il présente un risque photosensible.
|
||
|
||
**Automatisation `led_chambre_webhook`** :
|
||
- Webhook avec `local_only: true`, `allowed_methods: [POST]` et un ID aléatoire long.
|
||
- `mode: restart` : si plusieurs commandes arrivent très vite, seule la dernière compte.
|
||
- Contrat du corps JSON :
|
||
|
||
| `action` | champ | effet |
|
||
|---|---|---|
|
||
| `off` | — | arrête tous les effets et éteint le ruban (fondu 1 s) |
|
||
| `couleur` | `teinte` 0–359 | arrête les effets et applique une couleur fixe |
|
||
| `luminosite` | `valeur` 5–100 | change la luminosité sans couper l'effet en cours |
|
||
| `effet` | `nom` | arrête l'effet en cours et lance `script.led_<nom>` s'il est dans la liste blanche |
|
||
|
||
- Liste blanche stricte : le JSON ne sert jamais à construire un entity_id. Les valeurs sont bornées et ont des valeurs par défaut, et toute autre requête est ignorée.
|
||
- La luminosité **revient automatiquement à 100 %** quand le ruban était éteint, ou quand on sort de la veilleuse, de la respiration ou de Boum.
|
||
|
||
---
|
||
|
||
## 4. Interventions sur le lecteur MP3
|
||
|
||
- Installation via ADB de l'appli **« Ma lumière »**, aujourd'hui en **version 1.1**.
|
||
- Aucun autre réglage n'a été modifié sur l'appareil.
|
||
- Le débogage USB a été activé pour l'installation.
|
||
|
||
**Ce que fait l'appli** : c'est un *wrapper* WebView de 37 Ko qui affiche en plein écran la page servie par HA.
|
||
- Elle n'autorise la navigation que vers `http://<HA>/local/led/`, et bloque toute requête vers une autre adresse.
|
||
- Le HTTP non chiffré n'est autorisé que vers l'IP de HA.
|
||
- Si HA ne répond pas, elle affiche « La lumière ne répond pas » avec un bouton Réessayer.
|
||
- Le cache est désactivé, et la page est rechargée à chaque retour dans l'appli (ajouté en v1.1). Une modification de la page sur HA apparaît donc sans réinstaller l'appli.
|
||
|
||
---
|
||
|
||
## 5. Fichiers du dépôt
|
||
|
||
```
|
||
├── CLAUDE.md consignes du projet
|
||
├── README.md ce document
|
||
├── config.example.env modèle de configuration (à copier en config.local.env)
|
||
├── deploy.sh génère dist/ depuis les modèles et déploie sur HA
|
||
├── packages/led_chambre.yaml modèle → /config/packages/led_chambre.yaml
|
||
├── www/led/ modèles → /config/www/led/
|
||
│ ├── index.html la page (HTML/CSS/JS ES5 en un seul fichier)
|
||
│ ├── manifest.json
|
||
│ └── icon.svg, icon-*.png
|
||
└── android/ sources de l'APK
|
||
├── build.sh compilation sans Gradle → build/ma-lumiere.apk
|
||
├── AndroidManifest.xml
|
||
├── res/ icônes, thème, network_security_config (modèle)
|
||
└── src/app/lumiere/MainActivity.java
|
||
```
|
||
|
||
**La page** est pensée pour tenir sur un seul écran du lecteur :
|
||
- en haut, Veilleuse et Éteindre ;
|
||
- 9 pastilles de couleur ;
|
||
- 4 niveaux de luminosité ;
|
||
- 4 animations calmes ;
|
||
- 3 animations « boum » au style néon.
|
||
|
||
Elle n'affiche aucun état du ruban, puisque le webhook ne renvoie rien. Un appui réussi fait apparaître un halo blanc ; une erreur fait trembler le bouton et affiche un message rouge.
|
||
|
||
---
|
||
|
||
## 6. Tests réalisés
|
||
|
||
- **Ruban, en direct** : couleurs, fondus, luminosité, puis effets natifs, qui se sont révélés peu fiables.
|
||
- **Webhook avec curl** :
|
||
- toutes les actions et les 8 effets fonctionnent, et un seul script tourne à la fois ;
|
||
- les entrées non valides sont ignorées sans erreur : nom hors liste blanche (`../light.autre`), action inconnue, corps non JSON ;
|
||
- les valeurs sont bornées (teinte 999 → 359, luminosité 1 → 5 %) ;
|
||
- une requête GET est refusée (405) ;
|
||
- les logs HA sont propres.
|
||
- **De bout en bout sur le lecteur** : des taps simulés via ADB (couleur, arc-en-ciel, veilleuse, éteindre) ont été vérifiés dans l'état HA.
|
||
- **Modèles** : les fichiers générés par `deploy.sh --render-only` sont identiques aux fichiers en service, hors commentaires. L'APK généré contient la bonne adresse et le bon identifiant.
|
||
|
||
**Bug corrigé :** `led_disco` plantait au bout d'environ 10 s, parce qu'une teinte `0` tirée d'une palette en `variables` arrivait vide dans `hs_color`. Le rouge vaut maintenant `360` dans les palettes de Disco et de Boum.
|
||
|
||
---
|
||
|
||
## 7. Maintenance
|
||
|
||
**Tester le webhook** (avec les valeurs de `config.local.env`) :
|
||
```bash
|
||
curl -X POST -H "Content-Type: application/json" \
|
||
-d '{"action":"effet","nom":"arc_en_ciel"}' \
|
||
http://<HA_IP>:<HA_PORT>/api/webhook/<WEBHOOK_ID>
|
||
```
|
||
|
||
**Modifier la page ou un effet :**
|
||
1. Modifier le modèle (`www/led/index.html` ou `packages/led_chambre.yaml`).
|
||
2. Lancer `./deploy.sh`.
|
||
3. Rouvrir l'appli sur le lecteur. Pas de réinstallation.
|
||
|
||
**Ajouter un effet :**
|
||
1. Ajouter le script `led_<nom>` dans le package.
|
||
2. Ajouter `<nom>` dans `effets` **et** dans `tous_les_effets` de l'automatisation.
|
||
3. Ajouter une tuile dans la page.
|
||
4. Lancer `./deploy.sh`.
|
||
|
||
**Recompiler l'appli**, par exemple si l'IP de HA change :
|
||
1. Mettre à jour `config.local.env`.
|
||
2. Lancer `android/build.sh`, puis `adb install -r android/build/ma-lumiere.apk`.
|
||
|
||
Il faut garder la même clé de signature, `~/.android/debug.keystore`. Sinon Android refusera la mise à jour et il faudra désinstaller l'appli d'abord.
|
||
|
||
**Tout retirer :**
|
||
1. Supprimer `/config/packages/led_chambre.yaml` et `/config/www/led/`.
|
||
2. Retirer la ligne `packages:` de `configuration.yaml`, ou restaurer la sauvegarde.
|
||
3. Lancer `ha core check`, puis redémarrer HA.
|
||
4. Désinstaller l'appli : `adb uninstall <APP_ID>`.
|
||
|
||
---
|
||
|
||
## 8. Reste à faire
|
||
|
||
- [ ] Autoriser « Ma lumière » dans l'**app lock** du lecteur.
|
||
- [ ] **Réserver l'IP** de HA sur la box, puisqu'elle est compilée dans l'appli.
|
||
- [ ] **Supprimer le jeton** HA de développement (profil HA → Sécurité) et le fichier `.env` quand ils ne sont plus utiles. L'appli n'en a pas besoin.
|
||
- [ ] Éventuellement, désactiver le **débogage USB** sur le lecteur.
|
||
- [ ] Éventuellement, supprimer les sauvegardes `*.bak*` dans `/config` une fois que tout est validé.
|
||
|
||
ℹ️ Le dossier `/local/` de HA est accessible sans authentification depuis le LAN. C'est voulu : la page ne contient aucun secret, et le webhook n'accepte que des commandes en liste blanche, uniquement depuis le réseau local.
|