From 2497ff5a39292b7167733206fe2e1e63a6a3b86e Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 24 Aug 2022 11:09:23 +0200 Subject: [PATCH] Add energy and gas sensors to demo integration (#77206) --- homeassistant/components/demo/__init__.py | 84 +++++++++++++++-- homeassistant/components/demo/sensor.py | 105 ++++++++++++++++++++-- tests/components/demo/test_init.py | 6 +- tests/components/sensor/test_recorder.py | 2 +- 4 files changed, 181 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py index 2e01e2c3c6b..6d0f6499001 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -6,6 +6,7 @@ from random import random from homeassistant import config_entries, setup from homeassistant.components import persistent_notification from homeassistant.components.recorder import get_instance +from homeassistant.components.recorder.models import StatisticMetaData from homeassistant.components.recorder.statistics import ( async_add_external_statistics, get_last_statistics, @@ -260,15 +261,16 @@ def _generate_sum_statistics(start, end, init_value, max_diff): return statistics -async def _insert_statistics(hass): +async def _insert_statistics(hass: HomeAssistant) -> None: """Insert some fake statistics.""" now = dt_util.now() yesterday = now - datetime.timedelta(days=1) yesterday_midnight = yesterday.replace(hour=0, minute=0, second=0, microsecond=0) # Fake yesterday's temperatures - metadata = { + metadata: StatisticMetaData = { "source": DOMAIN, + "name": "Outdoor temperature", "statistic_id": f"{DOMAIN}:temperature_outdoor", "unit_of_measurement": "°C", "has_mean": True, @@ -279,26 +281,94 @@ async def _insert_statistics(hass): ) async_add_external_statistics(hass, metadata, statistics) - # Fake yesterday's energy consumption + # Add external energy consumption in kWh, ~ 12 kWh / day + # This should be possible to pick for the energy dashboard + statistic_id = f"{DOMAIN}:energy_consumption_kwh" metadata = { "source": DOMAIN, - "statistic_id": f"{DOMAIN}:energy_consumption", + "name": "Energy consumption 1", + "statistic_id": statistic_id, "unit_of_measurement": "kWh", "has_mean": False, "has_sum": True, } - statistic_id = f"{DOMAIN}:energy_consumption" sum_ = 0 last_stats = await get_instance(hass).async_add_executor_job( get_last_statistics, hass, 1, statistic_id, True ) - if "domain:energy_consumption" in last_stats: - sum_ = last_stats["domain.electricity_total"]["sum"] or 0 + if statistic_id in last_stats: + sum_ = last_stats[statistic_id][0]["sum"] or 0 + statistics = _generate_sum_statistics( + yesterday_midnight, yesterday_midnight + datetime.timedelta(days=1), sum_, 2 + ) + async_add_external_statistics(hass, metadata, statistics) + + # Add external energy consumption in MWh, ~ 12 kWh / day + # This should not be possible to pick for the energy dashboard + statistic_id = f"{DOMAIN}:energy_consumption_mwh" + metadata = { + "source": DOMAIN, + "name": "Energy consumption 2", + "statistic_id": statistic_id, + "unit_of_measurement": "MWh", + "has_mean": False, + "has_sum": True, + } + sum_ = 0 + last_stats = await get_instance(hass).async_add_executor_job( + get_last_statistics, hass, 1, statistic_id, True + ) + if statistic_id in last_stats: + sum_ = last_stats[statistic_id][0]["sum"] or 0 + statistics = _generate_sum_statistics( + yesterday_midnight, yesterday_midnight + datetime.timedelta(days=1), sum_, 0.002 + ) + async_add_external_statistics(hass, metadata, statistics) + + # Add external gas consumption in m³, ~6 m3/day + # This should be possible to pick for the energy dashboard + statistic_id = f"{DOMAIN}:gas_consumption_m3" + metadata = { + "source": DOMAIN, + "name": "Gas consumption 1", + "statistic_id": statistic_id, + "unit_of_measurement": "m³", + "has_mean": False, + "has_sum": True, + } + sum_ = 0 + last_stats = await get_instance(hass).async_add_executor_job( + get_last_statistics, hass, 1, statistic_id, True + ) + if statistic_id in last_stats: + sum_ = last_stats[statistic_id][0]["sum"] or 0 statistics = _generate_sum_statistics( yesterday_midnight, yesterday_midnight + datetime.timedelta(days=1), sum_, 1 ) async_add_external_statistics(hass, metadata, statistics) + # Add external gas consumption in ft³, ~180 ft3/day + # This should not be possible to pick for the energy dashboard + statistic_id = f"{DOMAIN}:gas_consumption_ft3" + metadata = { + "source": DOMAIN, + "name": "Gas consumption 2", + "statistic_id": statistic_id, + "unit_of_measurement": "ft³", + "has_mean": False, + "has_sum": True, + } + sum_ = 0 + last_stats = await get_instance(hass).async_add_executor_job( + get_last_statistics, hass, 1, statistic_id, True + ) + if statistic_id in last_stats: + sum_ = last_stats[statistic_id][0]["sum"] or 0 + statistics = _generate_sum_statistics( + yesterday_midnight, yesterday_midnight + datetime.timedelta(days=1), sum_, 30 + ) + async_add_external_statistics(hass, metadata, statistics) + async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Set the config entry up.""" diff --git a/homeassistant/components/demo/sensor.py b/homeassistant/components/demo/sensor.py index aa93bdd8b71..1adc8616593 100644 --- a/homeassistant/components/demo/sensor.py +++ b/homeassistant/components/demo/sensor.py @@ -1,7 +1,12 @@ """Demo platform that has a couple of fake sensors.""" from __future__ import annotations +from datetime import datetime, timedelta +from typing import cast + from homeassistant.components.sensor import ( + DOMAIN as SENSOR_DOMAIN, + RestoreSensor, SensorDeviceClass, SensorEntity, SensorStateClass, @@ -11,13 +16,17 @@ from homeassistant.const import ( ATTR_BATTERY_LEVEL, CONCENTRATION_PARTS_PER_MILLION, ENERGY_KILO_WATT_HOUR, + ENERGY_MEGA_WATT_HOUR, PERCENTAGE, POWER_WATT, TEMP_CELSIUS, + VOLUME_CUBIC_FEET, + VOLUME_CUBIC_METERS, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType from . import DOMAIN @@ -77,14 +86,45 @@ async def async_setup_platform( POWER_WATT, None, ), - DemoSensor( + DemoSumSensor( "sensor_6", - "Today energy", - 15, + "Total energy 1", + 0.5, # 6kWh / h SensorDeviceClass.ENERGY, - SensorStateClass.MEASUREMENT, + SensorStateClass.TOTAL, ENERGY_KILO_WATT_HOUR, None, + "total_energy_kwh", + ), + DemoSumSensor( + "sensor_7", + "Total energy 2", + 0.00025, # 0.003 MWh/h (3 kWh / h) + SensorDeviceClass.ENERGY, + SensorStateClass.TOTAL, + ENERGY_MEGA_WATT_HOUR, + None, + "total_energy_mwh", + ), + DemoSumSensor( + "sensor_8", + "Total gas 1", + 0.025, # 0.30 m³/h (10.6 ft³ / h) + SensorDeviceClass.GAS, + SensorStateClass.TOTAL, + VOLUME_CUBIC_METERS, + None, + "total_gas_m3", + ), + DemoSumSensor( + "sensor_9", + "Total gas 2", + 1.0, # 12 ft³/h (0.34 m³ / h) + SensorDeviceClass.GAS, + SensorStateClass.TOTAL, + VOLUME_CUBIC_FEET, + None, + "total_gas_ft3", ), ] ) @@ -129,3 +169,58 @@ class DemoSensor(SensorEntity): if battery: self._attr_extra_state_attributes = {ATTR_BATTERY_LEVEL: battery} + + +class DemoSumSensor(RestoreSensor): + """Representation of a Demo sensor.""" + + _attr_should_poll = False + _attr_native_value: float + + def __init__( + self, + unique_id: str, + name: str, + five_minute_increase: float, + device_class: SensorDeviceClass, + state_class: SensorStateClass | None, + unit_of_measurement: str | None, + battery: StateType, + suggested_entity_id: str, + ) -> None: + """Initialize the sensor.""" + self.entity_id = f"{SENSOR_DOMAIN}.{suggested_entity_id}" + self._attr_device_class = device_class + self._attr_name = name + self._attr_native_unit_of_measurement = unit_of_measurement + self._attr_native_value = 0 + self._attr_state_class = state_class + self._attr_unique_id = unique_id + self._five_minute_increase = five_minute_increase + + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, unique_id)}, + name=name, + ) + + if battery: + self._attr_extra_state_attributes = {ATTR_BATTERY_LEVEL: battery} + + @callback + def _async_bump_sum(self, now: datetime) -> None: + """Bump the sum.""" + self._attr_native_value += self._five_minute_increase + self.async_write_ha_state() + + async def async_added_to_hass(self) -> None: + """Call when entity about to be added to hass.""" + await super().async_added_to_hass() + state = await self.async_get_last_sensor_data() + if state: + self._attr_native_value = cast(float, state.native_value) + + self.async_on_remove( + async_track_time_interval( + self.hass, self._async_bump_sum, timedelta(minutes=5) + ), + ) diff --git a/tests/components/demo/test_init.py b/tests/components/demo/test_init.py index ba8baa0d487..413badde18d 100644 --- a/tests/components/demo/test_init.py +++ b/tests/components/demo/test_init.py @@ -57,7 +57,7 @@ async def test_demo_statistics(hass, recorder_mock): assert { "has_mean": True, "has_sum": False, - "name": None, + "name": "Outdoor temperature", "source": "demo", "statistic_id": "demo:temperature_outdoor", "unit_of_measurement": "°C", @@ -65,9 +65,9 @@ async def test_demo_statistics(hass, recorder_mock): assert { "has_mean": False, "has_sum": True, - "name": None, + "name": "Energy consumption 1", "source": "demo", - "statistic_id": "demo:energy_consumption", + "statistic_id": "demo:energy_consumption_kwh", "unit_of_measurement": "kWh", } in statistic_ids diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index cc2f9c76f1f..64fd1884ae6 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -767,7 +767,7 @@ def test_compile_hourly_sum_statistics_nan_inf_state( "bug report at https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue", ), ( - "sensor.today_energy", + "sensor.power_consumption", "from integration demo ", "bug report at https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+demo%22", ),