add CLAUDE.md

- CLAUDE.md: document project architecture, resilience constraint,
  workflow and all helper scripts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-15 17:26:46 +01:00
parent 531acd4d05
commit 8ad19d84e2
2 changed files with 140 additions and 7 deletions

134
CLAUDE.md Normal file
View File

@@ -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.

View File

@@ -299,15 +299,14 @@ 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);
}
}
)
// after a Switch Event, disable temp update