Compare commits

..

1 Commits

Author SHA1 Message Date
2014350bc7 cleaned filtration time
* filtration time is now fixed inside each range, instead
  of trying to follow linear curve
* don't bother being super accurate, that's too much
  over-engineering after all ;)
2024-07-21 15:32:22 +02:00
3 changed files with 15 additions and 150 deletions

134
CLAUDE.md
View File

@@ -1,134 +0,0 @@
# 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.

29
pool.js
View File

@@ -72,18 +72,17 @@ function compute_filtration_time(t) {
// [ start1, stop1, start2, stop2, ... ]
function compute_schedule_filt(d) {
let t_start = 17 - d/2;
let t_stop = 17 + d/2;
let start = 17 - d/2;
let stop = 17 + d/2;
while (t_stop >= 24)
t_stop = t_stop - 24;
while (stop >= 24)
stop = stop - 24;
let s;
if (d < 24) {
s = [ t_start, t_stop ];
let s = [ start, stop ];
}
else {
s = [ t_start ];
let s = [ start ];
}
return s;
@@ -147,6 +146,7 @@ function do_call(calls) {
}
// compute & configure pump schedule
// TODO force on when duration==24
// TODO force off when duration==0
// TODO freeze mode if cur < 1
@@ -184,7 +184,7 @@ function update_pump(temp, max, time) {
}
// compute the current switch state according to the schedule
on = false;
let on = false;
if (schedule.length === 1) {
on = true;
}
@@ -300,13 +300,14 @@ MQTT.subscribe(
function (topic, msg) {
status.tick_mqtt++;
print("[POOL] mqtt", topic, msg);
if (msg != "") {
let obj = JSON.parse(msg);
if (obj.temperature === undefined) {
return;
}
update_temp(obj.temperature);
if (msg == "") {
return;
}
let obj = JSON.parse(msg);
if (obj.temperature === undefined) {
return;
}
update_temp(obj.temperature);
}
)

View File

@@ -2,6 +2,4 @@
source $(dirname $0)/shelly.conf
curl -s "$SHELLY_RPC/Script.Stop?id=$SCRIPT_ID" | jq .
$ROOT_DIR/put_script $SHELLY_HOST $SCRIPT_ID $ROOT_DIR/$SCRIPT_FILENAME
curl -s "$SHELLY_RPC/Script.Start?id=$SCRIPT_ID" | jq .