fix pv vyroby (unsinged)
This commit is contained in:
@@ -26,6 +26,14 @@ DEYE_REG_PV1_POWER = 672
|
||||
DEYE_REG_PV2_POWER = 673
|
||||
|
||||
|
||||
def aggregate_pv_production_w(pv1_w: int, pv2_w: int, gen_port_w: int) -> int:
|
||||
"""
|
||||
Okamžitá „výroba FVE“ pro dashboard / audit součtu: Deye registry 672/673/667
|
||||
jsou int16 W; záporné hodnoty (např. večer při exportu) nejsou DC výroba.
|
||||
"""
|
||||
return max(0, int(pv1_w)) + max(0, int(pv2_w)) + max(0, int(gen_port_w))
|
||||
|
||||
|
||||
async def poll_inverter(site_id: int, db: asyncpg.Connection) -> None:
|
||||
rows = await db.fetch(
|
||||
"""
|
||||
@@ -54,13 +62,12 @@ async def poll_inverter(site_id: int, db: asyncpg.Connection) -> None:
|
||||
battery_power = await mb.read_register_signed(DEYE_REG_BATTERY_POWER_FLOW)
|
||||
batt_charge_today = await mb.read_register(DEYE_REG_BATT_CHARGE_TODAY)
|
||||
batt_discharge_today = await mb.read_register(DEYE_REG_BATT_DISCHARGE_TODAY)
|
||||
gen_port_power = await mb.read_register(DEYE_REG_GEN_PORT_POWER)
|
||||
grid_power = await mb.read_register_signed(DEYE_REG_GRID_TOTAL_POWER)
|
||||
load_power = await mb.read_register(DEYE_REG_LOAD_TOTAL_POWER)
|
||||
pv1_power = await mb.read_register(DEYE_REG_PV1_POWER)
|
||||
pv2_power = await mb.read_register(DEYE_REG_PV2_POWER)
|
||||
# Celková výroba FVE na této instalaci = stringy PV + výkon přes GEN port.
|
||||
pv_power_w = int(pv1_power) + int(pv2_power) + int(gen_port_power)
|
||||
pv1_power = await mb.read_register_signed(DEYE_REG_PV1_POWER)
|
||||
pv2_power = await mb.read_register_signed(DEYE_REG_PV2_POWER)
|
||||
gen_port_power = await mb.read_register_signed(DEYE_REG_GEN_PORT_POWER)
|
||||
pv_power_w = aggregate_pv_production_w(pv1_power, pv2_power, gen_port_power)
|
||||
|
||||
logger.debug("inverter:%s Deye run_state raw=%s", code, run_state)
|
||||
|
||||
|
||||
34
backend/tests/test_telemetry_pv_signed.py
Normal file
34
backend/tests/test_telemetry_pv_signed.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""Signed PV kanály Deye → agregovaná pv_power_w (kladné příspěvky jen)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from services.telemetry_collector import aggregate_pv_production_w
|
||||
|
||||
|
||||
class TestAggregatePvProductionW(unittest.TestCase):
|
||||
def test_daytime_typical(self) -> None:
|
||||
self.assertEqual(aggregate_pv_production_w(6000, 4000, 0), 10_000)
|
||||
|
||||
def test_negative_gen_ignored_in_total(self) -> None:
|
||||
self.assertEqual(aggregate_pv_production_w(0, 0, -61), 0)
|
||||
|
||||
def test_false_uint16_gen_becomes_small_negative(self) -> None:
|
||||
raw = 65472
|
||||
signed = raw - 65536 if raw > 32767 else raw
|
||||
self.assertEqual(signed, -64)
|
||||
self.assertEqual(aggregate_pv_production_w(0, 0, signed), 0)
|
||||
|
||||
def test_false_uint16_pv1_like_grid_export_clamped(self) -> None:
|
||||
raw = 52038
|
||||
signed = raw - 65536 if raw > 32767 else raw
|
||||
self.assertEqual(signed, -13_498)
|
||||
self.assertEqual(aggregate_pv_production_w(signed, 0, 0), 0)
|
||||
|
||||
def test_mixed_one_negative(self) -> None:
|
||||
self.assertEqual(aggregate_pv_production_w(8000, -100, 2000), 10_000)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
12
db/migration/V038__telemetry_pv_signed_semantics.sql
Normal file
12
db/migration/V038__telemetry_pv_signed_semantics.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- PV1/PV2/GEN na Deye jsou int16 W; sběr ukládá signed hodnoty do sloupců.
|
||||
-- pv_power_w = součet max(0, kanál) pro metriku okamžité výroby FVE.
|
||||
|
||||
COMMENT ON COLUMN ems.telemetry_inverter.pv1_power_w IS
|
||||
'Výkon PV1 v W (signed int16 z Modbus). Záporné = není kladná DC výroba tohoto vstupu.';
|
||||
COMMENT ON COLUMN ems.telemetry_inverter.pv2_power_w IS
|
||||
'Výkon PV2 v W (signed int16 z Modbus). Záporné = není kladná DC výroba tohoto vstupu.';
|
||||
COMMENT ON COLUMN ems.telemetry_inverter.gen_port_power_w IS
|
||||
'Výkon GEN portu v W (signed int16 z Modbus). FVE pole B (ongrid); záporné při zpětném toku / režimu bez výroby.
|
||||
Nelze curtailovat. Klíčový pro audit zeleného bonusu.';
|
||||
COMMENT ON COLUMN ems.telemetry_inverter.pv_power_w IS
|
||||
'Okamžitá výroba FVE v W: max(0,pv1)+max(0,pv2)+max(0,gen_port) — součet kladných příspěvků kanálů po signed čtení z Deye.';
|
||||
@@ -128,9 +128,9 @@ Limity nabíjení/vybíjení v ampérech a export z **site_grid_connection** / *
|
||||
| 590 | Battery power | 1 W S16 | + vybíjení / − nabíjení |
|
||||
| 625 | Grid total power | 1 W S16 | + import / − export |
|
||||
| 653 | Load total power | 1 W S16 | |
|
||||
| 667 | GEN port power | 1 W | FVE pole B |
|
||||
| 672 | PV1 power | 1 W | |
|
||||
| 673 | PV2 power | 1 W | |
|
||||
| 667 | GEN port power | 1 W S16 | FVE pole B; signed — záporné při zpětném toku / bez výroby |
|
||||
| 672 | PV1 power | 1 W S16 | signed; EMS ukládá raw signed W, do `pv_power_w` jen max(0, kanál) |
|
||||
| 673 | PV2 power | 1 W S16 | jako PV1 |
|
||||
|
||||
## Přepočty
|
||||
|
||||
|
||||
@@ -46,12 +46,17 @@ Komunikace: Modbus TCP, Unit ID dle DIP přepínače na střídači (typicky 1).
|
||||
| 590 (0x024E) | int16 | Tok výkonu baterie | W | signed: **+ vybíjení, − nabíjení** |
|
||||
| 625 (0x0271) | int16 | Výkon sítě | W | signed: **+ import, − export** |
|
||||
| 653 (0x028D) | uint16 | Celková spotřeba | W | `load_power_w` |
|
||||
| 667 (0x029B) | uint16 | Výkon GEN portu (FVE pole B) | W | `gen_port_power_w`, nelze curtailovat |
|
||||
| 672 (0x02A0) | uint16 | Výkon PV1 | W | `pv1_power_w` |
|
||||
| 673 (0x02A1) | uint16 | Výkon PV2 | W | `pv2_power_w` |
|
||||
| 667 (0x029B) | int16 | Výkon GEN portu (FVE pole B) | W (signed) | `gen_port_power_w`; záporné při zpětném toku / bez výroby — **číst signed** |
|
||||
| 672 (0x02A0) | int16 | Výkon PV1 | W (signed) | `pv1_power_w`; při špatném unsigned čtení se záporné hodnoty jeví jako desítky kW |
|
||||
| 673 (0x02A1) | int16 | Výkon PV2 | W (signed) | `pv2_power_w` |
|
||||
|
||||
`pv_power_w` v DB = **PV1 + PV2 + GEN port** (celková výroba na instalaci home-01).
|
||||
`gen_port_power_w` zůstává i nadále uložen samostatně pro audit a detailní diagnostiku.
|
||||
`pv1_power_w` / `pv2_power_w` / `gen_port_power_w` v DB = **signed W** z Modbus (mohou být záporné).
|
||||
|
||||
`pv_power_w` = **max(0, PV1) + max(0, PV2) + max(0, GEN)** — okamžitá **kladná** výroba FVE pro dashboard a souhrny; záporné kanály do součtu nepatří (typicky večer při exportu z baterie do sítě).
|
||||
|
||||
`gen_port_power_w` zůstává uložen samostatně pro audit zeleného bonusu a diagnostiku.
|
||||
|
||||
**Ověření po změně sběru:** za denního SVitu zkontrolovat, že `pv_power_w` a jednotlivé kanály odpovídají očekávanému max. výkonu instalace (logika: `aggregate_pv_production_w` v `telemetry_collector.py`, unit testy `tests/test_telemetry_pv_signed.py`).
|
||||
|
||||
**Zápis setpointů (plánování → Deye):**
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Shrnutí otevřených bodů z `docs/06-open-questions.md`, checklistů v modulec
|
||||
| **Import OTE – robustní provoz:** timeouty + retry/backoff v `price_importer.py`, detailní error kódy v API, fallback D+1 → dnešek, scheduler importů 13:30 / 14:00 / 00:05. |
|
||||
| **Fail-safe bez OTE dat:** při predikovaných cenách v kritickém okně je zákaz exportu; vybíjení baterie omezeno jen v predikovaných slotech; runtime guard v `control_exporter.py` brání SELL v nejistém stavu. |
|
||||
| **Forecast provoz:** refresh každé 2 hodiny (`:05`), konfigurovatelný Open-Meteo horizont (`OPEN_METEO_FORECAST_DAYS`, default 7, clamp 2..16), endpoint pro UI vrací latest-run bez duplicit slotů. |
|
||||
| **Telemetry – výroba FVE:** `pv_power_w` je součet `pv1 + pv2 + gen_port`, takže dashboard reflektuje obě pole i GEN větev instalace home-01. |
|
||||
| **Telemetry – výroba FVE:** Registry 672/673/667 jsou **signed** W; `pv_power_w` = max(0,pv1)+max(0,pv2)+max(0,gen) (dashboard); sloupce pv1/pv2/gen ukládají signed pro audit. |
|
||||
| **Ekonomika baterie:** snížení `reserve_soc_percent` na 10 % a `degradation_cost_czk_kwh` na 0.1500 (migrace `V026__battery_economics_tuning.sql`), úpravy objective pro ekonomicky konzistentnější nabíjení/vybíjení. |
|
||||
| **Planning UI operátor akce:** trvale viditelné akce import/forecast/init plan, volba data OTE (dnes/zítra), zobrazení `pv_scarcity_factor` ve stavu plánu. |
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@ REGISTERS: dict[str, tuple[int, str, int, str, str]] = {
|
||||
"battery_power_w": (590, "sint", 1, "W", "+ vybíjení / − nabíjení"),
|
||||
"batt_charge_today_wh": (514, "uint", 1, "Wh", "dnešní nabití baterie"),
|
||||
"batt_discharge_today_wh": (515, "uint", 1, "Wh", "dnešní vybití baterie"),
|
||||
"gen_port_power_w": (667, "uint", 1, "W", "GEN port – FVE pole B"),
|
||||
"gen_port_power_w": (667, "sint", 1, "W", "GEN port – FVE pole B (signed)"),
|
||||
"grid_total_power_w": (625, "sint", 1, "W", "+ import ze sítě / − export"),
|
||||
"load_total_power_w": (653, "uint", 1, "W", "celková spotřeba"),
|
||||
"pv1_power_w": (672, "uint", 1, "W", "výkon PV1"),
|
||||
"pv2_power_w": (673, "uint", 1, "W", "výkon PV2"),
|
||||
"pv1_power_w": (672, "sint", 1, "W", "výkon PV1 (signed)"),
|
||||
"pv2_power_w": (673, "sint", 1, "W", "výkon PV2 (signed)"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user