Skip to content

v1.0.13

Choose a tag to compare

@github-actions github-actions released this 26 Apr 12:01
527711f

Added

  • Pacing-transparency UI on the control card (UX #4 / #6 / #8) — four new data-surface attributes on sensor.foxess_smart_operations (discharge_deferred_reason + charge_deferred_reason, discharge_safety_floor_w + discharge_peak_consumption_kw + discharge_paced_target_w, discharge_grid_export_limit_w + discharge_clamp_active) are rendered directly as rows on foxess-control-card:
    • Deferred reason (UX #4) — both charge and discharge sections render a wide row with the explanatory text produced by _explain_*_deferral() whenever the deferred phase is active. Users finally see why pacing is holding back (e.g. "Feed-in limit reached — holding until more solar is available", "Export clamp slack 5.5 kW exceeds projected peak 3.0 kW; single headroom applied"). Longer strings wrap via new .detail-row-wide / .detail-value-wrap styling.
    • Safety floor (UX #6) — a safety_floor row appears whenever discharge_safety_floor_w > 0. An upward-arrow icon (mdi:arrow-up-bold) surfaces when the paced target is below the floor (the C-001 floor is actively raising paced power). The row is click-expandable: tapping it reveals a translation-aware explainer with the actual peak value interpolated (e.g. "Minimum discharge power. Computed as peak household load (1.0 kW) × 1.5, so the battery can cover sudden load spikes without pulling from the grid."). Mobile-friendly — no hover needed.
    • Export clamp split (UX #8) — when grid_export_limit is configured, the discharge power row splits into an inverter / export pair separated by /. The export side takes the warning colour and shows a mdi:fence icon when discharge_clamp_active is true, so the user can see immediately that hardware clamping is capping their export. Unchanged for sites without an export limit.
  • Standalone foxess-taper-card (UX #5) rendering the BMS acceptance-ratio histogram from the new taper_profile attribute on sensor.foxess_smart_operations (both charge and discharge histograms as chart-friendly {soc, ratio, count} lists). The card shows per-SoC-bin horizontal bars with observation count annotation and a low-confidence marker (·) for bins with fewer than 3 observations. Users opt in by adding type: custom:foxess-taper-card to their dashboard; an ApexCharts variant is also covered as a user template in docs/lovelace-examples.md.
  • Card translation coverage test (tests/test_card_translations.py): parses each card's TRANSLATIONS table and asserts every non-English locale carries every English key. Structurally prevents the class of bug that left the info_log sensor ID broken in beta.7 — a locale missing a key silently falls back to the raw key name.
  • E2E card-wiring tests: 7 new TestControlCard tests (split power row, clamp-active toggle, safety-floor row + expandable explainer, charge/discharge deferred-reason rows, charge section title phase distinction) and 3 new TestTaperCard tests (card mount, empty state, seeded-profile bar widths). Taper card added to the E2E Lovelace dashboard.
  • D-051 "Transparency attributes surfaced via card rows, not tooltips" captures the design decision — visible rows over hover tooltips / debug panels / consolidated toggles — in docs/knowledge/04-design/lovelace-cards.md, tracing to C-020 and C-034.

Fixed

  • Structured events silently dropped when a user set a per-module log level: emit_event used logger.info(message, extra=...), which Python's logging framework evaluates against Logger.isEnabledFor() before propagation to ancestor handlers. On a live HA (v1.0.12) with a logger: YAML config pinning custom_components.foxess_control.foxess.inverter above INFO, every SCHEDULE_WRITE event emitted from Inverter._post_schedule() was dropped at the child's level check — never reaching the debug-log sensor attached to the parent custom_components.foxess_control logger. Fixed by changing emit_event to build the LogRecord via logger.makeRecord() and dispatch via logger.handle(), bypassing isEnabledFor() while still running the logger's filter chain and every handler's level/filter chain. Structured events are telemetry the integration emits on its own behalf; visibility is controlled at the handler level, not at the logger level. Applies to all six event types (ALGO_DECISION, TICK_SNAPSHOT, SCHEDULE_WRITE, TAPER_UPDATE, SERVICE_CALL, SESSION_TRANSITION). Three-test regression suite TestInverterScheduleWriteReachesParentHandler covers the exact production symptom (child at WARNING) + default-levels baseline + session-context-survives-child-override neighbourhood.
  • Page-fixture flake in E2E CI caused by HA's housekeeping navigation (Flaky Test Detection surfaced this on four consecutive tag runs v1.0.11–v1.0.13-beta.2; live-diagnosed by observing a real HA container). Two compounding bugs in the staged Lovelace-panel wait (tests/e2e/conftest.py): (a) the final stage's predicate was a bare !!panel attach check, which could return truthy moments before HA's service-worker/auth-refresh navigation at t≈12s detached the panel — surfacing either as a 30s wait_for_function timeout or, in the test body, as Locator.screenshot: Element is not attached to the DOM; (b) _wait_for_stage re-capped every retry at max_stage_ms=30000 even when 50+ seconds of overall budget remained, so a post-navigation rebuild under slow-shard CPU contention had insufficient time to complete. Stage-3 predicate now requires three settled signals (hass.connected === true, ha-panel-lovelace mounted, panel.hass wired) — proving the panel is past HA's initial navigation churn, not merely attached; and a new _LOVELACE_STAGE_TIMEOUTS_MS mapping lets the final stage use the full remaining overall budget on retries while keeping the tight 30s cap on stages 1+2 (shadow-root attachment is synchronous and catastrophic failures must still time out fast).
  • SoakRecorder dropped invariant-violation detail when persisting run results: save_run in tests/soak/results_db.py wrote a violations count to the runs row but never inserted corresponding events rows, so the rule name and detail message were lost. Observed on a 2026-04-23 nightly when test_charge_solar_then_spike recorded violations=3 with zero persisted detail — making post-mortem diagnosis impossible and hollowing out the C-020 observability value of the recorder. Each InvariantViolation now emits an event_type='violation' row with {rule}: {detail} as the event detail, anchored to the nearest sample's SoC/power/state so cross-run queries (SELECT * FROM events WHERE event_type='violation') can correlate violations back to system state.