121 lines
9.7 KiB
Markdown
121 lines
9.7 KiB
Markdown
# 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í*).
|
||
|
||
**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 179** (bits0–1) 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ů.\n+- **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ě.\n+ - U instalací typu **BA81** je proto k dispozici jen **tvrdý cut-off** (reg 179 bits0–1).\n+ - 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ě.\n+ - 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.\n+
|
||
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=True` → **108 i 109 = max** (bez variant ZERO výše), reg. **142** dle DB, TOU SOC = **`min_soc_percent`**. **PRESERVE:** `lock_battery` → **108 = 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 | TČ | 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`)
|