Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ All notable changes to this project will be documented in this file.

- **PV nameplate capacity unit** — Corrected the PV nameplate capacity sensor unit to watts.

- **Recorder database growth** — Energy sensors still expose grace-period and dip-compensation diagnostics, plus circuit `tabs` and `voltage`, on the entity, but
those attributes are no longer written to the recorder, which greatly reduces churn in the `state_attributes` table (#197).

## [2.0.3] - 3/2026

**Important** 2.0.1 cautions still apply — read those carefully if not already on 2.0.1 BEFORE proceeding:
Expand Down
23 changes: 23 additions & 0 deletions custom_components/span_panel/sensor_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@
# Sentinel value to distinguish "never synced" from "circuit name is None"
_NAME_UNSET: object = object()

# Keys from Span energy sensors' extra_state_attributes that we omit from the recorder
# (SpanEnergySensorBase: panel-wide and circuit energy entities). High-churn grace/dip
# diagnostics dominated DB growth (#197). tabs and voltage are merged in by circuit
# subclasses; they stay on the live entity for Developer tools and automations.
_ENERGY_SENSOR_UNRECORDED_ATTRIBUTES: frozenset[str] = frozenset(
{
"energy_offset",
"grace_period_remaining",
"last_dip_delta",
"last_valid_changed",
"last_valid_state",
"tabs",
"using_grace_period",
"voltage",
}
)


def _parse_numeric_state(state: State | None) -> tuple[float | None, datetime | None]:
"""Extract a numeric value and naive timestamp from a restored HA state.
Expand Down Expand Up @@ -445,8 +462,14 @@ class SpanEnergySensorBase[T: SensorEntityDescription, D](SpanSensorBase[T, D],
- Grace period tracking for offline scenarios
- State restoration across HA restarts via RestoreSensor mixin
- Automatic persistence of last_valid_state and last_valid_changed

High-churn diagnostic attributes are listed in ``extra_state_attributes`` for
the UI but omitted from recorder history via ``_unrecorded_attributes`` so the
database is not flooded with unique attribute blobs on every energy update.
"""

_unrecorded_attributes = _ENERGY_SENSOR_UNRECORDED_ATTRIBUTES

def __init__(
self,
data_coordinator: SpanPanelCoordinator,
Expand Down
26 changes: 26 additions & 0 deletions tests/test_energy_sensor_recorder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Energy sensors exclude volatile attributes from recorder history (#197)."""

from homeassistant.components.sensor import SensorEntity

from custom_components.span_panel.sensor_base import SpanEnergySensorBase


def test_span_energy_sensor_combined_unrecorded_includes_high_churn_attributes() -> None:
"""Recorder must not persist grace-period / dip diagnostic attributes."""

combined = getattr(SpanEnergySensorBase, "_Entity__combined_unrecorded_attributes")
for key in (
"energy_offset",
"grace_period_remaining",
"last_dip_delta",
"last_valid_changed",
"last_valid_state",
"tabs",
"using_grace_period",
"voltage",
):
assert key in combined, f"missing unrecorded key: {key}"

assert SensorEntity._entity_component_unrecorded_attributes <= combined, (
"sensor component exclusions (e.g. options) must remain"
)
Loading