diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2094fe8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,134 @@ +# Shelly Pool — Automatisation de la pompe de piscine + +## Vue d'ensemble + +Script JavaScript (`pool.js`) tournant sur un **Shelly Pro 1PM** (Gen2) pour piloter automatiquement la pompe de filtration d'une piscine en fonction de la température de l'eau. + +## Contrainte principale : résilience + +**Le Shelly fonctionne de manière totalement autonome.** Home Assistant n'intervient pas dans la logique de contrôle — c'est uniquement un frontend de visualisation. + +Si le capteur, Zigbee2MQTT, le broker MQTT ou le réseau tombent, le Shelly **continue à filtrer selon le dernier planning calculé**. Ce planning est matérialisé par des schedules natifs Shelly (`Schedule.Create`) qui persistent dans le firmware et s'exécutent même sans script actif ni réseau. + +La conception doit toujours respecter ce principe : +- Pas de dépendance à Home Assistant pour le fonctionnement +- Toute mise à jour du planning est un effet de bord d'une nouvelle mesure de température — le planning précédent reste actif en cas de silence +- Les données critiques (`temp_today`, `temp_yesterday`) sont persistées dans le KVS du Shelly pour survivre à un redémarrage + +### Architecture + +``` +Sonde Zigbee (immergée) + │ + ▼ +Zigbee2MQTT ──► MQTT broker ──► Shelly Pro 1PM ──► Pompe + │ + Home Assistant (frontend) +``` + +- **Capteur** : sonde Zigbee waterproof immergée, publie sur `zigbee2mqtt/Piscine` +- **Shelly Pro 1PM** : exécute `pool.js` en natif (ShellyScript), contrôle la pompe via le relais +- **Input 0** : interrupteur physique raccordé à l'entrée du Shelly (déclenche un toggle event) +- **MQTT** : broker géré par Zigbee2MQTT / Home Assistant + +## Logique principale (`pool.js`) + +### Calcul de la durée de filtration + +Table de correspondance température → durée (`filt_time`) avec interpolation linéaire : + +| Temp (°C) | Durée (h) | +|-----------|-----------| +| ≤ 5 | 2 | +| 10 | 4 | +| 12 | 6 | +| 16 | 8 | +| 24 | 12 | +| 27 | 20 | +| ≥ 30 | 24 | + +### Calcul du planning + +Une seule plage de filtration **centrée sur 17h00** : +- `start = 17 - duration/2` +- `stop = 17 + duration/2` + +Les horaires sont convertis en crontab Shelly (`Schedule.Create`) après suppression de tous les schedules existants (`Schedule.DeleteAll`). + +### Température de référence + +La durée de filtration est calculée sur `temp_max = max(temp_today, temp_yesterday)` pour éviter de sous-filtrer le lendemain d'une journée chaude. + +Les maxima journaliers sont persistés dans le **KVS** (Key-Value Store) du Shelly : +- `pool.temp_today` +- `pool.temp_yesterday` + +### Comportement au démarrage + +1. Restauration de `temp_yesterday` et `temp_today` depuis le KVS +2. Si `temp_today` est disponible, appel immédiat de `update_temp()` pour recalculer le planning + +### Verrouillage manuel + +Quand l'interrupteur physique (input0) génère un événement `toggle`, les mises à jour automatiques sont **désactivées pendant 10 minutes** pour ne pas contrecarrer une action manuelle. + +## TODOs + +- **Force ON si `duration == 24`** : quand il fait très chaud, mettre la pompe en marche continue sans schedule +- **Mode antigel si `temp < 1°C`** : protection contre le gel (marche continue ou intermittente) + +## Fichiers + +| Fichier | Rôle | +|---------|------| +| `pool.js` | Script principal (déployé sur le Shelly) | +| `shelly.conf` | Config locale (gitignored) — host, script ID, port UDP | +| `shelly.conf.example` | Modèle de configuration | +| `upload_script` | Envoie `pool.js` sur le Shelly via `put_script` | +| `put_script` | Script Python (Allterco) — upload par chunks de 1024 octets | +| `exec_script` | Stop + Start du script sur le Shelly | +| `restart_script` | Redémarre le script s'il n'est pas en cours d'exécution | +| `eval_script` | Évalue du JS arbitraire sur le Shelly (debug) | +| `inspect_script` | Affiche `status` du script + état système Shelly | +| `shelly_log` | Écoute les messages UDP de debug du Shelly (port 6901) | +| `follow_log` | Tail du log filtré sur les lignes `[POOL]` et `script` | +| `fake_mqtt_pool` | Simule une mesure de température via `mosquitto_pub` | +| `kvs_list` | Liste toutes les clés KVS du Shelly | +| `kvs_set` | Positionne une clé KVS | +| `kvs_delete` | Supprime une clé KVS | +| `reboot` | Redémarre le Shelly | + +## Workflow de développement + +```bash +# 1. Modifier pool.js +# 2. Déployer +./upload_script + +# 3. Redémarrer le script +./exec_script + +# 4. Observer les logs (dans un autre terminal) +./shelly_log # écoute UDP +./follow_log # filtre les lignes pertinentes + +# 5. Simuler une température pour tester +./fake_mqtt_pool 24.5 + +# 6. Inspecter l'état interne +./inspect_script +``` + +## Branches Git + +- **`dev`** : branche active, code en production sur le Shelly +- `main` / `prod` : non maintenues à jour + +## Configuration (`shelly.conf`) + +Copier `shelly.conf.example` vers `shelly.conf` et adapter : +- `SHELLY_HOST` : hostname ou IP du Shelly (ex: `shelly_pool` ou `192.168.1.x`) +- `SCRIPT_ID` : ID du script sur le Shelly (défaut: `1`) +- `UDP_LOG_PORT` : port UDP configuré sur le Shelly pour les logs (défaut: `6901`) + +Le fichier `shelly.conf` est gitignored. diff --git a/pool.js b/pool.js index d03ba2e..8e4e444 100644 --- a/pool.js +++ b/pool.js @@ -299,14 +299,13 @@ MQTT.subscribe( function (topic, msg) { status.tick_mqtt++; print("[POOL] mqtt", topic, msg); - if (msg == "") { - return; + if (msg != "") { + let obj = JSON.parse(msg); + if (obj.temperature === undefined) { + return; + } + update_temp(obj.temperature); } - let obj = JSON.parse(msg); - if (obj.temperature === undefined) { - return; - } - update_temp(obj.temperature); } )