Compare commits
1 Commits
main
...
2014350bc7
| Author | SHA1 | Date | |
|---|---|---|---|
| 2014350bc7 |
134
CLAUDE.md
134
CLAUDE.md
@@ -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.
|
|
||||||
21
pool.js
21
pool.js
@@ -72,18 +72,17 @@ function compute_filtration_time(t) {
|
|||||||
// [ start1, stop1, start2, stop2, ... ]
|
// [ start1, stop1, start2, stop2, ... ]
|
||||||
|
|
||||||
function compute_schedule_filt(d) {
|
function compute_schedule_filt(d) {
|
||||||
let t_start = 17 - d/2;
|
let start = 17 - d/2;
|
||||||
let t_stop = 17 + d/2;
|
let stop = 17 + d/2;
|
||||||
|
|
||||||
while (t_stop >= 24)
|
while (stop >= 24)
|
||||||
t_stop = t_stop - 24;
|
stop = stop - 24;
|
||||||
|
|
||||||
let s;
|
|
||||||
if (d < 24) {
|
if (d < 24) {
|
||||||
s = [ t_start, t_stop ];
|
let s = [ start, stop ];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
s = [ t_start ];
|
let s = [ start ];
|
||||||
}
|
}
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
@@ -147,6 +146,7 @@ function do_call(calls) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compute & configure pump schedule
|
// compute & configure pump schedule
|
||||||
|
// TODO force on when duration==24
|
||||||
// TODO force off when duration==0
|
// TODO force off when duration==0
|
||||||
// TODO freeze mode if cur < 1
|
// 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
|
// compute the current switch state according to the schedule
|
||||||
on = false;
|
let on = false;
|
||||||
if (schedule.length === 1) {
|
if (schedule.length === 1) {
|
||||||
on = true;
|
on = true;
|
||||||
}
|
}
|
||||||
@@ -300,14 +300,15 @@ 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
|
||||||
|
|||||||
@@ -2,6 +2,4 @@
|
|||||||
|
|
||||||
source $(dirname $0)/shelly.conf
|
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
|
$ROOT_DIR/put_script $SHELLY_HOST $SCRIPT_ID $ROOT_DIR/$SCRIPT_FILENAME
|
||||||
curl -s "$SHELLY_RPC/Script.Start?id=$SCRIPT_ID" | jq .
|
|
||||||
|
|||||||
Reference in New Issue
Block a user