gpt5.5 - odladeni dokumentace dle kodu
This commit is contained in:
@@ -5,8 +5,8 @@
|
||||
- Čte aktivní plán z DB pro daný 15min interval
|
||||
- Zkontroluje override záznamy
|
||||
- Zapíše setpointy do Deye přes Modbus TCP
|
||||
- Zapíše setpointy EV nabíječek přes Modbus TCP
|
||||
- Zapíše setpointy tepelného čerpadla přes Modbus TCP
|
||||
- EV nabíječky a tepelné čerpadlo zatím pouze vyhodnotí a zaloguje; konkrétní
|
||||
Modbus registry jsou TODO
|
||||
- Odešle potvrzovací setpointy do Loxone přes HTTP (Loxone jako exekutor fallback logiky)
|
||||
- Loguje každý write pro audit
|
||||
|
||||
@@ -19,10 +19,10 @@ DB (planning_interval + site_override)
|
||||
↓
|
||||
control_exporter.py (každých 15min nebo on-demand)
|
||||
├── Modbus write → Deye (baterie, grid limit)
|
||||
├── Modbus write → Teltonika EV nabíječka 1
|
||||
├── Modbus write → Teltonika EV nabíječka 2
|
||||
├── Modbus write → Samsung TČ
|
||||
└── HTTP POST → Loxone Virtual Inputs (informační setpointy)
|
||||
├── TODO/log → Teltonika EV nabíječka 1
|
||||
├── TODO/log → Teltonika EV nabíječka 2
|
||||
├── TODO/log → Samsung TČ
|
||||
└── HTTP GET → Loxone Virtual Inputs (informační setpointy)
|
||||
```
|
||||
|
||||
**Loxone role:** Loxone dostává setpointy jako informaci a jako fallback ochranu.
|
||||
@@ -34,7 +34,7 @@ Rozhodovací logika je v EMS, ne v Loxone.
|
||||
|
||||
| Trigger | Čas | Popis |
|
||||
|---|---|---|
|
||||
| Scheduled | každých 15min (xx:00, xx:15, xx:30, xx:45) | Standardní export na začátku intervalu |
|
||||
| Scheduled | každých 15min (`xx:14`, `xx:29`, `xx:44`, `xx:59`) | Export těsně před dalším slotem |
|
||||
| On-demand | po vytvoření nového plánu | Okamžitý export pokud plán překrývá aktuální čas |
|
||||
| On-demand | po vytvoření override | Okamžitá aplikace přepisu |
|
||||
|
||||
@@ -51,6 +51,8 @@ Ověření: logy backendu kolem pokusu **nebo** `select id,status,created_at fro
|
||||
## Logika exportu
|
||||
|
||||
```python
|
||||
# Zjednodušený historický náčrt. Aktuální implementace používá
|
||||
# ems.fn_planning_interval_at_offset(), ControlSetpoints a Deye journal.
|
||||
async def export_setpoints_for_interval(site_id: int, interval_start: datetime, db):
|
||||
"""
|
||||
Načte plánované setpointy pro daný interval, aplikuje overrides
|
||||
@@ -84,14 +86,11 @@ async def export_setpoints_for_interval(site_id: int, interval_start: datetime,
|
||||
|
||||
setpoints = apply_overrides(plan, overrides)
|
||||
|
||||
# 3. Zapsat do zařízení (paralelně)
|
||||
await asyncio.gather(
|
||||
write_inverter_setpoints(site_id, setpoints, db),
|
||||
write_ev_charger_setpoints(site_id, setpoints, db),
|
||||
write_heat_pump_setpoints(site_id, setpoints, db),
|
||||
write_loxone_setpoints(site_id, setpoints, db),
|
||||
return_exceptions=True
|
||||
)
|
||||
# 3. Zapsat Deye, zalogovat EV/TČ TODO, poslat Loxone
|
||||
await write_inverter_setpoints(site_id, setpoints, db)
|
||||
await write_ev_setpoints(site_id, setpoints, db) # TODO registry
|
||||
await write_heat_pump_setpoint(site_id, setpoints, db) # TODO registry
|
||||
await send_loxone_setpoints(site_id, setpoints, mode, db)
|
||||
|
||||
|
||||
def apply_overrides(plan, overrides) -> Setpoints:
|
||||
@@ -153,7 +152,7 @@ bits 0–1). Detail registrů: [`modbus-registers.md`](modbus-registers.md) (reg
|
||||
| **CHARGE** | `battery_w` > 0 **a** `grid_setpoint_w` > 0 |
|
||||
| **PASSIVE (ZERO)** | vše ostatní — reg. **108/109** dle `_deye_zero_export_amps_for_passive` (viz `operating-modes.md`) |
|
||||
|
||||
**PASSIVE** (AUTO, ZERO): výchozí **108** i **109** = maximum z DB; u exportu bez vybíjení **108 = 0**, u importu bez nabíjení **109 = 0** (`_deye_zero_export_amps_for_passive`). **TOU** z plánu. Reg. **142** = `deye_zero_export_mode`. Reg. **145** (solar sell): v kódu vždy **1** — význam přepínače a rozdíl vůči neřízeným FVE polím je v [`operating-modes.md`](operating-modes.md) (sekce *ZERO a zakázaný export*).
|
||||
**PASSIVE** (AUTO, ZERO): výchozí **108** i **109** = maximum z DB; u exportu bez vybíjení **108 = 0**, u importu bez nabíjení **109 = 0** (`_deye_zero_export_amps_for_passive`). **TOU** z plánu. Reg. **142** = `deye_zero_export_mode`. Reg. **145** (solar sell): **0** při `export_ban` mimo SELL, jinak **1** — význam přepínače a rozdíl vůči neřízeným FVE polím je v [`operating-modes.md`](operating-modes.md) (sekce *ZERO a zakázaný export*).
|
||||
|
||||
**SELF_SUSTAIN** zůstává **PASSIVE** v `get_deye_mode`; **108/109** jsou vždy **max z DB** (bez variant ZERO). Rozdíl je **`self_sustain_local_use=True`**: **TOU SOC** = **`min_soc_percent`**, `battery_w=None`.
|
||||
|
||||
@@ -165,7 +164,7 @@ bits 0–1). Detail registrů: [`modbus-registers.md`](modbus-registers.md) (reg
|
||||
| **109** (discharge A) | **0** | max / **0** (import, držet bat.) | **max z DB** | dle varianty |
|
||||
| **142** (limit control) | `deye_zero_export_mode` | `deye_zero_export_mode` | **0** (selling first) | `deye_zero_export_mode` |
|
||||
| **143** (export cap) | max z DB | max z DB | `min(max_site, max(200, \|grid_setpoint_w\|))` | max z DB |
|
||||
| **145** (solar sell) | 1 | 1 | 1 | 1 |
|
||||
| **145** (solar sell) | 1 / 0 při `export_ban` | 1 / 0 při `export_ban` | 1 | 1 / 0 při `export_ban` |
|
||||
| **178** (peak shaving) | 48 | 48 | **32** | 48 |
|
||||
|
||||
U **AUTO PASSIVE** závisí **108/109** na znaménkách plánu (viz `operating-modes.md`). **SELF_SUSTAIN** drží oba **max z DB**; **TOU SOC** ve všech PASSIVE větvích je **`min_soc_percent`** (viz `_deye_passive_tou_battery_soc_pct`). Liší se především **`battery_w`** a mapování **108/109**.
|
||||
@@ -184,6 +183,9 @@ Po zápisu na Modbus se hodnoty ověřují v `verify_modbus_commands` (`control_
|
||||
Při přechodu **SELF_SUSTAIN → AUTO** (`run_fn_set_mode_with_discord`) se na pozadí spustí **rolling replan**, aby aktivní plán odpovídal plné optimalizaci. Viz [`modbus-command-journal.md`](modbus-command-journal.md).
|
||||
|
||||
```python
|
||||
# Historický pseudokód. Aktuální Deye implementace používá journal
|
||||
# ems.modbus_command a FC 0x10 (`write_registers`) nad registry
|
||||
# 108/109/141/142/143/145/178/340 + TOU bloky.
|
||||
async def write_inverter_setpoints(site_id: int, setpoints: Setpoints, db):
|
||||
inverters = await db.fetch(
|
||||
"SELECT ai.*, se.host, se.port, se.unit_id "
|
||||
@@ -215,6 +217,10 @@ async def write_inverter_setpoints(site_id: int, setpoints: Setpoints, db):
|
||||
|
||||
## Zápis do Teltonika EV nabíječek (Modbus)
|
||||
|
||||
Aktuální implementace registry zatím nezapisuje. Funkce `write_ev_setpoints`
|
||||
načte schedulable nabíječky, spočítá proud podle `ev1/ev2_setpoint_w` a jen ho
|
||||
zaloguje jako `Modbus TODO`.
|
||||
|
||||
```python
|
||||
async def write_ev_charger_setpoints(site_id: int, setpoints: Setpoints, db):
|
||||
chargers = await db.fetch(
|
||||
@@ -250,6 +256,10 @@ async def write_ev_charger_setpoints(site_id: int, setpoints: Setpoints, db):
|
||||
|
||||
## Zápis do Samsung TČ (Modbus)
|
||||
|
||||
Aktuální implementace registry zatím nezapisuje. Funkce
|
||||
`write_heat_pump_setpoint` načte schedulable TČ a zaloguje požadované
|
||||
`heat_pump_enable` jako `Modbus TODO`.
|
||||
|
||||
```python
|
||||
async def write_heat_pump_setpoints(site_id: int, setpoints: Setpoints, db):
|
||||
heat_pumps = await db.fetch(
|
||||
@@ -297,20 +307,22 @@ async def write_loxone_setpoints(site_id: int, setpoints: Setpoints, db):
|
||||
# Loxone Virtual HTTP Input – každý setpoint = jeden HTTP GET/POST
|
||||
# Formát: /dev/sps/io/{VirtualInputName}/{value}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
await session.get(f"{base_url}/EMS_BatterySetpoint/{setpoints.battery_setpoint_w}")
|
||||
await session.get(f"{base_url}/EMS_GridSetpoint/{setpoints.grid_setpoint_w or 0}")
|
||||
await session.get(f"{base_url}/EMS_EVChargeTotal/{setpoints.ev_charge_power_w or 0}")
|
||||
await session.get(f"{base_url}/EMS_HeatPumpEnable/{1 if setpoints.heat_pump_enabled else 0}")
|
||||
await session.get(f"{base_url}/EMS_Mode/{mode.loxone_mode_value}")
|
||||
await session.get(f"{base_url}/EMS_Battery_Setpoint_W/{battery_w}")
|
||||
await session.get(f"{base_url}/EMS_Grid_Setpoint_W/{grid_w}")
|
||||
await session.get(f"{base_url}/EMS_EV1_Power_W/{ev1_power_w}")
|
||||
await session.get(f"{base_url}/EMS_EV2_Power_W/{ev2_power_w}")
|
||||
await session.get(f"{base_url}/EMS_HeatPump_Enable/{heat_pump_enable}")
|
||||
```
|
||||
|
||||
> Virtual Input jména v Loxone (`EMS_BatterySetpoint` atd.) je nutné vytvořit při konfiguraci Loxone projektu.
|
||||
> Virtual Input jména v Loxone (`EMS_Battery_Setpoint_W`, `EMS_Grid_Setpoint_W`
|
||||
> atd.) je nutné vytvořit při konfiguraci Loxone projektu.
|
||||
|
||||
---
|
||||
|
||||
## Konfigurace (env proměnné)
|
||||
|
||||
```env
|
||||
CONTROL_EXPORT_LEAD_TIME_SEC=10 # kolik sekund před začátkem intervalu exportovat
|
||||
CONTROL_MODBUS_TIMEOUT_SEC=5
|
||||
LOXONE_USER=admin # nebo přes auth_reference v site_endpoint
|
||||
LOXONE_PASSWORD=secret
|
||||
@@ -331,9 +343,8 @@ Fallback: pokud per-site webhook není vyplněný, použije se env `DISCORD_WEBH
|
||||
|
||||
## Otevřené body
|
||||
|
||||
- [ ] Doplnit Modbus write registry Deye (charge/discharge/export limit)
|
||||
- [ ] Doplnit Modbus write registry Teltonika (current limit, enable)
|
||||
- [ ] Doplnit Modbus write registry Samsung TČ (enable, target temp)
|
||||
- [ ] Loxone Virtual Input jména – dohodnout a vytvořit v Loxone projektu
|
||||
- [ ] Loxone Virtual Input jména z tohoto dokumentu vytvořit v Loxone projektu
|
||||
- [ ] Strategie rozdělení EV výkonu mezi 2 nabíječky (rovnoměrně vs dle stavu session)
|
||||
- [ ] Co dělat při selhání zápisu do jednoho zařízení (rollback ostatních?)
|
||||
|
||||
Reference in New Issue
Block a user