Add support for state class measurement to energy cost sensor (#55962)

This commit is contained in:
Erik Montnemery 2021-09-08 21:46:28 +02:00 committed by GitHub
parent ee7202d10a
commit bb6c2093a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 332 additions and 36 deletions

View File

@ -1,13 +1,16 @@
"""Helper sensor for calculating utility costs.""" """Helper sensor for calculating utility costs."""
from __future__ import annotations from __future__ import annotations
import copy
from dataclasses import dataclass from dataclasses import dataclass
import logging import logging
from typing import Any, Final, Literal, TypeVar, cast from typing import Any, Final, Literal, TypeVar, cast
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
ATTR_LAST_RESET,
ATTR_STATE_CLASS, ATTR_STATE_CLASS,
DEVICE_CLASS_MONETARY, DEVICE_CLASS_MONETARY,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_TOTAL_INCREASING,
SensorEntity, SensorEntity,
) )
@ -18,14 +21,19 @@ from homeassistant.const import (
ENERGY_WATT_HOUR, ENERGY_WATT_HOUR,
VOLUME_CUBIC_METERS, VOLUME_CUBIC_METERS,
) )
from homeassistant.core import HomeAssistant, callback, split_entity_id from homeassistant.core import HomeAssistant, State, callback, split_entity_id
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import homeassistant.util.dt as dt_util
from .const import DOMAIN from .const import DOMAIN
from .data import EnergyManager, async_get_manager from .data import EnergyManager, async_get_manager
SUPPORTED_STATE_CLASSES = [
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -206,15 +214,16 @@ class EnergyCostSensor(SensorEntity):
f"{config[adapter.entity_energy_key]}_{adapter.entity_id_suffix}" f"{config[adapter.entity_energy_key]}_{adapter.entity_id_suffix}"
) )
self._attr_device_class = DEVICE_CLASS_MONETARY self._attr_device_class = DEVICE_CLASS_MONETARY
self._attr_state_class = STATE_CLASS_TOTAL_INCREASING self._attr_state_class = STATE_CLASS_MEASUREMENT
self._config = config self._config = config
self._last_energy_sensor_state: StateType | None = None self._last_energy_sensor_state: State | None = None
self._cur_value = 0.0 self._cur_value = 0.0
def _reset(self, energy_state: StateType) -> None: def _reset(self, energy_state: State) -> None:
"""Reset the cost sensor.""" """Reset the cost sensor."""
self._attr_native_value = 0.0 self._attr_native_value = 0.0
self._cur_value = 0.0 self._cur_value = 0.0
self._attr_last_reset = dt_util.utcnow()
self._last_energy_sensor_state = energy_state self._last_energy_sensor_state = energy_state
self.async_write_ha_state() self.async_write_ha_state()
@ -228,9 +237,8 @@ class EnergyCostSensor(SensorEntity):
if energy_state is None: if energy_state is None:
return return
if ( state_class = energy_state.attributes.get(ATTR_STATE_CLASS)
state_class := energy_state.attributes.get(ATTR_STATE_CLASS) if state_class not in SUPPORTED_STATE_CLASSES:
) != STATE_CLASS_TOTAL_INCREASING:
if not self._wrong_state_class_reported: if not self._wrong_state_class_reported:
self._wrong_state_class_reported = True self._wrong_state_class_reported = True
_LOGGER.warning( _LOGGER.warning(
@ -240,6 +248,13 @@ class EnergyCostSensor(SensorEntity):
) )
return return
# last_reset must be set if the sensor is STATE_CLASS_MEASUREMENT
if (
state_class == STATE_CLASS_MEASUREMENT
and ATTR_LAST_RESET not in energy_state.attributes
):
return
try: try:
energy = float(energy_state.state) energy = float(energy_state.state)
except ValueError: except ValueError:
@ -273,7 +288,7 @@ class EnergyCostSensor(SensorEntity):
if self._last_energy_sensor_state is None: if self._last_energy_sensor_state is None:
# Initialize as it's the first time all required entities are in place. # Initialize as it's the first time all required entities are in place.
self._reset(energy_state.state) self._reset(energy_state)
return return
energy_unit = energy_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) energy_unit = energy_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
@ -298,20 +313,29 @@ class EnergyCostSensor(SensorEntity):
) )
return return
if reset_detected( if state_class != STATE_CLASS_TOTAL_INCREASING and energy_state.attributes.get(
ATTR_LAST_RESET
) != self._last_energy_sensor_state.attributes.get(ATTR_LAST_RESET):
# Energy meter was reset, reset cost sensor too
energy_state_copy = copy.copy(energy_state)
energy_state_copy.state = "0.0"
self._reset(energy_state_copy)
elif state_class == STATE_CLASS_TOTAL_INCREASING and reset_detected(
self.hass, self.hass,
cast(str, self._config[self._adapter.entity_energy_key]), cast(str, self._config[self._adapter.entity_energy_key]),
energy, energy,
float(self._last_energy_sensor_state), float(self._last_energy_sensor_state.state),
): ):
# Energy meter was reset, reset cost sensor too # Energy meter was reset, reset cost sensor too
self._reset(0) energy_state_copy = copy.copy(energy_state)
energy_state_copy.state = "0.0"
self._reset(energy_state_copy)
# Update with newly incurred cost # Update with newly incurred cost
old_energy_value = float(self._last_energy_sensor_state) old_energy_value = float(self._last_energy_sensor_state.state)
self._cur_value += (energy - old_energy_value) * energy_price self._cur_value += (energy - old_energy_value) * energy_price
self._attr_native_value = round(self._cur_value, 2) self._attr_native_value = round(self._cur_value, 2)
self._last_energy_sensor_state = energy_state.state self._last_energy_sensor_state = energy_state
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Register callbacks.""" """Register callbacks."""

View File

@ -113,7 +113,11 @@ def _async_validate_usage_stat(
state_class = state.attributes.get("state_class") state_class = state.attributes.get("state_class")
if state_class != sensor.STATE_CLASS_TOTAL_INCREASING: supported_state_classes = [
sensor.STATE_CLASS_MEASUREMENT,
sensor.STATE_CLASS_TOTAL_INCREASING,
]
if state_class not in supported_state_classes:
result.append( result.append(
ValidationIssue( ValidationIssue(
"entity_unexpected_state_class_total_increasing", "entity_unexpected_state_class_total_increasing",
@ -140,16 +144,13 @@ def _async_validate_price_entity(
return return
try: try:
value: float | None = float(state.state) float(state.state)
except ValueError: except ValueError:
result.append( result.append(
ValidationIssue("entity_state_non_numeric", entity_id, state.state) ValidationIssue("entity_state_non_numeric", entity_id, state.state)
) )
return return
if value is not None and value < 0:
result.append(ValidationIssue("entity_negative_state", entity_id, value))
unit = state.attributes.get("unit_of_measurement") unit = state.attributes.get("unit_of_measurement")
if unit is None or not unit.endswith( if unit is None or not unit.endswith(
@ -203,7 +204,11 @@ def _async_validate_cost_entity(
state_class = state.attributes.get("state_class") state_class = state.attributes.get("state_class")
if state_class != sensor.STATE_CLASS_TOTAL_INCREASING: supported_state_classes = [
sensor.STATE_CLASS_MEASUREMENT,
sensor.STATE_CLASS_TOTAL_INCREASING,
]
if state_class not in supported_state_classes:
result.append( result.append(
ValidationIssue( ValidationIssue(
"entity_unexpected_state_class_total_increasing", entity_id, state_class "entity_unexpected_state_class_total_increasing", entity_id, state_class

View File

@ -78,7 +78,7 @@ async def test_cost_sensor_no_states(hass, hass_storage) -> None:
), ),
], ],
) )
async def test_cost_sensor_price_entity( async def test_cost_sensor_price_entity_total_increasing(
hass, hass,
hass_storage, hass_storage,
hass_ws_client, hass_ws_client,
@ -90,7 +90,7 @@ async def test_cost_sensor_price_entity(
cost_sensor_entity_id, cost_sensor_entity_id,
flow_type, flow_type,
) -> None: ) -> None:
"""Test energy cost price from sensor entity.""" """Test energy cost price from total_increasing type sensor entity."""
def _compile_statistics(_): def _compile_statistics(_):
return compile_statistics(hass, now, now + timedelta(seconds=1)) return compile_statistics(hass, now, now + timedelta(seconds=1))
@ -137,6 +137,7 @@ async def test_cost_sensor_price_entity(
} }
now = dt_util.utcnow() now = dt_util.utcnow()
last_reset_cost_sensor = now.isoformat()
# Optionally initialize dependent entities # Optionally initialize dependent entities
if initial_energy is not None: if initial_energy is not None:
@ -153,7 +154,9 @@ async def test_cost_sensor_price_entity(
state = hass.states.get(cost_sensor_entity_id) state = hass.states.get(cost_sensor_entity_id)
assert state.state == initial_cost assert state.state == initial_cost
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_MONETARY assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_MONETARY
assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_TOTAL_INCREASING if initial_cost != "unknown":
assert state.attributes["last_reset"] == last_reset_cost_sensor
assert state.attributes[ATTR_STATE_CLASS] == "measurement"
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == "EUR" assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == "EUR"
# Optional late setup of dependent entities # Optional late setup of dependent entities
@ -169,7 +172,8 @@ async def test_cost_sensor_price_entity(
state = hass.states.get(cost_sensor_entity_id) state = hass.states.get(cost_sensor_entity_id)
assert state.state == "0.0" assert state.state == "0.0"
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_MONETARY assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_MONETARY
assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_TOTAL_INCREASING assert state.attributes["last_reset"] == last_reset_cost_sensor
assert state.attributes[ATTR_STATE_CLASS] == "measurement"
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == "EUR" assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == "EUR"
# # Unique ID temp disabled # # Unique ID temp disabled
@ -186,6 +190,7 @@ async def test_cost_sensor_price_entity(
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(cost_sensor_entity_id) state = hass.states.get(cost_sensor_entity_id)
assert state.state == "10.0" # 0 EUR + (10-0) kWh * 1 EUR/kWh = 10 EUR assert state.state == "10.0" # 0 EUR + (10-0) kWh * 1 EUR/kWh = 10 EUR
assert state.attributes["last_reset"] == last_reset_cost_sensor
# Nothing happens when price changes # Nothing happens when price changes
if price_entity is not None: if price_entity is not None:
@ -200,6 +205,7 @@ async def test_cost_sensor_price_entity(
assert msg["success"] assert msg["success"]
state = hass.states.get(cost_sensor_entity_id) state = hass.states.get(cost_sensor_entity_id)
assert state.state == "10.0" # 10 EUR + (10-10) kWh * 2 EUR/kWh = 10 EUR assert state.state == "10.0" # 10 EUR + (10-10) kWh * 2 EUR/kWh = 10 EUR
assert state.attributes["last_reset"] == last_reset_cost_sensor
# Additional consumption is using the new price # Additional consumption is using the new price
hass.states.async_set( hass.states.async_set(
@ -210,6 +216,7 @@ async def test_cost_sensor_price_entity(
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(cost_sensor_entity_id) state = hass.states.get(cost_sensor_entity_id)
assert state.state == "19.0" # 10 EUR + (14.5-10) kWh * 2 EUR/kWh = 19 EUR assert state.state == "19.0" # 10 EUR + (14.5-10) kWh * 2 EUR/kWh = 19 EUR
assert state.attributes["last_reset"] == last_reset_cost_sensor
# Check generated statistics # Check generated statistics
await async_wait_recording_done_without_instance(hass) await async_wait_recording_done_without_instance(hass)
@ -226,6 +233,7 @@ async def test_cost_sensor_price_entity(
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(cost_sensor_entity_id) state = hass.states.get(cost_sensor_entity_id)
assert state.state == "18.0" # 19 EUR + (14-14.5) kWh * 2 EUR/kWh = 18 EUR assert state.state == "18.0" # 19 EUR + (14-14.5) kWh * 2 EUR/kWh = 18 EUR
assert state.attributes["last_reset"] == last_reset_cost_sensor
# Energy sensor is reset, with initial state at 4kWh, 0 kWh is used as zero-point # Energy sensor is reset, with initial state at 4kWh, 0 kWh is used as zero-point
hass.states.async_set( hass.states.async_set(
@ -236,6 +244,8 @@ async def test_cost_sensor_price_entity(
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(cost_sensor_entity_id) state = hass.states.get(cost_sensor_entity_id)
assert state.state == "8.0" # 0 EUR + (4-0) kWh * 2 EUR/kWh = 8 EUR assert state.state == "8.0" # 0 EUR + (4-0) kWh * 2 EUR/kWh = 8 EUR
assert state.attributes["last_reset"] != last_reset_cost_sensor
last_reset_cost_sensor = state.attributes["last_reset"]
# Energy use bumped to 10 kWh # Energy use bumped to 10 kWh
hass.states.async_set( hass.states.async_set(
@ -246,6 +256,213 @@ async def test_cost_sensor_price_entity(
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(cost_sensor_entity_id) state = hass.states.get(cost_sensor_entity_id)
assert state.state == "20.0" # 8 EUR + (10-4) kWh * 2 EUR/kWh = 20 EUR assert state.state == "20.0" # 8 EUR + (10-4) kWh * 2 EUR/kWh = 20 EUR
assert state.attributes["last_reset"] == last_reset_cost_sensor
# Check generated statistics
await async_wait_recording_done_without_instance(hass)
statistics = await hass.loop.run_in_executor(None, _compile_statistics, hass)
assert cost_sensor_entity_id in statistics
assert statistics[cost_sensor_entity_id]["stat"]["sum"] == 38.0
@pytest.mark.parametrize("initial_energy,initial_cost", [(0, "0.0"), (None, "unknown")])
@pytest.mark.parametrize(
"price_entity,fixed_price", [("sensor.energy_price", None), (None, 1)]
)
@pytest.mark.parametrize(
"usage_sensor_entity_id,cost_sensor_entity_id,flow_type",
[
("sensor.energy_consumption", "sensor.energy_consumption_cost", "flow_from"),
(
"sensor.energy_production",
"sensor.energy_production_compensation",
"flow_to",
),
],
)
@pytest.mark.parametrize("energy_state_class", ["measurement"])
async def test_cost_sensor_price_entity_total(
hass,
hass_storage,
hass_ws_client,
initial_energy,
initial_cost,
price_entity,
fixed_price,
usage_sensor_entity_id,
cost_sensor_entity_id,
flow_type,
energy_state_class,
) -> None:
"""Test energy cost price from total type sensor entity."""
def _compile_statistics(_):
return compile_statistics(hass, now, now + timedelta(seconds=1))
energy_attributes = {
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
ATTR_STATE_CLASS: energy_state_class,
}
await async_init_recorder_component(hass)
energy_data = data.EnergyManager.default_preferences()
energy_data["energy_sources"].append(
{
"type": "grid",
"flow_from": [
{
"stat_energy_from": "sensor.energy_consumption",
"entity_energy_from": "sensor.energy_consumption",
"stat_cost": None,
"entity_energy_price": price_entity,
"number_energy_price": fixed_price,
}
]
if flow_type == "flow_from"
else [],
"flow_to": [
{
"stat_energy_to": "sensor.energy_production",
"entity_energy_to": "sensor.energy_production",
"stat_compensation": None,
"entity_energy_price": price_entity,
"number_energy_price": fixed_price,
}
]
if flow_type == "flow_to"
else [],
"cost_adjustment_day": 0,
}
)
hass_storage[data.STORAGE_KEY] = {
"version": 1,
"data": energy_data,
}
now = dt_util.utcnow()
last_reset = dt_util.utc_from_timestamp(0).isoformat()
last_reset_cost_sensor = now.isoformat()
# Optionally initialize dependent entities
if initial_energy is not None:
hass.states.async_set(
usage_sensor_entity_id,
initial_energy,
{**energy_attributes, **{"last_reset": last_reset}},
)
hass.states.async_set("sensor.energy_price", "1")
with patch("homeassistant.util.dt.utcnow", return_value=now):
await setup_integration(hass)
state = hass.states.get(cost_sensor_entity_id)
assert state.state == initial_cost
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_MONETARY
if initial_cost != "unknown":
assert state.attributes["last_reset"] == last_reset_cost_sensor
assert state.attributes[ATTR_STATE_CLASS] == "measurement"
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == "EUR"
# Optional late setup of dependent entities
if initial_energy is None:
with patch("homeassistant.util.dt.utcnow", return_value=now):
hass.states.async_set(
usage_sensor_entity_id,
"0",
{**energy_attributes, **{"last_reset": last_reset}},
)
await hass.async_block_till_done()
state = hass.states.get(cost_sensor_entity_id)
assert state.state == "0.0"
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_MONETARY
assert state.attributes["last_reset"] == last_reset_cost_sensor
assert state.attributes[ATTR_STATE_CLASS] == "measurement"
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == "EUR"
# # Unique ID temp disabled
# # entity_registry = er.async_get(hass)
# # entry = entity_registry.async_get(cost_sensor_entity_id)
# # assert entry.unique_id == "energy_energy_consumption cost"
# Energy use bumped to 10 kWh
hass.states.async_set(
usage_sensor_entity_id,
"10",
{**energy_attributes, **{"last_reset": last_reset}},
)
await hass.async_block_till_done()
state = hass.states.get(cost_sensor_entity_id)
assert state.state == "10.0" # 0 EUR + (10-0) kWh * 1 EUR/kWh = 10 EUR
assert state.attributes["last_reset"] == last_reset_cost_sensor
# Nothing happens when price changes
if price_entity is not None:
hass.states.async_set(price_entity, "2")
await hass.async_block_till_done()
else:
energy_data = copy.deepcopy(energy_data)
energy_data["energy_sources"][0][flow_type][0]["number_energy_price"] = 2
client = await hass_ws_client(hass)
await client.send_json({"id": 5, "type": "energy/save_prefs", **energy_data})
msg = await client.receive_json()
assert msg["success"]
state = hass.states.get(cost_sensor_entity_id)
assert state.state == "10.0" # 10 EUR + (10-10) kWh * 2 EUR/kWh = 10 EUR
assert state.attributes["last_reset"] == last_reset_cost_sensor
# Additional consumption is using the new price
hass.states.async_set(
usage_sensor_entity_id,
"14.5",
{**energy_attributes, **{"last_reset": last_reset}},
)
await hass.async_block_till_done()
state = hass.states.get(cost_sensor_entity_id)
assert state.state == "19.0" # 10 EUR + (14.5-10) kWh * 2 EUR/kWh = 19 EUR
assert state.attributes["last_reset"] == last_reset_cost_sensor
# Check generated statistics
await async_wait_recording_done_without_instance(hass)
statistics = await hass.loop.run_in_executor(None, _compile_statistics, hass)
assert cost_sensor_entity_id in statistics
assert statistics[cost_sensor_entity_id]["stat"]["sum"] == 19.0
# Energy sensor has a small dip
hass.states.async_set(
usage_sensor_entity_id,
"14",
{**energy_attributes, **{"last_reset": last_reset}},
)
await hass.async_block_till_done()
state = hass.states.get(cost_sensor_entity_id)
assert state.state == "18.0" # 19 EUR + (14-14.5) kWh * 2 EUR/kWh = 18 EUR
assert state.attributes["last_reset"] == last_reset_cost_sensor
# Energy sensor is reset, with initial state at 4kWh, 0 kWh is used as zero-point
last_reset = (now + timedelta(seconds=1)).isoformat()
hass.states.async_set(
usage_sensor_entity_id,
"4",
{**energy_attributes, **{"last_reset": last_reset}},
)
await hass.async_block_till_done()
state = hass.states.get(cost_sensor_entity_id)
assert state.state == "8.0" # 0 EUR + (4-0) kWh * 2 EUR/kWh = 8 EUR
assert state.attributes["last_reset"] != last_reset_cost_sensor
last_reset_cost_sensor = state.attributes["last_reset"]
# Energy use bumped to 10 kWh
hass.states.async_set(
usage_sensor_entity_id,
"10",
{**energy_attributes, **{"last_reset": last_reset}},
)
await hass.async_block_till_done()
state = hass.states.get(cost_sensor_entity_id)
assert state.state == "20.0" # 8 EUR + (10-4) kWh * 2 EUR/kWh = 20 EUR
assert state.attributes["last_reset"] == last_reset_cost_sensor
# Check generated statistics # Check generated statistics
await async_wait_recording_done_without_instance(hass) await async_wait_recording_done_without_instance(hass)
@ -285,6 +502,7 @@ async def test_cost_sensor_handle_wh(hass, hass_storage) -> None:
now = dt_util.utcnow() now = dt_util.utcnow()
# Initial state: 10kWh
hass.states.async_set( hass.states.async_set(
"sensor.energy_consumption", "sensor.energy_consumption",
10000, 10000,
@ -297,7 +515,7 @@ async def test_cost_sensor_handle_wh(hass, hass_storage) -> None:
state = hass.states.get("sensor.energy_consumption_cost") state = hass.states.get("sensor.energy_consumption_cost")
assert state.state == "0.0" assert state.state == "0.0"
# Energy use bumped to 10 kWh # Energy use bumped by 10 kWh
hass.states.async_set( hass.states.async_set(
"sensor.energy_consumption", "sensor.energy_consumption",
20000, 20000,
@ -362,7 +580,7 @@ async def test_cost_sensor_handle_gas(hass, hass_storage) -> None:
async def test_cost_sensor_wrong_state_class( async def test_cost_sensor_wrong_state_class(
hass, hass_storage, caplog, state_class hass, hass_storage, caplog, state_class
) -> None: ) -> None:
"""Test energy sensor rejects wrong state_class.""" """Test energy sensor rejects state_class with wrong state_class."""
energy_attributes = { energy_attributes = {
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
ATTR_STATE_CLASS: state_class, ATTR_STATE_CLASS: state_class,
@ -418,3 +636,61 @@ async def test_cost_sensor_wrong_state_class(
state = hass.states.get("sensor.energy_consumption_cost") state = hass.states.get("sensor.energy_consumption_cost")
assert state.state == STATE_UNKNOWN assert state.state == STATE_UNKNOWN
@pytest.mark.parametrize("state_class", ["measurement"])
async def test_cost_sensor_state_class_measurement_no_reset(
hass, hass_storage, caplog, state_class
) -> None:
"""Test energy sensor rejects state_class with no last_reset."""
energy_attributes = {
ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
ATTR_STATE_CLASS: state_class,
}
energy_data = data.EnergyManager.default_preferences()
energy_data["energy_sources"].append(
{
"type": "grid",
"flow_from": [
{
"stat_energy_from": "sensor.energy_consumption",
"entity_energy_from": "sensor.energy_consumption",
"stat_cost": None,
"entity_energy_price": None,
"number_energy_price": 0.5,
}
],
"flow_to": [],
"cost_adjustment_day": 0,
}
)
hass_storage[data.STORAGE_KEY] = {
"version": 1,
"data": energy_data,
}
now = dt_util.utcnow()
hass.states.async_set(
"sensor.energy_consumption",
10000,
energy_attributes,
)
with patch("homeassistant.util.dt.utcnow", return_value=now):
await setup_integration(hass)
state = hass.states.get("sensor.energy_consumption_cost")
assert state.state == STATE_UNKNOWN
# Energy use bumped to 10 kWh
hass.states.async_set(
"sensor.energy_consumption",
20000,
energy_attributes,
)
await hass.async_block_till_done()
state = hass.states.get("sensor.energy_consumption_cost")
assert state.state == STATE_UNKNOWN

View File

@ -382,15 +382,6 @@ async def test_validation_grid_price_not_exist(hass, mock_energy_manager):
"value": "123,123.12", "value": "123,123.12",
}, },
), ),
(
"-100",
"$/kWh",
{
"type": "entity_negative_state",
"identifier": "sensor.grid_price_1",
"value": -100.0,
},
),
( (
"123", "123",
"$/Ws", "$/Ws",
@ -414,7 +405,7 @@ async def test_validation_grid_price_errors(
hass.states.async_set( hass.states.async_set(
"sensor.grid_price_1", "sensor.grid_price_1",
state, state,
{"unit_of_measurement": unit, "state_class": "total_increasing"}, {"unit_of_measurement": unit, "state_class": "measurement"},
) )
await mock_energy_manager.async_update( await mock_energy_manager.async_update(
{ {