From 6d043f2ca12ca6e218c86e168f40da4fe5348fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Sun, 27 Dec 2020 16:36:35 +0100 Subject: [PATCH] Tado: add full list of devices (#44475) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Fernández Rojas --- homeassistant/components/tado/__init__.py | 17 +-- .../components/tado/binary_sensor.py | 127 ++++++++++++++++++ homeassistant/components/tado/climate.py | 10 +- homeassistant/components/tado/const.py | 5 +- homeassistant/components/tado/entity.py | 43 ++++-- homeassistant/components/tado/sensor.py | 102 +------------- homeassistant/components/tado/water_heater.py | 4 +- tests/components/tado/test_binary_sensor.py | 14 ++ tests/components/tado/test_sensor.py | 9 -- 9 files changed, 190 insertions(+), 141 deletions(-) create mode 100644 homeassistant/components/tado/binary_sensor.py create mode 100644 tests/components/tado/test_binary_sensor.py diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index 44a0f551ae0a..ab64183a1424 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -30,7 +30,7 @@ _LOGGER = logging.getLogger(__name__) -TADO_COMPONENTS = ["sensor", "climate", "water_heater"] +TADO_COMPONENTS = ["binary_sensor", "sensor", "climate", "water_heater"] MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) SCAN_INTERVAL = timedelta(seconds=15) @@ -174,7 +174,6 @@ def __init__(self, hass, username, password, fallback): self.devices = None self.data = { "zone": {}, - "device": {}, } @property @@ -188,16 +187,15 @@ def setup(self): self.tado.setDebugging(True) # Load zones and devices self.zones = self.tado.getZones() - self.devices = self.tado.getMe()["homes"] - self.device_id = self.devices[0]["id"] + self.devices = self.tado.getDevices() + self.device_id = self.tado.getMe()["homes"][0]["id"] @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Update the registered zones.""" for zone in self.zones: self.update_sensor("zone", zone["id"]) - for device in self.devices: - self.update_sensor("device", device["id"]) + self.devices = self.tado.getDevices() def update_sensor(self, sensor_type, sensor): """Update the internal data from Tado.""" @@ -205,13 +203,6 @@ def update_sensor(self, sensor_type, sensor): try: if sensor_type == "zone": data = self.tado.getZoneState(sensor) - elif sensor_type == "device": - devices_data = self.tado.getDevices() - if not devices_data: - _LOGGER.info("There are no devices to setup on this tado account") - return - - data = devices_data[0] else: _LOGGER.debug("Unknown sensor: %s", sensor_type) return diff --git a/homeassistant/components/tado/binary_sensor.py b/homeassistant/components/tado/binary_sensor.py new file mode 100644 index 000000000000..852ff6ae5444 --- /dev/null +++ b/homeassistant/components/tado/binary_sensor.py @@ -0,0 +1,127 @@ +"""Support for Tado sensors for each zone.""" +import logging + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CONNECTIVITY, + BinarySensorEntity, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DATA, DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED, TYPE_BATTERY, TYPE_POWER +from .entity import TadoDeviceEntity + +_LOGGER = logging.getLogger(__name__) + +DEVICE_SENSORS = { + TYPE_BATTERY: [ + "battery state", + "connection state", + ], + TYPE_POWER: [ + "connection state", + ], +} + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities +): + """Set up the Tado sensor platform.""" + + tado = hass.data[DOMAIN][entry.entry_id][DATA] + devices = tado.devices + entities = [] + + # Create device sensors + for device in devices: + if "batteryState" in device: + device_type = TYPE_BATTERY + else: + device_type = TYPE_POWER + + entities.extend( + [ + TadoDeviceSensor(tado, device, variable) + for variable in DEVICE_SENSORS[device_type] + ] + ) + + if entities: + async_add_entities(entities, True) + + +class TadoDeviceSensor(TadoDeviceEntity, BinarySensorEntity): + """Representation of a tado Sensor.""" + + def __init__(self, tado, device_info, device_variable): + """Initialize of the Tado Sensor.""" + self._tado = tado + super().__init__(device_info) + + self.device_variable = device_variable + + self._unique_id = f"{device_variable} {self.device_id} {tado.device_id}" + + self._state = None + + async def async_added_to_hass(self): + """Register for sensor updates.""" + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_TADO_UPDATE_RECEIVED.format( + self._tado.device_id, "device", self.device_id + ), + self._async_update_callback, + ) + ) + self._async_update_device_data() + + @property + def unique_id(self): + """Return the unique id.""" + return self._unique_id + + @property + def name(self): + """Return the name of the sensor.""" + return f"{self.device_name} {self.device_variable}" + + @property + def is_on(self): + """Return true if sensor is on.""" + return self._state + + @property + def device_class(self): + """Return the class of this sensor.""" + if self.device_variable == "battery state": + return DEVICE_CLASS_BATTERY + if self.device_variable == "connection state": + return DEVICE_CLASS_CONNECTIVITY + return None + + @callback + def _async_update_callback(self): + """Update and write state.""" + self._async_update_device_data() + self.async_write_ha_state() + + @callback + def _async_update_device_data(self): + """Handle update callbacks.""" + for device in self._tado.devices: + if device["serialNo"] == self.device_id: + self._device_info = device + break + + if self.device_variable == "battery state": + self._state = self._device_info["batteryState"] == "LOW" + elif self.device_variable == "connection state": + self._state = self._device_info.get("connectionState", {}).get( + "value", False + ) diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 3e0c79ad65e0..438b7a213940 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -89,15 +89,13 @@ def _generate_entities(tado): entities = [] for zone in tado.zones: if zone["type"] in [TYPE_HEATING, TYPE_AIR_CONDITIONING]: - entity = create_climate_entity( - tado, zone["name"], zone["id"], zone["devices"][0] - ) + entity = create_climate_entity(tado, zone["name"], zone["id"]) if entity: entities.append(entity) return entities -def create_climate_entity(tado, name: str, zone_id: int, zone: dict): +def create_climate_entity(tado, name: str, zone_id: int): """Create a Tado climate entity.""" capabilities = tado.get_capabilities(zone_id) _LOGGER.debug("Capabilities for zone %s: %s", zone_id, capabilities) @@ -180,7 +178,6 @@ def create_climate_entity(tado, name: str, zone_id: int, zone: dict): supported_hvac_modes, supported_fan_modes, support_flags, - zone, ) return entity @@ -203,11 +200,10 @@ def __init__( supported_hvac_modes, supported_fan_modes, support_flags, - device_info, ): """Initialize of Tado climate entity.""" self._tado = tado - super().__init__(zone_name, device_info, tado.device_id, zone_id) + super().__init__(zone_name, tado.device_id, zone_id) self.zone_id = zone_id self.zone_type = zone_type diff --git a/homeassistant/components/tado/const.py b/homeassistant/components/tado/const.py index 9fc7b1980542..95c524b0433e 100644 --- a/homeassistant/components/tado/const.py +++ b/homeassistant/components/tado/const.py @@ -53,6 +53,9 @@ TYPE_HEATING = "HEATING" TYPE_HOT_WATER = "HOT_WATER" +TYPE_BATTERY = "BATTERY" +TYPE_POWER = "POWER" + # Base modes CONST_MODE_OFF = "OFF" CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Use the schedule @@ -144,6 +147,6 @@ DEFAULT_NAME = "Tado" -TADO_BRIDGE = "Tado Bridge" +TADO_ZONE = "Zone" UPDATE_LISTENER = "update_listener" diff --git a/homeassistant/components/tado/entity.py b/homeassistant/components/tado/entity.py index d91896a4e128..fa2ce2f3afff 100644 --- a/homeassistant/components/tado/entity.py +++ b/homeassistant/components/tado/entity.py @@ -1,17 +1,44 @@ -"""Base class for August entity.""" +"""Base class for Tado entity.""" from homeassistant.helpers.entity import Entity -from .const import DEFAULT_NAME, DOMAIN +from .const import DEFAULT_NAME, DOMAIN, TADO_ZONE + + +class TadoDeviceEntity(Entity): + """Base implementation for Tado device.""" + + def __init__(self, device_info): + """Initialize a Tado device.""" + super().__init__() + self._device_info = device_info + self.device_name = device_info["shortSerialNo"] + self.device_id = device_info["serialNo"] + + @property + def device_info(self): + """Return the device_info of the device.""" + return { + "identifiers": {(DOMAIN, self.device_id)}, + "name": self.device_name, + "manufacturer": DEFAULT_NAME, + "sw_version": self._device_info["currentFwVersion"], + "model": self._device_info["deviceType"], + "via_device": (DOMAIN, self._device_info["serialNo"]), + } + + @property + def should_poll(self): + """Do not poll.""" + return False class TadoZoneEntity(Entity): - """Base implementation for tado device.""" + """Base implementation for Tado zone.""" - def __init__(self, zone_name, device_info, device_id, zone_id): - """Initialize an August device.""" + def __init__(self, zone_name, device_id, zone_id): + """Initialize a Tado zone.""" super().__init__() self._device_zone_id = f"{device_id}_{zone_id}" - self._device_info = device_info self.zone_name = zone_name @property @@ -21,9 +48,7 @@ def device_info(self): "identifiers": {(DOMAIN, self._device_zone_id)}, "name": self.zone_name, "manufacturer": DEFAULT_NAME, - "sw_version": self._device_info["currentFwVersion"], - "model": self._device_info["deviceType"], - "via_device": (DOMAIN, self._device_info["serialNo"]), + "model": TADO_ZONE, } @property diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index 56be5eb0123f..a937aea25e47 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -9,10 +9,8 @@ from .const import ( DATA, - DEFAULT_NAME, DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED, - TADO_BRIDGE, TYPE_AIR_CONDITIONING, TYPE_HEATING, TYPE_HOT_WATER, @@ -46,8 +44,6 @@ TYPE_HOT_WATER: ["power", "link", "tado mode", "overlay"], } -DEVICE_SENSORS = ["tado bridge status"] - async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities @@ -57,7 +53,6 @@ async def async_setup_entry( tado = hass.data[DOMAIN][entry.entry_id][DATA] # Create zone sensors zones = tado.zones - devices = tado.devices entities = [] for zone in zones: @@ -68,22 +63,11 @@ async def async_setup_entry( entities.extend( [ - TadoZoneSensor( - tado, zone["name"], zone["id"], variable, zone["devices"][0] - ) + TadoZoneSensor(tado, zone["name"], zone["id"], variable) for variable in ZONE_SENSORS[zone_type] ] ) - # Create device sensors - for device in devices: - entities.extend( - [ - TadoDeviceSensor(tado, device["name"], device["id"], variable, device) - for variable in DEVICE_SENSORS - ] - ) - if entities: async_add_entities(entities, True) @@ -91,10 +75,10 @@ async def async_setup_entry( class TadoZoneSensor(TadoZoneEntity, Entity): """Representation of a tado Sensor.""" - def __init__(self, tado, zone_name, zone_id, zone_variable, device_info): + def __init__(self, tado, zone_name, zone_id, zone_variable): """Initialize of the Tado Sensor.""" self._tado = tado - super().__init__(zone_name, device_info, tado.device_id, zone_id) + super().__init__(zone_name, tado.device_id, zone_id) self.zone_id = zone_id self.zone_variable = zone_variable @@ -227,83 +211,3 @@ def _async_update_zone_data(self): or self._tado_zone_data.open_window_detected ) self._state_attributes = self._tado_zone_data.open_window_attr - - -class TadoDeviceSensor(Entity): - """Representation of a tado Sensor.""" - - def __init__(self, tado, device_name, device_id, device_variable, device_info): - """Initialize of the Tado Sensor.""" - self._tado = tado - - self._device_info = device_info - self.device_name = device_name - self.device_id = device_id - self.device_variable = device_variable - - self._unique_id = f"{device_variable} {device_id} {tado.device_id}" - - self._state = None - self._state_attributes = None - self._tado_device_data = None - - async def async_added_to_hass(self): - """Register for sensor updates.""" - - self.async_on_remove( - async_dispatcher_connect( - self.hass, - SIGNAL_TADO_UPDATE_RECEIVED.format( - self._tado.device_id, "device", self.device_id - ), - self._async_update_callback, - ) - ) - self._async_update_device_data() - - @property - def unique_id(self): - """Return the unique id.""" - return self._unique_id - - @property - def name(self): - """Return the name of the sensor.""" - return f"{self.device_name} {self.device_variable}" - - @property - def state(self): - """Return the state of the sensor.""" - return self._state - - @property - def should_poll(self): - """Do not poll.""" - return False - - @callback - def _async_update_callback(self): - """Update and write state.""" - self._async_update_device_data() - self.async_write_ha_state() - - @callback - def _async_update_device_data(self): - """Handle update callbacks.""" - try: - data = self._tado.data["device"][self.device_id] - except KeyError: - return - - if self.device_variable == "tado bridge status": - self._state = data.get("connectionState", {}).get("value", False) - - @property - def device_info(self): - """Return the device_info of the device.""" - return { - "identifiers": {(DOMAIN, self.device_id)}, - "name": self.device_name, - "manufacturer": DEFAULT_NAME, - "model": TADO_BRIDGE, - } diff --git a/homeassistant/components/tado/water_heater.py b/homeassistant/components/tado/water_heater.py index 1a99db5c24ce..2755d14a3a34 100644 --- a/homeassistant/components/tado/water_heater.py +++ b/homeassistant/components/tado/water_heater.py @@ -113,7 +113,6 @@ def create_water_heater_entity(tado, name: str, zone_id: int, zone: str): supports_temperature_control, min_temp, max_temp, - zone["devices"][0], ) return entity @@ -130,12 +129,11 @@ def __init__( supports_temperature_control, min_temp, max_temp, - device_info, ): """Initialize of Tado water heater entity.""" self._tado = tado - super().__init__(zone_name, device_info, tado.device_id, zone_id) + super().__init__(zone_name, tado.device_id, zone_id) self.zone_id = zone_id self._unique_id = f"{zone_id} {tado.device_id}" diff --git a/tests/components/tado/test_binary_sensor.py b/tests/components/tado/test_binary_sensor.py new file mode 100644 index 000000000000..39dd068f5a60 --- /dev/null +++ b/tests/components/tado/test_binary_sensor.py @@ -0,0 +1,14 @@ +"""The sensor tests for the tado platform.""" + +from homeassistant.const import STATE_ON + +from .util import async_init_integration + + +async def test_home_create_binary_sensors(hass): + """Test creation of home binary sensors.""" + + await async_init_integration(hass) + + state = hass.states.get("binary_sensor.wr1_connection_state") + assert state.state == STATE_ON diff --git a/tests/components/tado/test_sensor.py b/tests/components/tado/test_sensor.py index 2ea2c0508ee4..646e77415303 100644 --- a/tests/components/tado/test_sensor.py +++ b/tests/components/tado/test_sensor.py @@ -85,12 +85,3 @@ async def test_water_heater_create_sensors(hass): state = hass.states.get("sensor.water_heater_power") assert state.state == "ON" - - -async def test_home_create_sensors(hass): - """Test creation of home sensors.""" - - await async_init_integration(hass) - - state = hass.states.get("sensor.home_name_tado_bridge_status") - assert state.state == "True"