second version
This commit is contained in:
@@ -1,132 +1,65 @@
|
||||
# Modul: Operating Modes (Provozní režimy)
|
||||
# Provozní režimy EMS
|
||||
|
||||
## Koncept
|
||||
## Přehled
|
||||
|
||||
EMS a Loxone komunikují přes **provozní režimy** – pojmenované stavy které mají smysl pro obě strany.
|
||||
| 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 | — |
|
||||
|
||||
EMS rozhoduje a přepíná režimy. Loxone dostane kód režimu jako číslo přes Virtual Input a ví jak se v daném režimu chovat **autonomně a nezávisle na EMS**.
|
||||
Implementace: omezení LP v `planning_engine.solve_dispatch()` podle `mode_code` z `ems.site_operating_mode`; zápis Deye v `control_exporter.write_inverter_setpoints()` (včetně `lock_battery` u PRESERVE).
|
||||
|
||||
```
|
||||
EMS backend (každou minutu)
|
||||
→ HTTP GET /dev/sps/io/EMS_Heartbeat/1 ← pulz do Loxone
|
||||
## Fyzické režimy Deye (výstup control_exporteru)
|
||||
|
||||
EMS backend (při přepnutí režimu)
|
||||
→ ems.fn_set_mode(site_id, 'SELF_SUSTAIN') ← zapsat do DB
|
||||
→ HTTP GET /dev/sps/io/EMS_Mode/2 ← informovat Loxone
|
||||
Detekce z `battery_w` a `grid_setpoint_w` (`get_deye_mode`):
|
||||
|
||||
Loxone (zcela nezávisle na EMS)
|
||||
→ sleduje přítomnost EMS_Heartbeat pulzů
|
||||
→ pokud 5min žádný pulz → sám přepne na SELF_SUSTAIN
|
||||
→ řídí střídač dle aktivního režimu bez čekání na setpointy
|
||||
```
|
||||
- **PASSIVE:** `grid_setpoint_w >= -200` → reg142=1, reg178=48, 108/109=max z DB (nebo 0/0 při `lock_battery`)
|
||||
- **SELL:** `grid_setpoint_w < -200` → reg142=0, reg178=32, 108/109=max
|
||||
- **CHARGE:** `grid_setpoint_w > 200` **a** `battery_w > 500` → reg142=1, reg178=48
|
||||
|
||||
**Klíčový princip:** Loxone watchdog nečte DB. Sleduje pouze HTTP pulzy přímo.
|
||||
Pokud padne celý server (RPi, Docker, síť) – Loxone to pozná sám a přepne bezpečný režim.
|
||||
`battery_w = None` (SELF_SUSTAIN – Deye řídí sám) ⇒ pro detekci režimu se bere jako 0 ⇒ při `grid_setpoint_w = 0` je výsledek **PASSIVE**; registry 108/109 se nastaví na **plné limity z DB** (ne na nulu).
|
||||
|
||||
Viz `docs/loxone-integration.md` pro kompletní popis Loxone implementace.
|
||||
## 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.
|
||||
|
||||
---
|
||||
|
||||
## Přehled režimů
|
||||
## Loxone a UI (shrnutí)
|
||||
|
||||
| Kód | Loxone int | EV | TČ | Baterie | Síť | Loxone autonomní |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `AUTO` | 1 | dle plánu | dle plánu | dle plánu | dle plánu | **ne** – čeká na setpointy |
|
||||
| `SELF_SUSTAIN` | 2 | ❌ stop | ❌ stop | vybíjí do domu | bez exportu | **ano** |
|
||||
| `CHARGE_CHEAP` | 3 | ❌ stop | ❌ stop | max nabíjení | import ok | **ne** – EMS posílá výkon |
|
||||
| `PRESERVE` | 4 | ❌ stop | ❌ stop | drží SoC | import ok | **ano** |
|
||||
| `MANUAL` | 0 | ❌ stop | ❌ stop | žádné akce | žádné akce | **ano** |
|
||||
|
||||
### `AUTO`
|
||||
Normální provoz. EMS posílá přesné setpointy W každých 15 minut.
|
||||
Loxone je čistý exekutor – přijme číslo a zapíše do střídače.
|
||||
Pokud setpoint nepřijde (výpadek EMS) → Loxone watchdog přepne na `SELF_SUSTAIN`.
|
||||
|
||||
### `SELF_SUSTAIN` ← výchozí stav + fallback
|
||||
Aktivuje se:
|
||||
- automaticky watchdogem při výpadku EMS (5min bez pulzu)
|
||||
- manuálně uživatelem z UI (dovolená, odchod z domu)
|
||||
- při prvním startu systému (seed data)
|
||||
|
||||
Loxone sám bez EMS:
|
||||
- FVE pokrývá spotřebu
|
||||
- baterie vybíjí do domu (ne do sítě)
|
||||
- blokuje export do sítě
|
||||
- zastavuje EV nabíjení a TČ
|
||||
|
||||
### `CHARGE_CHEAP`
|
||||
Manuální přepis. EMS posílá max charge setpoint.
|
||||
Použít při levné ceně nebo přetoku FVE ze sousedství (pokud víš o levné ceně dopředu).
|
||||
|
||||
### `PRESERVE`
|
||||
Dovolená / servis. Loxone drží baterii na aktuálním SoC, žádné optimalizace.
|
||||
Autonomní – Loxone nevyžaduje setpointy od EMS.
|
||||
|
||||
### `MANUAL`
|
||||
Technické práce. Žádná logika neřídí střídač. Pouze pro servis.
|
||||
|
||||
---
|
||||
|
||||
## Přepínání z UI (React)
|
||||
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/sites/{site_id}/mode
|
||||
{
|
||||
"mode": "SELF_SUSTAIN",
|
||||
"valid_until": null, // nebo "2025-03-15T06:00:00+01:00" pro dočasný přepis
|
||||
"notes": "Odjezd na dovolenou"
|
||||
"valid_until": null,
|
||||
"notes": "…"
|
||||
}
|
||||
```
|
||||
|
||||
Backend při přepnutí:
|
||||
1. Zavolá `ems.fn_set_mode(site_id, mode, 'user:'+username)` → zápis do DB + log
|
||||
2. Okamžitě odešle HTTP do Loxone: `/dev/sps/io/EMS_Mode/{loxone_mode_value}`
|
||||
3. Pokud `CHARGE_CHEAP` nebo návrat na `AUTO` → spustí replanning
|
||||
Backend: `ems.fn_set_mode` + HTTP na Loxone `/dev/sps/io/EMS_Mode/{loxone_mode_value}`. Dočasné přepisy s `valid_until` ruší `fn_expire_modes()`.
|
||||
|
||||
**Dočasný přepis s automatickým návratem:**
|
||||
`fn_expire_modes()` běží každou minutu a přepíná zpět lokality s prosahlým `valid_until`.
|
||||
**Klíčový princip:** Loxone watchdog nečte DB – sleduje pulzy `EMS_Heartbeat`. Detail: `docs/loxone-integration.md`.
|
||||
|
||||
---
|
||||
### Tabulka režimů (Loxone / zátěže)
|
||||
|
||||
## EMS restart / reconnect
|
||||
| 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 |
|
||||
|
||||
Při startu backendu:
|
||||
1. Přečíst z Loxone aktuální `EMS_Mode_Active` (Virtual Output) přes HTTP GET
|
||||
2. Porovnat s `ems.site_operating_mode` v DB
|
||||
3. Pokud Loxone přepnul na `SELF_SUSTAIN` během výpadku → logovat, informovat, spustit nový plán
|
||||
4. Přepnout na `AUTO` a začít posílat setpointy + heartbeat pulzy
|
||||
### Otevřené body
|
||||
|
||||
---
|
||||
|
||||
## Heartbeat v DB – pouze informační
|
||||
|
||||
Tabulka `ems.site_heartbeat` zaznamenává kdy EMS naposledy úspěšně odeslal pulz do Loxone.
|
||||
Slouží pro EMS dashboard (`vw_site_status.ems_heartbeat_status`) a případný alerting.
|
||||
|
||||
**Neplní funkci watchdogu** – to je čistě na Loxone straně.
|
||||
|
||||
```python
|
||||
# backend/services/control_exporter.py – každou minutu
|
||||
async def send_heartbeat(site_id: int, loxone_endpoint, db):
|
||||
try:
|
||||
await loxone_http.get(f"/dev/sps/io/EMS_Heartbeat/1")
|
||||
await db.execute(
|
||||
"SELECT ems.fn_update_heartbeat($1, 'ok', $2)",
|
||||
site_id, EMS_VERSION
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Heartbeat failed for site {site_id}: {e}")
|
||||
await db.execute(
|
||||
"SELECT ems.fn_update_heartbeat($1, 'error', $2)",
|
||||
site_id, EMS_VERSION
|
||||
)
|
||||
# EMS nemůže nic dělat – Loxone watchdog to vyřeší sám
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Otevřené body
|
||||
|
||||
- [ ] Ověřit Deye Modbus registry pro přepnutí Self-Consumption / Grid-First modu (pro SELF_SUSTAIN)
|
||||
- [ ] Implementace Loxone watchdog – viz `docs/loxone-integration.md`
|
||||
- [ ] Alert notifikace (email / push) pokud `ems_heartbeat_status = 'stale'` déle než 10 minut
|
||||
- [ ] Doplnit alerty při `ems_heartbeat_status = 'stale'`
|
||||
|
||||
Reference in New Issue
Block a user