|
69 | 69 | _LOGGER = logging.getLogger(__name__) |
70 | 70 | _LAST_SIGN_CORRECTION_SIGNATURE: tuple[Any, ...] | None = None |
71 | 71 |
|
| 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 | + |
72 | 78 |
|
73 | 79 | # ── Sensor-Beschreibungen ───────────────────────────────────────────────────── |
74 | 80 |
|
@@ -140,16 +146,29 @@ def _normalized_power_components(data: dict[str, Any]) -> tuple[float, float, fl |
140 | 146 | battery_raw = float(stream.battery_w) |
141 | 147 |
|
142 | 148 | # 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 |
147 | 162 |
|
148 | 163 | # 2) Batterie-Vorzeichen so wählen, dass die Bilanz bei gewähltem Netz passt. |
149 | 164 | battery_expected = load - solar - grid |
150 | 165 | err_batt_keep = abs(battery_raw - battery_expected) |
151 | 166 | 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 | + ) |
153 | 172 | battery = -battery_raw if battery_flipped else battery_raw |
154 | 173 |
|
155 | 174 | # 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: |
200 | 219 | return _normalized_power_components(data)[3] |
201 | 220 |
|
202 | 221 |
|
| 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 | + |
203 | 238 | # ── Batterie-Pack-Sensoren ──────────────────────────────────────────────────── |
204 | 239 |
|
205 | 240 | BATTERY_SENSOR_TYPES: tuple[EcoFlowBatterySensorDescription, ...] = ( |
@@ -332,8 +367,8 @@ def _battery_power_w(data: dict[str, Any]) -> float: |
332 | 367 | native_unit_of_measurement=PERCENTAGE, |
333 | 368 | device_class=SensorDeviceClass.BATTERY, |
334 | 369 | 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), |
337 | 372 | ), |
338 | 373 | EcoFlowSystemSensorDescription( |
339 | 374 | key="bp_remain_wh", |
|
0 commit comments