diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index 36af3bb0dba..094465d38aa 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -144,11 +144,13 @@ class TadoConnector: self._fallback = fallback self.home_id = None + self.home_name = None self.tado = None self.zones = None self.devices = None self.data = { "device": {}, + "weather": {}, "zone": {}, } @@ -164,7 +166,9 @@ class TadoConnector: # Load zones and devices self.zones = self.tado.getZones() self.devices = self.tado.getDevices() - self.home_id = self.tado.getMe()["homes"][0]["id"] + tado_home = self.tado.getMe()["homes"][0] + self.home_id = tado_home["id"] + self.home_name = tado_home["name"] @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): @@ -173,6 +177,11 @@ class TadoConnector: self.update_sensor("device", device["shortSerialNo"]) for zone in self.zones: self.update_sensor("zone", zone["id"]) + self.data["weather"] = self.tado.getWeather() + dispatcher_send( + self.hass, + SIGNAL_TADO_UPDATE_RECEIVED.format(self.home_id, "weather", "data"), + ) def update_sensor(self, sensor_type, sensor): """Update the internal data from Tado.""" diff --git a/homeassistant/components/tado/const.py b/homeassistant/components/tado/const.py index 6e009df7ca2..2c86fa2d642 100644 --- a/homeassistant/components/tado/const.py +++ b/homeassistant/components/tado/const.py @@ -48,6 +48,21 @@ CONF_FALLBACK = "fallback" DATA = "data" UPDATE_TRACK = "update_track" +# Weather +CONDITIONS_MAP = { + "clear-night": {"NIGHT_CLEAR"}, + "cloudy": {"CLOUDY", "CLOUDY_MOSTLY", "NIGHT_CLOUDY"}, + "fog": {"FOGGY"}, + "hail": {"HAIL", "RAIN_HAIL"}, + "lightning": {"THUNDERSTORM"}, + "partlycloudy": {"CLOUDY_PARTLY"}, + "rainy": {"DRIZZLE", "RAIN", "SCATTERED_RAIN"}, + "snowy": {"FREEZING", "SCATTERED_SNOW", "SNOW"}, + "snowy-rainy": {"RAIN_SNOW", "SCATTERED_RAIN_SNOW"}, + "sunny": {"SUN"}, + "windy": {"WIND"}, +} + # Types TYPE_AIR_CONDITIONING = "AIR_CONDITIONING" TYPE_HEATING = "HEATING" @@ -149,6 +164,7 @@ UNIQUE_ID = "unique_id" DEFAULT_NAME = "Tado" +TADO_HOME = "Home" TADO_ZONE = "Zone" UPDATE_LISTENER = "update_listener" diff --git a/homeassistant/components/tado/entity.py b/homeassistant/components/tado/entity.py index 34473a45c98..270d6f1e911 100644 --- a/homeassistant/components/tado/entity.py +++ b/homeassistant/components/tado/entity.py @@ -1,7 +1,7 @@ """Base class for Tado entity.""" from homeassistant.helpers.entity import Entity -from .const import DEFAULT_NAME, DOMAIN, TADO_ZONE +from .const import DEFAULT_NAME, DOMAIN, TADO_HOME, TADO_ZONE class TadoDeviceEntity(Entity): @@ -32,6 +32,26 @@ class TadoDeviceEntity(Entity): return False +class TadoHomeEntity(Entity): + """Base implementation for Tado home.""" + + def __init__(self, tado): + """Initialize a Tado home.""" + super().__init__() + self.home_name = tado.home_name + self.home_id = tado.home_id + + @property + def device_info(self): + """Return the device_info of the device.""" + return { + "identifiers": {(DOMAIN, self.home_id)}, + "name": self.home_name, + "manufacturer": DEFAULT_NAME, + "model": TADO_HOME, + } + + class TadoZoneEntity(Entity): """Base implementation for Tado zone.""" diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index 6613de82bff..8d38d9eab96 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -13,6 +13,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from .const import ( + CONDITIONS_MAP, DATA, DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED, @@ -20,10 +21,16 @@ from .const import ( TYPE_HEATING, TYPE_HOT_WATER, ) -from .entity import TadoZoneEntity +from .entity import TadoHomeEntity, TadoZoneEntity _LOGGER = logging.getLogger(__name__) +HOME_SENSORS = { + "outdoor temperature", + "solar percentage", + "weather condition", +} + ZONE_SENSORS = { TYPE_HEATING: [ "temperature", @@ -41,6 +48,14 @@ ZONE_SENSORS = { } +def format_condition(condition: str) -> str: + """Return condition from dict CONDITIONS_MAP.""" + for key, value in CONDITIONS_MAP.items(): + if condition in value: + return key + return condition + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities ): @@ -50,6 +65,9 @@ async def async_setup_entry( zones = tado.zones entities = [] + # Create home sensors + entities.extend([TadoHomeSensor(tado, variable) for variable in HOME_SENSORS]) + # Create zone sensors for zone in zones: zone_type = zone["type"] @@ -68,6 +86,111 @@ async def async_setup_entry( async_add_entities(entities, True) +class TadoHomeSensor(TadoHomeEntity, Entity): + """Representation of a Tado Sensor.""" + + def __init__(self, tado, home_variable): + """Initialize of the Tado Sensor.""" + super().__init__(tado) + self._tado = tado + + self.home_variable = home_variable + + self._unique_id = f"{home_variable} {tado.home_id}" + + self._state = None + self._state_attributes = None + self._tado_weather_data = self._tado.data["weather"] + + 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.home_id, "weather", "data" + ), + self._async_update_callback, + ) + ) + self._async_update_home_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._tado.home_name} {self.home_variable}" + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._state_attributes + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + if self.home_variable == "temperature": + return TEMP_CELSIUS + if self.home_variable == "solar percentage": + return PERCENTAGE + if self.home_variable == "weather condition": + return None + + @property + def device_class(self): + """Return the device class.""" + if self.home_variable == "outdoor temperature": + return DEVICE_CLASS_TEMPERATURE + return None + + @callback + def _async_update_callback(self): + """Update and write state.""" + self._async_update_home_data() + self.async_write_ha_state() + + @callback + def _async_update_home_data(self): + """Handle update callbacks.""" + try: + self._tado_weather_data = self._tado.data["weather"] + except KeyError: + return + + if self.home_variable == "outdoor temperature": + self._state = self.hass.config.units.temperature( + self._tado_weather_data["outsideTemperature"]["celsius"], + TEMP_CELSIUS, + ) + self._state_attributes = { + "time": self._tado_weather_data["outsideTemperature"]["timestamp"], + } + + elif self.home_variable == "solar percentage": + self._state = self._tado_weather_data["solarIntensity"]["percentage"] + self._state_attributes = { + "time": self._tado_weather_data["solarIntensity"]["timestamp"], + } + + elif self.home_variable == "weather condition": + self._state = format_condition( + self._tado_weather_data["weatherState"]["value"] + ) + self._state_attributes = { + "time": self._tado_weather_data["weatherState"]["timestamp"] + } + + class TadoZoneSensor(TadoZoneEntity, Entity): """Representation of a tado Sensor.""" diff --git a/tests/components/tado/test_sensor.py b/tests/components/tado/test_sensor.py index 2fac88bc22e..bb926ff1ae2 100644 --- a/tests/components/tado/test_sensor.py +++ b/tests/components/tado/test_sensor.py @@ -21,6 +21,21 @@ async def test_air_con_create_sensors(hass): assert state.state == "60.9" +async def test_home_create_sensors(hass): + """Test creation of home sensors.""" + + await async_init_integration(hass) + + state = hass.states.get("sensor.home_name_outdoor_temperature") + assert state.state == "7.46" + + state = hass.states.get("sensor.home_name_solar_percentage") + assert state.state == "2.1" + + state = hass.states.get("sensor.home_name_weather_condition") + assert state.state == "fog" + + async def test_heater_create_sensors(hass): """Test creation of heater sensors.""" diff --git a/tests/components/tado/util.py b/tests/components/tado/util.py index c5bf8cf28a4..ce1dd92942d 100644 --- a/tests/components/tado/util.py +++ b/tests/components/tado/util.py @@ -18,6 +18,7 @@ async def async_init_integration( token_fixture = "tado/token.json" devices_fixture = "tado/devices.json" me_fixture = "tado/me.json" + weather_fixture = "tado/weather.json" zones_fixture = "tado/zones.json" # WR1 Device @@ -55,6 +56,10 @@ async def async_init_integration( "https://my.tado.com/api/v2/me", text=load_fixture(me_fixture), ) + m.get( + "https://my.tado.com/api/v2/homes/1/weather", + text=load_fixture(weather_fixture), + ) m.get( "https://my.tado.com/api/v2/homes/1/devices", text=load_fixture(devices_fixture), diff --git a/tests/fixtures/tado/weather.json b/tests/fixtures/tado/weather.json new file mode 100644 index 00000000000..72379f05512 --- /dev/null +++ b/tests/fixtures/tado/weather.json @@ -0,0 +1,22 @@ +{ + "outsideTemperature": { + "celsius": 7.46, + "fahrenheit": 45.43, + "precision": { + "celsius": 0.01, + "fahrenheit": 0.01 + }, + "timestamp": "2020-12-22T08:13:13.652Z", + "type": "TEMPERATURE" + }, + "solarIntensity": { + "percentage": 2.1, + "timestamp": "2020-12-22T08:13:13.652Z", + "type": "PERCENTAGE" + }, + "weatherState": { + "timestamp": "2020-12-22T08:13:13.652Z", + "type": "WEATHER_STATE", + "value": "FOGGY" + } +}