nastavitelny max sollar dle stridace (ulozeno v DB)
This commit is contained in:
@@ -108,7 +108,7 @@ Projekt je **SQL-first**: doménová logika, agregace, joiny mezi tabulkami a st
|
||||
|
||||
17. **Modbus zápis = journal.** Každý zápis do zařízení přes control exporter se loguje do `ems.modbus_command`. **Verifikační job** běží každé **2 minuty** a ověřuje nedávno zápis (`written` → čtení registru). Při **mismatch** po max. **3** pokusech o zápis → u běžných registrů přepnutí na **SELF_SUSTAIN** (`run_fn_set_mode_with_discord` → `fn_set_mode`, `activated_by` = `system:mismatch`) + **Discord** při skutečné změně režimu. **Výjimka:** souvislý blok Deye **62–64** (čas) → po 3 neúspěšných ověřeních **bez** změny režimu, kritický **Discord** (`notify_modbus_clock_verify_exhausted`). **Obecně:** při jakékoli změně `mode_code` z Pythonu (`POST /api/v1/sites/{id}/mode`, mismatch → SELF_SUSTAIN, `fn_expire_modes`) lze Discord zapnout přes `DISCORD_WEBHOOK_URL`. Detail: `docs/04-modules/modbus-command-journal.md`.
|
||||
|
||||
18. **Deye zápis registrů 60–499:** pouze **FC 0x10** (`write_registers`), **nikdy** FC 0x06 pro tento rozsah; **`execute_modbus_commands`** slučuje souvislé adresy do jednoho FC 0x10. **Fyzický režim Deye** (`PASSIVE` / `CHARGE` / `SELL`): **výhradně** `get_deye_mode` v `exporter_monolith.py` (bez wattových prahů: **SELL** při `battery_w` < 0 a `grid_setpoint_w` < 0; **CHARGE** při obou > 0; jinak **PASSIVE**). **PASSIVE (ZERO, AUTO):** **`export_mode=PV_SURPLUS`** → **108=0**, **109=max**, **142**=`deye_zero_export_mode` (ne selling first); jinak **108/109** dle `deye_battery_charge_discharge_amps` / `_deye_zero_export_amps_for_passive` (import bez vybíjení → **109=0**); **TOU SOC** (reg 166+): **PASSIVE** = **`min_soc_percent`**, **CHARGE** = **`max_soc_percent`** (clamp 10–100 z DB), **SELL** = **`reserve_soc_percent`** (`_deye_passive_tou_battery_soc_pct`, `_deye_tou_params`). **SELL:** 108=0, 109=max, **178**=32 (peak shaving off), **143** omezeno podle `|grid_setpoint_w|`; **142/145/TOU** jako v `write_inverter_setpoints`. **Reg 340** (*max solar power*, W): jen pokud `ems.fn_site_has_active_green_bonus_pv(site_id)` (lokalita se zeleným bonusem na PV poli) **a** `ems.fn_inverter_pv_a_max_w(inverter_id) > 0`; hodnota z `pv_a_forecast_solver_w` / `pv_a_curtailed_w` (AUTO). **Není** v `DEYE_CRITICAL_REGS_SELF_SUSTAIN` — verify mismatch nečeká přepnutí do SELF_SUSTAIN. **PRESERVE:** `lock_battery` → 108/109=0. **Čas 62–64**, bloky TOU **1–2** vs **3–6**, verify, Discord: beze změny oproti dřívějšímu chování — plný popis **`docs/04-modules/modbus-registers.md`** a **`docs/04-modules/operating-modes.md`**.
|
||||
18. **Deye zápis registrů 60–499:** pouze **FC 0x10** (`write_registers`), **nikdy** FC 0x06 pro tento rozsah; **`execute_modbus_commands`** slučuje souvislé adresy do jednoho FC 0x10. **Fyzický režim Deye** (`PASSIVE` / `CHARGE` / `SELL`): **výhradně** `get_deye_mode` v `exporter_monolith.py` (bez wattových prahů: **SELL** při `battery_w` < 0 a `grid_setpoint_w` < 0; **CHARGE** při obou > 0; jinak **PASSIVE**). **PASSIVE (ZERO, AUTO):** **`export_mode=PV_SURPLUS`** → **108=0**, **109=max**, **142**=`deye_zero_export_mode` (ne selling first); jinak **108/109** dle `deye_battery_charge_discharge_amps` / `_deye_zero_export_amps_for_passive` (import bez vybíjení → **109=0**); **TOU SOC** (reg 166+): **PASSIVE** = **`min_soc_percent`**, **CHARGE** = **`max_soc_percent`** (clamp 10–100 z DB), **SELL** = **`reserve_soc_percent`** (`_deye_passive_tou_battery_soc_pct`, `_deye_tou_params`). **SELL:** 108=0, 109=max, **178**=32 (peak shaving off), **143** omezeno podle `|grid_setpoint_w|`; **142/145/TOU** jako v `write_inverter_setpoints`. **Reg 340** (*max solar power*, W): jen pokud `ems.fn_site_has_active_green_bonus_pv(site_id)` **a** `ems.fn_inverter_pv_a_max_w(inverter_id) > 0` (strop z `deye_reg340_max_solar_w`, typ. 32k home-01 / 65k jinde, ne součet Wp; min `deye_reg340_min_solar_w`, home-01 400); hodnota z plánu / curtailu (AUTO). **Není** v `DEYE_CRITICAL_REGS_SELF_SUSTAIN` — verify mismatch nečeká přepnutí do SELF_SUSTAIN. **PRESERVE:** `lock_battery` → 108/109=0. **Čas 62–64**, bloky TOU **1–2** vs **3–6**, verify, Discord: beze změny oproti dřívějšímu chování — plný popis **`docs/04-modules/modbus-registers.md`** a **`docs/04-modules/operating-modes.md`**.
|
||||
|
||||
19. **Baterie – export v LP:** V `solve_dispatch` binárka `z_export[t]`: pokud `grid_export` v daném slotu **≥ 1** W, platí koncové `soc[t] ≥ arb_base_wh` (ekonomická rezerva z DB, ne časová řada `arb_floor_series`). Bez exportu může plán jít k `min_soc_percent` (provozní podlaha; u paralelních packů často 11–12 %, migrace V029 + komentář sloupce).
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ DEYE_REGISTER_NAMES: dict[int, str] = {
|
||||
142: "limit_control (0=selling first, 1=zero export to load, 2=zero export to CT)",
|
||||
143: "export_limit_w (max export do sítě)",
|
||||
145: "solar_sell (0=disabled, 1=enabled)",
|
||||
340: "max_solar_power_w (strop DC PV A v W; součet nominal_power_wp řiditelných polí)",
|
||||
340: "max_solar_power_w (strop DC PV A v W; cap z fn_inverter_pv_a_max_w / deye_reg340_max_solar_w)",
|
||||
178: "control_board_special_1 (bits0-1: MI export cutoff disable=2 enable=3; bits4-5 peak shaving 32/48)",
|
||||
148: "time_point_1_time",
|
||||
149: "time_point_2_time",
|
||||
@@ -157,11 +157,21 @@ def next_slot_hhmm() -> int:
|
||||
return next_hour * 100 + next_min
|
||||
|
||||
|
||||
def compute_pv_a_reg340_max_solar_w(cap_w: int, forecast_w: int, curtail_w: int) -> int:
|
||||
def compute_pv_a_reg340_max_solar_w(
|
||||
cap_w: int,
|
||||
forecast_w: int,
|
||||
curtail_w: int,
|
||||
*,
|
||||
min_w: int = 0,
|
||||
) -> int:
|
||||
"""Hodnota pro Deye reg 340 (max solar power, W) z capu a plánovaného curtailmentu pole A."""
|
||||
if curtail_w <= 0:
|
||||
return int(cap_w)
|
||||
return max(0, min(int(cap_w), int(forecast_w) - int(curtail_w)))
|
||||
raw = int(cap_w)
|
||||
else:
|
||||
raw = max(0, min(int(cap_w), int(forecast_w) - int(curtail_w)))
|
||||
if raw > 0 and int(min_w) > 0:
|
||||
return max(int(min_w), raw)
|
||||
return raw
|
||||
|
||||
|
||||
def _prague_minute_start_utc() -> datetime:
|
||||
|
||||
@@ -30,8 +30,10 @@ class InverterConfig:
|
||||
deye_tou_inactive_signature: str | None = None
|
||||
deye_zero_export_mode: int = 1
|
||||
deye_gen_microinverter_cutoff_enabled: bool = False
|
||||
#: Součet nominal_power_wp controllable PV na invertoru; 0 = EMS nezapisuje reg 340.
|
||||
#: Strop reg 340 (W) z fn_inverter_pv_a_max_w; 0 = EMS nezapisuje reg 340.
|
||||
pv_a_cap_w: int = 0
|
||||
#: Minimální hodnota reg 340 přijatá firmwarem (0 nebo např. 400 u staršího Deye).
|
||||
pv_a_reg340_min_w: int = 0
|
||||
#: True = EMS smí řídit Deye reg 340 (max solar power); z SQL `fn_site_has_active_green_bonus_pv(site_id)`.
|
||||
deye_reg340_pv_a_control_enabled: bool = False
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ async def export_setpoints(site_id: int, db: asyncpg.Connection) -> None:
|
||||
try:
|
||||
inv_for_pv = await _load_inverter_config(site_id, db)
|
||||
cap_pv = int(inv_for_pv.pv_a_cap_w) if inv_for_pv is not None else 0
|
||||
min_pv = int(inv_for_pv.pv_a_reg340_min_w) if inv_for_pv is not None else 0
|
||||
reg340_en = (
|
||||
bool(inv_for_pv.deye_reg340_pv_a_control_enabled)
|
||||
if inv_for_pv is not None
|
||||
@@ -49,12 +50,14 @@ async def export_setpoints(site_id: int, db: asyncpg.Connection) -> None:
|
||||
mode,
|
||||
pi_now,
|
||||
pv_a_cap_w=cap_pv,
|
||||
pv_a_reg340_min_w=min_pv,
|
||||
reg340_pv_a_control_enabled=reg340_en,
|
||||
)
|
||||
sp_next = _build_setpoints(
|
||||
mode,
|
||||
pi_next,
|
||||
pv_a_cap_w=cap_pv,
|
||||
pv_a_reg340_min_w=min_pv,
|
||||
reg340_pv_a_control_enabled=reg340_en,
|
||||
)
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ async def _load_inverter_config(
|
||||
SELECT
|
||||
ai.id, ai.code,
|
||||
coalesce(ems.fn_inverter_pv_a_max_w(ai.id), 0) AS pv_a_cap_w,
|
||||
coalesce(ai.deye_reg340_min_solar_w, 0) AS pv_a_reg340_min_w,
|
||||
se.host, se.port, se.unit_id,
|
||||
sgc.max_export_power_w,
|
||||
sgc.max_import_power_w,
|
||||
@@ -182,6 +183,7 @@ async def _load_inverter_config(
|
||||
row["deye_gen_microinverter_cutoff_enabled"] or False
|
||||
),
|
||||
pv_a_cap_w=int(row["pv_a_cap_w"] or 0),
|
||||
pv_a_reg340_min_w=int(row["pv_a_reg340_min_w"] or 0),
|
||||
deye_reg340_pv_a_control_enabled=bool(
|
||||
row["deye_reg340_pv_a_control_enabled"] or False
|
||||
),
|
||||
|
||||
@@ -97,6 +97,7 @@ def _build_setpoints(
|
||||
pi: Any | None,
|
||||
*,
|
||||
pv_a_cap_w: int = 0,
|
||||
pv_a_reg340_min_w: int = 0,
|
||||
reg340_pv_a_control_enabled: bool = False,
|
||||
) -> ControlSetpoints | None:
|
||||
code = mode.mode_code
|
||||
@@ -154,7 +155,10 @@ def _build_setpoints(
|
||||
pv_a_allowed = None
|
||||
else:
|
||||
pv_a_allowed = compute_pv_a_reg340_max_solar_w(
|
||||
int(pv_a_cap_w), forecast, curtail
|
||||
int(pv_a_cap_w),
|
||||
forecast,
|
||||
curtail,
|
||||
min_w=int(pv_a_reg340_min_w),
|
||||
)
|
||||
return ControlSetpoints(
|
||||
battery_w=bat_w,
|
||||
|
||||
@@ -52,6 +52,15 @@ class ComputePvAReg340Tests(unittest.TestCase):
|
||||
def test_curtail_floor_zero(self) -> None:
|
||||
self.assertEqual(compute_pv_a_reg340_max_solar_w(10_000, 1000, 5000), 0)
|
||||
|
||||
def test_min_clamp_when_positive(self) -> None:
|
||||
self.assertEqual(
|
||||
compute_pv_a_reg340_max_solar_w(32_000, 5000, 4600, min_w=400),
|
||||
400,
|
||||
)
|
||||
|
||||
def test_min_not_applied_when_curtail_to_zero(self) -> None:
|
||||
self.assertEqual(compute_pv_a_reg340_max_solar_w(10_000, 1000, 5000, min_w=400), 0)
|
||||
|
||||
|
||||
class BuildSetpointsReg340Tests(unittest.TestCase):
|
||||
def test_with_cap_sets_pv_a_allowed(self) -> None:
|
||||
|
||||
33
db/migration/V082__deye_reg340_inverter_limits.sql
Normal file
33
db/migration/V082__deye_reg340_inverter_limits.sql
Normal file
@@ -0,0 +1,33 @@
|
||||
-- Reg 340 (max solar power): strop dle výkonu střídače, ne součtu Wp polí; min dle firmware.
|
||||
alter table ems.asset_inverter
|
||||
add column if not exists deye_reg340_max_solar_w int,
|
||||
add column if not exists deye_reg340_min_solar_w int not null default 0;
|
||||
|
||||
comment on column ems.asset_inverter.deye_reg340_max_solar_w is
|
||||
'Horní strop zápisu Deye reg 340 (max solar power, W). Studené panely mohou překročit součet Wp — použít plný DC limit střídače (např. 32000 home-01, 65000 větší hybridy). NULL = fallback max_dc_input_w, pak součet Wp řiditelných polí.';
|
||||
|
||||
comment on column ems.asset_inverter.deye_reg340_min_solar_w is
|
||||
'Minimální hodnota reg 340 přijatá firmwarem střídače (W). 0 = bez spodního limitu; starší Deye (home-01) často 400.';
|
||||
|
||||
-- home-01: SUN-20K, reg 340 max 32 kW, firmware min 400 W
|
||||
update ems.asset_inverter inv
|
||||
set
|
||||
deye_reg340_max_solar_w = 32000,
|
||||
deye_reg340_min_solar_w = 400
|
||||
from ems.site s
|
||||
where s.id = inv.site_id
|
||||
and s.code = 'home-01'
|
||||
and inv.code = 'deye-main'
|
||||
and inv.controllable = true;
|
||||
|
||||
-- Ostatní řízené Deye hybridy: 65 kW strop, min 0 (novější firmware)
|
||||
update ems.asset_inverter inv
|
||||
set
|
||||
deye_reg340_max_solar_w = coalesce(inv.deye_reg340_max_solar_w, 65000),
|
||||
deye_reg340_min_solar_w = 0
|
||||
from ems.site s
|
||||
where s.id = inv.site_id
|
||||
and s.code <> 'home-01'
|
||||
and inv.code = 'deye-main'
|
||||
and inv.controllable = true
|
||||
and inv.active = true;
|
||||
@@ -1,14 +1,27 @@
|
||||
-- Cap pro reg 340 max solar power (W): součet nominal_power_wp řiditelných PV polí na invertoru.
|
||||
-- Cap pro reg 340 max solar power (W): plný výkon střídače, ne jen součet Wp polí A.
|
||||
create or replace function ems.fn_inverter_pv_a_max_w(p_inverter_id int)
|
||||
returns int
|
||||
language sql
|
||||
stable
|
||||
as $$
|
||||
select coalesce(sum(nominal_power_wp), 0)::int
|
||||
from ems.asset_pv_array
|
||||
where inverter_id = p_inverter_id
|
||||
and controllable = true
|
||||
with pv as (
|
||||
select coalesce(sum(nominal_power_wp), 0)::int as wp_sum
|
||||
from ems.asset_pv_array
|
||||
where inverter_id = p_inverter_id
|
||||
and controllable = true
|
||||
)
|
||||
select case
|
||||
when (select wp_sum from pv) <= 0 then 0
|
||||
else coalesce(
|
||||
nullif(ai.deye_reg340_max_solar_w, 0),
|
||||
nullif(ai.max_dc_input_w, 0),
|
||||
(select wp_sum from pv),
|
||||
0
|
||||
)::int
|
||||
end
|
||||
from ems.asset_inverter ai
|
||||
where ai.id = p_inverter_id
|
||||
$$;
|
||||
|
||||
comment on function ems.fn_inverter_pv_a_max_w(int) is
|
||||
'Cap pro reg 340 (max solar power, W) = součet nominal_power_wp řiditelných PV polí na daném invertoru. 0 = EMS reg 340 neaktivní (skip zápisu).';
|
||||
'Cap pro reg 340 (max solar power, W): deye_reg340_max_solar_w, jinak max_dc_input_w, jinak součet Wp řiditelných polí. 0 = bez řiditelného PV A nebo bez capu — EMS reg 340 nezapisuje.';
|
||||
|
||||
@@ -135,7 +135,7 @@ CREATE TABLE asset_battery (
|
||||
### `asset_pv_array`
|
||||
Každé FVE pole zvlášť – důležité pro predikci (azimut, sklon). **Zelený bonus** (dotace za vyrobenou elektřinu) se váže na **úroveň pole**, ne na celou lokalitu: které pole má bonus, jaká je sazba Kč/kWh a platnost, určují sloupce `green_bonus_*`. Výpočet příjmu za interval probíhá funkcí `ems.fn_green_bonus_revenue` z výroby v Wh; není součástí efektivní prodejní ceny ze sítě.
|
||||
|
||||
**Deye reg 340 (max solar power, W):** strop pro řiditelné DC pole A na hybridu počítá `ems.fn_inverter_pv_a_max_w(inverter_id)` jako **součet `nominal_power_wp`** řádků s `controllable = true` vázaných na daný invertor. Zápis z EMS je povolen jen na lokalitách se **zeleným bonusem na PV poli** (`ems.fn_site_has_active_green_bonus_pv(site_id)` — aktivní `asset_pv_array.green_bonus_*` v kalendářním dni Europe/Prague); jinak EMS reg 340 nemění (invertor zůstane na poslední hodnotě).
|
||||
**Deye reg 340 (max solar power, W):** strop `ems.fn_inverter_pv_a_max_w(inverter_id)` = `asset_inverter.deye_reg340_max_solar_w` (seed home-01 **32 000**, ostatní Deye **65 000**), fallback `max_dc_input_w`, pak součet Wp řiditelných polí; funkce vrací **0** bez řiditelného PV A. Spodní limit zápisu: `deye_reg340_min_solar_w` (home-01 **400**, jinde **0**). Zápis jen se zeleným bonusem (`fn_site_has_active_green_bonus_pv`).
|
||||
|
||||
```sql
|
||||
CREATE TABLE asset_pv_array (
|
||||
|
||||
@@ -144,7 +144,7 @@ bits 0–1). Detail registrů: [`modbus-registers.md`](modbus-registers.md) (reg
|
||||
### PV A curtailment — zápis reg 340 (max solar power)
|
||||
|
||||
- **Implementace:** `backend/services/control/exporter_monolith.py` — `export_setpoints` načte cap v `_load_inverter_config` (`ems.fn_inverter_pv_a_max_w(ai.id)`), `_build_setpoints` v režimu **AUTO** dopočítá `ControlSetpoints.pv_a_allowed_w`, `write_inverter_setpoints` zařadí **reg 340**, pokud je `fn_site_has_active_green_bonus_pv` aktivní, cap > 0 a `pv_a_allowed_w` je vyplněné.
|
||||
- **Data:** `pv_a_forecast_solver_w` / `pv_a_curtailed_w` z aktivního `planning_interval` (json z `ems.fn_planning_interval_at_offset`); cap = součet `nominal_power_wp` řiditelných polí na invertoru (bez nového sloupce v DB).
|
||||
- **Data:** `pv_a_forecast_solver_w` / `pv_a_curtailed_w` z aktivního `planning_interval`; cap = `fn_inverter_pv_a_max_w` (`deye_reg340_max_solar_w` na `asset_inverter`, home-01 **32 kW**, ostatní **65 kW**); min = `deye_reg340_min_solar_w` (home-01 **400 W**).
|
||||
- **Policy PV A off (jen na site se zeleným bonusem na PV):** pokud `ems.fn_site_has_active_green_bonus_pv(site_id)` a v aktuálním slotu zároveň `effective_buy_price < 0` a `effective_sell_price < 0` a `pv_b_forecast_solver_w > 0` (PV B vyrábí), exporter nastaví `pv_a_allowed_w = 0` (reg 340) i když je forecast PV A nulový — cílem je držet headroom v baterii pro PV B / další záporný nákup.
|
||||
- **Bez zápisu reg 340:** `plan_skips_deye_reg340_write` — žádný export z plánu, `battery_setpoint_w ≤ 0`, `pv_a_curtailed_w = 0` → `pv_a_allowed_w = None` (invertor řídí pole A sám). Ověření: `pytest backend/tests/test_control_exporter_reg340.py`.
|
||||
- **Verify:** reg **340** není kritický → po 3× mismatch verify **bez** přepnutí do SELF_SUSTAIN (stejně jako reg 178); viz [`modbus-command-journal.md`](modbus-command-journal.md).
|
||||
|
||||
@@ -19,7 +19,7 @@ EMS zapisuje řídící hodnoty přes journal (`modbus_command`) a **`write_regi
|
||||
| 141 | Energy mgmt mode | bitmask | — | EMS vždy **0** (neměnit jinak) |
|
||||
| 142 | Limit control (System work mode) | 0/1/2 | — | **0** = selling first, **1** = zero export to load, **2** = zero export to CT. Hodnota v non-SELL režimech pochází z `asset_inverter.deye_zero_export_mode` (závisí na instalaci – viz tabulka níže). V režimu SELL vždy **0**. |
|
||||
| 145 | Solar sell | 0/1 | — | **0** = disabled (přebytek FVE na **straně měniče** se nesmí vést do sítě — curtailment vůči síti), **1** = enabled. Platí jen pro **FVE pod kontrolou Deye** (`controllable = true`); druhá pole (např. **pv-b** u home-01) EMS tímto registerem neřídí. EMS dnes **vždy zapisuje 1**; směr přebytku (baterie vs. síť) řeší energie management měniče a **142**, ne umělé **108 = 0** (viz pass-through níže). |
|
||||
| 340 | Max solar power | 0 … cap (W) | 1 W | Strop výkonu DC PV řízeného střídačem (pole A). EMS zapisuje jen pokud `ems.fn_site_has_active_green_bonus_pv(site_id)` (zelený bonus na PV poli) **a** `ems.fn_inverter_pv_a_max_w(inverter_id) > 0`. Hodnota z aktivního `planning_interval`: bez curtailmentu = cap; s `pv_a_curtailed_w > 0` = `clamp(0, cap, pv_a_forecast_solver_w − pv_a_curtailed_w)`. **Není** v `DEYE_CRITICAL_REGS_SELF_SUSTAIN` — verify mismatch nečeká přepnutí do SELF_SUSTAIN. |
|
||||
| 340 | Max solar power | min … cap (W) | 1 W | Strop výkonu DC PV řízeného střídačem (pole A). Cap z `fn_inverter_pv_a_max_w` (`deye_reg340_max_solar_w`, typ. **32 000** home-01, **65 000** větší hybridy), ne součet Wp — studené panely mohou překročit nominál. Min z `deye_reg340_min_solar_w` (home-01 **400 W**, jinde **0** dle firmware). EMS zapisuje jen při zeleném bonusu a cap > 0. **Není** v `DEYE_CRITICAL_REGS_SELF_SUSTAIN`. |
|
||||
| 143 | Export limit W | závisí na typu (SUN-20K až ~13 500) | 1 W | Max export do sítě; hodnota z `site_grid_connection.max_export_power_w`. EMS ji neodvozuje z forecastu ani z `grid_setpoint_w`; pro exportní sloty je to tvrdý site/inverter cap. |
|
||||
| 178 | Control board special 1 | bitmask | — | Bitové pole pro více funkcí. **EMS používá:** (a) bits **4–5** pro peak shaving switch: **32** (`0b00100000`, bit4–5 = **10**) v režimu **SELL**; **48** (`0b00110000`, bit4–5 = **11**) v **PASSIVE/CHARGE**. (b) **BA81:** bits **0–1** pro „MI export to Grid cutoff“ (AC coupling / GEN): **2** = disable (cutoff OFF), **3** = enable (cutoff ON). EMS zapisuje jako **read-modify-write** (zachová ostatní bity). V některých manuálech/UI je to označené jako „register 179“ (1-based). |
|
||||
| 190 | GEN peak shaving | 0–16000 | 1 W | Peak shaving na GEN portu |
|
||||
@@ -30,7 +30,7 @@ EMS zapisuje řídící hodnoty přes journal (`modbus_command`) a **`write_regi
|
||||
### Reg 340 (max solar power)
|
||||
|
||||
- **FC 0x10**, jednotka **W**; firmware omezuje strop výroby z řiditelných stringů (pole A na hybridu).
|
||||
- **Kdy EMS zapisuje:** `ems.fn_site_has_active_green_bonus_pv(site_id)` **a** `ems.fn_inverter_pv_a_max_w(inverter_id) > 0` (součet `nominal_power_wp` z `asset_pv_array` kde `controllable = true`). Při součtu **0** nebo bez aktivního zeleného bonusu EMS reg 340 **nezapisuje** (ruční hodnota v invertoru zůstane).
|
||||
- **Kdy EMS zapisuje:** `ems.fn_site_has_active_green_bonus_pv(site_id)` **a** `ems.fn_inverter_pv_a_max_w(inverter_id) > 0` (řiditelné pole A + nenulový strop střídače z `deye_reg340_max_solar_w` / `max_dc_input_w`). Bez bonusu nebo cap **0** EMS reg 340 **nezapisuje**.
|
||||
- **Hodnota:** z `ControlSetpoints.pv_a_allowed_w` (AUTO): bez curtailmentu = plný cap; při `pv_a_curtailed_w > 0` viz tabulka výše. Režimy **SELF_SUSTAIN / PRESERVE / CHARGE_CHEAP** mají `pv_a_allowed_w = None` → žádný zápis 340 z EMS v daném ticku.
|
||||
- **Bez zápisu 340 (2026-05):** pokud plán má **bez exportu** (`export_mode = NONE` nebo `grid_setpoint_w ≥ 0` a `export_limit_w = 0`), **bez nabíjení baterie** (`battery_setpoint_w ≤ 0`) a **bez curtailu A** (`pv_a_curtailed_w = 0`), EMS reg 340 **neposílá** — Deye řídí PV A přes **108/109/142** (zero export + 0 A nabíjení). Funkce `plan_skips_deye_reg340_write` v `setpoints.py`. Výjimka: explicitní curtail nebo záporné buy+sell s PV B → `pv_a_allowed_w` se dopočítá / vynuluje jako dřív.
|
||||
- **Živé čtení:** `read_deye_registers_live` vrací **`reg340_max_solar_power_w`** (integer) jen pokud je přepínač zapnutý; jinak **`null`** (bez extra FC3 čtení reg 340).
|
||||
|
||||
@@ -5,6 +5,14 @@ Formát: **datum (ISO)** · stručný důvod · soubory · chování / ověřen
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-28 — reg 340 cap z výkonu střídače, min dle firmware (V082)
|
||||
|
||||
**Změna:** `asset_inverter.deye_reg340_max_solar_w` / `deye_reg340_min_solar_w`; `fn_inverter_pv_a_max_w` bere strop z DB sloupce (home-01 **32 000 W**, ostatní Deye **65 000 W**), ne součet Wp polí — studené panely mohou překročit nominál. `compute_pv_a_reg340_max_solar_w(..., min_w=)` — spodní limit jen pro kladné hodnoty (home-01 min **400 W**).
|
||||
|
||||
**Ověření:** `select ems.fn_inverter_pv_a_max_w(<deye-main id>);` · `pytest backend/tests/test_control_exporter_reg340.py`.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-28 — reg 340 jen když plán curtailuje / exportuje / nabíjí
|
||||
|
||||
**Změna:** `plan_skips_deye_reg340_write` v `setpoints.py` — bez FC 0x10 na reg **340**, pokud slot nemá export, nabíjení baterie ani `pv_a_curtailed_w` (Deye řídí PV A přes 108/109/142).
|
||||
|
||||
Reference in New Issue
Block a user