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:
134
CLAUDE.md
Normal file
134
CLAUDE.md
Normal 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.
|
||||||
5
pool.js
5
pool.js
@@ -299,15 +299,14 @@ MQTT.subscribe(
|
|||||||
function (topic, msg) {
|
function (topic, msg) {
|
||||||
status.tick_mqtt++;
|
status.tick_mqtt++;
|
||||||
print("[POOL] mqtt", topic, msg);
|
print("[POOL] mqtt", topic, msg);
|
||||||
if (msg == "") {
|
if (msg != "") {
|
||||||
return;
|
|
||||||
}
|
|
||||||
let obj = JSON.parse(msg);
|
let obj = JSON.parse(msg);
|
||||||
if (obj.temperature === undefined) {
|
if (obj.temperature === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
update_temp(obj.temperature);
|
update_temp(obj.temperature);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// after a Switch Event, disable temp update
|
// after a Switch Event, disable temp update
|
||||||
|
|||||||
Reference in New Issue
Block a user