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
10 changes: 7 additions & 3 deletions homeassistant/components/sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,14 @@

# The state represents a measurement in present time
STATE_CLASS_MEASUREMENT: Final = "measurement"
# The state represents a total amount, e.g. net energy consumption
STATE_CLASS_TOTAL: Final = "total"
# The state represents a monotonically increasing total, e.g. an amount of consumed gas
STATE_CLASS_TOTAL_INCREASING: Final = "total_increasing"

STATE_CLASSES: Final[list[str]] = [
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL,
STATE_CLASS_TOTAL_INCREASING,
]

Expand Down Expand Up @@ -214,9 +217,10 @@ def state_attributes(self) -> dict[str, Any] | None:
report_issue = self._suggest_report_issue()
_LOGGER.warning(
"Entity %s (%s) with state_class %s has set last_reset. Setting "
"last_reset is deprecated and will be unsupported from Home "
"Assistant Core 2021.11. Please update your configuration if "
"state_class is manually configured, otherwise %s",
"last_reset for entities with state_class other than 'total' is "
"deprecated and will be removed from Home Assistant Core 2021.11. "
"Please update your configuration if state_class is manually "
"configured, otherwise %s",
self.entity_id,
type(self),
self.state_class,
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/components/sensor/recorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL,
STATE_CLASS_TOTAL_INCREASING,
STATE_CLASSES,
)
Expand Down Expand Up @@ -56,10 +57,12 @@
DEVICE_CLASS_GAS: {"sum"},
DEVICE_CLASS_MONETARY: {"sum"},
},
STATE_CLASS_TOTAL: {},
STATE_CLASS_TOTAL_INCREASING: {},
}
DEFAULT_STATISTICS = {
STATE_CLASS_MEASUREMENT: {"mean", "min", "max"},
STATE_CLASS_TOTAL: {"sum"},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is the idea for now to just keep track of sum and not do the increasing/decreasing part yet ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I have an implementation of it ready, but I think it makes sense to add that in a follow-up PR.

STATE_CLASS_TOTAL_INCREASING: {"sum"},
}

Expand Down Expand Up @@ -393,7 +396,7 @@ def compile_statistics( # noqa: C901

for fstate, state in fstates:

# Deprecated, will be removed in Home Assistant 2021.10
# Deprecated, will be removed in Home Assistant 2021.11
if (
"last_reset" not in state.attributes
and state_class == STATE_CLASS_MEASUREMENT
Expand Down
3 changes: 2 additions & 1 deletion tests/components/energy/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from homeassistant.components.energy import data
from homeassistant.components.sensor import (
ATTR_STATE_CLASS,
STATE_CLASS_TOTAL,
STATE_CLASS_TOTAL_INCREASING,
)
from homeassistant.components.sensor.recorder import compile_statistics
Expand Down Expand Up @@ -357,7 +358,7 @@ async def test_cost_sensor_handle_gas(hass, hass_storage) -> None:
assert state.state == "50.0"


@pytest.mark.parametrize("state_class", [None])
@pytest.mark.parametrize("state_class", [None, STATE_CLASS_TOTAL])
async def test_cost_sensor_wrong_state_class(
hass, hass_storage, caplog, state_class
) -> None:
Expand Down
9 changes: 5 additions & 4 deletions tests/components/sensor/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ async def test_deprecated_last_reset(hass, caplog, enable_custom_integrations):

assert (
"Entity sensor.test (<class 'custom_components.test.sensor.MockSensor'>) "
"with state_class measurement has set last_reset. Setting last_reset is "
"deprecated and will be unsupported from Home Assistant Core 2021.11. Please "
"update your configuration if state_class is manually configured, otherwise "
"report it to the custom component author."
"with state_class measurement has set last_reset. Setting last_reset for "
"entities with state_class other than 'total' is deprecated and will be "
"removed from Home Assistant Core 2021.11. Please update your configuration if "
"state_class is manually configured, otherwise report it to the custom "
"component author."
) in caplog.text


Expand Down
84 changes: 83 additions & 1 deletion tests/components/sensor/test_recorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes
assert "Error while processing event StatisticsTask" not in caplog.text


@pytest.mark.parametrize("state_class", ["measurement"])
@pytest.mark.parametrize("state_class", ["measurement", "total"])
@pytest.mark.parametrize(
"device_class,unit,native_unit,factor",
[
Expand Down Expand Up @@ -349,6 +349,88 @@ def test_compile_hourly_sum_statistics_amount_reset_every_state_change(
assert "Error while processing event StatisticsTask" not in caplog.text


@pytest.mark.parametrize(
"device_class,unit,native_unit,factor",
[
("energy", "kWh", "kWh", 1),
("energy", "Wh", "kWh", 1 / 1000),
("monetary", "EUR", "EUR", 1),
("monetary", "SEK", "SEK", 1),
("gas", "m³", "m³", 1),
("gas", "ft³", "m³", 0.0283168466),
],
)
def test_compile_hourly_sum_statistics_total_no_reset(
hass_recorder, caplog, device_class, unit, native_unit, factor
):
"""Test compiling hourly statistics."""
zero = dt_util.utcnow()
hass = hass_recorder()
recorder = hass.data[DATA_INSTANCE]
setup_component(hass, "sensor", {})
attributes = {
"device_class": device_class,
"state_class": "total",
"unit_of_measurement": unit,
}
seq = [10, 15, 20, 10, 30, 40, 50, 60, 70]

four, eight, states = record_meter_states(
hass, zero, "sensor.test1", attributes, seq
)
hist = history.get_significant_states(
hass, zero - timedelta.resolution, eight + timedelta.resolution
)
assert dict(states)["sensor.test1"] == dict(hist)["sensor.test1"]

recorder.do_adhoc_statistics(period="hourly", start=zero)
wait_recording_done(hass)
recorder.do_adhoc_statistics(period="hourly", start=zero + timedelta(hours=1))
wait_recording_done(hass)
recorder.do_adhoc_statistics(period="hourly", start=zero + timedelta(hours=2))
wait_recording_done(hass)
statistic_ids = list_statistic_ids(hass)
assert statistic_ids == [
{"statistic_id": "sensor.test1", "unit_of_measurement": native_unit}
]
stats = statistics_during_period(hass, zero)
assert stats == {
"sensor.test1": [
{
"statistic_id": "sensor.test1",
"start": process_timestamp_to_utc_isoformat(zero),
"max": None,
"mean": None,
"min": None,
"last_reset": None,
"state": approx(factor * seq[2]),
"sum": approx(factor * 10.0),
},
{
"statistic_id": "sensor.test1",
"start": process_timestamp_to_utc_isoformat(zero + timedelta(hours=1)),
"max": None,
"mean": None,
"min": None,
"last_reset": None,
"state": approx(factor * seq[5]),
"sum": approx(factor * 30.0),
},
{
"statistic_id": "sensor.test1",
"start": process_timestamp_to_utc_isoformat(zero + timedelta(hours=2)),
"max": None,
"mean": None,
"min": None,
"last_reset": None,
"state": approx(factor * seq[8]),
"sum": approx(factor * 60.0),
},
]
}
assert "Error while processing event StatisticsTask" not in caplog.text


@pytest.mark.parametrize(
"device_class,unit,native_unit,factor",
[
Expand Down