diff --git a/homeassistant/components/energy/sensor.py b/homeassistant/components/energy/sensor.py index 1c42ea5a050..e974035cbd6 100644 --- a/homeassistant/components/energy/sensor.py +++ b/homeassistant/components/energy/sensor.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass from functools import partial +import logging from typing import Any, Final, Literal, TypeVar, cast from homeassistant.components.sensor import ( @@ -11,6 +12,11 @@ from homeassistant.components.sensor import ( STATE_CLASS_MEASUREMENT, SensorEntity, ) +from homeassistant.const import ( + ATTR_UNIT_OF_MEASUREMENT, + ENERGY_KILO_WATT_HOUR, + ENERGY_WATT_HOUR, +) from homeassistant.core import HomeAssistant, State, callback, split_entity_id from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_state_change_event @@ -20,6 +26,8 @@ import homeassistant.util.dt as dt_util from .const import DOMAIN from .data import EnergyManager, async_get_manager +_LOGGER = logging.getLogger(__name__) + async def async_setup_platform( hass: HomeAssistant, @@ -188,6 +196,12 @@ class EnergyCostSensor(SensorEntity): energy_price = float(energy_price_state.state) except ValueError: return + + if energy_price_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT, "").endswith( + f"/{ENERGY_WATT_HOUR}" + ): + energy_price *= 1000.0 + else: energy_price_state = None energy_price = cast(float, self._flow["number_energy_price"]) @@ -197,6 +211,16 @@ class EnergyCostSensor(SensorEntity): self._reset(energy_state) return + energy_unit = energy_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + + if energy_unit == ENERGY_WATT_HOUR: + energy_price /= 1000 + elif energy_unit != ENERGY_KILO_WATT_HOUR: + _LOGGER.warning( + "Found unexpected unit %s for %s", energy_unit, energy_state.entity_id + ) + return + if ( energy_state.attributes[ATTR_LAST_RESET] != self._last_energy_sensor_state.attributes[ATTR_LAST_RESET] diff --git a/tests/components/energy/test_sensor.py b/tests/components/energy/test_sensor.py index f3af93c06c1..978b21e1919 100644 --- a/tests/components/energy/test_sensor.py +++ b/tests/components/energy/test_sensor.py @@ -16,6 +16,8 @@ from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT, DEVICE_CLASS_MONETARY, + ENERGY_KILO_WATT_HOUR, + ENERGY_WATT_HOUR, ) from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -133,7 +135,9 @@ async def test_cost_sensor_price_entity( # Optionally initialize dependent entities if initial_energy is not None: hass.states.async_set( - usage_sensor_entity_id, initial_energy, {"last_reset": last_reset} + usage_sensor_entity_id, + initial_energy, + {"last_reset": last_reset, ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}, ) hass.states.async_set("sensor.energy_price", "1") @@ -152,7 +156,12 @@ async def test_cost_sensor_price_entity( if initial_energy is None: with patch("homeassistant.util.dt.utcnow", return_value=now): hass.states.async_set( - usage_sensor_entity_id, "0", {"last_reset": last_reset} + usage_sensor_entity_id, + "0", + { + "last_reset": last_reset, + ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, + }, ) await hass.async_block_till_done() @@ -169,7 +178,11 @@ async def test_cost_sensor_price_entity( # # assert entry.unique_id == "energy_energy_consumption cost" # Energy use bumped to 10 kWh - hass.states.async_set(usage_sensor_entity_id, "10", {"last_reset": last_reset}) + hass.states.async_set( + usage_sensor_entity_id, + "10", + {"last_reset": last_reset, ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}, + ) 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 @@ -189,7 +202,11 @@ async def test_cost_sensor_price_entity( assert state.state == "10.0" # 10 EUR + (10-10) kWh * 2 EUR/kWh = 10 EUR # Additional consumption is using the new price - hass.states.async_set(usage_sensor_entity_id, "14.5", {"last_reset": last_reset}) + hass.states.async_set( + usage_sensor_entity_id, + "14.5", + {"last_reset": last_reset, ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}, + ) 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 @@ -202,13 +219,21 @@ async def test_cost_sensor_price_entity( # Energy sensor is reset, with start point at 4kWh last_reset = (now + timedelta(seconds=1)).isoformat() - hass.states.async_set(usage_sensor_entity_id, "4", {"last_reset": last_reset}) + hass.states.async_set( + usage_sensor_entity_id, + "4", + {"last_reset": last_reset, ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}, + ) await hass.async_block_till_done() state = hass.states.get(cost_sensor_entity_id) assert state.state == "0.0" # 0 EUR + (4-4) kWh * 2 EUR/kWh = 0 EUR # Energy use bumped to 10 kWh - hass.states.async_set(usage_sensor_entity_id, "10", {"last_reset": last_reset}) + hass.states.async_set( + usage_sensor_entity_id, + "10", + {"last_reset": last_reset, ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR}, + ) await hass.async_block_till_done() state = hass.states.get(cost_sensor_entity_id) assert state.state == "12.0" # 0 EUR + (10-4) kWh * 2 EUR/kWh = 12 EUR @@ -218,3 +243,55 @@ async def test_cost_sensor_price_entity( 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"] == 31.0 + + +async def test_cost_sensor_handle_wh(hass, hass_storage) -> None: + """Test energy cost price from sensor entity.""" + 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() + last_reset = dt_util.utc_from_timestamp(0).isoformat() + + hass.states.async_set( + "sensor.energy_consumption", + 10000, + {"last_reset": last_reset, "unit_of_measurement": ENERGY_WATT_HOUR}, + ) + + with patch("homeassistant.util.dt.utcnow", return_value=now): + await setup_integration(hass) + + state = hass.states.get("sensor.energy_consumption_cost") + assert state.state == "0.0" + + # Energy use bumped to 10 kWh + hass.states.async_set( + "sensor.energy_consumption", + 20000, + {"last_reset": last_reset, "unit_of_measurement": ENERGY_WATT_HOUR}, + ) + await hass.async_block_till_done() + + state = hass.states.get("sensor.energy_consumption_cost") + assert state.state == "5.0"