From ccdca068a1eb7bae25a311e60fe3851972f2d226 Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Thu, 11 Jun 2026 22:37:42 +0200 Subject: [PATCH] =?UTF-8?q?DB:=20baz=C3=A9nov=C3=A9=20=C4=8Derpadlo=20p?= =?UTF-8?q?=C5=99es=20Shelly=20rel=C3=A9=20(V085)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ems.asset_pool_pump (endpoint http, rated_power_w, min_run_min, daily_runtime_min jako aktuální sezónní hodnota, schedulable) - ems.telemetry_pool_pump — 1min hypertable (is_on, power_w, energy_wh_total) - signal_def POOL_PUMP_ON (bool) pro ovládání přes signal infrastrukturu - fn_telemetry_pool_pump_sample (R__092), vw_asset_pool_pump_http_poll (R__093) - fn_signal_enqueue_bool (R__094) — SQL-first zařazení bool signálu do fronty Co-Authored-By: Claude Fable 5 --- db/migration/V085__pool_shelly.sql | 107 ++++++++++++++++++ .../R__092_fn_telemetry_pool_pump_sample.sql | 32 ++++++ db/routines/R__094_fn_signal_enqueue_bool.sql | 59 ++++++++++ .../R__093_vw_asset_pool_pump_http_poll.sql | 18 +++ 4 files changed, 216 insertions(+) create mode 100644 db/migration/V085__pool_shelly.sql create mode 100644 db/routines/R__092_fn_telemetry_pool_pump_sample.sql create mode 100644 db/routines/R__094_fn_signal_enqueue_bool.sql create mode 100644 db/views/R__093_vw_asset_pool_pump_http_poll.sql diff --git a/db/migration/V085__pool_shelly.sql b/db/migration/V085__pool_shelly.sql new file mode 100644 index 0000000..c7c45a0 --- /dev/null +++ b/db/migration/V085__pool_shelly.sql @@ -0,0 +1,107 @@ +-- Bazénové čerpadlo přes Shelly relé (Gen2 RPC). +-- (a) asset + 1min telemetrie vlastním pollingem (Shelly drží jen okamžitý stav a čítač +-- aenergy.total — historii si stavíme sami jako u ostatních zařízení, 60 s), +-- (b) ovládání on/off přes existující signal infrastrukturu (signal_def POOL_PUMP_ON, +-- route http_rest na Switch.Set — route je per site, seed v docs/04-modules/pool-shelly.md), +-- (c) plánovač: odložitelná zátěž s denní povinnou dobou filtrace (follow-up, viz docs). + +-- ------------------------------------------------------------ +-- Aktivum: bazénové čerpadlo za Shelly relé +-- ------------------------------------------------------------ +create table ems.asset_pool_pump ( + id serial primary key, + site_id int not null references ems.site (id), + code text not null, + manufacturer text, + model text, + endpoint_id int references ems.site_endpoint (id), + shelly_switch_id int not null default 0, + rated_power_w int not null, + min_run_min int not null default 15, + daily_runtime_min int not null default 240, + schedulable boolean not null default true, + notes text, + constraint uq_asset_pool_pump_site_code unique (site_id, code) +); + +comment on table ems.asset_pool_pump is + 'Bazénové (filtrační) čerpadlo spínané přes Shelly relé (Gen2 RPC, HTTP). Konstantní příkon, odložitelná zátěž s denní povinnou dobou běhu.'; + +comment on column ems.asset_pool_pump.site_id is + 'Vazba na lokalitu.'; + +comment on column ems.asset_pool_pump.code is + 'Kód aktiva, unikátní v rámci lokality. Příklad: pool-pump-01.'; + +comment on column ems.asset_pool_pump.endpoint_id is + 'HTTP endpoint Shelly relé (ems.site_endpoint, endpoint_type http_api nebo shelly_http). Bez endpointu se čerpadlo nepolluje.'; + +comment on column ems.asset_pool_pump.shelly_switch_id is + 'Id Switch komponenty v Shelly Gen2 RPC (Switch.GetStatus?id=N). U 1kanálových relé 0.'; + +comment on column ems.asset_pool_pump.rated_power_w is + 'Jmenovitý příkon čerpadla ve W. Plánovač s ním počítá jako s konstantním výkonem při běhu.'; + +comment on column ems.asset_pool_pump.min_run_min is + 'Minimální nepřerušený běh v minutách (ochrana čerpadla před krátkým cyklováním). Násobky 15min slotů.'; + +comment on column ems.asset_pool_pump.daily_runtime_min is + 'Denní povinná doba filtrace v minutách — AKTUÁLNÍ sezónní hodnota (léto typ. více, zima méně / 0). Mění ji provozovatel ručně podle sezóny; plnohodnotný sezónní profil (tabulka měsíc → minuty) je follow-up, viz docs/04-modules/pool-shelly.md. 0 = filtrace vypnutá (mimo sezónu).'; + +comment on column ems.asset_pool_pump.schedulable is + 'true = plánovač smí rozkládat běh do levných/přebytkových slotů; false = EMS jen měří, nespíná.'; + +-- ------------------------------------------------------------ +-- 1min telemetrie (TimescaleDB hypertable) +-- ------------------------------------------------------------ +create table ems.telemetry_pool_pump ( + site_id int not null references ems.site (id), + pump_id int not null references ems.asset_pool_pump (id), + measured_at timestamptz not null, + is_on boolean, + power_w int, + energy_wh_total bigint, + primary key (pump_id, measured_at) +); + +comment on table ems.telemetry_pool_pump is + 'Telemetrie bazénového čerpadla ze Shelly relé (Gen2 Switch.GetStatus), 1min polling. TimescaleDB hypertable. Historie se staví výhradně tady — Shelly ji nedrží.'; + +comment on column ems.telemetry_pool_pump.site_id is + 'Vazba na lokalitu.'; + +comment on column ems.telemetry_pool_pump.pump_id is + 'Vazba na ems.asset_pool_pump.'; + +comment on column ems.telemetry_pool_pump.measured_at is + 'Čas měření (UTC).'; + +comment on column ems.telemetry_pool_pump.is_on is + 'Stav relé (Switch.GetStatus output).'; + +comment on column ems.telemetry_pool_pump.power_w is + 'Okamžitý činný příkon ve W (Switch.GetStatus apower). NULL pokud model neměří výkon.'; + +comment on column ems.telemetry_pool_pump.energy_wh_total is + 'Kumulativní čítač energie ve Wh (Switch.GetStatus aenergy.total). Po výpadku napájení Shelly může čítač začít znovu — energii za interval počítat jako kladnou diferenci.'; + +select create_hypertable( + 'ems.telemetry_pool_pump', + 'measured_at', + chunk_time_interval => interval '1 week', + if_not_exists => true +); + +create index idx_telemetry_pool_pump_site_time + on ems.telemetry_pool_pump (site_id, measured_at desc); + +-- ------------------------------------------------------------ +-- Signál pro ovládání relé (route per site se seeduje provozně, šablona v docs) +-- ------------------------------------------------------------ +insert into ems.signal_def (code, value_type, description) +values ( + 'POOL_PUMP_ON', + 'bool', + 'Požadovaný stav bazénového čerpadla (Shelly relé). Doručuje signal_service přes signal_route http_rest na Shelly Gen2 Switch.Set, readback verify přes Switch.GetStatus. Hodnotu nastavuje plánovač / operátor (fn_signal_enqueue_bool).' +) +on conflict (code) do nothing; diff --git a/db/routines/R__092_fn_telemetry_pool_pump_sample.sql b/db/routines/R__092_fn_telemetry_pool_pump_sample.sql new file mode 100644 index 0000000..fc85e91 --- /dev/null +++ b/db/routines/R__092_fn_telemetry_pool_pump_sample.sql @@ -0,0 +1,32 @@ +create or replace function ems.fn_telemetry_pool_pump_sample( + p_site_id int, + p_pump_id int, + p_measured_at timestamptz, + p_is_on boolean, + p_power_w int, + p_energy_wh_total bigint +) +returns void +language sql +as $fn$ + insert into ems.telemetry_pool_pump ( + site_id, + pump_id, + measured_at, + is_on, + power_w, + energy_wh_total + ) + values ( + p_site_id, + p_pump_id, + p_measured_at, + p_is_on, + p_power_w, + p_energy_wh_total + ) + on conflict (pump_id, measured_at) do nothing; +$fn$; + +comment on function ems.fn_telemetry_pool_pump_sample is + 'Insert 1min telemetrie bazénového čerpadla (Shelly Switch.GetStatus: output, apower, aenergy.total). Volá telemetry_collector.poll_pool_pumps.'; diff --git a/db/routines/R__094_fn_signal_enqueue_bool.sql b/db/routines/R__094_fn_signal_enqueue_bool.sql new file mode 100644 index 0000000..40ab4bd --- /dev/null +++ b/db/routines/R__094_fn_signal_enqueue_bool.sql @@ -0,0 +1,59 @@ +create or replace function ems.fn_signal_enqueue_bool( + p_site_id int, + p_signal_code text, + p_value boolean +) +returns int +language plpgsql +as $fn$ +declare + v_route record; + v_value_text text; + v_count int := 0; +begin + -- Zařadí bool signál do odchozí fronty pro všechny aktivní routy (site, kód). + -- Transformaci na text dělá per route stejně jako backend (_bool_to_text): + -- transform_json->'map_bool'->>'true'/'false', default '1'/'0'. + for v_route in + select r.id, r.site_id, r.destination_type, r.destination_key, r.transform_json + from ems.signal_route r + where r.site_id = p_site_id + and r.signal_code = p_signal_code + and r.enabled = true + loop + v_value_text := coalesce( + v_route.transform_json -> 'map_bool' ->> (case when p_value then 'true' else 'false' end), + case when p_value then '1' else '0' end + ); + + insert into ems.signal_state ( + site_id, signal_code, destination_type, destination_key, + last_desired_value_text, updated_at + ) + values ( + p_site_id, p_signal_code, v_route.destination_type, v_route.destination_key, + v_value_text, now() + ) + on conflict (site_id, signal_code, destination_type, destination_key) + do update set + last_desired_value_text = excluded.last_desired_value_text, + updated_at = now(); + + insert into ems.signal_outbound_journal ( + route_id, site_id, signal_code, value_text, value_num, status, + attempt_count, next_attempt_at + ) + values ( + v_route.id, p_site_id, p_signal_code, v_value_text, + case when p_value then 1 else 0 end, 'queued', 0, now() + ); + + v_count := v_count + 1; + end loop; + + return v_count; +end; +$fn$; + +comment on function ems.fn_signal_enqueue_bool is + 'Zařadí bool signál (např. POOL_PUMP_ON) do signal_outbound_journal pro všechny aktivní routy daného site a kódu; doručení a verify řeší signal_service (každých 15 s). Vrací počet zařazených řádků. Použití: select ems.fn_signal_enqueue_bool(1, ''POOL_PUMP_ON'', true);'; diff --git a/db/views/R__093_vw_asset_pool_pump_http_poll.sql b/db/views/R__093_vw_asset_pool_pump_http_poll.sql new file mode 100644 index 0000000..f9bf130 --- /dev/null +++ b/db/views/R__093_vw_asset_pool_pump_http_poll.sql @@ -0,0 +1,18 @@ +drop view if exists ems.vw_asset_pool_pump_http_poll; + +create view ems.vw_asset_pool_pump_http_poll as +select + pp.site_id, + pp.id as pump_id, + pp.code, + pp.shelly_switch_id, + se.protocol, + se.host, + se.port +from ems.asset_pool_pump pp +join ems.site_endpoint se on se.id = pp.endpoint_id +where se.enabled = true + and se.endpoint_type in ('http_api', 'shelly_http'); + +comment on view ems.vw_asset_pool_pump_http_poll is + 'Bazénová čerpadla se Shelly HTTP endpointem pro telemetry_collector (Gen2 RPC polling, 60 s).';