Files
ems/docs/04-modules/operating-modes.md
Dusan Vojacek 43b594c8d5
Some checks failed
CI and deploy / migration-check (push) Failing after 14s
CI and deploy / deploy (push) Has been skipped
solver nastavuje stavy deye
2026-04-20 08:33:56 +02:00

7.9 KiB
Raw Blame History

Provozní režimy EMS

Keep it simple

  • Žádné wattové prahy pro výběr SELL / CHARGE — mapování z MILP setpointů je čistě ze znamének battery_setpoint_w a grid_setpoint_w (viz get_deye_mode v exporter_monolith.py).
  • ZERO (PASSIVE) = zero export k CT/zátěži (142 = deye_zero_export_mode), s plnými proudy 108/109 jen ve výchozím stavu; pro přetok FVE do sítě nebo odběr ze sítě bez vybíjení baterie se jeden z proudů vynuluje (_deye_zero_export_amps_for_passive).
  • SELL = plánovaný export i plánované vybíjení (oba záporné) → 142 = selling first, 178 = vypnutý grid peak shaving (32); po návratu do ZERO/CHARGE zase 178 = 48.
  • Novou logiku vždy ověřit proti reálnému řádku plánu (audit / planning_interval).

Přehled

Mode Solver constraints Deye fyzický režim Baterie
AUTO žádné PASSIVE/SELL/CHARGE dle plánu dle plánu
SELF_SUSTAIN no_export, min_import vždy PASSIVE plné limity
CHARGE_CHEAP no_export, no_discharge CHARGE nabíjení max
PRESERVE no_charge, no_discharge PASSIVE lock (0/0)
MANUAL solver neběží EMS nezapisuje

Implementace:

  • EMS provozní režim (AUTO, SELF_SUSTAIN, …): jediný zdroj v DB ems.site_operating_mode.mode_code + větev v _build_setpoints / export_setpoints (např. CHARGE_CHEAP přepíše setpointy před zápisem — stále jedna funkce exportu).
  • Deye fyzický režim (PASSIVE / CHARGE / SELL): jediný zdroj get_deye_mode (exporter_monolith.py); zápis v write_inverter_setpoints().
  • Omezení LP v planning_engine.solve_dispatch() podle mode_code; zápis Deye včetně lock_battery u PRESERVE.

Odkud jsou battery_setpoint_w a grid_setpoint_w (AUTO)

Nejde o samostatný „tip“ z predikce FVE, který by exporter náhodně přetáhl do SELL nebo CHARGE.

  1. Zdroj dat: pro režim AUTO exporter načte aktivní řádek ems.planning_interval pro aktuální 15min slot (_fetch_plan_row_for_slot_offset_build_setpoints v exporter_monolith.py).
  2. Kdo je naplnil: sloupce pocházejí z výstupu planning_engine.solve_dispatch() — MILP nad bilanční rovnicí za slot (základní značky v kódu: gi[t] ≥ 0 import ze sítě, ge[t] ≥ 0 export ze sítě, bc[t] / bd[t] nabíjení / vybíjení baterie). Uložené hodnoty odpovídají grid_setpoint_w = round(gi[t] - ge[t]) a battery_setpoint_w = round(bc[t] - bd[t]) (viz sestavení DispatchResult a zápis plánu).
  3. Fyzika u elektroměru: v jednom slotu model pracuje s čistým tokem ze sítě jako rozdílem gi a ge; predikce PV a spotřeba vstupují do bilance a omezení řešiče, ne jako náhradní logika mapování na Deye.
  4. Role get_deye_mode: pouze přeloží už hotový plán na kombinaci registrů (PASSIVE / CHARGE / SELL). Očekávání provozu (např. kdy přesně má být výdej baterie do sítě vs. přetok FVE) má držet LP a výběr slotů (allow_charge, allow_discharge_export, …), ne dodatečné wattové heuristiky v exporteru.

Fyzické režimy Deye (výstup control exporteru)

Jediné místo pro klasifikaci Deye PASSIVE | CHARGE | SELL z MILP setpointů je get_deye_mode v exporter_monolith.py.

Značení: battery_w = battery_setpoint_w (kladné = nabíjení, záporné = vybíjení); grid_setpoint_w (kladné = import, záporné = export).

Režim Podmínka z plánu 108 / 109 (zkráceně) 142 178
CHARGE battery_w > 0 a grid_setpoint_w > 0 dle plánu nabíjení / 0 vybíjení větev CHARGE 48
SELL battery_w < 0 a grid_setpoint_w < 0 0 nabíjení / max vybíjení 0 (selling first) 32 (peak shaving off)
PASSIVE (ZERO) vše ostatní viz tabulka ZERO níže deye_zero_export_mode 48

