feat(pool): řízení bazénu Phase 1 — nejlevnější okno + dump-load (bez solveru)

fn_pool_schedule_slot: nejlevnější souvislé okno denního runtime budgetu
(fn_pool_daily_runtime_min) z vw_site_effective_price + dump-load při sell<=0.
fn_pool_control_tick: každých 15 min spočte stav a zařadí POOL_PUMP_ON (jen když
existuje signal_route → bezpečné před aktivací). lifespan job pool_control.
Shelly přes signal_service, žádné Modbus. Bazál odečet (R__003) se tím stává
správným (řízená+plánovaná zátěž). Aktivace provozně: daily_runtime_min=480,
schedulable, signal_route.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dusan Vojacek
2026-06-14 22:00:49 +02:00
parent 8ffe5460f1
commit f70111f44b
3 changed files with 210 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
# Bazénové čerpadlo (Shelly) — řízení
Řízená zátěž: filtrační čerpadlo bazénu přes Shelly Plug S Gen3 (relé). EMS ho
spíná podle cen, čte telemetrii a započítává do plánu.
## Datový model (V087, V092)
- `ems.asset_pool_pump``rated_power_w`, `min_run_min`, `daily_runtime_min`
(statický cíl filtrace/den), `schedulable`, `shelly_switch_id`, `endpoint_id`,
teplotní parametry: `runtime_base_min`, `runtime_min_per_c`, `runtime_ref_temp_c`,
`runtime_min_min`, `runtime_max_min`, `water_temp_sensor_id`.
- `ems.telemetry_pool_pump` — 1min: `is_on`, `power_w`, `energy_wh_total` (hypertable).
## Denní runtime budget — `fn_pool_daily_runtime_min(pump_id)`
`clamp(runtime_base_min + runtime_min_per_c × (teplota runtime_ref_temp_c),
runtime_min_min, runtime_max_min)` z poslední teploty vody (`telemetry_loxone_sensor`,
< 24 h). **Bez čidla** → fallback `daily_runtime_min` (např. 480 = 8 h). Teplotní
režim se zapne pouhým napojením `water_temp_sensor_id` — žádný kód navíc.
## Rozvrh do slotů (Phase 1, bez solveru) — `fn_pool_schedule_slot(pump_id, slot)`
Vrací ON/OFF pro 15min slot:
- **Nejlevnější souvislé okno** délky = runtime budget (z `vw_site_effective_price`,
kalendářní den slotu v Europe/Prague, řazeno dle efektivní nákupní ceny). PV/záporné
dny → okno padne automaticky přes poledne.
- **+ dump-load overlay:** `sell <= 0` (záporná/nulová výkupní cena) → ON i mimo okno
(zkonzumuj přebytek místo exportu se ztrátou).
- `false` když pump není `schedulable` nebo nejsou ceny pro den.
## Control smyčka — `fn_pool_control_tick()` + APScheduler
Job `pool_control` (každých 15 min, hranice slotu, `lifespan.py`) volá
`fn_pool_control_tick()` → pro každý aktivní řiditelný bazén spočte
`fn_pool_schedule_slot` a **idempotentně** zařadí signál `POOL_PUMP_ON`
(`fn_signal_enqueue_bool`) — **jen když existuje `signal_route`** (jinak bezpečně
nic). Doručení na Shelly (`Switch.Set`) + readback verify (`Switch.GetStatus`) řeší
`signal_service` (každých 15 s). Žádné Modbus.
## Bazál
`fn_update_baseline_stats` (R__003) bazén **odečítá** z bazálu — to je správné
**jen** když ho zároveň řídíme (řízená + plánovaná zátěž). Bez řízení by to
plánovač oslepilo. S tímto řízením je odečet korektní.
## Aktivace (provozní, per site)
1. `asset_pool_pump.daily_runtime_min` = cílové minuty (480 = 8 h), `schedulable = true`.
2. Seed `signal_route` (`POOL_PUMP_ON``http_rest` na Shelly endpoint, `map_bool`
true/false → on/off; `verify_config_json` přes `Switch.GetStatus`).
3. Ověřit `fn_pool_schedule_slot` vrací rozumné sloty + telemetrii Shelly, pak teprve
ostře (control tick enqueueuje až s existující route).
## Roadmap
- **Phase 2:** `pool_on[t]` do solveru (`solver_v2`) — co-optimalizace proti
baterii/exportu (golden gate). Dump-load pak z živého SoC/PV, ne jen z ceny.
- Teplotní čidlo: napojit `water_temp_sensor_id` → runtime se prodlouží/zkrátí dle
teploty vody automaticky.