From 3ce1049d21af5c82df524c8d770a95fa2afaa4a7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 9 Jul 2019 14:18:51 +0200 Subject: [PATCH] Centralizes Toon data, reducing API calls (#23988) * Centralizes Toon data, reducing API calls Fixes #21825 Signed-off-by: Franck Nijhof * Fixes bad copy past action in services.yaml Signed-off-by: Franck Nijhof * Addresses review comments Signed-off-by: Franck Nijhof * :shirt: Fixes too many blank lines * Unsub dispatcher --- homeassistant/components/toon/__init__.py | 178 +++++++-- .../components/toon/binary_sensor.py | 159 +++++--- homeassistant/components/toon/climate.py | 41 ++- homeassistant/components/toon/const.py | 8 +- homeassistant/components/toon/sensor.py | 344 ++++++++++++------ homeassistant/components/toon/services.yaml | 6 + 6 files changed, 538 insertions(+), 198 deletions(-) create mode 100644 homeassistant/components/toon/services.yaml diff --git a/homeassistant/components/toon/__init__.py b/homeassistant/components/toon/__init__.py index ba39462941f..0cbce959103 100644 --- a/homeassistant/components/toon/__init__.py +++ b/homeassistant/components/toon/__init__.py @@ -5,26 +5,59 @@ from functools import partial import voluptuous as vol -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.helpers import (config_validation as cv, - device_registry as dr) +from homeassistant.core import callback +from homeassistant.const import ( + CONF_PASSWORD, + CONF_USERNAME, + CONF_SCAN_INTERVAL, +) +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.dispatcher import ( + dispatcher_send, + async_dispatcher_connect, +) from . import config_flow # noqa pylint_disable=unused-import from .const import ( - CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_DISPLAY, CONF_TENANT, - DATA_TOON_CLIENT, DATA_TOON_CONFIG, DOMAIN) + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_DISPLAY, + CONF_TENANT, + DATA_TOON_CLIENT, + DATA_TOON_CONFIG, + DATA_TOON_UPDATED, + DATA_TOON, + DEFAULT_SCAN_INTERVAL, + DOMAIN, +) _LOGGER = logging.getLogger(__name__) # Validation of the user's configuration -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_CLIENT_SECRET): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + vol.Required( + CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL + ): vol.All(cv.time_period, cv.positive_timedelta), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + +SERVICE_SCHEMA = vol.Schema( + {vol.Optional(CONF_DISPLAY): cv.string} +) async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: @@ -40,49 +73,119 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: return True -async def async_setup_entry(hass: HomeAssistantType, - entry: ConfigType) -> bool: +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigType +) -> bool: """Set up Toon from a config entry.""" from toonapilib import Toon conf = hass.data.get(DATA_TOON_CONFIG) - toon = await hass.async_add_executor_job(partial( - Toon, entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], - conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET], - tenant_id=entry.data[CONF_TENANT], - display_common_name=entry.data[CONF_DISPLAY])) - + toon = await hass.async_add_executor_job( + partial( + Toon, + entry.data[CONF_USERNAME], + entry.data[CONF_PASSWORD], + conf[CONF_CLIENT_ID], + conf[CONF_CLIENT_SECRET], + tenant_id=entry.data[CONF_TENANT], + display_common_name=entry.data[CONF_DISPLAY], + ) + ) hass.data.setdefault(DATA_TOON_CLIENT, {})[entry.entry_id] = toon + toon_data = ToonData(hass, entry, toon) + hass.data.setdefault(DATA_TOON, {})[entry.entry_id] = toon_data + async_track_time_interval(hass, toon_data.update, conf[CONF_SCAN_INTERVAL]) + # Register device for the Meter Adapter, since it will have no entities. device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, - identifiers={ - (DOMAIN, toon.agreement.id, 'meter_adapter'), - }, + identifiers={(DOMAIN, toon.agreement.id, 'meter_adapter')}, manufacturer='Eneco', name="Meter Adapter", - via_device=(DOMAIN, toon.agreement.id) + via_device=(DOMAIN, toon.agreement.id), + ) + + def update(call): + """Service call to manually update the data.""" + called_display = call.data.get(CONF_DISPLAY, None) + for toon_data in hass.data[DATA_TOON].values(): + if (called_display and called_display == toon_data.display_name) \ + or not called_display: + toon_data.update() + + hass.services.async_register( + DOMAIN, "update", update, schema=SERVICE_SCHEMA ) for component in 'binary_sensor', 'climate', 'sensor': hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component)) + hass.config_entries.async_forward_entry_setup(entry, component) + ) return True +class ToonData: + """Communication class for interacting with toonapilib.""" + + def __init__( + self, + hass: HomeAssistantType, + entry: ConfigType, + toon + ): + """Initialize the Toon data object.""" + self._hass = hass + self._toon = toon + self._entry = entry + self.agreement = toon.agreement + self.gas = toon.gas + self.power = toon.power + self.solar = toon.solar + self.temperature = toon.temperature + self.thermostat = toon.thermostat + self.thermostat_info = toon.thermostat_info + self.thermostat_state = toon.thermostat_state + + @property + def display_name(self): + """Return the display connected to.""" + return self._entry.data[CONF_DISPLAY] + + def update(self, now=None): + """Update all Toon data and notify entities.""" + # Ignore the TTL meganism from client library + # It causes a lots of issues, hence we take control over caching + self._toon._clear_cache() # noqa pylint: disable=W0212 + + # Gather data from client library (single API call) + self.gas = self._toon.gas + self.power = self._toon.power + self.solar = self._toon.solar + self.temperature = self._toon.temperature + self.thermostat = self._toon.thermostat + self.thermostat_info = self._toon.thermostat_info + self.thermostat_state = self._toon.thermostat_state + + # Notify all entities + dispatcher_send( + self._hass, DATA_TOON_UPDATED, self._entry.data[CONF_DISPLAY] + ) + + class ToonEntity(Entity): """Defines a base Toon entity.""" - def __init__(self, toon, name: str, icon: str) -> None: + def __init__(self, toon: ToonData, name: str, icon: str) -> None: """Initialize the Toon entity.""" self._name = name self._state = None self._icon = icon self.toon = toon + self._unsub_dispatcher = None @property def name(self) -> str: @@ -94,6 +197,27 @@ class ToonEntity(Entity): """Return the mdi icon of the entity.""" return self._icon + @property + def should_poll(self) -> bool: + """Return the polling requirement of the entity.""" + return False + + async def async_added_to_hass(self) -> None: + """Connect to dispatcher listening for entity data notifications.""" + self._unsub_dispatcher = async_dispatcher_connect( + self.hass, DATA_TOON_UPDATED, self._schedule_immediate_update + ) + + async def async_will_remove_from_hass(self) -> None: + """Disconnect from update signal.""" + self._unsub_dispatcher() + + @callback + def _schedule_immediate_update(self, display_name: str) -> None: + """Schedule an immediate update of the entity.""" + if display_name == self.toon.display_name: + self.async_schedule_update_ha_state(True) + class ToonDisplayDeviceEntity(ToonEntity): """Defines a Toon display device entity.""" @@ -105,9 +229,7 @@ class ToonDisplayDeviceEntity(ToonEntity): model = agreement.display_hardware_version.rpartition('/')[0] sw_version = agreement.display_software_version.rpartition('/')[-1] return { - 'identifiers': { - (DOMAIN, agreement.id), - }, + 'identifiers': {(DOMAIN, agreement.id)}, 'name': 'Toon Display', 'manufacturer': 'Eneco', 'model': model, diff --git a/homeassistant/components/toon/binary_sensor.py b/homeassistant/components/toon/binary_sensor.py index c9bec0f3e6a..6a4f81b56cb 100644 --- a/homeassistant/components/toon/binary_sensor.py +++ b/homeassistant/components/toon/binary_sensor.py @@ -1,6 +1,5 @@ """Support for Toon binary sensors.""" -from datetime import timedelta import logging from typing import Any @@ -8,62 +7,123 @@ from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType -from . import (ToonEntity, ToonDisplayDeviceEntity, ToonBoilerDeviceEntity, - ToonBoilerModuleDeviceEntity) -from .const import DATA_TOON_CLIENT, DOMAIN +from . import ( + ToonData, + ToonEntity, + ToonDisplayDeviceEntity, + ToonBoilerDeviceEntity, + ToonBoilerModuleDeviceEntity, +) +from .const import DATA_TOON, DOMAIN _LOGGER = logging.getLogger(__name__) -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) -SCAN_INTERVAL = timedelta(seconds=300) - -async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Set up a Toon binary sensor based on a config entry.""" - toon = hass.data[DATA_TOON_CLIENT][entry.entry_id] + toon = hass.data[DATA_TOON][entry.entry_id] sensors = [ - ToonBoilerModuleBinarySensor(toon, 'thermostat_info', - 'boiler_connected', None, - 'Boiler Module Connection', - 'mdi:check-network-outline', - 'connectivity'), - - ToonDisplayBinarySensor(toon, 'thermostat_info', 'active_state', 4, - "Toon Holiday Mode", 'mdi:airport', None), - - ToonDisplayBinarySensor(toon, 'thermostat_info', 'next_program', None, - "Toon Program", 'mdi:calendar-clock', None), + ToonBoilerModuleBinarySensor( + toon, + 'thermostat_info', + 'boiler_connected', + None, + 'Boiler Module Connection', + 'mdi:check-network-outline', + 'connectivity', + ), + ToonDisplayBinarySensor( + toon, + 'thermostat_info', + 'active_state', + 4, + "Toon Holiday Mode", + 'mdi:airport', + None, + ), + ToonDisplayBinarySensor( + toon, + 'thermostat_info', + 'next_program', + None, + "Toon Program", + 'mdi:calendar-clock', + None, + ), ] if toon.thermostat_info.have_ot_boiler: - sensors.extend([ - ToonBoilerBinarySensor(toon, 'thermostat_info', - 'ot_communication_error', '0', - "OpenTherm Connection", - 'mdi:check-network-outline', - 'connectivity'), - ToonBoilerBinarySensor(toon, 'thermostat_info', 'error_found', 255, - "Boiler Status", 'mdi:alert', 'problem', - inverted=True), - ToonBoilerBinarySensor(toon, 'thermostat_info', 'burner_info', - None, "Boiler Burner", 'mdi:fire', None), - ToonBoilerBinarySensor(toon, 'thermostat_info', 'burner_info', '2', - "Hot Tap Water", 'mdi:water-pump', None), - ToonBoilerBinarySensor(toon, 'thermostat_info', 'burner_info', '3', - "Boiler Preheating", 'mdi:fire', None), - ]) + sensors.extend( + [ + ToonBoilerBinarySensor( + toon, + 'thermostat_info', + 'ot_communication_error', + '0', + "OpenTherm Connection", + 'mdi:check-network-outline', + 'connectivity', + ), + ToonBoilerBinarySensor( + toon, + 'thermostat_info', + 'error_found', + 255, + "Boiler Status", + 'mdi:alert', + 'problem', + inverted=True, + ), + ToonBoilerBinarySensor( + toon, + 'thermostat_info', + 'burner_info', + None, + "Boiler Burner", + 'mdi:fire', + None, + ), + ToonBoilerBinarySensor( + toon, + 'thermostat_info', + 'burner_info', + '2', + "Hot Tap Water", + 'mdi:water-pump', + None, + ), + ToonBoilerBinarySensor( + toon, + 'thermostat_info', + 'burner_info', + '3', + "Boiler Preheating", + 'mdi:fire', + None, + ), + ] + ) - async_add_entities(sensors) + async_add_entities(sensors, True) class ToonBinarySensor(ToonEntity, BinarySensorDevice): """Defines an Toon binary sensor.""" - def __init__(self, toon, section: str, measurement: str, on_value: Any, - name: str, icon: str, device_class: str, - inverted: bool = False) -> None: + def __init__( + self, + toon: ToonData, + section: str, + measurement: str, + on_value: Any, + name: str, + icon: str, + device_class: str, + inverted: bool = False, + ) -> None: """Initialize the Toon sensor.""" self._state = inverted self._device_class = device_class @@ -77,8 +137,16 @@ class ToonBinarySensor(ToonEntity, BinarySensorDevice): @property def unique_id(self) -> str: """Return the unique ID for this binary sensor.""" - return '_'.join([DOMAIN, self.toon.agreement.id, 'binary_sensor', - self.section, self.measurement, str(self.on_value)]) + return '_'.join( + [ + DOMAIN, + self.toon.agreement.id, + 'binary_sensor', + self.section, + self.measurement, + str(self.on_value), + ] + ) @property def device_class(self) -> str: @@ -118,8 +186,9 @@ class ToonDisplayBinarySensor(ToonBinarySensor, ToonDisplayDeviceEntity): pass -class ToonBoilerModuleBinarySensor(ToonBinarySensor, - ToonBoilerModuleDeviceEntity): +class ToonBoilerModuleBinarySensor( + ToonBinarySensor, ToonBoilerModuleDeviceEntity +): """Defines a Boiler module binary sensor.""" pass diff --git a/homeassistant/components/toon/climate.py b/homeassistant/components/toon/climate.py index 8750c8d0751..ee2607d1969 100644 --- a/homeassistant/components/toon/climate.py +++ b/homeassistant/components/toon/climate.py @@ -1,6 +1,5 @@ """Support for Toon thermostat.""" -from datetime import timedelta import logging from typing import Any, Dict, List, Optional @@ -12,39 +11,45 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.helpers.typing import HomeAssistantType -from . import ToonDisplayDeviceEntity -from .const import DATA_TOON_CLIENT, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN +from . import ToonData, ToonDisplayDeviceEntity +from .const import ( + DATA_TOON_CLIENT, + DATA_TOON, + DEFAULT_MAX_TEMP, + DEFAULT_MIN_TEMP, + DOMAIN, +) _LOGGER = logging.getLogger(__name__) SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE SUPPORT_PRESET = [PRESET_AWAY, PRESET_COMFORT, PRESET_HOME, PRESET_SLEEP] -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) -SCAN_INTERVAL = timedelta(seconds=300) - -async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Set up a Toon binary sensors based on a config entry.""" - toon = hass.data[DATA_TOON_CLIENT][entry.entry_id] - async_add_entities([ToonThermostatDevice(toon)], True) + toon_client = hass.data[DATA_TOON_CLIENT][entry.entry_id] + toon_data = hass.data[DATA_TOON][entry.entry_id] + async_add_entities([ToonThermostatDevice(toon_client, toon_data)], True) class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateDevice): """Representation of a Toon climate device.""" - def __init__(self, toon) -> None: + def __init__(self, toon_client, toon_data: ToonData) -> None: """Initialize the Toon climate device.""" - self._state = None + self._client = toon_client + self._state = None self._current_temperature = None self._target_temperature = None self._next_target_temperature = None self._heating_type = None - super().__init__(toon, "Toon Thermostat", 'mdi:thermostat') + super().__init__(toon_data, "Toon Thermostat", 'mdi:thermostat') @property def unique_id(self) -> str: @@ -112,19 +117,19 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateDevice): @property def device_state_attributes(self) -> Dict[str, Any]: """Return the current state of the burner.""" - return { - 'heating_type': self._heating_type, - } + return {'heating_type': self._heating_type} def set_temperature(self, **kwargs) -> None: """Change the setpoint of the thermostat.""" temperature = kwargs.get(ATTR_TEMPERATURE) - self.toon.thermostat = temperature + self._client.thermostat = temperature + self.schedule_update_ha_state() def set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" if preset_mode is not None: - self.toon.thermostat_state = preset_mode + self._client.thermostat_state = preset_mode + self.schedule_update_ha_state() def set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" diff --git a/homeassistant/components/toon/const.py b/homeassistant/components/toon/const.py index 4d8ccd70e12..8ba7c03e22f 100644 --- a/homeassistant/components/toon/const.py +++ b/homeassistant/components/toon/const.py @@ -1,23 +1,23 @@ """Constants for the Toon integration.""" -from homeassistant.const import ENERGY_KILO_WATT_HOUR +from datetime import timedelta DOMAIN = 'toon' DATA_TOON = 'toon' -DATA_TOON_CONFIG = 'toon_config' DATA_TOON_CLIENT = 'toon_client' +DATA_TOON_CONFIG = 'toon_config' +DATA_TOON_UPDATED = 'toon_updated' CONF_CLIENT_ID = 'client_id' CONF_CLIENT_SECRET = 'client_secret' CONF_DISPLAY = 'display' CONF_TENANT = 'tenant' +DEFAULT_SCAN_INTERVAL = timedelta(seconds=300) DEFAULT_MAX_TEMP = 30.0 DEFAULT_MIN_TEMP = 6.0 CURRENCY_EUR = 'EUR' -POWER_WATT = 'W' -POWER_KWH = ENERGY_KILO_WATT_HOUR RATIO_PERCENT = '%' VOLUME_CM3 = 'CM3' VOLUME_M3 = 'M3' diff --git a/homeassistant/components/toon/sensor.py b/homeassistant/components/toon/sensor.py index 7762aa0d822..2e5753afa0a 100644 --- a/homeassistant/components/toon/sensor.py +++ b/homeassistant/components/toon/sensor.py @@ -1,113 +1,232 @@ """Support for Toon sensors.""" -from datetime import timedelta import logging from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.const import ENERGY_KILO_WATT_HOUR, POWER_WATT -from . import (ToonEntity, ToonElectricityMeterDeviceEntity, - ToonGasMeterDeviceEntity, ToonSolarDeviceEntity, - ToonBoilerDeviceEntity) -from .const import (CURRENCY_EUR, DATA_TOON_CLIENT, DOMAIN, POWER_KWH, - POWER_WATT, VOLUME_CM3, VOLUME_M3, RATIO_PERCENT) +from . import ( + ToonData, + ToonEntity, + ToonElectricityMeterDeviceEntity, + ToonGasMeterDeviceEntity, + ToonSolarDeviceEntity, + ToonBoilerDeviceEntity, +) +from .const import ( + CURRENCY_EUR, + DATA_TOON, + DOMAIN, + VOLUME_CM3, + VOLUME_M3, + RATIO_PERCENT, +) _LOGGER = logging.getLogger(__name__) -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) -SCAN_INTERVAL = timedelta(seconds=300) - -async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Set up Toon sensors based on a config entry.""" - toon = hass.data[DATA_TOON_CLIENT][entry.entry_id] + toon = hass.data[DATA_TOON][entry.entry_id] sensors = [ - ToonElectricityMeterDeviceSensor(toon, 'power', 'value', - "Current Power Usage", - 'mdi:power-plug', POWER_WATT), - ToonElectricityMeterDeviceSensor(toon, 'power', 'average', - "Average Power Usage", - 'mdi:power-plug', POWER_WATT), - ToonElectricityMeterDeviceSensor(toon, 'power', 'daily_value', - "Power Usage Today", - 'mdi:power-plug', POWER_KWH), - ToonElectricityMeterDeviceSensor(toon, 'power', 'daily_cost', - "Power Cost Today", - 'mdi:power-plug', CURRENCY_EUR), - ToonElectricityMeterDeviceSensor(toon, 'power', 'average_daily', - "Average Daily Power Usage", - 'mdi:power-plug', POWER_KWH), - ToonElectricityMeterDeviceSensor(toon, 'power', 'meter_reading', - "Power Meter Feed IN Tariff 1", - 'mdi:power-plug', POWER_KWH), - ToonElectricityMeterDeviceSensor(toon, 'power', 'meter_reading_low', - "Power Meter Feed IN Tariff 2", - 'mdi:power-plug', POWER_KWH), + ToonElectricityMeterDeviceSensor( + toon, + 'power', + 'value', + "Current Power Usage", + 'mdi:power-plug', + POWER_WATT, + ), + ToonElectricityMeterDeviceSensor( + toon, + 'power', + 'average', + "Average Power Usage", + 'mdi:power-plug', + POWER_WATT, + ), + ToonElectricityMeterDeviceSensor( + toon, + 'power', + 'daily_value', + "Power Usage Today", + 'mdi:power-plug', + ENERGY_KILO_WATT_HOUR, + ), + ToonElectricityMeterDeviceSensor( + toon, + 'power', + 'daily_cost', + "Power Cost Today", + 'mdi:power-plug', + CURRENCY_EUR, + ), + ToonElectricityMeterDeviceSensor( + toon, + 'power', + 'average_daily', + "Average Daily Power Usage", + 'mdi:power-plug', + ENERGY_KILO_WATT_HOUR, + ), + ToonElectricityMeterDeviceSensor( + toon, + 'power', + 'meter_reading', + "Power Meter Feed IN Tariff 1", + 'mdi:power-plug', + ENERGY_KILO_WATT_HOUR, + ), + ToonElectricityMeterDeviceSensor( + toon, + 'power', + 'meter_reading_low', + "Power Meter Feed IN Tariff 2", + 'mdi:power-plug', + ENERGY_KILO_WATT_HOUR, + ), ] if toon.gas: - sensors.extend([ - ToonGasMeterDeviceSensor(toon, 'gas', 'value', "Current Gas Usage", - 'mdi:gas-cylinder', VOLUME_CM3), - ToonGasMeterDeviceSensor(toon, 'gas', 'average', - "Average Gas Usage", 'mdi:gas-cylinder', - VOLUME_CM3), - ToonGasMeterDeviceSensor(toon, 'gas', 'daily_usage', - "Gas Usage Today", 'mdi:gas-cylinder', - VOLUME_M3), - ToonGasMeterDeviceSensor(toon, 'gas', 'average_daily', - "Average Daily Gas Usage", - 'mdi:gas-cylinder', VOLUME_M3), - ToonGasMeterDeviceSensor(toon, 'gas', 'meter_reading', "Gas Meter", - 'mdi:gas-cylinder', VOLUME_M3), - ToonGasMeterDeviceSensor(toon, 'gas', 'daily_cost', - "Gas Cost Today", 'mdi:gas-cylinder', - CURRENCY_EUR), - ]) + sensors.extend( + [ + ToonGasMeterDeviceSensor( + toon, + 'gas', + 'value', + "Current Gas Usage", + 'mdi:gas-cylinder', + VOLUME_CM3, + ), + ToonGasMeterDeviceSensor( + toon, + 'gas', + 'average', + "Average Gas Usage", + 'mdi:gas-cylinder', + VOLUME_CM3, + ), + ToonGasMeterDeviceSensor( + toon, + 'gas', + 'daily_usage', + "Gas Usage Today", + 'mdi:gas-cylinder', + VOLUME_M3, + ), + ToonGasMeterDeviceSensor( + toon, + 'gas', + 'average_daily', + "Average Daily Gas Usage", + 'mdi:gas-cylinder', + VOLUME_M3, + ), + ToonGasMeterDeviceSensor( + toon, + 'gas', + 'meter_reading', + "Gas Meter", + 'mdi:gas-cylinder', + VOLUME_M3, + ), + ToonGasMeterDeviceSensor( + toon, + 'gas', + 'daily_cost', + "Gas Cost Today", + 'mdi:gas-cylinder', + CURRENCY_EUR, + ), + ] + ) if toon.solar: - sensors.extend([ - ToonSolarDeviceSensor(toon, 'solar', 'value', - "Current Solar Production", - 'mdi:solar-power', POWER_WATT), - ToonSolarDeviceSensor(toon, 'solar', 'maximum', - "Max Solar Production", 'mdi:solar-power', - POWER_WATT), - ToonSolarDeviceSensor(toon, 'solar', 'produced', - "Solar Production to Grid", - 'mdi:solar-power', POWER_WATT), - ToonSolarDeviceSensor(toon, 'solar', 'average_produced', - "Average Solar Production to Grid", - 'mdi:solar-power', POWER_WATT), - ToonElectricityMeterDeviceSensor(toon, 'solar', - 'meter_reading_produced', - "Power Meter Feed OUT Tariff 1", - 'mdi:solar-power', - POWER_KWH), - ToonElectricityMeterDeviceSensor(toon, 'solar', - 'meter_reading_low_produced', - "Power Meter Feed OUT Tariff 2", - 'mdi:solar-power', POWER_KWH), - ]) + sensors.extend( + [ + ToonSolarDeviceSensor( + toon, + 'solar', + 'value', + "Current Solar Production", + 'mdi:solar-power', + POWER_WATT, + ), + ToonSolarDeviceSensor( + toon, + 'solar', + 'maximum', + "Max Solar Production", + 'mdi:solar-power', + POWER_WATT, + ), + ToonSolarDeviceSensor( + toon, + 'solar', + 'produced', + "Solar Production to Grid", + 'mdi:solar-power', + POWER_WATT, + ), + ToonSolarDeviceSensor( + toon, + 'solar', + 'average_produced', + "Average Solar Production to Grid", + 'mdi:solar-power', + POWER_WATT, + ), + ToonElectricityMeterDeviceSensor( + toon, + 'solar', + 'meter_reading_produced', + "Power Meter Feed OUT Tariff 1", + 'mdi:solar-power', + ENERGY_KILO_WATT_HOUR, + ), + ToonElectricityMeterDeviceSensor( + toon, + 'solar', + 'meter_reading_low_produced', + "Power Meter Feed OUT Tariff 2", + 'mdi:solar-power', + ENERGY_KILO_WATT_HOUR, + ), + ] + ) if toon.thermostat_info.have_ot_boiler: - sensors.extend([ - ToonBoilerDeviceSensor(toon, 'thermostat_info', - 'current_modulation_level', - "Boiler Modulation Level", - 'mdi:percent', - RATIO_PERCENT), - ]) + sensors.extend( + [ + ToonBoilerDeviceSensor( + toon, + 'thermostat_info', + 'current_modulation_level', + "Boiler Modulation Level", + 'mdi:percent', + RATIO_PERCENT, + ) + ] + ) - async_add_entities(sensors) + async_add_entities(sensors, True) class ToonSensor(ToonEntity): """Defines a Toon sensor.""" - def __init__(self, toon, section: str, measurement: str, - name: str, icon: str, unit_of_measurement: str) -> None: + def __init__( + self, + toon: ToonData, + section: str, + measurement: str, + name: str, + icon: str, + unit_of_measurement: str, + ) -> None: """Initialize the Toon sensor.""" self._state = None self._unit_of_measurement = unit_of_measurement @@ -119,8 +238,15 @@ class ToonSensor(ToonEntity): @property def unique_id(self) -> str: """Return the unique ID for this sensor.""" - return '_'.join([DOMAIN, self.toon.agreement.id, 'sensor', - self.section, self.measurement]) + return '_'.join( + [ + DOMAIN, + self.toon.agreement.id, + 'sensor', + self.section, + self.measurement, + ] + ) @property def state(self): @@ -137,34 +263,46 @@ class ToonSensor(ToonEntity): section = getattr(self.toon, self.section) value = None + if not section: + return + if self.section == 'power' and self.measurement == 'daily_value': - value = round((float(section.daily_usage) - + float(section.daily_usage_low)) / 1000.0, 2) + value = round( + (float(section.daily_usage) + float(section.daily_usage_low)) + / 1000.0, + 2, + ) if value is None: value = getattr(section, self.measurement) - if self.section == 'power' and \ - self.measurement in ['meter_reading', 'meter_reading_low', - 'average_daily']: - value = round(float(value)/1000.0, 2) + if self.section == 'power' and self.measurement in [ + 'meter_reading', + 'meter_reading_low', + 'average_daily', + ]: + value = round(float(value) / 1000.0, 2) - if self.section == 'solar' and \ - self.measurement in ['meter_reading_produced', - 'meter_reading_low_produced']: - value = float(value)/1000.0 + if self.section == 'solar' and self.measurement in [ + 'meter_reading_produced', + 'meter_reading_low_produced', + ]: + value = float(value) / 1000.0 - if self.section == 'gas' and \ - self.measurement in ['average_daily', 'daily_usage', - 'meter_reading']: - value = round(float(value)/1000.0, 2) + if self.section == 'gas' and self.measurement in [ + 'average_daily', + 'daily_usage', + 'meter_reading', + ]: + value = round(float(value) / 1000.0, 2) self._state = max(0, value) -class ToonElectricityMeterDeviceSensor(ToonSensor, - ToonElectricityMeterDeviceEntity): - """Defines a Eletricity Meter sensor.""" +class ToonElectricityMeterDeviceSensor( + ToonSensor, ToonElectricityMeterDeviceEntity +): + """Defines a Electricity Meter sensor.""" pass diff --git a/homeassistant/components/toon/services.yaml b/homeassistant/components/toon/services.yaml new file mode 100644 index 00000000000..7afedeb4bf6 --- /dev/null +++ b/homeassistant/components/toon/services.yaml @@ -0,0 +1,6 @@ +update: + description: Update all entities with fresh data from Toon + fields: + display: + description: Toon display to update (optional) + example: eneco-001-123456