- V092: ems.loxone_sensor + telemetry_loxone_sensor (hypertable) — generické čtení Loxone hodnot (poslouží i ohřevu/akumulačce); pool sloupce teplotní funkce (ref/base/per_c/min/max) + water_temp_sensor_id - R__098 fn_pool_daily_runtime_min: clamp(base+per_c×(t−ref)) z poslední teploty <24 h, fallback daily_runtime_min; JSON detail pro UI/solver - collector poll_loxone_sensors: /jdev/sps/io/<name>/state, LL.value parse, no-op bez čidel - sezóna = schedulable přepínač (dokumentováno vč. SQL); hranice filtrace × ohřev TČ (oddělené logiky, sdílí jen čidlo) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 KiB
Bazénové čerpadlo přes Shelly relé
Stav: telemetrie + ovládací infrastruktura implementováno (V085); integrace do LP solveru 📋 návrh / follow-up (viz planning-neg-sell-strategy.md, sekce bazén a UI workshop).
Cíl: (a) vlastní historie spotřeby čerpadla, (b) ovládání on/off, (c) plánovač jako odložitelná zátěž — „denní povinné hodiny filtrace, ideálně v levných / přebytkových slotech".
1. Architektura
Shelly relé (Gen2 RPC, HTTP)
▲ poll 60 s ▲ Switch.Set + readback verify
│ Switch.GetStatus │
telemetry_collector.poll_pool_pumps signal_service (15 s worker)
│ ▲
▼ │ signal_outbound_journal (queued)
ems.telemetry_pool_pump ems.fn_signal_enqueue_bool(site, 'POOL_PUMP_ON', bool)
(hypertable, 1 min) ▲
│ zatím: operátor / cron; follow-up: plánovač
- Jen Gen2 RPC (
/rpc/Switch.GetStatus,/rpc/Switch.Set) — Plus/Pro řada. Gen1 REST (/relay/0?turn=on) záměrně nepodporujeme; parser odmítne Gen1 odpověď (ison) chybou, aby se chybná konfigurace neprojevila tichým výpadkem dat. - Historie: Shelly drží jen okamžitý stav a kumulativní čítač
aenergy.total(Wh). Historii si stavíme sami 1min pollingem jako u všeho ostatního (Deye, EV, TČ). - Ovládání: žádný zásah do control exporteru — používá se existující signal infrastruktura (
signal_def/signal_route/signal_outbound_journal,services/signal_service.py) s journalem, retry a readback verify.
2. DB objekty (V085 + repeatables)
| Objekt | Soubor | Popis |
|---|---|---|
ems.asset_pool_pump |
db/migration/V085__pool_shelly.sql |
Aktivum: endpoint_id → site_endpoint (typ http_api / shelly_http), shelly_switch_id (Gen2 Switch id, typ. 0), rated_power_w (konstantní příkon), min_run_min, daily_runtime_min, schedulable. |
ems.telemetry_pool_pump |
tamtéž | Hypertable 1min: is_on, power_w (apower), energy_wh_total (aenergy.total, Wh). PK (pump_id, measured_at). |
signal_def POOL_PUMP_ON |
tamtéž | Bool signál — požadovaný stav relé. |
ems.fn_telemetry_pool_pump_sample |
db/routines/R__092_… |
Insert vzorku (on conflict do nothing). |
ems.vw_asset_pool_pump_http_poll |
db/views/R__093_… |
Čerpadla s aktivním HTTP endpointem pro collector. |
ems.fn_signal_enqueue_bool |
db/routines/R__094_… |
SQL-first zařazení bool signálu do odchozí fronty (všechny aktivní routy site+kód); aplikuje transform_json.map_bool per route stejně jako backend. |
Sezónnost: daily_runtime_min je aktuální sezónní hodnota (léto typicky 240–480 min, zima méně / 0 = filtrace vypnutá). Mění ji provozovatel ručně; plnohodnotný sezónní profil (tabulka měsíc → minuty, případně podle teploty vody) je follow-up — viz §6.
3. Telemetrie
telemetry_collector.poll_pool_pumps(site_id, db)— součást 60s smyčky (run_telemetry_loop), čtevw_asset_pool_pump_http_poll, voláservices/shelly_client.get_switch_status, zapisuje přesfn_telemetry_pool_pump_sample.- Při výpadku čtení se nic nezapisuje (žádná fabrikovaná nula — stejný princip jako EV nabíječky).
energy_wh_totalje čítač — energie za interval = kladná diference po sobě jdoucích vzorků (po výpadku napájení Shelly může čítač začít znovu; záporné diference zahazovat).- Follow-up: zařadit
power_wčerpadla mezi řízené zátěže při výpočtu bazálu (fn_update_baseline_stats) a dovw_latest_telemetry, jakmile poteče reálná telemetrie.
4. Ovládání on/off (signál POOL_PUMP_ON)
Route je per site a obsahuje IP — neseeduje se migrací, zakládá se provozně podle šablony (placeholdery <...>):
-- 1) endpoint Shelly relé
insert into ems.site_endpoint (site_id, endpoint_type, host, port, protocol, enabled, notes)
values (<site_id>, 'http_api', '<SHELLY_IP>', 80, 'http', true, 'Shelly relé bazénového čerpadla')
returning id; -- → <endpoint_id>
-- 2) aktivum
insert into ems.asset_pool_pump (site_id, code, endpoint_id, rated_power_w, min_run_min, daily_runtime_min)
values (<site_id>, 'pool-pump-01', <endpoint_id>, <RATED_POWER_W>, 15, <DAILY_RUNTIME_MIN>);
-- 3) route signálu na Shelly (Gen2 RPC; bool v query musí být doslova true/false → map_bool)
insert into ems.signal_route (
site_id, destination_type, endpoint_id, signal_code, destination_key,
route_config_json, transform_json, verify_readback, verify_config_json
)
values (
<site_id>, 'http_rest', <endpoint_id>, 'POOL_PUMP_ON', 'switch0',
'{"method": "GET", "path_template": "/rpc/Switch.Set?id=0&on={value}"}',
'{"map_bool": {"true": "true", "false": "false"}}',
true,
'{"read_path": "/rpc/Switch.GetStatus?id=0", "json_path": "$.output"}'
);
Tok: select ems.fn_signal_enqueue_bool(<site_id>, 'POOL_PUMP_ON', true); → signal_outbound_journal (queued) → worker signal_outbound_send (15 s) pošle GET /rpc/Switch.Set?id=0&on=true → sent → worker signal_outbound_verify přečte Switch.GetStatus a porovná $.output → verified (retry/backoff a abandoned po 12 pokusech dle signal_service).
Kdo signál nastavuje (fáze):
- Teď: operátor ručně (
fn_signal_enqueue_bool) nebo jednoduchý cron (např. pg_cron / APScheduler tick: zapnout v naplánovaných hodinách, vypnout mimo ně). - Follow-up (plná integrace): plánovač zapíše běh bazénu do
planning_interval(nový sloupec, např.pool_pump_on boolean); tick na hranici 15min slotu (analogický control exporteru, ale přes signály) porovná plán ssignal_statea zavoláfn_signal_enqueue_booljen při změně (idempotenci řešísignal_state+_should_skip_enqueuelogika).
5. Integrace do solveru (📋 návrh — analogie hp[t])
V solver_v2.py je TČ spojitá proměnná hp[t] ∈ [0, rated_w] vstupující do bilance load_site. Bazén je jednodušší — konstantní příkon, binární běh:
- Proměnné:
pool[t] ∈ {0, 1}(LpBinary) pro sloty v plánovacím horizontu; příkon ve slotu =pool[t] * rated_power_w. - Bilance:
load_site = load_baseline + ev + hp[t] + pool[t] * rated_power_w. - Denní povinný runtime (kalendářní den v
site.timezone, jako ostatní denní logika): pro každý dendplně pokrytý horizontem:sum(pool[t] for t in day_d) * 15 >= daily_runtime_min(zbytek dne při rolling replanu: odečíst již odběhané minuty ztelemetry_pool_pumpod půlnoci — vizfn_battery_cycle_auditvzor agregace). - Min. souvislý běh:
min_run_min / 15slotů — klasická minimální up-time formulace přes binárku startupool_start[t] >= pool[t] − pool[t−1]apool[t..t+k] >= pool_start[t]. - Cíl: žádný extra term — levné/přebytkové sloty vyberou samy ceny v účelové funkci (import za
buy[t], ušlý export zasell[t]); v okněsell < 0funguje bazén přirozeně jako flex sink (vizplanning-neg-sell-strategy.md,E_surplus_after_t). schedulable = false→ solver čerpadlo ignoruje (jen telemetrie),daily_runtime_min = 0→ žádný constraint.- Pozor na MILP velikost: +96–144 binárek/den; držet se vzoru
z_export/y_imp(HiGHS to zvládá). - Výstup:
planning_interval.pool_pump_on(nová migrace) + export přes signál (§4 fáze 2); audit follow-up: skutečnost ztelemetry_pool_pumpdoaudit_interval.
6. Otevřené otázky / follow-upy
- Sezónní profil
daily_runtime_min(tabulka měsíc → minuty? řízení podle teploty vody?) — zatím ruční změna hodnoty. - Produktové rozhodnutí UI pro flex zátěže (workshop dle
planning-neg-sell-strategy.md§UI) — bazén do slot detailu a „Dnes X/Y h filtrace". - Bazál: odečítat
telemetry_pool_pump.power_wvfn_update_baseline_stats(jinak se bazén započte do baseline a solver by ho počítal dvakrát). - PostgREST granty (
ems_anon) natelemetry_pool_pump/ view, až bude UI číst. - Více čerpadel na jednom Shelly Pro 2PM (
shelly_switch_id0/1) — schéma to umí, collector i route ano; netestováno.
7. Checklist oživení (placeholdery)
- Shelly připojené na LAN, statická IP
<SHELLY_IP>, ověřit ručně:curl http://<SHELLY_IP>/rpc/Switch.GetStatus?id=0→ JSON soutput(Gen2!). insert into ems.site_endpoint …(šablona §4) →<endpoint_id>.insert into ems.asset_pool_pump …s<RATED_POWER_W>(štítek čerpadla, typ. 400–1100 W) a<DAILY_RUNTIME_MIN>(sezóna).- Počkat ≤ 60 s, ověřit telemetrii:
select * from ems.telemetry_pool_pump order by measured_at desc limit 5; insert into ems.signal_route …(šablona §4).- Test zapnutí:
select ems.fn_signal_enqueue_bool(<site_id>, 'POOL_PUMP_ON', true);→ do ~30 ssignal_outbound_journal.status = 'verified'a relé sepnuté; pak vypnout (false). - Zkontrolovat
power_wv telemetrii při běhu ≈rated_power_w(případně upravit). - Nastavit dočasné spínání (cron / ručně) do doby solver integrace (§5).
Vizualizace (2026-06-12, dev)
vw_latest_pool_pump(LATERAL poslední vzorek + data_age) avw_pool_pump_day_energy(denní kWh z delty čítače + minuty běhu, 8 dní) — PostgREST grantems_anon(R__097).- Dashboard:
PoolCard(frontend/src/components/PoolCard.tsx) pod StatePanel — stav (běží/stojí/stale), aktuální W, dnešní kWh a hodiny běhu, mini sloupce 7 dní. Poll 60 s.
Sezóna a teplotní funkce (2026-06-12, dev)
Sezóna = jeden přepínač (asset_pool_pump.schedulable):
update ems.asset_pool_pump set schedulable = false where code = 'pool-pump-1'; -- konec sezóny
update ems.asset_pool_pump set schedulable = true where code = 'pool-pump-1'; -- začátek
Off-season: telemetrie běží dál (zimování čerpadla vidíš), plánovač a signály ne. (Později tlačítko v Konfiguraci.)
Délka filtrace dle teploty vody (fn_pool_daily_runtime_min, V092):
runtime = clamp(base + per_°C × (t − ref), min, max); defaulty pro 30 m³ /
8 m³/h (obrátka 3.75 h, slaná voda — chlorinátor potřebuje průtok):
20 °C→4.5 h, 24 °C→6.5 h, 28 °C→8.5 h, strop 10 h. Bez čidla / měření
staršího 24 h → fallback daily_runtime_min. Vše sloupce na čerpadle.
Čidlo teploty: až přidáš do Loxonu, jeden INSERT:
insert into ems.loxone_sensor (site_id, code, loxone_name, unit)
values ((select id from ems.site where code='home-01'), 'pool-water-temp', '<JmenoVLoxonu>', '°C');
update ems.asset_pool_pump set water_temp_sensor_id =
(select id from ems.loxone_sensor where code='pool-water-temp') where code='pool-pump-1';
Collector čte každou minutu (poll_loxone_sensors — generické, poslouží i
akumulační nádrži pro ohřev).
Hranice s ohřevem přes TČ (žádný šelmostroj): filtrace = denní rozpočet minut pro solver; ohřev = samostatný explicitní program (docs z 12. 6.: sekvence Shelly pump → HEX pump → TČ), který si čerpadlo prostě zapne (interlock) — minuty běhu se započtou samy přes telemetrii. Jediná vazba je teplotní čidlo, které sdílí obě logiky.