You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Control and taper cards showed "No active operations" on non-English Home Assistant installs (C-020 violation): during an active smart charge session, a user running HA in German saw "Keine aktiven Vorgänge" on the control card despite charge_active=true and charge_phase=scheduled on the backing sensor. The cards hardcoded sensor.foxess_smart_operations as the operations_entity default, but HA derives entity_ids from the translated friendly name at entity-creation time — in DE the real entity is sensor.foxess_intelligente_steuerung, in FR sensor.foxess_operations_intelligentes, etc. The integration already exposes foxess_control/entity_map for role-based discovery, and the forecast/history cards already consult it via a _resolve(key) helper; the control and taper cards ignored it. Users had no way to determine system state from the UI alone, requiring entity-registry inspection — a direct C-020 violation. Fixed by routing both cards through a _resolve(key) helper matching the forecast/history pattern: explicit operations_entity: config > _entityMap["smart_operations"] from the WS command > hardcoded English default as last-resort fallback. The taper card previously didn't fetch the entity map at all; it now does. Backwards-compatible for users with explicit dashboard YAML overrides. Seven-test regression suite (tests/test_card_entity_resolution.py) drives the card JS in a Playwright-chromium stub, reproducing the exact DE-locale symptom — four cases fail against pre-fix card code, three passed throughout (backwards-compat + graceful-degradation + source-level guards that catch any future regression into direct this._config.operations_entity reads).
Charge-phase sensor oscillated rapidly between charging and deferred during an active session: observed 2026-04-27 on a live charge session (11:00–13:59 window) where sensor.foxess_smart_operations flipped phase many times per minute (including a 5-second flip at 02:39:01 → 02:39:36) while the inverter's actual work mode only transitioned twice in the same 3 hours. The control-card title ("Smart Charge" vs "Charge Deferred") faithfully tracked the sensor's thrashing state, so the user saw the card lag reality by a couple of minutes after the algorithm had actually re-deferred — the sensor kept flipping back to charging as inputs jittered. Root cause: is_effectively_charging() in smart_battery/sensor_base.py independently recomputed calculate_deferred_start() from live coordinator data on every ~5s WS refresh; net_consumption_kw jitter of ±1 kW (appliances cycling, solar flicker) swung the computed deferred_start by 10–30 minutes tick-to-tick, crossing the now >= deferred threshold in both directions. SoC jitter of 0.1% (BMS reporting granularity / interpolation noise) had the same effect. Fix: the listener now commits its calculate_deferred_start() result to the session state (deferred_start_committed) on every tick; the sensor reads that committed value instead of recomputing. Same formula on both sides (preserving C-038 sensor-listener parameter parity), but the sensor is now a stable read-only view of the listener's most recent decision — no sub-minute volatility. Four-test regression suite (TestIsEffectivelyChargingStability) drives is_effectively_charging() with realistic input-noise sequences (±0.1% SoC, ±0.4 kW consumption) and asserts no phase flip under noise; neighbourhood tests confirm real qualitative changes still flip phase promptly.
WebSocket-driven power sensors flashed to ~20% of true value on single frames: production incident 2026-04-27 during a smart discharge session — the display sensors for discharge power, grid export and house load showed 49 single-sample dips within a 2-hour window (11 below 2 kW, down to 0.82 kW while real output was ~5.4 kW). Root cause was a mix of partial/stale ~5 s WS frames and energy-counter quantisation glitches. The control loop was unaffected because it reads unfiltered gridConsumptionPower, which stayed at 0 throughout — C-001 (no grid import) held. Fix applies a 3-sample rolling median in the per-sensor display path (FoxESSPolledSensor.native_value) to the seven WS-fed power channels (batChargePower, batDischargePower, loadsPower, pvPower, gridConsumptionPower, feedinPower, meterPower). Cumulative energy counters, SoC, voltage, current, temperature and frequency are NOT filtered — smoothing would distort their semantics. Display-only: coordinator.data retains raw values; listeners and safety checks continue to read unfiltered data directly via _get_coordinator_value (C-038 parity preserved — the filter sits below the listener-formula-parity boundary). Window semantics: fewer than 3 samples → display = most recent sample (fresh data never delayed waiting for history); 3 samples → display = median; any raw None surfaces as unavailability rather than being papered over.
Misleading wording in charge-deferral reason: the deferred-charge reason text previously read "solar surplus or cheaper-later pricing; waiting to start forced charge". The integration has never had tariff awareness (tariff optimisation is an explicit non-goal per the vision), so "cheaper-later pricing" was factually incorrect. Simplified to describe only what the integration actually reasons about: solar surplus and deferred-start math.
Test infrastructure
Screenshot and card-injection retry on transient DOM / execution-context races (flaky-detection hardening): _screenshot_card and the taper-card page.evaluate injection in tests/e2e/test_ui.py now wrap their Playwright calls in retry helpers (_safe_screenshot / _safe_evaluate) that catch "Element is not attached to the DOM", "Execution context was destroyed", and "Target closed", wait for networkidle, and retry with a freshly resolved locator/context. Mirrors the existing _find_card retry pattern so all gallery-screenshot and card-injection tests inherit the recovery in one place; unrelated errors (genuine Timeout, etc.) propagate unchanged. Each helper has deterministic stub-based unit coverage for the retry, re-resolve, and error-passthrough contracts.
pytest-playwright event-loop leak under -n auto (C-031 flake eliminated): pytest-playwright's session-scoped playwright fixture calls sync_playwright().start(), which keeps a greenlet-backed asyncio event loop pinned as "running" on the main thread until end-of-session. Any @pytest.mark.asyncio test that pytest-randomly placed later on the same xdist worker failed with RuntimeError: Runner.run() cannot be called from a running event loop — intermittent on roughly half of full-suite runs. Fix is a scope-aware two-layer override: tests/conftest.py declares function-scopedplaywright / browser_type / browser / context / page for the unit suite (each unit test that uses page starts a fresh sync_playwright context and releases it on teardown, clearing the loop); tests/e2e/conftest.py carries a counter-override re-declaring playwright / browser_type / browser at session scope so the E2E browser_context (session-scoped, depends on playwright) still resolves against a session-scoped fixture chain. Pytest's nearer-conftest-wins rule guarantees E2E tests see the session-scope version. Deterministic reproduction test (tests/test_playwright_fixture_isolation.py) runs a two-test sandbox subprocess under -p no:randomly to prove both the leak and the fix in isolation.