Centralizes Toon data, reducing API calls (#23988)

* Centralizes Toon data, reducing API calls

Fixes #21825

Signed-off-by: Franck Nijhof <frenck@addons.community>

* Fixes bad copy past action in services.yaml

Signed-off-by: Franck Nijhof <frenck@addons.community>

* Addresses review comments

Signed-off-by: Franck Nijhof <frenck@addons.community>

* 👕 Fixes too many blank lines

* Unsub dispatcher
This commit is contained in:
Franck Nijhof 2019-07-09 14:18:51 +02:00 committed by Pascal Vizeli
parent f3e542542a
commit 3ce1049d21
6 changed files with 538 additions and 198 deletions

View File

@ -5,26 +5,59 @@ from functools import partial
import voluptuous as vol import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback
from homeassistant.helpers import (config_validation as cv, from homeassistant.const import (
device_registry as dr) 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.entity import Entity
from homeassistant.helpers.typing import ConfigType, HomeAssistantType 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 . import config_flow # noqa pylint_disable=unused-import
from .const import ( from .const import (
CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_DISPLAY, CONF_TENANT, CONF_CLIENT_ID,
DATA_TOON_CLIENT, DATA_TOON_CONFIG, DOMAIN) 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__) _LOGGER = logging.getLogger(__name__)
# Validation of the user's configuration # Validation of the user's configuration
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema(
DOMAIN: vol.Schema({ {
vol.Required(CONF_CLIENT_ID): cv.string, DOMAIN: vol.Schema(
vol.Required(CONF_CLIENT_SECRET): cv.string, {
}), vol.Required(CONF_CLIENT_ID): cv.string,
}, extra=vol.ALLOW_EXTRA) 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: async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
@ -40,49 +73,119 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
return True return True
async def async_setup_entry(hass: HomeAssistantType, async def async_setup_entry(
entry: ConfigType) -> bool: hass: HomeAssistantType, entry: ConfigType
) -> bool:
"""Set up Toon from a config entry.""" """Set up Toon from a config entry."""
from toonapilib import Toon from toonapilib import Toon
conf = hass.data.get(DATA_TOON_CONFIG) conf = hass.data.get(DATA_TOON_CONFIG)
toon = await hass.async_add_executor_job(partial( toon = await hass.async_add_executor_job(
Toon, entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], partial(
conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET], Toon,
tenant_id=entry.data[CONF_TENANT], entry.data[CONF_USERNAME],
display_common_name=entry.data[CONF_DISPLAY])) 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 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. # Register device for the Meter Adapter, since it will have no entities.
device_registry = await dr.async_get_registry(hass) device_registry = await dr.async_get_registry(hass)
device_registry.async_get_or_create( device_registry.async_get_or_create(
config_entry_id=entry.entry_id, config_entry_id=entry.entry_id,
identifiers={ identifiers={(DOMAIN, toon.agreement.id, 'meter_adapter')},
(DOMAIN, toon.agreement.id, 'meter_adapter'),
},
manufacturer='Eneco', manufacturer='Eneco',
name="Meter Adapter", 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': for component in 'binary_sensor', 'climate', 'sensor':
hass.async_create_task( hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)) hass.config_entries.async_forward_entry_setup(entry, component)
)
return True 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): class ToonEntity(Entity):
"""Defines a base Toon 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.""" """Initialize the Toon entity."""
self._name = name self._name = name
self._state = None self._state = None
self._icon = icon self._icon = icon
self.toon = toon self.toon = toon
self._unsub_dispatcher = None
@property @property
def name(self) -> str: def name(self) -> str:
@ -94,6 +197,27 @@ class ToonEntity(Entity):
"""Return the mdi icon of the entity.""" """Return the mdi icon of the entity."""
return self._icon 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): class ToonDisplayDeviceEntity(ToonEntity):
"""Defines a Toon display device entity.""" """Defines a Toon display device entity."""
@ -105,9 +229,7 @@ class ToonDisplayDeviceEntity(ToonEntity):
model = agreement.display_hardware_version.rpartition('/')[0] model = agreement.display_hardware_version.rpartition('/')[0]
sw_version = agreement.display_software_version.rpartition('/')[-1] sw_version = agreement.display_software_version.rpartition('/')[-1]
return { return {
'identifiers': { 'identifiers': {(DOMAIN, agreement.id)},
(DOMAIN, agreement.id),
},
'name': 'Toon Display', 'name': 'Toon Display',
'manufacturer': 'Eneco', 'manufacturer': 'Eneco',
'model': model, 'model': model,

View File

@ -1,6 +1,5 @@
"""Support for Toon binary sensors.""" """Support for Toon binary sensors."""
from datetime import timedelta
import logging import logging
from typing import Any from typing import Any
@ -8,62 +7,123 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from . import (ToonEntity, ToonDisplayDeviceEntity, ToonBoilerDeviceEntity, from . import (
ToonBoilerModuleDeviceEntity) ToonData,
from .const import DATA_TOON_CLIENT, DOMAIN ToonEntity,
ToonDisplayDeviceEntity,
ToonBoilerDeviceEntity,
ToonBoilerModuleDeviceEntity,
)
from .const import DATA_TOON, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
SCAN_INTERVAL = timedelta(seconds=300)
async def async_setup_entry(
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry, hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
async_add_entities) -> None: ) -> None:
"""Set up a Toon binary sensor based on a config entry.""" """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 = [ sensors = [
ToonBoilerModuleBinarySensor(toon, 'thermostat_info', ToonBoilerModuleBinarySensor(
'boiler_connected', None, toon,
'Boiler Module Connection', 'thermostat_info',
'mdi:check-network-outline', 'boiler_connected',
'connectivity'), None,
'Boiler Module Connection',
ToonDisplayBinarySensor(toon, 'thermostat_info', 'active_state', 4, 'mdi:check-network-outline',
"Toon Holiday Mode", 'mdi:airport', None), 'connectivity',
),
ToonDisplayBinarySensor(toon, 'thermostat_info', 'next_program', None, ToonDisplayBinarySensor(
"Toon Program", 'mdi:calendar-clock', None), 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: if toon.thermostat_info.have_ot_boiler:
sensors.extend([ sensors.extend(
ToonBoilerBinarySensor(toon, 'thermostat_info', [
'ot_communication_error', '0', ToonBoilerBinarySensor(
"OpenTherm Connection", toon,
'mdi:check-network-outline', 'thermostat_info',
'connectivity'), 'ot_communication_error',
ToonBoilerBinarySensor(toon, 'thermostat_info', 'error_found', 255, '0',
"Boiler Status", 'mdi:alert', 'problem', "OpenTherm Connection",
inverted=True), 'mdi:check-network-outline',
ToonBoilerBinarySensor(toon, 'thermostat_info', 'burner_info', 'connectivity',
None, "Boiler Burner", 'mdi:fire', None), ),
ToonBoilerBinarySensor(toon, 'thermostat_info', 'burner_info', '2', ToonBoilerBinarySensor(
"Hot Tap Water", 'mdi:water-pump', None), toon,
ToonBoilerBinarySensor(toon, 'thermostat_info', 'burner_info', '3', 'thermostat_info',
"Boiler Preheating", 'mdi:fire', None), '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): class ToonBinarySensor(ToonEntity, BinarySensorDevice):
"""Defines an Toon binary sensor.""" """Defines an Toon binary sensor."""
def __init__(self, toon, section: str, measurement: str, on_value: Any, def __init__(
name: str, icon: str, device_class: str, self,
inverted: bool = False) -> None: toon: ToonData,
section: str,
measurement: str,
on_value: Any,
name: str,
icon: str,
device_class: str,
inverted: bool = False,
) -> None:
"""Initialize the Toon sensor.""" """Initialize the Toon sensor."""
self._state = inverted self._state = inverted
self._device_class = device_class self._device_class = device_class
@ -77,8 +137,16 @@ class ToonBinarySensor(ToonEntity, BinarySensorDevice):
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
"""Return the unique ID for this binary sensor.""" """Return the unique ID for this binary sensor."""
return '_'.join([DOMAIN, self.toon.agreement.id, 'binary_sensor', return '_'.join(
self.section, self.measurement, str(self.on_value)]) [
DOMAIN,
self.toon.agreement.id,
'binary_sensor',
self.section,
self.measurement,
str(self.on_value),
]
)
@property @property
def device_class(self) -> str: def device_class(self) -> str:
@ -118,8 +186,9 @@ class ToonDisplayBinarySensor(ToonBinarySensor, ToonDisplayDeviceEntity):
pass pass
class ToonBoilerModuleBinarySensor(ToonBinarySensor, class ToonBoilerModuleBinarySensor(
ToonBoilerModuleDeviceEntity): ToonBinarySensor, ToonBoilerModuleDeviceEntity
):
"""Defines a Boiler module binary sensor.""" """Defines a Boiler module binary sensor."""
pass pass

View File

@ -1,6 +1,5 @@
"""Support for Toon thermostat.""" """Support for Toon thermostat."""
from datetime import timedelta
import logging import logging
from typing import Any, Dict, List, Optional 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.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from . import ToonDisplayDeviceEntity from . import ToonData, ToonDisplayDeviceEntity
from .const import DATA_TOON_CLIENT, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN from .const import (
DATA_TOON_CLIENT,
DATA_TOON,
DEFAULT_MAX_TEMP,
DEFAULT_MIN_TEMP,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
SUPPORT_PRESET = [PRESET_AWAY, PRESET_COMFORT, PRESET_HOME, PRESET_SLEEP] 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(
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry, hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
async_add_entities) -> None: ) -> None:
"""Set up a Toon binary sensors based on a config entry.""" """Set up a Toon binary sensors based on a config entry."""
toon = hass.data[DATA_TOON_CLIENT][entry.entry_id] toon_client = hass.data[DATA_TOON_CLIENT][entry.entry_id]
async_add_entities([ToonThermostatDevice(toon)], True) toon_data = hass.data[DATA_TOON][entry.entry_id]
async_add_entities([ToonThermostatDevice(toon_client, toon_data)], True)
class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateDevice): class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateDevice):
"""Representation of a Toon climate device.""" """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.""" """Initialize the Toon climate device."""
self._state = None self._client = toon_client
self._state = None
self._current_temperature = None self._current_temperature = None
self._target_temperature = None self._target_temperature = None
self._next_target_temperature = None self._next_target_temperature = None
self._heating_type = None self._heating_type = None
super().__init__(toon, "Toon Thermostat", 'mdi:thermostat') super().__init__(toon_data, "Toon Thermostat", 'mdi:thermostat')
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
@ -112,19 +117,19 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateDevice):
@property @property
def device_state_attributes(self) -> Dict[str, Any]: def device_state_attributes(self) -> Dict[str, Any]:
"""Return the current state of the burner.""" """Return the current state of the burner."""
return { return {'heating_type': self._heating_type}
'heating_type': self._heating_type,
}
def set_temperature(self, **kwargs) -> None: def set_temperature(self, **kwargs) -> None:
"""Change the setpoint of the thermostat.""" """Change the setpoint of the thermostat."""
temperature = kwargs.get(ATTR_TEMPERATURE) 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: def set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode.""" """Set new preset mode."""
if preset_mode is not None: 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: def set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode.""" """Set new target hvac mode."""

View File

@ -1,23 +1,23 @@
"""Constants for the Toon integration.""" """Constants for the Toon integration."""
from homeassistant.const import ENERGY_KILO_WATT_HOUR from datetime import timedelta
DOMAIN = 'toon' DOMAIN = 'toon'
DATA_TOON = 'toon' DATA_TOON = 'toon'
DATA_TOON_CONFIG = 'toon_config'
DATA_TOON_CLIENT = 'toon_client' DATA_TOON_CLIENT = 'toon_client'
DATA_TOON_CONFIG = 'toon_config'
DATA_TOON_UPDATED = 'toon_updated'
CONF_CLIENT_ID = 'client_id' CONF_CLIENT_ID = 'client_id'
CONF_CLIENT_SECRET = 'client_secret' CONF_CLIENT_SECRET = 'client_secret'
CONF_DISPLAY = 'display' CONF_DISPLAY = 'display'
CONF_TENANT = 'tenant' CONF_TENANT = 'tenant'
DEFAULT_SCAN_INTERVAL = timedelta(seconds=300)
DEFAULT_MAX_TEMP = 30.0 DEFAULT_MAX_TEMP = 30.0
DEFAULT_MIN_TEMP = 6.0 DEFAULT_MIN_TEMP = 6.0
CURRENCY_EUR = 'EUR' CURRENCY_EUR = 'EUR'
POWER_WATT = 'W'
POWER_KWH = ENERGY_KILO_WATT_HOUR
RATIO_PERCENT = '%' RATIO_PERCENT = '%'
VOLUME_CM3 = 'CM3' VOLUME_CM3 = 'CM3'
VOLUME_M3 = 'M3' VOLUME_M3 = 'M3'

View File

@ -1,113 +1,232 @@
"""Support for Toon sensors.""" """Support for Toon sensors."""
from datetime import timedelta
import logging import logging
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.const import ENERGY_KILO_WATT_HOUR, POWER_WATT
from . import (ToonEntity, ToonElectricityMeterDeviceEntity, from . import (
ToonGasMeterDeviceEntity, ToonSolarDeviceEntity, ToonData,
ToonBoilerDeviceEntity) ToonEntity,
from .const import (CURRENCY_EUR, DATA_TOON_CLIENT, DOMAIN, POWER_KWH, ToonElectricityMeterDeviceEntity,
POWER_WATT, VOLUME_CM3, VOLUME_M3, RATIO_PERCENT) ToonGasMeterDeviceEntity,
ToonSolarDeviceEntity,
ToonBoilerDeviceEntity,
)
from .const import (
CURRENCY_EUR,
DATA_TOON,
DOMAIN,
VOLUME_CM3,
VOLUME_M3,
RATIO_PERCENT,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
SCAN_INTERVAL = timedelta(seconds=300)
async def async_setup_entry(
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry, hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
async_add_entities) -> None: ) -> None:
"""Set up Toon sensors based on a config entry.""" """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 = [ sensors = [
ToonElectricityMeterDeviceSensor(toon, 'power', 'value', ToonElectricityMeterDeviceSensor(
"Current Power Usage", toon,
'mdi:power-plug', POWER_WATT), 'power',
ToonElectricityMeterDeviceSensor(toon, 'power', 'average', 'value',
"Average Power Usage", "Current Power Usage",
'mdi:power-plug', POWER_WATT), 'mdi:power-plug',
ToonElectricityMeterDeviceSensor(toon, 'power', 'daily_value', POWER_WATT,
"Power Usage Today", ),
'mdi:power-plug', POWER_KWH), ToonElectricityMeterDeviceSensor(
ToonElectricityMeterDeviceSensor(toon, 'power', 'daily_cost', toon,
"Power Cost Today", 'power',
'mdi:power-plug', CURRENCY_EUR), 'average',
ToonElectricityMeterDeviceSensor(toon, 'power', 'average_daily', "Average Power Usage",
"Average Daily Power Usage", 'mdi:power-plug',
'mdi:power-plug', POWER_KWH), POWER_WATT,
ToonElectricityMeterDeviceSensor(toon, 'power', 'meter_reading', ),
"Power Meter Feed IN Tariff 1", ToonElectricityMeterDeviceSensor(
'mdi:power-plug', POWER_KWH), toon,
ToonElectricityMeterDeviceSensor(toon, 'power', 'meter_reading_low', 'power',
"Power Meter Feed IN Tariff 2", 'daily_value',
'mdi:power-plug', POWER_KWH), "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: if toon.gas:
sensors.extend([ sensors.extend(
ToonGasMeterDeviceSensor(toon, 'gas', 'value', "Current Gas Usage", [
'mdi:gas-cylinder', VOLUME_CM3), ToonGasMeterDeviceSensor(
ToonGasMeterDeviceSensor(toon, 'gas', 'average', toon,
"Average Gas Usage", 'mdi:gas-cylinder', 'gas',
VOLUME_CM3), 'value',
ToonGasMeterDeviceSensor(toon, 'gas', 'daily_usage', "Current Gas Usage",
"Gas Usage Today", 'mdi:gas-cylinder', 'mdi:gas-cylinder',
VOLUME_M3), VOLUME_CM3,
ToonGasMeterDeviceSensor(toon, 'gas', 'average_daily', ),
"Average Daily Gas Usage", ToonGasMeterDeviceSensor(
'mdi:gas-cylinder', VOLUME_M3), toon,
ToonGasMeterDeviceSensor(toon, 'gas', 'meter_reading', "Gas Meter", 'gas',
'mdi:gas-cylinder', VOLUME_M3), 'average',
ToonGasMeterDeviceSensor(toon, 'gas', 'daily_cost', "Average Gas Usage",
"Gas Cost Today", 'mdi:gas-cylinder', 'mdi:gas-cylinder',
CURRENCY_EUR), 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: if toon.solar:
sensors.extend([ sensors.extend(
ToonSolarDeviceSensor(toon, 'solar', 'value', [
"Current Solar Production", ToonSolarDeviceSensor(
'mdi:solar-power', POWER_WATT), toon,
ToonSolarDeviceSensor(toon, 'solar', 'maximum', 'solar',
"Max Solar Production", 'mdi:solar-power', 'value',
POWER_WATT), "Current Solar Production",
ToonSolarDeviceSensor(toon, 'solar', 'produced', 'mdi:solar-power',
"Solar Production to Grid", POWER_WATT,
'mdi:solar-power', POWER_WATT), ),
ToonSolarDeviceSensor(toon, 'solar', 'average_produced', ToonSolarDeviceSensor(
"Average Solar Production to Grid", toon,
'mdi:solar-power', POWER_WATT), 'solar',
ToonElectricityMeterDeviceSensor(toon, 'solar', 'maximum',
'meter_reading_produced', "Max Solar Production",
"Power Meter Feed OUT Tariff 1", 'mdi:solar-power',
'mdi:solar-power', POWER_WATT,
POWER_KWH), ),
ToonElectricityMeterDeviceSensor(toon, 'solar', ToonSolarDeviceSensor(
'meter_reading_low_produced', toon,
"Power Meter Feed OUT Tariff 2", 'solar',
'mdi:solar-power', POWER_KWH), '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: if toon.thermostat_info.have_ot_boiler:
sensors.extend([ sensors.extend(
ToonBoilerDeviceSensor(toon, 'thermostat_info', [
'current_modulation_level', ToonBoilerDeviceSensor(
"Boiler Modulation Level", toon,
'mdi:percent', 'thermostat_info',
RATIO_PERCENT), 'current_modulation_level',
]) "Boiler Modulation Level",
'mdi:percent',
RATIO_PERCENT,
)
]
)
async_add_entities(sensors) async_add_entities(sensors, True)
class ToonSensor(ToonEntity): class ToonSensor(ToonEntity):
"""Defines a Toon sensor.""" """Defines a Toon sensor."""
def __init__(self, toon, section: str, measurement: str, def __init__(
name: str, icon: str, unit_of_measurement: str) -> None: self,
toon: ToonData,
section: str,
measurement: str,
name: str,
icon: str,
unit_of_measurement: str,
) -> None:
"""Initialize the Toon sensor.""" """Initialize the Toon sensor."""
self._state = None self._state = None
self._unit_of_measurement = unit_of_measurement self._unit_of_measurement = unit_of_measurement
@ -119,8 +238,15 @@ class ToonSensor(ToonEntity):
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
"""Return the unique ID for this sensor.""" """Return the unique ID for this sensor."""
return '_'.join([DOMAIN, self.toon.agreement.id, 'sensor', return '_'.join(
self.section, self.measurement]) [
DOMAIN,
self.toon.agreement.id,
'sensor',
self.section,
self.measurement,
]
)
@property @property
def state(self): def state(self):
@ -137,34 +263,46 @@ class ToonSensor(ToonEntity):
section = getattr(self.toon, self.section) section = getattr(self.toon, self.section)
value = None value = None
if not section:
return
if self.section == 'power' and self.measurement == 'daily_value': if self.section == 'power' and self.measurement == 'daily_value':
value = round((float(section.daily_usage) value = round(
+ float(section.daily_usage_low)) / 1000.0, 2) (float(section.daily_usage) + float(section.daily_usage_low))
/ 1000.0,
2,
)
if value is None: if value is None:
value = getattr(section, self.measurement) value = getattr(section, self.measurement)
if self.section == 'power' and \ if self.section == 'power' and self.measurement in [
self.measurement in ['meter_reading', 'meter_reading_low', 'meter_reading',
'average_daily']: 'meter_reading_low',
value = round(float(value)/1000.0, 2) 'average_daily',
]:
value = round(float(value) / 1000.0, 2)
if self.section == 'solar' and \ if self.section == 'solar' and self.measurement in [
self.measurement in ['meter_reading_produced', 'meter_reading_produced',
'meter_reading_low_produced']: 'meter_reading_low_produced',
value = float(value)/1000.0 ]:
value = float(value) / 1000.0
if self.section == 'gas' and \ if self.section == 'gas' and self.measurement in [
self.measurement in ['average_daily', 'daily_usage', 'average_daily',
'meter_reading']: 'daily_usage',
value = round(float(value)/1000.0, 2) 'meter_reading',
]:
value = round(float(value) / 1000.0, 2)
self._state = max(0, value) self._state = max(0, value)
class ToonElectricityMeterDeviceSensor(ToonSensor, class ToonElectricityMeterDeviceSensor(
ToonElectricityMeterDeviceEntity): ToonSensor, ToonElectricityMeterDeviceEntity
"""Defines a Eletricity Meter sensor.""" ):
"""Defines a Electricity Meter sensor."""
pass pass

View File

@ -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