fix soc v TOU (ne 100) pri ne-grid-charge
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`.
|
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):** **108/109** dle `_deye_zero_export_amps_for_passive`; **TOU** z plánu. **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):** **108/109** dle `_deye_zero_export_amps_for_passive`; **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`**.
|
||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
||||||
|
|||||||
@@ -40,9 +40,6 @@ REG143_SELL_CAP_MIN_W = 200
|
|||||||
# Ostatní bity zachovat → read-modify-write.
|
# Ostatní bity zachovat → read-modify-write.
|
||||||
REG178_SELL = 0b00100000 # 32, grid peak shaving disable
|
REG178_SELL = 0b00100000 # 32, grid peak shaving disable
|
||||||
REG178_PASSIVE = 0b00110000 # 48, grid peak shaving enable (PASSIVE i CHARGE)
|
REG178_PASSIVE = 0b00110000 # 48, grid peak shaving enable (PASSIVE i CHARGE)
|
||||||
# TOU reg 166+ ve PASSIVE při prioritě baterie: signál střídači „využij celý dostupný rozsah“,
|
|
||||||
# ne provozní strop z DB (ten je pro LP / Wh – viz asset_battery.max_soc_percent).
|
|
||||||
DEYE_TOU_SOC_PASSIVE_BATTERY_PRIORITY_PCT = 100
|
|
||||||
# Verify: jen bity 4–5 (horní byte layout v dokumentaci); ostatní bity mohou mít firmware / Loxone
|
# Verify: jen bity 4–5 (horní byte layout v dokumentaci); ostatní bity mohou mít firmware / Loxone
|
||||||
REG178_VERIFY_MASK = 0x0030
|
REG178_VERIFY_MASK = 0x0030
|
||||||
# Reg 178 bits 0–1: MI export cutoff (AC coupling / GEN).
|
# Reg 178 bits 0–1: MI export cutoff (AC coupling / GEN).
|
||||||
@@ -1411,11 +1408,6 @@ def _clamp_deye_tou_soc_pct(pct: int) -> int:
|
|||||||
return max(5, min(95, pct))
|
return max(5, min(95, pct))
|
||||||
|
|
||||||
|
|
||||||
def _clamp_deye_tou_soc_pct_hi(pct: int, hi: int) -> int:
|
|
||||||
"""Stejné dolní omezení 5 % jako u TOU; horní mez z parametru (např. 100 u priority baterie)."""
|
|
||||||
return max(5, min(int(hi), int(pct)))
|
|
||||||
|
|
||||||
|
|
||||||
def _deye_tou_min_soc_pct(inv: InverterConfig) -> int:
|
def _deye_tou_min_soc_pct(inv: InverterConfig) -> int:
|
||||||
if inv.min_soc_percent is not None:
|
if inv.min_soc_percent is not None:
|
||||||
return _clamp_deye_tou_soc_pct(int(inv.min_soc_percent))
|
return _clamp_deye_tou_soc_pct(int(inv.min_soc_percent))
|
||||||
@@ -1429,39 +1421,23 @@ def _deye_tou_reserve_soc_pct(inv: InverterConfig) -> int:
|
|||||||
|
|
||||||
|
|
||||||
def _deye_passive_tou_battery_soc_pct(
|
def _deye_passive_tou_battery_soc_pct(
|
||||||
inv: InverterConfig,
|
inv: InverterConfig, _setpoints: ControlSetpoints
|
||||||
setpoints: ControlSetpoints,
|
|
||||||
) -> int:
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Hodnota SOC u Deye TOU řádku (reg 166+) ve fyzickém PASSIVE.
|
Hodnota SOC u Deye TOU řádku (reg 166+) ve fyzickém PASSIVE.
|
||||||
|
|
||||||
Na home-01 Deye interpretuje TOU % jako „kam má směřovat využití baterie“:
|
Vždy provozní minimum z DB (**``min_soc_percent``**, clamp 5–95 jako u všech TOU SOC)
|
||||||
je-li zapsané procento **nižší než skutečný SoC**, přebytek FVE míří spíš do sítě.
|
— signál „spodní pásmo“ pro firmware, aby baterii šlo použít pro překrytí zátěže bez
|
||||||
|
snahy o vysoký cílový SoC jen přes TOU.
|
||||||
|
|
||||||
Při **záporné vykupní** nebo **plánovaném nabíjení** (kladný ``battery_w``) EMS
|
Riziko spojené v minulosti s nízkým TOU („přebytek FVE tíhne do sítě“ při nízkém %
|
||||||
zapíše **100 %** do TOU (signál střídači „ber přebytek do baterie v celém rozsahu“).
|
oproti skutečnému SoC) řeší **LP**, **145** (**``export_ban``** při záporné vykupní),
|
||||||
**``max_soc_percent`` v DB** je odděleně: horní limit pro **plánovač / Wh bilance**
|
řez GEN (**178**) a další páky — ne zvyšování TOU nad **min_soc**. Přímé dobíjení ze
|
||||||
(denní provoz, viz komentář sloupce), **nikoli** časové „do kdy“.
|
sítě a cílové horní pásmo: větev **CHARGE** v ``_deye_tou_params`` (**``max_soc_percent``**).
|
||||||
|
|
||||||
Jinak zůstane provozní podlaha ``min_soc_percent`` (typicky nízká % → přetok do sítě
|
Argument ``_setpoints`` zůstává kvůli volajícím API; hodnoty z něj PASSIVE SOC nebere.
|
||||||
možný dle chování Deye).
|
|
||||||
|
|
||||||
Režim **SELF_SUSTAIN** (``self_sustain_local_use``): vždy ``min_soc_percent`` — nízké
|
|
||||||
TOU drží prioritu „baterie jako buffer“ při plném reg. 108/109 a reg. 142 zero-export;
|
|
||||||
neaplikuje se sem logika 100 % podle ceny (LP se v SELF_SUSTAIN nepoužívá).
|
|
||||||
"""
|
"""
|
||||||
mn = _deye_tou_min_soc_pct(inv)
|
return _deye_tou_min_soc_pct(inv)
|
||||||
if setpoints.self_sustain_local_use:
|
|
||||||
return mn
|
|
||||||
|
|
||||||
bat_w = 0 if setpoints.battery_w is None else int(setpoints.battery_w)
|
|
||||||
sell = setpoints.effective_sell_price_czk_kwh
|
|
||||||
want_battery_priority = bat_w > 0 or (sell is not None and float(sell) < 0)
|
|
||||||
|
|
||||||
if not want_battery_priority:
|
|
||||||
return mn
|
|
||||||
|
|
||||||
return _clamp_deye_tou_soc_pct_hi(DEYE_TOU_SOC_PASSIVE_BATTERY_PRIORITY_PCT, hi=100)
|
|
||||||
|
|
||||||
|
|
||||||
def _deye_zero_export_amps_for_passive(
|
def _deye_zero_export_amps_for_passive(
|
||||||
@@ -1521,7 +1497,8 @@ def _deye_tou_params(
|
|||||||
) -> tuple[int, int, bool]:
|
) -> tuple[int, int, bool]:
|
||||||
"""
|
"""
|
||||||
Parametry jednoho Deye time pointu: výkon W, SOC % (TOU reg 166+), grid_charge.
|
Parametry jednoho Deye time pointu: výkon W, SOC % (TOU reg 166+), grid_charge.
|
||||||
Ve PASSIVE viz _deye_passive_tou_battery_soc_pct (min vs. plný max z DB).
|
Ve PASSIVE: TOU SOC = ``min_soc_percent`` z DB; v CHARGE: horní hraniční SoC =
|
||||||
|
``asset_battery.max_soc_percent`` (clamp 10–100).
|
||||||
"""
|
"""
|
||||||
max_batt_w_discharge = int(inv.max_discharge_a * BATT_VOLTAGE_V)
|
max_batt_w_discharge = int(inv.max_discharge_a * BATT_VOLTAGE_V)
|
||||||
tp_discharge_w = 0 if setpoints.lock_battery else max_batt_w_discharge
|
tp_discharge_w = 0 if setpoints.lock_battery else max_batt_w_discharge
|
||||||
@@ -1534,7 +1511,7 @@ def _deye_tou_params(
|
|||||||
raw_bat = setpoints.battery_w
|
raw_bat = setpoints.battery_w
|
||||||
battery_w = int(raw_bat) if raw_bat is not None else 0
|
battery_w = int(raw_bat) if raw_bat is not None else 0
|
||||||
cap = int(inv.max_soc_percent) if inv.max_soc_percent is not None else 95
|
cap = int(inv.max_soc_percent) if inv.max_soc_percent is not None else 95
|
||||||
target_soc = max(10, min(95, cap))
|
target_soc = max(10, min(100, cap))
|
||||||
tp_charge_w = (
|
tp_charge_w = (
|
||||||
battery_watts_to_amps(battery_w, int(inv.max_charge_a)) * int(BATT_VOLTAGE_V)
|
battery_watts_to_amps(battery_w, int(inv.max_charge_a)) * int(BATT_VOLTAGE_V)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -158,8 +158,8 @@ class DeyeTouParamsTests(unittest.TestCase):
|
|||||||
self.assertFalse(g)
|
self.assertFalse(g)
|
||||||
self.assertEqual(s, 12)
|
self.assertEqual(s, 12)
|
||||||
|
|
||||||
def test_passive_negative_sell_steers_tou_above_current_soc(self) -> None:
|
def test_passive_negative_sell_tou_stays_min_soc(self) -> None:
|
||||||
"""Záporná vykupní → TOU SOC = 100 % (priorita akumulace vs. přetok)."""
|
"""PASSIVE: záporná vykupní nenastavuje TOU na 100 — zůstává min_soc (145/export_ban řeší síť)."""
|
||||||
sp = ControlSetpoints(
|
sp = ControlSetpoints(
|
||||||
battery_w=-400,
|
battery_w=-400,
|
||||||
grid_export_limit=0,
|
grid_export_limit=0,
|
||||||
@@ -175,9 +175,10 @@ class DeyeTouParamsTests(unittest.TestCase):
|
|||||||
self.assertEqual(get_deye_mode(sp), "PASSIVE")
|
self.assertEqual(get_deye_mode(sp), "PASSIVE")
|
||||||
_p, s, g = _deye_tou_params(sp, _inv(min_soc=12, reserve_soc=20))
|
_p, s, g = _deye_tou_params(sp, _inv(min_soc=12, reserve_soc=20))
|
||||||
self.assertFalse(g)
|
self.assertFalse(g)
|
||||||
self.assertEqual(s, 100)
|
self.assertEqual(s, 12)
|
||||||
|
|
||||||
def test_passive_planned_charge_steers_tou(self) -> None:
|
def test_passive_planned_pv_charge_tou_stays_min_soc(self) -> None:
|
||||||
|
"""PASSIVE s kladným battery_w bez grid importu: CHARGE to není — TOU je stále min_soc."""
|
||||||
sp = ControlSetpoints(
|
sp = ControlSetpoints(
|
||||||
battery_w=800,
|
battery_w=800,
|
||||||
grid_export_limit=0,
|
grid_export_limit=0,
|
||||||
@@ -193,7 +194,7 @@ class DeyeTouParamsTests(unittest.TestCase):
|
|||||||
self.assertEqual(get_deye_mode(sp), "PASSIVE")
|
self.assertEqual(get_deye_mode(sp), "PASSIVE")
|
||||||
_p, s, g = _deye_tou_params(sp, _inv(min_soc=12, reserve_soc=20))
|
_p, s, g = _deye_tou_params(sp, _inv(min_soc=12, reserve_soc=20))
|
||||||
self.assertFalse(g)
|
self.assertFalse(g)
|
||||||
self.assertEqual(s, 100)
|
self.assertEqual(s, 12)
|
||||||
|
|
||||||
def test_charge_unchanged_grid_charge(self) -> None:
|
def test_charge_unchanged_grid_charge(self) -> None:
|
||||||
sp = ControlSetpoints(
|
sp = ControlSetpoints(
|
||||||
@@ -212,6 +213,24 @@ class DeyeTouParamsTests(unittest.TestCase):
|
|||||||
self.assertTrue(g)
|
self.assertTrue(g)
|
||||||
self.assertEqual(s, 95)
|
self.assertEqual(s, 95)
|
||||||
|
|
||||||
|
def test_charge_target_soc_respects_max_soc_100(self) -> None:
|
||||||
|
sp = ControlSetpoints(
|
||||||
|
battery_w=5000,
|
||||||
|
grid_export_limit=0,
|
||||||
|
ev1_current_a=0,
|
||||||
|
ev2_current_a=0,
|
||||||
|
heat_pump_enable=False,
|
||||||
|
grid_setpoint_w=5000,
|
||||||
|
ev1_power_w=0,
|
||||||
|
ev2_power_w=0,
|
||||||
|
target_soc_pct=80,
|
||||||
|
)
|
||||||
|
self.assertEqual(get_deye_mode(sp), "CHARGE")
|
||||||
|
inv = replace(_inv(), max_soc_percent=100)
|
||||||
|
_p, s, g = _deye_tou_params(sp, inv)
|
||||||
|
self.assertTrue(g)
|
||||||
|
self.assertEqual(s, 100)
|
||||||
|
|
||||||
def test_charge_any_positive_pair_without_w_threshold(self) -> None:
|
def test_charge_any_positive_pair_without_w_threshold(self) -> None:
|
||||||
sp = ControlSetpoints(
|
sp = ControlSetpoints(
|
||||||
battery_w=50,
|
battery_w=50,
|
||||||
|
|||||||
@@ -168,11 +168,11 @@ bits 0–1). Detail registrů: [`modbus-registers.md`](modbus-registers.md) (reg
|
|||||||
| **145** (solar sell) | 1 | 1 | 1 | 1 |
|
| **145** (solar sell) | 1 | 1 | 1 | 1 |
|
||||||
| **178** (peak shaving) | 48 | 48 | **32** | 48 |
|
| **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**; liší se **TOU SOC** a `battery_w`.
|
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**.
|
||||||
|
|
||||||
Hodnota `deye_zero_export_mode` (1 = zero export to load, 2 = zero export to CT) pochází z `asset_inverter.deye_zero_export_mode` a závisí na fyzické instalaci (přítomnost CT). Detail v [`modbus-registers.md`](modbus-registers.md).
|
Hodnota `deye_zero_export_mode` (1 = zero export to load, 2 = zero export to CT) pochází z `asset_inverter.deye_zero_export_mode` a závisí na fyzické instalaci (přítomnost CT). Detail v [`modbus-registers.md`](modbus-registers.md).
|
||||||
|
|
||||||
**TOU (time points, reg. 166+):** SOC závisí na fyzickém režimu z `get_deye_mode` — **SELL** zapisuje ekonomickou rezervu (`reserve_soc_percent`), **PASSIVE** a neaktivní řádky **3–6** provozní minimum (`min_soc_percent`). Viz [`modbus-registers.md`](modbus-registers.md).
|
**TOU (time points, reg. 166+):** SOC podle `get_deye_mode` — **CHARGE**: `max_soc_percent` z DB (clamp 10–100), **SELL**: `reserve_soc_percent`, **PASSIVE** + neaktivní řádky **3–6**: **`min_soc_percent`**. Viz [`modbus-registers.md`](modbus-registers.md).
|
||||||
|
|
||||||
### Verifikace zápisů (journal) a SELF_SUSTAIN
|
### Verifikace zápisů (journal) a SELF_SUSTAIN
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ Z hlediska `get_deye_mode` je **SELF_SUSTAIN** stále **PASSIVE** (`battery_w` z
|
|||||||
|
|
||||||
- **108 / 109** = **max** z invertoru (DB) — plný rozsah nabíjení i vybíjení, aby přebytek FVE mohl do baterie.
|
- **108 / 109** = **max** z invertoru (DB) — plný rozsah nabíjení i vybíjení, aby přebytek FVE mohl do baterie.
|
||||||
- **142** = `asset_inverter.deye_zero_export_mode` (**1** = zero export to load, **2** = zero export to CT), stejně jako u ostatního PASSIVE mimo SELL.
|
- **142** = `asset_inverter.deye_zero_export_mode` (**1** = zero export to load, **2** = zero export to CT), stejně jako u ostatního PASSIVE mimo SELL.
|
||||||
- **TOU SOC** (reg 166+) = vždy **`min_soc_percent`** (typicky 12 %) — `_deye_passive_tou_battery_soc_pct` při tomto příznaku **ne** přepíná na 100 % podle vykupní ceny, protože LP se v SELF_SUSTAIN nepoužívá.
|
- **TOU SOC** (reg 166+) = vždy **`min_soc_percent`** (typicky 12 %) — stejně jako u běžného **AUTO PASSIVE**: akumulace vs. síť řeší plán a **145** / **178**, ne výška TOU %.
|
||||||
|
|
||||||
### Čtyři typy slotů a mapování na registry
|
### Čtyři typy slotů a mapování na registry
|
||||||
|
|
||||||
@@ -102,9 +102,11 @@ Solver předvybírá sloty pro nabíjení a export-vybíjení (`_select_charge_s
|
|||||||
| **178** peak shaving | 48 (PASSIVE) | 48 (PASSIVE) | **32** (SELL) | 48 (PASSIVE) |
|
| **178** peak shaving | 48 (PASSIVE) | 48 (PASSIVE) | **32** (SELL) | 48 (PASSIVE) |
|
||||||
| **143** export limit | max export W z DB | max export W z DB | max export W z DB | max export W z DB |
|
| **143** export limit | max export W z DB | max export W z DB | max export W z DB | max export W z DB |
|
||||||
| **141** energy mode | 0 | 0 | 0 | 0 |
|
| **141** energy mode | 0 | 0 | 0 | 0 |
|
||||||
| **TOU SOC** (reg 166+) | viz níže | min_soc_pct | reserve_soc_pct | min_soc_pct |
|
| **TOU SOC** (reg 166+) | **`max_soc_percent`** (clamp 10–100), grid charge ON | **`min_soc_percent`** z DB | reserve_soc_pct | min_soc_pct |
|
||||||
|
|
||||||
**PASSIVE – TOU SOC % (home-01 / Deye):** EMS ukládá do řádku time pointu procento, které na zařízení řídí **prioritu baterie vs. přetok FVE do sítě** (viz firmware / instalace). Je-li zapsané procento **níž než skutečný SoC**, přebytek tíhne do sítě; při **záporné efektivní vykupní** (`effective_sell_price` ze slotu) nebo při **kladném `battery_setpoint_w`** (plánované nabíjení) EMS nastaví **100 %** (signál „využij baterii naplno“) — **ne** v režimu **SELF_SUSTAIN** (`self_sustain_local_use`), tam je vždy **`min_soc_percent`**. **`asset_battery.max_soc_percent`** (typicky 95) je **jiný účel**: horní limit pro **plánovač / denní provoz v % SoC** (komfort, degradace, rezerva výrobce), **ne** časové „do kdy“ ani hodnota zapisovaná do tohoto TOU při této priorité. Jinak zůstane **`min_soc_percent`**.
|
**PASSIVE – TOU SOC % (Deye):** EMS zapisuje vždy **`min_soc_percent`** z ``asset_battery`` (clamp jako u všech TOU SOC 5–95). Slouží jako spodní pásmový signál pro firmware; výšku nepoužíváme k řízení „honit akumulaci na 100 %“ ve PASSIVE — to u levného importu řeší **108/109** (viz ``operating-modes.md``), u záporné vykupní **BLOCK_EXPORT** přes **`export_ban`** → **145**, případně **178** na GEN.
|
||||||
|
|
||||||
|
**CHARGE:** TOU řádek nese **`max_soc_percent`** z DB (**clamp 10–100**) jako cíl při **grid charge** (spolu s příznakem grid charge v time pointu). **Energy pattern** („load first“ / „battery first“) v UI střídače zůstává v kompetenci instalace — EMS ho přes Modbus nenastavuje.
|
||||||
|
|
||||||
**Jak funguje pass-through fyzicky:**
|
**Jak funguje pass-through fyzicky:**
|
||||||
|
|
||||||
@@ -147,9 +149,9 @@ Příklad v 14:18: blok 1 má čas **1415**, blok 2 čas **1430** – mezi 14:15
|
|||||||
|
|
||||||
| Režim | Výkon (W) | SOC min (reg 166+) | Grid charge |
|
| Režim | Výkon (W) | SOC min (reg 166+) | Grid charge |
|
||||||
|-------|-----------|---------------------|-------------|
|
|-------|-----------|---------------------|-------------|
|
||||||
| **PASSIVE** | `max_discharge_a × 51,2` | `_deye_passive_tou_battery_soc_pct`: při neg. vykupní / plánovaném nabíjení = **100 %**, jinak **`min_soc_percent`** | NE |
|
| **PASSIVE** | `max_discharge_a × 51,2` | **`min_soc_percent`** z DB (**`_deye_passive_tou_battery_soc_pct`**) | NE |
|
||||||
| **SELL** | `max_discharge_a × 51,2` | **`reserve_soc_percent`** z DB | NE |
|
| **SELL** | `max_discharge_a × 51,2` | **`reserve_soc_percent`** z DB | NE |
|
||||||
| **CHARGE** | `battery_watts_to_amps(battery_w, max_charge_a) × 51,2` | min(95, cíl SoC z plánu nebo 80) | ANO |
|
| **CHARGE** | `battery_watts_to_amps(battery_w, max_charge_a) × 51,2` | **clamp**(10 … **100**, **`asset_battery.max_soc_percent`**) | ANO |
|
||||||
|
|
||||||
Bloky 3–6 používají čas **2355** a stejnou **SOC** hodnotu jako PASSIVE (`min_soc_percent`, grid charge = NE).
|
Bloky 3–6 používají čas **2355** a stejnou **SOC** hodnotu jako PASSIVE (`min_soc_percent`, grid charge = NE).
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user