Files
ems/docs/04-modules/operating-modes.md
Dusan Vojacek eb425a26f2
Some checks failed
CI and deploy / migration-check (push) Failing after 12s
CI and deploy / deploy (push) Has been skipped
narovnani spravneho rezimu - nastavenim charge A
2026-05-21 10:23:53 +02:00

10 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).
  • Přetok FVE do sítě se neodvozuje z forecastového capu: plán nese explicitní export_limit_w jako tvrdý limit lokality / invertoru, ne jako tipované maximum z předpovědi.
  • ZERO (PASSIVE) = 142 = deye_zero_export_mode (1/2, ne selling first). PV_SURPLUS: 108 = 0, 109 = max — přebytek FVE do sítě (145 = 1), ne do baterie. Jinak 108/109 typicky max; výjimka import bez vybíjení → 109 = 0.
  • 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 min_import; export jen jako nouzový ventil (silně penalizovaný) 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)
Přetok FVE do sítě export_mode = PV_SURPLUS (ne SELL) 0 max
Výchozí ostatní PASSIVE (nabíjení bez exportního záměru) max max
Držet baterii, brát ze sítě grid_setpoint_w > 0 a battery_w ≤ 0 max 0

V obou exportních případech platí, že export_limit_w je tvrdý site/inverter cap. Nejde o forecastový odhad exportu, takže se může pustit plný přetok v rámci distribučního limitu.

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 (vůči režimu 142 zero export a interní logice měniče — viz modbus-registers.md, pass-through).

  • 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í).

Implementace (BLOCK_EXPORT): při effective_sell_price < 0 (slot z plánu) EMS drží fyzicky stále PASSIVE, ale zapne zákaz exportu přebytků pro řiditelnou FVE:

  • reg 145 = 0 (solar sell disabled) mimo SELL
  • BA81: navíc přes reg 178 (bits01; v některých UI jako “register 179”) aktivuje „MI export to Grid cutoff“ pro mikroinvertory na GEN portu (jen pokud je asset_inverter.deye_gen_microinverter_cutoff_enabled = true). Týká se jen výroby, kterou Deye umí ovlivnit; pv-b / ongrid GEN u home-01 tímto neomezíš.

PV1/PV2 vs. GEN port (důležité pro BLOCK_EXPORT)

  • PV1/PV2 (hlavní stringy na DC vstupu Deye): výkon je v režimu zero-export řiditelný (střídač umí výrobu stáhnout až k nule, pokud není odběr a baterie už nemůže nabíjet). Při BLOCK_EXPORT tedy dává smysl „zakázat export“ přes reg 145 = 0 Deye zamezí přetokům z těchto stringů.
  • GEN port (AC coupling / mikroinvertory / ongrid GEN): výkon nelze plynule omezovat. Pole vyrábí „co dá slunce“ a pokud ho nespotřebuje dům + EV/TČ + baterie, přebytek fyzicky teče do sítě.
    • U instalací typu BA81 je proto k dispozici jen tvrdý cut-off (reg 179 bits01).
    • U malé baterie (např. BA81 ~6 kW max charge a navíc při vysokém SoC ještě méně) může při plném osvitu často nastat přebytek i při BLOCK_EXPORT a bez cut-off by šel do sítě.
    • Naopak při malém osvitu / velké spotřebě jsou „každé watty z GEN“ užitečné (jít do domu/baterie) a cut-off by zbytečně zahodil výrobu.

Z toho plyne: cut-off GEN portu je smysluplné řídit podle očekávaného přebytku, ne jen podle „sell < 0“. Detail návrhu implementace je v docs/04-modules/planning.md (sekce o GEN portu a export banu).

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 (hard constraint, pokud má lokalita k dispozici GEN port cut-off; jinak solver export jen penalizuje přes zápornou cenu)
  • 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)