feat(filtration): autonomous ESPHome filtration scheduling#6
Merged
feat(filtration): autonomous ESPHome filtration scheduling#6
Conversation
- packages/base.yaml: migrate time source from HA to SNTP, add web_server port 80 - packages/filtration.yaml: new package with full autonomous scheduling logic - 4 modes: Off / Hiver / Courbe / Auto (hysteresis ±1°C around 16°C) - 2 daily cycles (morning + evening) split by configurable pause centered on pivot - Configurable: coefficient, pivot, pause duration, morning ratio, winter params - NTP guard: pump off until synced, single HA alert notification - End-of-cycle recalculation (phase 1→0 and 2→0) for temperature-aware scheduling - Antifreeze override preserved with priority over all scheduling - All 8 entry files: add filtration package (local !include for local testing) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…red) SNTP requires internet access. platform: homeassistant syncs from the local HA instance over WiFi — no internet dependency. Once synced, the ESP maintains time internally via its hardware clock. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
P1 — bugs bloquants : - Clamp pause_start/pause_end dans [0,1439] avant de dériver les fenêtres (h_fin1 pouvait être négatif → cycle matin silencieusement absent) - Inverser l'ordre dans _ntp_alert_once : HA action en premier, flag ensuite - Remplacer !include local par github://...@main dans les 8 presets P2 — corrections : - Déplacer le check antigel avant le guard NTP dans l'interval (antigel actif + NTP invalide ne bloque plus la pompe) - Ajouter g_cycle_phase = 0 dans le bloc Mode Off de _calcul_filtration - Documenter le comportement de mode: queued, max_runs: 1 - Utiliser ${friendly_name} dans le titre de la notification NTP P3 — qualité et observabilité : - Nommer la constante 1439 → MAX_MIN avec commentaire - Documenter le fallback NaN→10°C et les seuils 15/16/17°C du mode Auto - Documenter le comportement de calc_courbe aux hautes températures - Ajouter bouton "Recalcul Filtration" (force recalcul depuis HA) - Ajouter sensors diagnostiques : Horaires Filtration, Phase Filtration, Durée Filtration Journalière, Mode Auto Actif - Ajouter CI GitHub Actions : esphome config sur les 8 presets, avec substitution des packages distants par les fichiers locaux du commit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
La filtration est désormais gérée directement par l'ESP (packages/filtration.yaml). Le package HA et le blueprint sont obsolètes et leur présence prête à confusion. Le README documente la migration depuis le blueprint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dashboard page unique avec coloration contextuelle pH/Redox, graphiques 7j apexcharts-card avec zones colorées, paramètres de filtration et électrolyseur. Sections conditionnelles adaptées à tous les presets (salt_minimal → salt_booster_full). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New global g_forced_remaining_s (int, restore_value: false) decremented 30 s per interval tick; guarantees no persistence across reboots (R8) - New text sensor forced_countdown shows "Xh Ymin" / "Ymin" / "Inactif" (R9) - Four buttons: Forcer Filtration 2h / 6h / 24h + Arrêter Mode Forcé Each preset immediately turns pump ON and arms the countdown (R1, R2, R3, R6) - 30 s interval: forced mode block inserted after antifreeze, before NTP guard so it functions without time-sync (R4, R5, R10) - _calcul_filtration calls at cycle boundaries guarded by g_forced_remaining_s == 0 to suppress spurious recalculation during active forced mode (R4) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a "Mode forcé" section under Filtration autonome explaining the three preset buttons (2h / 6h / 24h), the Stop button, countdown sensor, priority over all modes including Off, and no-persistence-on-reboot behavior. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Single Consigne Redox (default 730 mV, range 680–760 mV) replaces the two separate min/max entities. The 30 mV hysteresis is now hardcoded in the firmware (ON at setpoint-30, OFF at setpoint). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pin ESPHome to 2026.3.3 in CI (was unpinned, risking silent breakage) - Normalize restore_value/optimistic to True/False (match base.yaml convention) - Arrêter Mode Forcé: turn pump off immediately on press (was deferred 30s) - filtration_phase: update_interval never + explicit updates on phase transitions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rn on empty windows Forced mode counter now decrements before the antifreeze check so the 24h/6h/2h countdown elapses correctly even when antifreeze is simultaneously active. Without this fix, an overnight temperature drop would suspend the countdown indefinitely. Also add ESP_LOGE validation after window calculation: logs an error if pivot/pause configuration causes a zero-duration morning or evening window (e.g. pivot=00:30 with pause=6h), giving the operator visibility instead of silent missed filtration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ve web_server & Improv Close unauthenticated LAN attack surface in packages/base.yaml: any device on the LAN could previously trigger the pump for 24 h, force the electrolyser into continuous chlorine generation, modify ORP setpoints, or flash arbitrary firmware (see todo 001). - packages/base.yaml: add wifi.ssid/password + wifi.ap.password + api.encryption.key + ota.password via !secret; remove improv_serial, esp32_improv, web_server; keep captive_portal for AP fallback recovery. - secrets.example.yaml: new template with validation-passing placeholders (32-byte base64 API key, 8+ char AP password). - .gitignore: exclude secrets.yaml and todos/. - .github/workflows/validate.yml: seed secrets.yaml from example before esphome config (esphome has no skip-secrets flag). - README.md: remove "Interface web locale" section, update packages/base.yaml description, add Secrets section covering first-flash USB requirement, key generation, loss recovery, and captive portal fallback. No deployed fleet → no migration path; breaking OTA/API compat is acceptable. Validated locally with `esphome config` (2026.4.1) on all 8 CI matrix presets. 🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via [Claude Code](https://claude.com/claude-code) + Compound Engineering v2.46.0 Co-Authored-By: Claude Opus 4.7 (1M context, extended thinking) <noreply@anthropic.com>
Remove duplicate pump-write paths in packages/base.yaml so filtration.yaml becomes the sole actuator for the pump switch (see todo 002). - Delete unconditional `switch.turn_off: pump` from the antifreeze on_release handler. Caused a ~30s pump drop when antifreeze released mid-cycle during a 24h forced chemical treatment, before the filtration interval corrected it. - Delete the 30s antifreeze interval that redundantly turned the pump ON in parallel with filtration.yaml:458-461. The two unsynchronized 30s loops could assert conflicting states within the same second, causing relay chatter. - Preserve the HA notification "Fin du mode hors-gel" on on_release. - Preserve the `on_turn_on` handler on the pump switch (updates `pump_last_turn_on` for the pump_uptime sensor). All 8 CI matrix presets include filtration.yaml, so the belt-and-suspenders approach (keeping base.yaml as fallback) wasn't worth the chatter cost. Validated `esphome config` on the 8 presets locally. 🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via [Claude Code](https://claude.com/claude-code) + Compound Engineering v2.46.0 Co-Authored-By: Claude Opus 4.7 (1M context, extended thinking) <noreply@anthropic.com>
Three parallel regulation paths in packages/redox_electrolyser.yaml contradicted each other: the on_value_range below-setpoint-30 trigger fired the electrolyser ON immediately on any dip, bypassing the 30-min redox_stable_minutes gate that the interval regulator enforced. The stability gate was effectively dead code (see todo 003). Policy chosen: asymmetric — fast OFF (overdose protection), slow ON (sensor noise filter). - Delete the on_value_range `below: setpoint-30 → turn_on` path. This was the conflicting trigger; the interval regulator becomes the sole ON authority with its 30-min stability gate actually enforced. - Keep on_value_range `above: setpoint → turn_off` as the fast overdose protection (responds in seconds to threshold crossings). - Keep the interval's OFF block as a backstop for cases without a threshold crossing (e.g. Off → Auto while ORP is already above setpoint). - Document the asymmetric policy in a 9-line comment at the top of the file so future readers understand that the stability gate is load-bearing. Rationale: over-chlorination is the worse failure mode in a pool; delaying recovery by 30 min after a real ORP drop is tolerable given chlorine kinetics are slow anyway. Validated `esphome config` on the 8 preset configs locally. 🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via [Claude Code](https://claude.com/claude-code) + Compound Engineering v2.46.0 Co-Authored-By: Claude Opus 4.7 (1M context, extended thinking) <noreply@anthropic.com>
…filtration Optimistic + restore_value number templates can return NaN if NVS is corrupted or the partition layout shifts before initial_value is reapplied. NaN propagating through the lambda could produce an implementation-defined (int)NaN cast on Xtensa — typically INT_MIN — overflowing the window arithmetic downstream. See todo 004. - Add a safe_f helper and sanitize all 5 template-number reads: coeff (100), ratio (33), pause (8h), hiver_min (3h), diviseur (3). Fallbacks match each template's initial_value. - Add an extra `divisr < 0.5` guard against divide-by-zero in calc_hiver. - Add a lower clamp `dur_total_min >= 0` alongside the existing upper clamp, as a belt-and-suspenders safety net against any NaN that escapes the upstream guards. - Add a defensive clamp on pivot_min (datetime .hour/.minute are uint8_t and cannot be NaN, but corrupt NVS could return out-of-range values). Fallback = 13:30 (initial_value). Validated `esphome config` on the 8 preset configs locally. 🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via [Claude Code](https://claude.com/claude-code) + Compound Engineering v2.46.0 Co-Authored-By: Claude Opus 4.7 (1M context, extended thinking) <noreply@anthropic.com>
If the DS18B20 sensor is still unsynced on the very first boot, pool_temp reads NaN and falls back to 10 °C. In Auto mode, the first-boot initializer would then set g_auto_submode = true (Hiver) and g_auto_initialized = true — locking the seasonal submode until water temp actually rises above 17 °C. For a summer deployment, this can persist for the entire season (see todo 005). - Track a `temp_valid` flag alongside the existing pool_temp NaN fallback. - In the Auto first-boot branch, only persist the seasonal submode when `temp_valid` is true. Otherwise pick a transient default (Courbe — safer for the common summer-deployment case) and leave g_auto_initialized false so the next recalc with a valid temperature reading does the real init. Validated `esphome config` on the 8 preset configs locally. 🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via [Claude Code](https://claude.com/claude-code) + Compound Engineering v2.46.0 Co-Authored-By: Claude Opus 4.7 (1M context, extended thinking) <noreply@anthropic.com>
…on, scoped PR trigger Apply standard GitHub Actions hardening to .github/workflows/validate.yml (see todo 006): - Add `permissions: contents: read` at workflow level. Validation only needs to read the repo; deny the default write permissions. - Pin actions/checkout to commit SHA (v4.2.2) with a trailing version comment, so Dependabot can still update it but we're not floating on the major tag. - Scope the pull_request trigger to PRs targeting main, matching the push trigger. No functional change to the validation itself. 🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via [Claude Code](https://claude.com/claude-code) + Compound Engineering v2.46.0 Co-Authored-By: Claude Opus 4.7 (1M context, extended thinking) <noreply@anthropic.com>
…e force buttons The four force-mode buttons (2h / 6h / 24h / stop) each reimplemented the same 3-line lambda: set g_forced_remaining_s, toggle the pump, update the countdown sensor. Any future change to the force-mode semantics (log, HA notification, additional state reset) would need to be replicated four times (see todo 007). - Add a parameterized script `_start_forced_mode(hours: int)` that centralizes the three side effects. hours=0 cancels (turn pump off); hours>0 activates (turn pump on). - Collapse each of the four button on_press handlers to a single `script.execute: _start_forced_mode, hours: N` call. Validated `esphome config` on the 8 preset configs locally. 🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via [Claude Code](https://claude.com/claude-code) + Compound Engineering v2.46.0 Co-Authored-By: Claude Opus 4.7 (1M context, extended thinking) <noreply@anthropic.com>
packages/electrolyser.yaml maintained two globals that were written but never read anywhere in the codebase (see todo 008): - electrolyser_last_turn_on (updated on on_turn_on) - effective_electrolysis_minutes (incremented by a 1-min interval while pump + electrolyser are both on, reset on on_turn_off) Repo-wide grep confirms no readers — no HA dashboard binding, no blueprint (since removed), no other package consumes them. Pure dead code. If "minutes produced today" turns out useful later, re-adding it as a `sensor` is cheaper than carrying the dead tracking forever. File shrinks from 40 to 9 lines — just the GPIO switch definition. Validated `esphome config` on the 8 preset configs locally. 🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via [Claude Code](https://claude.com/claude-code) + Compound Engineering v2.46.0 Co-Authored-By: Claude Opus 4.7 (1M context, extended thinking) <noreply@anthropic.com>
Two related cleanups in packages/filtration.yaml (todos 009 + 010).
009 — forced_countdown.update() was firing every 30s inside the forced-mode
decrement block, but the rendered text is minute-granular ("Xh Ymin").
Half the publishes re-rendered the same string. Gate the update on
`% 60 == 0` so it only fires at minute boundaries (and on expiry when
remaining reaches 0, which is 0 mod 60).
010 — three behaviour-neutral simplifications:
- Remove the `filtration_recalculate` button. Every number/select/datetime
in the package already triggers `_calcul_filtration` via `on_value`, and
the scheduler also re-enters it at cycle boundaries + NTP sync. The
manual button adds no reachable capability.
- Remove the `g_dur_total_min` global. The `filtration_duree_totale`
diagnostic sensor now computes the total directly from the four window
bounds: `(h_fin1 - h_debut1) + (h_fin2 - h_debut2)`.
- Remove the two `if (g_forced_remaining_s == 0)` guards in the
"Hors fenêtres" branch. Control flow only reaches that branch when the
earlier `if (g_forced_remaining_s > 0) { pump.on(); return; }` did not
fire — meaning forced is already 0. The guards were always-true.
A short comment now documents the invariant.
Net -12 lines. Validated `esphome config` on the 8 preset configs locally.
🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via [Claude Code](https://claude.com/claude-code) + Compound Engineering v2.46.0
Co-Authored-By: Claude Opus 4.7 (1M context, extended thinking) <noreply@anthropic.com>
Previously the entire docs/ tree was gitignored. Remove that rule and commit the decision artifacts produced alongside the PR #6 fixes: - docs/brainstorms/ — requirements docs for the three p1 decisions (LAN hardening, pump authority consolidation, redox regulation policy) - docs/plans/ — structured implementation plan for the LAN hardening fix - docs/solutions/ — first compound learning capturing the asymmetric redox regulation policy (fast OFF / slow ON) Rationale: documenting the "why" behind these architectural decisions is high-compound-value. The redox policy in particular has non-obvious chemistry rationale that would be hard to recover from the code alone. 🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via [Claude Code](https://claude.com/claude-code) + Compound Engineering v2.46.0 Co-Authored-By: Claude Opus 4.7 (1M context, extended thinking) <noreply@anthropic.com>
- Dashboard: remove broken `button.frangipool_recalcul_filtration` reference; add Mode Forcé card exposing the 4 force buttons and the countdown sensor (PR #6 headline feature was previously invisible in the UI). - `_start_forced_mode`: clamp `hours` to [0, 24] to block int32 overflow (`hours*3600` wraps at ~596 k) and runaway pump lock-on via direct HA action calls bypassing the UI. Resolves todos 011, 012. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`packages/filtration.yaml` introduced the `g_` prefix for 9 package-owned globals; the other packages kept bare names. Retrofit so the convention is uniform across `packages/` and document the rule in README. - base.yaml: pump_last_turn_on, store_pool_temp - ph.yaml: store_pool_ph, ph_offset (entity id `ph_offset_sensor` preserved) - redox.yaml: store_pool_redox, redox_offset, last_redox_trend_value, redox_trend_state (entity ids `redox_offset_sensor` and `redox_manual_offset` preserved — different namespace) - redox_electrolyser.yaml: redox_stable_minutes Filtration.yaml was already compliant; 22 `id(...)` call sites updated across the other four packages. Resolves todo 019. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `api: actions:` block exposes `force_filtration(hours)` and `recalc_filtration` as HA-callable services so agents and automations can invoke arbitrary-duration forces or trigger manual recalcs (hours clamp from #012 is inherited via `_start_forced_mode`). - `esphome.min_version: 2024.6.0` pinned — matches the `datetime:` platform introduced in ESPHome 2024.6 used by `packages/filtration.yaml`. README gains a `## Prérequis` note. - Pump switch tagged `entity_category: diagnostic` with a one-line comment pointing at `packages/filtration.yaml` as the authoritative writer. Signals to agents/HA UI that direct `switch.turn_on/off` calls are volatile (reasserted by the 30s scheduler tick). Resolves todos 014, 021, 023. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…simplify - `_ntp_alert_once` wrapped in `api.connected` so the flag only latches when HA actually received the notification; next tick retries if HA was offline (comment vs. behaviour mismatch resolved). - `filtration_phase` now refreshes on Off-mode entry (transition guard) and at every `_calcul_filtration` tail (new `component.update` call). `filtration_auto_mode_actif` stays fresh via the existing tail update (bascule happens inside recalc). - `_calcul_filtration` uses `mode: restart` instead of `mode: queued` + `max_runs: 1`. Matches the original intent (coalesce bursts to the latest value); eliminates dropped-event warnings on slider drags. - Four force buttons lose `entity_category: config` — they're operational, not calibration; HA no longer hides them under Configuration. - NaN defense in `_calcul_filtration` collapsed from four tiers to one: `safe_f` helper deleted, along with the `divisr < 0.5`, post-cast `dur_total_min < 0`, and pivot clamps — all guarded states the ESPHome template + uint8_t arithmetic cannot produce. Kept the legitimate upper clamp (`MAX_MIN`) and window end-clamps. - `_start_forced_mode(hours=0)` no longer unconditionally calls `pump.turn_off()`; it resets the counter and lets the next 30s tick decide based on schedule/antifreeze. Prevents mid-cycle pump outage + electrolyser warmup reset when the user presses "Arrêter". - New numeric sensor `forced_remaining_seconds` alongside the existing `forced_countdown` string sensor so HA automations can template on seconds-remaining without parsing French. - Misc simplifications: merged identical phase-1/phase-2 post-cycle branches, dropped the pointless `if (g_ntp_alert_sent)` wrap, moved Off-mode check before NTP guard (Off doesn't need a clock), rounded up minutes in `forced_countdown` to eliminate the "< 1min" branch, trimmed apologetic/redundant comments. Resolves todos 015, 016, 017, 018, 022, 024, 025 (filtration portion), 026. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…x annotations - Six `conditional` cards had `state_not: unavailable` + `state_not: unknown` on the same entity; HA surfaces missing template entities as `unavailable` only, so the second check is dead weight. - pH and Redox apexcharts encoded threshold values twice — once in per-series `color_threshold`, once in `apex_config.annotations.yaxis` background bands. Dropped the `apex_config` subtree (was only annotations); `color_threshold` remains the single source of truth for threshold colouring, eliminating drift. −77 lines. No behaviour change other than simpler maintenance. Resolves todo 025 (dashboard portion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document the trap surfaced while fixing `_calcul_filtration`: `mode: queued` + `max_runs: 1` does NOT coalesce burst triggers — it keeps one running + one queued and *drops the rest with a warning*. For a pure recalc script, `mode: restart` is the coalesce-to-latest primitive most authors actually want. Includes a decision rule table for picking among restart / single / queued / parallel so the mistake doesn't recur. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Landing eleven fixes surfaced by the multi-persona code review: Safety - redox_electrolyser: Auto on_value no longer bypasses the 30-min stability gate; the interval regulator becomes the only turn_on path (resets g_redox_stable_minutes, keeps fast-OFF above setpoint). - base (antigel): std::isnan(t) guard preserves last known state on Dallas sensor failure — prevents frost damage when pipe_temp returns NaN. Supply chain - Rewrite github://frangipool/esphome-config/ → github://gaetanars/FrangiPool/ across the 8 salt_*.yaml presets, README badges + tables, and the CI sed pattern. Namespace now matches the actual repository owner. Contract / migration - Move api.actions (force_filtration, recalc_filtration) from packages/base.yaml to packages/filtration.yaml — package owns its API surface, removes the undeclared cross-package dependency. - Add contract comment on force_filtration + ESP_LOGW when the caller passes hours outside [0,24]; document that hours=0 cancels forced mode. - Remove entity_category: diagnostic from switch.pump so it returns to HA's Controls section instead of being hidden in Diagnostics. - README: new "Migration v1.x → v2.0" section covering the blueprint + HA-package helpers mapping, the filtration mode option rename (Inactif/Hivernage/Automatique/Forcé → Off/Hiver/Courbe/Auto), HA re-adoption with the new api_encryption_key, and the Redox Min/Max → single setpoint entity rename. Reliability / UX - filtration: _invalid_window_alert_once script + latch global — surfaces pivot/pause configurations that collapse a cycle window via an HA persistent_notification (mirrors the _ntp_alert_once pattern). - base: pump_uptime lambda returns 0 until g_pump_last_turn_on has been stamped at least once — first-flash no longer bypasses the 20-min warm-up guard that gates electrolyser sampling. - filtration: forced_countdown drops the "Nh 0min" display artifact at exact hour boundaries. Review artifact: .context/compound-engineering/ce-code-review/20260421-223003-d789f564/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two architecture learnings captured from the ce-code-review of PR #6. New — antifreeze-nan-silent-failure.md ESPHome antifreeze binary_sensor silently stays OFF when its Dallas pipe_temp_raw sensor returns NaN. C++ comparisons with NaN are always false, so the latched hysteresis never triggers. Fix: add `if (std::isnan(t)) return state;` at the top of the lambda to preserve last known state on sensor failure (matches the repo-canonical idiom already used in _calcul_filtration). Generalizes to every sensor-gated safety binary_sensor in the firmware — documented as a review checklist + reusable code snippet. Refresh — redox-asymmetric-regulation-policy.md Updated to reflect the second bypass discovered in PR #6: the select.electrolyser_mode.on_value Auto branch was calling turn_on immediately on low ORP, bypassing the 30-min stability gate the same way the deleted on_value_range below: trigger did. Root cause and Solution now document the second writer + its fix (commit 4358410, including the g_redox_stable_minutes = 0 counter reset on mode transition). Prevention → Review checklist rewritten to enumerate all five writer types explicitly (on_value_range, interval, select.on_value, button.on_press, api.actions) instead of the abstract "any trigger that writes to electrolyser" — the abstraction is exactly what masked the second bypass. Global names updated to the g_ prefix convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Capture repo-specific architecture that spans multiple files — preset/packages composition, pump-authority ordering inside the 30s interval, the asymmetric electrolyser regulation policy, and the ESPHome script-mode + NaN-guard gotchas already documented under docs/solutions/architecture/ — so future agent sessions start with load-bearing context instead of re-deriving it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
Les presets affichaient historiquement `version: "2.0.0"` (incohérent avec les tags v0.0.1 / v0.1.0) et le README narrait une migration v1.x → v2.0 inexistante. Ramène les 8 presets à "0.2.0" et réécrit la section migration en v0.1 → v0.2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Les 3 brainstorms du 2026-04-21 (pump authority, LAN hardening, redox policy) et le plan LAN hardening sont entièrement livrés dans cette PR. Les learnings réutilisables ont été capturés dans docs/solutions/architecture/ (commit 101c126), donc ces docs d'avant-vol n'ont plus lieu d'être. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Réintroduit le compteur de durée de session d'électrolyse (retiré en b6ee460 comme dead code faute de reader). Cette fois le global est exposé via un sensor template `Durée Electrolyse` et ajouté à la dashboard, donc il sert réellement. Sémantique : - `g_electrolyser_active_minutes` incrémente de 1 toutes les 60 s si la pompe ET l'électrolyseur sont tous les deux ON (pause silencieuse quand la pompe coupe, sans reset). - Reset à 0 uniquement via `switch.electrolyser.on_turn_off` — hook sur le switch plutôt que sur les 5 writers (select, interval, on_value_range, button, api), donc un seul point de vérité. - `restore_value: True` pour qu'un reboot ne perde pas la session en cours tant que le switch (`RESTORE_DEFAULT_ON`) conserve son état. Validé via `esphome config salt_full.yaml` (local 2026.4.1, CI 2026.3.3). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…time
Deux améliorations liées, issues du même feedback review :
1. **Uniformité du pattern template sensor**. Le capteur
`electrolyser_duration` utilisait `update_interval: 60s` alors que
tous les sensors de filtration.yaml sont en `update_interval: never`
avec `component.update` explicite. Deux timers 60 s indépendants
(sensor vs interval d'incrémentation) dérivaient et le reset suite à
`on_turn_off` pouvait mettre jusqu'à 60 s à s'afficher. Alignement
sur le pattern repo.
2. **Capteur "Durée Filtration Effective"**. Le capteur existant
`duree_filtration_journaliere` expose la durée *planifiée* (fin1-
debut1)+(fin2-debut2), pas le temps réellement tourné. Antigel hors
fenêtres, mode forcé, NTP drop → divergences invisibles.
Ajout d'un compteur séparé :
- `g_pump_effective_minutes_today` (restore_value: False, aligné sur
les autres globals filtration qui se recalculent au boot).
- Reset par la cron midnight existante (`0 0 0 * * *`).
- Tick par un interval 60 s indépendant (ne parasite pas le scheduler
30 s qui arbitre la pompe).
- Sensor `filtration_effective_minutes` en pattern never + update.
Dashboard : labels renommés "Planifiée (min)" / "Effective (min)"
pour lever l'ambiguïté sans changer les entity_ids.
Validé via `esphome config salt_full.yaml`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…=True Justification d'origine (alignement sur les autres globals False du package) était incorrecte : ces autres globals sont False parce qu'ils sont recalculés au boot par _calcul_filtration. Ce compteur, lui, n'est pas recalculé — il repartait simplement à 0 à chaque reboot. Un OTA ou une brève coupure mi-journée perdait le cumul. Avec True, on préserve le cumul au prix d'un risque résiduel : un reboot qui traverse minuit affiche la valeur d'hier jusqu'à la prochaine cron 00:00 (auto-heal ≤ 24 h). Les OTA / coupures brèves étant plus fréquentes que les power-off cross-midnight sur un ESP alimenté en permanence, le nouveau défaut est meilleur. Aligné avec g_pump_last_turn_on (base.yaml) qui accepte le même tradeoff pour l'état pompe. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds the full autonomous filtration scheduling system plus a timed forced mode for pool treatments.
Autonomous filtration scheduling (
packages/filtration.yaml)Timed forced mode (this addition)
g_forced_remaining_s, not NTP timestamp) — works offline, mirrors antifreeze resilience_calcul_filtrationguarded at cycle boundaries during active forced modeOther changes
homeassistant/dashboard/frangipool.yaml)Key design decisions
restore_value: false+ pumpRESTORE_DEFAULT_OFFTesting notes
ESPHome firmware has no unit test framework. Manual verification:
Post-Deploy Monitoring & Validation
Validation on first flash:
ESPHome logs to watch:
[W]logs during normal forced mode operationFailure signals:
g_forced_remaining_svia HA or web UIcomponent.updatecall path issueRollback: Flash previous firmware via OTA or serial. Pump defaults OFF at boot.
🤖 Generated with Claude Code · Model: claude-sonnet-4-6