ZERO: výchozí a dvě varianty proudu (reg. 108 / 109)

Všechny řádky předpokládají 142 = zero export (ne SELL).

Situace Podmínka (plán) Reg. 108 (max charge A) Reg. 109 (max discharge A)
Výchozí ostatní případy PASSIVE max max
Přetok FVE do sítě grid_setpoint_w < 0 a battery_w ≥ 0 0 max
Držet baterii, brát ze sítě grid_setpoint_w > 0 a battery_w ≤ 0 max 0

Nabíjení ze sítě s vysokým cílovým SoC v TOU řeší větev CHARGE (grid charge v time pointech), ne tato tabulka.

ZERO a „zakázaný export FVE do sítě“ (jen řiditelná pole)

Reg. 145 (solar sell) na Deye je přepínač typu enabled / disabled pro přebytek FVE na straně měniče (počítá se vůči režimu 142 zero export a stavu 108 — viz modbus-registers.md, pass-through krok za krokem).

  • Pouze to, co EMS umí přes Deye Modbus ovlivnit — typicky FVE pole řízené střídačem (asset_pv_array.controllable = true, u referenční lokality home-01 např. string za Deye).
  • Pole mimo tento kanál (např. pv-b u home-01, controllable = false, často samostatný ongrid GEN) reg. 145 neovládá; jejich výkon jde do plánovače jako pv_b_forecast_w, ale curtailment / solar sell logika Deye se jich netýká.

Implementace dnes: exporter vždy zapisuje 145 = 1 (solar sell enabled). Tvrdé vypnutí přebytku řiditelného FVE do sítě přes 145 = 0 z politik (no_export, BLOCK_EXPORT, …) je v plánu — viz docs/05-todo.md (sekce Deye řízení rozšíření).

SELF_SUSTAIN: battery_w = None ⇒ v get_deye_mode jako 0 ⇒ PASSIVE; v write_inverter_setpoints při self_sustain_local_use=True108 i 109 = max (bez variant ZERO výše), reg. 142 dle DB, TOU SOC = min_soc_percent. PRESERVE: lock_battery108 = 0, 109 = 0.

EMS politiky (nejsou fyzické stavy Deye)

  • PV_SELL_ONLY: AUTO + constraint solveru max_discharge_from_pv
  • BLOCK_EXPORT: AUTO + záporná sell_price → ge[t]=0
  • NEGATIVE_HARVEST: AUTO + záporná buy_price → max charge/load
  • PROTECT: SELF_SUSTAIN s konzervativními limity

Tyto politiky jsou parametrizace AUTO/SELF_SUSTAIN, ne samostatné fyzické stavy.


Loxone a UI (shrnutí)

EMS a Loxone sdílí pojmenované provozní režimy; Loxone dostává číslo režimu přes Virtual Input a může fungovat autonomně (watchdog při výpadku EMS).

POST /api/v1/sites/{site_id}/mode
{
  "mode": "SELF_SUSTAIN",
  "valid_until": null,
  "notes": "…"
}

Backend: ems.fn_set_mode přes run_fn_set_mode_with_discord (při skutečné změně mode_code → Discord, pokud je DISCORD_WEBHOOK_URL) + HTTP na Loxone /dev/sps/io/EMS_Mode/{loxone_mode_value}. Dočasné přepisy s valid_until ruší ems.fn_expire_modes(), která vrací řádky (site_id, site_code, old_mode, new_mode) pro každé přepnutí — scheduler je použije pro stejné Discord upozornění.

Klíčový princip: Loxone watchdog nečte DB sleduje pulzy EMS_Heartbeat. Detail: docs/loxone-integration.md. Detail Modbus / Discord: docs/04-modules/modbus-command-journal.md.

Tabulka režimů (Loxone / zátěže)

Kód Loxone int EV Poznámka
AUTO 1 dle plánu dle plánu setpointy z plánu
SELF_SUSTAIN 2 stop stop fallback / výpadek EMS
CHARGE_CHEAP 3 stop stop max nabíjení ze sítě
PRESERVE 4 stop stop baterie uzamčena (Modbus 0/0)
MANUAL 0 stop stop servis, EMS neexportuje

Otevřené body

  • Doplnit alerty při ems_heartbeat_status = 'stale' (Discord při změně provozního režimu z backendu je popsán v modbus-command-journal.md)