Skip to content

Commit ffccfc4

Browse files
committed
fix: stabilize grid sign handling and prefer stream SOC
1 parent f937564 commit ffccfc4

3 files changed

Lines changed: 46 additions & 11 deletions

File tree

custom_components/ecoflow_powerocean/coordinator.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class EcoFlowCoordinator(DataUpdateCoordinator[dict[str, Any]]):
8282
8383
Der Coordinator verbindet sich einmalig beim Setup mit dem EcoFlow MQTT-Broker
8484
und empfängt danach kontinuierlich Statusupdates. Ein periodischer Fallback-Refresh
85-
sendet alle 5 Minuten eine GET-Anfrage, falls das Gerät keine spontanen Updates sendet.
85+
sendet minütlich eine GET-Anfrage, falls das Gerät keine spontanen Updates sendet.
8686
8787
Attributes:
8888
serial_number: Seriennummer des PowerOcean Plus Geräts.
@@ -101,7 +101,7 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
101101
hass,
102102
_LOGGER,
103103
name=f"{DOMAIN}_{entry.data[CONF_SERIAL_NUMBER]}",
104-
update_interval=timedelta(minutes=5),
104+
update_interval=timedelta(minutes=1),
105105
)
106106

107107
self._entry = entry
@@ -377,7 +377,7 @@ def _send_get_request(self) -> None:
377377

378378
async def _async_update_data(self) -> dict[str, Any]:
379379
"""
380-
Wird periodisch vom DataUpdateCoordinator aufgerufen (Fallback alle 5 Min.).
380+
Wird periodisch vom DataUpdateCoordinator aufgerufen (Fallback minütlich).
381381
382382
Sendet eine GET-Anfrage um das Gerät zu einer sofortigen Datenlieferung zu
383383
bewegen. Die eigentlichen Daten kommen asynchron über den MQTT-Callback

custom_components/ecoflow_powerocean/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"domain": "ecoflow_powerocean",
33
"name": "EcoFlow PowerOcean",
4-
"version": "0.3.2",
4+
"version": "0.3.3",
55
"codeowners": [],
66
"config_flow": true,
77
"documentation": "https://github.com/Feberdin/ecoflow-powerocean-ha",

custom_components/ecoflow_powerocean/sensor.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@
6969
_LOGGER = logging.getLogger(__name__)
7070
_LAST_SIGN_CORRECTION_SIGNATURE: tuple[Any, ...] | None = None
7171

72+
# Kleine Netzleistungen liegen oft im Messrauschen und sollten nicht
73+
# per Vorzeichenkorrektur "umkippen".
74+
GRID_SIGN_DEADBAND_W = 20.0
75+
# Ein Flip wird nur akzeptiert, wenn die Bilanz erkennbar besser wird.
76+
MIN_SIGN_FLIP_IMPROVEMENT_W = 20.0
77+
7278

7379
# ── Sensor-Beschreibungen ─────────────────────────────────────────────────────
7480

@@ -140,16 +146,29 @@ def _normalized_power_components(data: dict[str, Any]) -> tuple[float, float, fl
140146
battery_raw = float(stream.battery_w)
141147

142148
# 1) Netz-Vorzeichen so wählen, dass die Leistungsbilanz bestmöglich passt.
143-
err_grid_keep = abs((solar + battery_raw + grid_raw) - load)
144-
err_grid_flip = abs((solar + battery_raw - grid_raw) - load)
145-
grid_flipped = err_grid_flip < err_grid_keep
146-
grid = -grid_raw if grid_flipped else grid_raw
149+
# Bei sehr kleinen Netzleistungen bleibt das Roh-Vorzeichen unverändert.
150+
if abs(grid_raw) <= GRID_SIGN_DEADBAND_W:
151+
grid = grid_raw
152+
grid_flipped = False
153+
else:
154+
err_grid_keep = abs((solar + battery_raw + grid_raw) - load)
155+
err_grid_flip = abs((solar + battery_raw - grid_raw) - load)
156+
grid_improvement = err_grid_keep - err_grid_flip
157+
grid_flipped = (
158+
err_grid_flip < err_grid_keep
159+
and grid_improvement >= MIN_SIGN_FLIP_IMPROVEMENT_W
160+
)
161+
grid = -grid_raw if grid_flipped else grid_raw
147162

148163
# 2) Batterie-Vorzeichen so wählen, dass die Bilanz bei gewähltem Netz passt.
149164
battery_expected = load - solar - grid
150165
err_batt_keep = abs(battery_raw - battery_expected)
151166
err_batt_flip = abs((-battery_raw) - battery_expected)
152-
battery_flipped = err_batt_flip < err_batt_keep
167+
batt_improvement = err_batt_keep - err_batt_flip
168+
battery_flipped = (
169+
err_batt_flip < err_batt_keep
170+
and batt_improvement >= MIN_SIGN_FLIP_IMPROVEMENT_W
171+
)
153172
battery = -battery_raw if battery_flipped else battery_raw
154173

155174
# Debug-Hilfe: zeigt nur Fälle, in denen ein Vorzeichen aktiv korrigiert wurde.
@@ -200,6 +219,22 @@ def _battery_power_w(data: dict[str, Any]) -> float:
200219
return _normalized_power_components(data)[3]
201220

202221

222+
def _total_soc(data: dict[str, Any]) -> int | None:
223+
"""
224+
Gesamt-SOC bevorzugt aus ENERGY_STREAM, fallback auf Batterie-Mittelwert.
225+
"""
226+
stream = data.get(DATA_ENERGY_STREAM)
227+
if stream is not None:
228+
stream_soc = int(getattr(stream, "soc", 0))
229+
if 0 <= stream_soc <= 100:
230+
return stream_soc
231+
232+
batteries = data.get(DATA_BATTERIES, {})
233+
if not batteries:
234+
return None
235+
return int(sum(p.soc for p in batteries.values()) / len(batteries))
236+
237+
203238
# ── Batterie-Pack-Sensoren ────────────────────────────────────────────────────
204239

205240
BATTERY_SENSOR_TYPES: tuple[EcoFlowBatterySensorDescription, ...] = (
@@ -332,8 +367,8 @@ def _battery_power_w(data: dict[str, Any]) -> float:
332367
native_unit_of_measurement=PERCENTAGE,
333368
device_class=SensorDeviceClass.BATTERY,
334369
state_class=SensorStateClass.MEASUREMENT,
335-
data_key=DATA_BATTERIES,
336-
value_fn=lambda d: int(sum(p.soc for p in d.values()) / len(d)) if d else None,
370+
uses_coordinator_data=True,
371+
value_fn=lambda d: _total_soc(d),
337372
),
338373
EcoFlowSystemSensorDescription(
339374
key="bp_remain_wh",

0 commit comments

Comments
 (0)