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 dispatcherpull/25010/head
parent
f3e542542a
commit
3ce1049d21
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue