From 5185fa75d3afaeb15b88edc62755bee8e6ddfdb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Betts?= Date: Wed, 7 Jul 2021 17:25:52 +0200 Subject: [PATCH 01/16] Fix service registration typo in Nuki integration (#52631) --- homeassistant/components/nuki/lock.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index 9cb1bd01524..25644a49f0f 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -59,6 +59,9 @@ async def async_setup_entry(hass, entry, async_add_entities): vol.Optional(ATTR_UNLATCH, default=False): cv.boolean, }, "lock_n_go", + ) + + platform.async_register_entity_service( "set_continuous_mode", { vol.Required(ATTR_ENABLE): cv.boolean, From 84be418bf1e3c009c8abc46c7a2110f038cab51b Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Wed, 7 Jul 2021 20:19:31 +0200 Subject: [PATCH 02/16] Fix Fritz default consider home value (#52648) --- homeassistant/components/fritz/common.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 776c7a7a22e..332c9b795f8 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -195,12 +195,13 @@ class FritzBoxTools: """Scan for new devices and return a list of found device ids.""" _LOGGER.debug("Checking devices for FRITZ!Box router %s", self.host) + _default_consider_home = DEFAULT_CONSIDER_HOME.total_seconds() if self._options: consider_home = self._options.get( - CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME.total_seconds() + CONF_CONSIDER_HOME, _default_consider_home ) else: - consider_home = DEFAULT_CONSIDER_HOME + consider_home = _default_consider_home new_device = False for known_host in self._update_info(): From 8173dd06fe7b33739314e56c24fd6aa717f9cd95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 7 Jul 2021 20:18:43 +0200 Subject: [PATCH 03/16] Handle KeyError when accessing device information (#52650) --- homeassistant/components/ecovacs/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ecovacs/__init__.py b/homeassistant/components/ecovacs/__init__.py index 964dd7a3f2a..8a6475c0192 100644 --- a/homeassistant/components/ecovacs/__init__.py +++ b/homeassistant/components/ecovacs/__init__.py @@ -59,8 +59,8 @@ def setup(hass, config): for device in devices: _LOGGER.info( "Discovered Ecovacs device on account: %s with nickname %s", - device["did"], - device["nick"], + device.get("did"), + device.get("nick"), ) vacbot = VacBot( ecovacs_api.uid, @@ -77,7 +77,8 @@ def setup(hass, config): """Shut down open connections to Ecovacs XMPP server.""" for device in hass.data[ECOVACS_DEVICES]: _LOGGER.info( - "Shutting down connection to Ecovacs device %s", device.vacuum["did"] + "Shutting down connection to Ecovacs device %s", + device.vacuum.get("did"), ) device.disconnect() From b9827a5b2eb64d91bc4483166fad2a5f73b74c3e Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 8 Jul 2021 02:15:56 -0500 Subject: [PATCH 04/16] Warn if `interface_addr` remains in Sonos configuration (#52652) --- homeassistant/components/sonos/__init__.py | 26 +++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index 218ddaa8e15..ec16ec5bd87 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -52,14 +52,17 @@ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { - MP_DOMAIN: vol.Schema( - { - vol.Optional(CONF_ADVERTISE_ADDR): cv.string, - vol.Optional(CONF_INTERFACE_ADDR): cv.string, - vol.Optional(CONF_HOSTS): vol.All( - cv.ensure_list_csv, [cv.string] - ), - } + MP_DOMAIN: vol.All( + cv.deprecated(CONF_INTERFACE_ADDR), + vol.Schema( + { + vol.Optional(CONF_ADVERTISE_ADDR): cv.string, + vol.Optional(CONF_INTERFACE_ADDR): cv.string, + vol.Optional(CONF_HOSTS): vol.All( + cv.ensure_list_csv, [cv.string] + ), + } + ), ) } ) @@ -126,6 +129,13 @@ async def async_setup_entry( # noqa: C901 if advertise_addr: pysonos.config.EVENT_ADVERTISE_IP = advertise_addr + if deprecated_address := config.get(CONF_INTERFACE_ADDR): + _LOGGER.warning( + "'%s' is deprecated, enable %s in the Network integration (https://www.home-assistant.io/integrations/network/)", + CONF_INTERFACE_ADDR, + deprecated_address, + ) + async def _async_stop_event_listener(event: Event) -> None: await asyncio.gather( *[speaker.async_unsubscribe() for speaker in data.discovered.values()], From ef309a7c1208d1b41f881a5ddcff5834f597e05d Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 8 Jul 2021 04:56:50 -0500 Subject: [PATCH 05/16] Ignore unused keys from Sonos device properties callback (#52660) * Ignore known but unused keys from device callback * Fix bug, add test --- homeassistant/components/sonos/speaker.py | 5 +++++ tests/components/sonos/test_sensor.py | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index ec59d946c09..14adbc337fb 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -71,6 +71,7 @@ SUBSCRIPTION_SERVICES = [ "zoneGroupTopology", ] UNAVAILABLE_VALUES = {"", "NOT_IMPLEMENTED", None} +UNUSED_DEVICE_KEYS = ["SPID", "TargetRoomName"] _LOGGER = logging.getLogger(__name__) @@ -407,6 +408,10 @@ class SonosSpeaker: """Update device properties from an event.""" if more_info := event.variables.get("more_info"): battery_dict = dict(x.split(":") for x in more_info.split(",")) + for unused in UNUSED_DEVICE_KEYS: + battery_dict.pop(unused, None) + if not battery_dict: + return if "BattChg" not in battery_dict: _LOGGER.debug( "Unknown device properties update for %s (%s), please report an issue: '%s'", diff --git a/tests/components/sonos/test_sensor.py b/tests/components/sonos/test_sensor.py index 8d402b589b0..80f050fe6fc 100644 --- a/tests/components/sonos/test_sensor.py +++ b/tests/components/sonos/test_sensor.py @@ -103,3 +103,23 @@ async def test_device_payload_without_battery( await hass.async_block_till_done() assert bad_payload in caplog.text + + +async def test_device_payload_without_battery_and_ignored_keys( + hass, config_entry, config, soco, battery_event, caplog +): + """Test device properties event update without battery info and ignored keys.""" + soco.get_battery_info.return_value = None + + await setup_platform(hass, config_entry, config) + + subscription = soco.deviceProperties.subscribe.return_value + sub_callback = subscription.callback + + ignored_payload = "SPID:InCeiling,TargetRoomName:Bouncy House" + battery_event.variables["more_info"] = ignored_payload + + sub_callback(battery_event) + await hass.async_block_till_done() + + assert ignored_payload not in caplog.text From 57f6a96e3178b2ebf7708a2f936f5e50dd5e690e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 8 Jul 2021 10:09:30 +0200 Subject: [PATCH 06/16] Ensure Forecast.Solar returns an iso formatted timestamp (#52669) --- homeassistant/components/forecast_solar/sensor.py | 6 +++++- tests/components/forecast_solar/test_sensor.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/forecast_solar/sensor.py b/homeassistant/components/forecast_solar/sensor.py index a6b1927926e..b32f1f341be 100644 --- a/homeassistant/components/forecast_solar/sensor.py +++ b/homeassistant/components/forecast_solar/sensor.py @@ -1,6 +1,8 @@ """Support for the Forecast.Solar sensor service.""" from __future__ import annotations +from datetime import datetime + from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_IDENTIFIERS, ATTR_MANUFACTURER, ATTR_NAME @@ -64,5 +66,7 @@ class ForecastSolarSensorEntity(CoordinatorEntity, SensorEntity): @property def state(self) -> StateType: """Return the state of the sensor.""" - state: StateType = getattr(self.coordinator.data, self._sensor.key) + state: StateType | datetime = getattr(self.coordinator.data, self._sensor.key) + if isinstance(state, datetime): + return state.isoformat() return state diff --git a/tests/components/forecast_solar/test_sensor.py b/tests/components/forecast_solar/test_sensor.py index a3513b86a5d..31c367678c1 100644 --- a/tests/components/forecast_solar/test_sensor.py +++ b/tests/components/forecast_solar/test_sensor.py @@ -70,7 +70,7 @@ async def test_sensors( assert entry assert state assert entry.unique_id == f"{entry_id}_power_highest_peak_time_today" - assert state.state == "2021-06-27 13:00:00+00:00" + assert state.state == "2021-06-27T13:00:00+00:00" assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Highest Power Peak Time - Today" assert state.attributes.get(ATTR_STATE_CLASS) is None assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TIMESTAMP @@ -82,7 +82,7 @@ async def test_sensors( assert entry assert state assert entry.unique_id == f"{entry_id}_power_highest_peak_time_tomorrow" - assert state.state == "2021-06-27 14:00:00+00:00" + assert state.state == "2021-06-27T14:00:00+00:00" assert ( state.attributes.get(ATTR_FRIENDLY_NAME) == "Highest Power Peak Time - Tomorrow" ) From 9f4014190729e599780ddf60eeb8550a6a11b48f Mon Sep 17 00:00:00 2001 From: avee87 Date: Thu, 8 Jul 2021 09:01:06 +0100 Subject: [PATCH 07/16] Use iso-formatted times in MetOffice weather forecast (#52672) * Fixed raw datetime in MetOffice weather forecast * Use datetime in sensor attribute --- homeassistant/components/metoffice/weather.py | 2 +- tests/components/metoffice/const.py | 3 +- tests/components/metoffice/test_sensor.py | 18 +- tests/components/metoffice/test_weather.py | 194 +++++++++--------- 4 files changed, 103 insertions(+), 114 deletions(-) diff --git a/homeassistant/components/metoffice/weather.py b/homeassistant/components/metoffice/weather.py index 0b1933c665f..b02539f0e31 100644 --- a/homeassistant/components/metoffice/weather.py +++ b/homeassistant/components/metoffice/weather.py @@ -47,7 +47,7 @@ async def async_setup_entry( def _build_forecast_data(timestep): data = {} - data[ATTR_FORECAST_TIME] = timestep.date + data[ATTR_FORECAST_TIME] = timestep.date.isoformat() if timestep.weather: data[ATTR_FORECAST_CONDITION] = _get_weather_condition(timestep.weather.value) if timestep.precipitation: diff --git a/tests/components/metoffice/const.py b/tests/components/metoffice/const.py index 5d8d781b042..c9a173e3f12 100644 --- a/tests/components/metoffice/const.py +++ b/tests/components/metoffice/const.py @@ -2,8 +2,7 @@ from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME -DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S%z" -TEST_DATETIME_STRING = "2020-04-25 12:00:00+0000" +TEST_DATETIME_STRING = "2020-04-25T12:00:00+00:00" TEST_API_KEY = "test-metoffice-api-key" diff --git a/tests/components/metoffice/test_sensor.py b/tests/components/metoffice/test_sensor.py index e603d0f93f6..201c5922d33 100644 --- a/tests/components/metoffice/test_sensor.py +++ b/tests/components/metoffice/test_sensor.py @@ -6,7 +6,6 @@ from homeassistant.components.metoffice.const import ATTRIBUTION, DOMAIN from . import NewDateTime from .const import ( - DATETIME_FORMAT, KINGSLYNN_SENSOR_RESULTS, METOFFICE_CONFIG_KINGSLYNN, METOFFICE_CONFIG_WAVERTREE, @@ -54,13 +53,10 @@ async def test_one_sensor_site_running(hass, requests_mock, legacy_patchable_tim for running_id in running_sensor_ids: sensor = hass.states.get(running_id) sensor_id = sensor.attributes.get("sensor_id") - sensor_name, sensor_value = WAVERTREE_SENSOR_RESULTS[sensor_id] + _, sensor_value = WAVERTREE_SENSOR_RESULTS[sensor_id] assert sensor.state == sensor_value - assert ( - sensor.attributes.get("last_update").strftime(DATETIME_FORMAT) - == TEST_DATETIME_STRING - ) + assert sensor.attributes.get("last_update").isoformat() == TEST_DATETIME_STRING assert sensor.attributes.get("site_id") == "354107" assert sensor.attributes.get("site_name") == TEST_SITE_NAME_WAVERTREE assert sensor.attributes.get("attribution") == ATTRIBUTION @@ -115,11 +111,10 @@ async def test_two_sensor_sites_running(hass, requests_mock, legacy_patchable_ti sensor = hass.states.get(running_id) sensor_id = sensor.attributes.get("sensor_id") if sensor.attributes.get("site_id") == "354107": - sensor_name, sensor_value = WAVERTREE_SENSOR_RESULTS[sensor_id] + _, sensor_value = WAVERTREE_SENSOR_RESULTS[sensor_id] assert sensor.state == sensor_value assert ( - sensor.attributes.get("last_update").strftime(DATETIME_FORMAT) - == TEST_DATETIME_STRING + sensor.attributes.get("last_update").isoformat() == TEST_DATETIME_STRING ) assert sensor.attributes.get("sensor_id") == sensor_id assert sensor.attributes.get("site_id") == "354107" @@ -127,11 +122,10 @@ async def test_two_sensor_sites_running(hass, requests_mock, legacy_patchable_ti assert sensor.attributes.get("attribution") == ATTRIBUTION else: - sensor_name, sensor_value = KINGSLYNN_SENSOR_RESULTS[sensor_id] + _, sensor_value = KINGSLYNN_SENSOR_RESULTS[sensor_id] assert sensor.state == sensor_value assert ( - sensor.attributes.get("last_update").strftime(DATETIME_FORMAT) - == TEST_DATETIME_STRING + sensor.attributes.get("last_update").isoformat() == TEST_DATETIME_STRING ) assert sensor.attributes.get("sensor_id") == sensor_id assert sensor.attributes.get("site_id") == "322380" diff --git a/tests/components/metoffice/test_weather.py b/tests/components/metoffice/test_weather.py index 6d4187c7023..21b2196804c 100644 --- a/tests/components/metoffice/test_weather.py +++ b/tests/components/metoffice/test_weather.py @@ -9,7 +9,6 @@ from homeassistant.util import utcnow from . import NewDateTime from .const import ( - DATETIME_FORMAT, METOFFICE_CONFIG_KINGSLYNN, METOFFICE_CONFIG_WAVERTREE, WAVERTREE_SENSOR_RESULTS, @@ -74,11 +73,11 @@ async def test_site_cannot_update(hass, requests_mock, legacy_patchable_time): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - entity = hass.states.get("weather.met_office_wavertree_3_hourly") - assert entity + weather = hass.states.get("weather.met_office_wavertree_3_hourly") + assert weather - entity = hass.states.get("weather.met_office_wavertree_daily") - assert entity + weather = hass.states.get("weather.met_office_wavertree_daily") + assert weather requests_mock.get("/public/data/val/wxfcs/all/json/354107?res=3hourly", text="") requests_mock.get("/public/data/val/wxfcs/all/json/354107?res=daily", text="") @@ -87,11 +86,11 @@ async def test_site_cannot_update(hass, requests_mock, legacy_patchable_time): async_fire_time_changed(hass, future_time) await hass.async_block_till_done() - entity = hass.states.get("weather.met_office_wavertree_3_hourly") - assert entity.state == STATE_UNAVAILABLE + weather = hass.states.get("weather.met_office_wavertree_3_hourly") + assert weather.state == STATE_UNAVAILABLE - entity = hass.states.get("weather.met_office_wavertree_daily") - assert entity.state == STATE_UNAVAILABLE + weather = hass.states.get("weather.met_office_wavertree_daily") + assert weather.state == STATE_UNAVAILABLE @patch( @@ -126,50 +125,49 @@ async def test_one_weather_site_running(hass, requests_mock, legacy_patchable_ti await hass.async_block_till_done() # Wavertree 3-hourly weather platform expected results - entity = hass.states.get("weather.met_office_wavertree_3_hourly") - assert entity + weather = hass.states.get("weather.met_office_wavertree_3_hourly") + assert weather - assert entity.state == "sunny" - assert entity.attributes.get("temperature") == 17 - assert entity.attributes.get("wind_speed") == 9 - assert entity.attributes.get("wind_bearing") == "SSE" - assert entity.attributes.get("visibility") == "Good - 10-20" - assert entity.attributes.get("humidity") == 50 + assert weather.state == "sunny" + assert weather.attributes.get("temperature") == 17 + assert weather.attributes.get("wind_speed") == 9 + assert weather.attributes.get("wind_bearing") == "SSE" + assert weather.attributes.get("visibility") == "Good - 10-20" + assert weather.attributes.get("humidity") == 50 # Forecasts added - just pick out 1 entry to check - assert len(entity.attributes.get("forecast")) == 35 + assert len(weather.attributes.get("forecast")) == 35 assert ( - entity.attributes.get("forecast")[26]["datetime"].strftime(DATETIME_FORMAT) - == "2020-04-28 21:00:00+0000" + weather.attributes.get("forecast")[26]["datetime"] + == "2020-04-28T21:00:00+00:00" ) - assert entity.attributes.get("forecast")[26]["condition"] == "cloudy" - assert entity.attributes.get("forecast")[26]["temperature"] == 10 - assert entity.attributes.get("forecast")[26]["wind_speed"] == 4 - assert entity.attributes.get("forecast")[26]["wind_bearing"] == "NNE" + assert weather.attributes.get("forecast")[26]["condition"] == "cloudy" + assert weather.attributes.get("forecast")[26]["temperature"] == 10 + assert weather.attributes.get("forecast")[26]["wind_speed"] == 4 + assert weather.attributes.get("forecast")[26]["wind_bearing"] == "NNE" # Wavertree daily weather platform expected results - entity = hass.states.get("weather.met_office_wavertree_daily") - assert entity + weather = hass.states.get("weather.met_office_wavertree_daily") + assert weather - assert entity.state == "sunny" - assert entity.attributes.get("temperature") == 19 - assert entity.attributes.get("wind_speed") == 9 - assert entity.attributes.get("wind_bearing") == "SSE" - assert entity.attributes.get("visibility") == "Good - 10-20" - assert entity.attributes.get("humidity") == 50 + assert weather.state == "sunny" + assert weather.attributes.get("temperature") == 19 + assert weather.attributes.get("wind_speed") == 9 + assert weather.attributes.get("wind_bearing") == "SSE" + assert weather.attributes.get("visibility") == "Good - 10-20" + assert weather.attributes.get("humidity") == 50 # Also has Forecasts added - again, just pick out 1 entry to check - assert len(entity.attributes.get("forecast")) == 8 + assert len(weather.attributes.get("forecast")) == 8 assert ( - entity.attributes.get("forecast")[7]["datetime"].strftime(DATETIME_FORMAT) - == "2020-04-29 12:00:00+0000" + weather.attributes.get("forecast")[7]["datetime"] == "2020-04-29T12:00:00+00:00" ) - assert entity.attributes.get("forecast")[7]["condition"] == "rainy" - assert entity.attributes.get("forecast")[7]["temperature"] == 13 - assert entity.attributes.get("forecast")[7]["wind_speed"] == 13 - assert entity.attributes.get("forecast")[7]["wind_bearing"] == "SE" + assert weather.attributes.get("forecast")[7]["condition"] == "rainy" + assert weather.attributes.get("forecast")[7]["temperature"] == 13 + assert weather.attributes.get("forecast")[7]["wind_speed"] == 13 + assert weather.attributes.get("forecast")[7]["wind_bearing"] == "SE" @patch( @@ -216,93 +214,91 @@ async def test_two_weather_sites_running(hass, requests_mock, legacy_patchable_t await hass.async_block_till_done() # Wavertree 3-hourly weather platform expected results - entity = hass.states.get("weather.met_office_wavertree_3_hourly") - assert entity + weather = hass.states.get("weather.met_office_wavertree_3_hourly") + assert weather - assert entity.state == "sunny" - assert entity.attributes.get("temperature") == 17 - assert entity.attributes.get("wind_speed") == 9 - assert entity.attributes.get("wind_bearing") == "SSE" - assert entity.attributes.get("visibility") == "Good - 10-20" - assert entity.attributes.get("humidity") == 50 + assert weather.state == "sunny" + assert weather.attributes.get("temperature") == 17 + assert weather.attributes.get("wind_speed") == 9 + assert weather.attributes.get("wind_bearing") == "SSE" + assert weather.attributes.get("visibility") == "Good - 10-20" + assert weather.attributes.get("humidity") == 50 # Forecasts added - just pick out 1 entry to check - assert len(entity.attributes.get("forecast")) == 35 + assert len(weather.attributes.get("forecast")) == 35 assert ( - entity.attributes.get("forecast")[18]["datetime"].strftime(DATETIME_FORMAT) - == "2020-04-27 21:00:00+0000" + weather.attributes.get("forecast")[18]["datetime"] + == "2020-04-27T21:00:00+00:00" ) - assert entity.attributes.get("forecast")[18]["condition"] == "sunny" - assert entity.attributes.get("forecast")[18]["temperature"] == 9 - assert entity.attributes.get("forecast")[18]["wind_speed"] == 4 - assert entity.attributes.get("forecast")[18]["wind_bearing"] == "NW" + assert weather.attributes.get("forecast")[18]["condition"] == "sunny" + assert weather.attributes.get("forecast")[18]["temperature"] == 9 + assert weather.attributes.get("forecast")[18]["wind_speed"] == 4 + assert weather.attributes.get("forecast")[18]["wind_bearing"] == "NW" # Wavertree daily weather platform expected results - entity = hass.states.get("weather.met_office_wavertree_daily") - assert entity + weather = hass.states.get("weather.met_office_wavertree_daily") + assert weather - assert entity.state == "sunny" - assert entity.attributes.get("temperature") == 19 - assert entity.attributes.get("wind_speed") == 9 - assert entity.attributes.get("wind_bearing") == "SSE" - assert entity.attributes.get("visibility") == "Good - 10-20" - assert entity.attributes.get("humidity") == 50 + assert weather.state == "sunny" + assert weather.attributes.get("temperature") == 19 + assert weather.attributes.get("wind_speed") == 9 + assert weather.attributes.get("wind_bearing") == "SSE" + assert weather.attributes.get("visibility") == "Good - 10-20" + assert weather.attributes.get("humidity") == 50 # Also has Forecasts added - again, just pick out 1 entry to check - assert len(entity.attributes.get("forecast")) == 8 + assert len(weather.attributes.get("forecast")) == 8 assert ( - entity.attributes.get("forecast")[7]["datetime"].strftime(DATETIME_FORMAT) - == "2020-04-29 12:00:00+0000" + weather.attributes.get("forecast")[7]["datetime"] == "2020-04-29T12:00:00+00:00" ) - assert entity.attributes.get("forecast")[7]["condition"] == "rainy" - assert entity.attributes.get("forecast")[7]["temperature"] == 13 - assert entity.attributes.get("forecast")[7]["wind_speed"] == 13 - assert entity.attributes.get("forecast")[7]["wind_bearing"] == "SE" + assert weather.attributes.get("forecast")[7]["condition"] == "rainy" + assert weather.attributes.get("forecast")[7]["temperature"] == 13 + assert weather.attributes.get("forecast")[7]["wind_speed"] == 13 + assert weather.attributes.get("forecast")[7]["wind_bearing"] == "SE" # King's Lynn 3-hourly weather platform expected results - entity = hass.states.get("weather.met_office_king_s_lynn_3_hourly") - assert entity + weather = hass.states.get("weather.met_office_king_s_lynn_3_hourly") + assert weather - assert entity.state == "sunny" - assert entity.attributes.get("temperature") == 14 - assert entity.attributes.get("wind_speed") == 2 - assert entity.attributes.get("wind_bearing") == "E" - assert entity.attributes.get("visibility") == "Very Good - 20-40" - assert entity.attributes.get("humidity") == 60 + assert weather.state == "sunny" + assert weather.attributes.get("temperature") == 14 + assert weather.attributes.get("wind_speed") == 2 + assert weather.attributes.get("wind_bearing") == "E" + assert weather.attributes.get("visibility") == "Very Good - 20-40" + assert weather.attributes.get("humidity") == 60 # Also has Forecast added - just pick out 1 entry to check - assert len(entity.attributes.get("forecast")) == 35 + assert len(weather.attributes.get("forecast")) == 35 assert ( - entity.attributes.get("forecast")[18]["datetime"].strftime(DATETIME_FORMAT) - == "2020-04-27 21:00:00+0000" + weather.attributes.get("forecast")[18]["datetime"] + == "2020-04-27T21:00:00+00:00" ) - assert entity.attributes.get("forecast")[18]["condition"] == "cloudy" - assert entity.attributes.get("forecast")[18]["temperature"] == 10 - assert entity.attributes.get("forecast")[18]["wind_speed"] == 7 - assert entity.attributes.get("forecast")[18]["wind_bearing"] == "SE" + assert weather.attributes.get("forecast")[18]["condition"] == "cloudy" + assert weather.attributes.get("forecast")[18]["temperature"] == 10 + assert weather.attributes.get("forecast")[18]["wind_speed"] == 7 + assert weather.attributes.get("forecast")[18]["wind_bearing"] == "SE" # King's Lynn daily weather platform expected results - entity = hass.states.get("weather.met_office_king_s_lynn_daily") - assert entity + weather = hass.states.get("weather.met_office_king_s_lynn_daily") + assert weather - assert entity.state == "cloudy" - assert entity.attributes.get("temperature") == 9 - assert entity.attributes.get("wind_speed") == 4 - assert entity.attributes.get("wind_bearing") == "ESE" - assert entity.attributes.get("visibility") == "Very Good - 20-40" - assert entity.attributes.get("humidity") == 75 + assert weather.state == "cloudy" + assert weather.attributes.get("temperature") == 9 + assert weather.attributes.get("wind_speed") == 4 + assert weather.attributes.get("wind_bearing") == "ESE" + assert weather.attributes.get("visibility") == "Very Good - 20-40" + assert weather.attributes.get("humidity") == 75 # All should have Forecast added - again, just picking out 1 entry to check - assert len(entity.attributes.get("forecast")) == 8 + assert len(weather.attributes.get("forecast")) == 8 assert ( - entity.attributes.get("forecast")[5]["datetime"].strftime(DATETIME_FORMAT) - == "2020-04-28 12:00:00+0000" + weather.attributes.get("forecast")[5]["datetime"] == "2020-04-28T12:00:00+00:00" ) - assert entity.attributes.get("forecast")[5]["condition"] == "cloudy" - assert entity.attributes.get("forecast")[5]["temperature"] == 11 - assert entity.attributes.get("forecast")[5]["wind_speed"] == 7 - assert entity.attributes.get("forecast")[5]["wind_bearing"] == "ESE" + assert weather.attributes.get("forecast")[5]["condition"] == "cloudy" + assert weather.attributes.get("forecast")[5]["temperature"] == 11 + assert weather.attributes.get("forecast")[5]["wind_speed"] == 7 + assert weather.attributes.get("forecast")[5]["wind_bearing"] == "ESE" From 2038fb04b14749d7176212656c2afd8f27f0e635 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 8 Jul 2021 11:39:56 +0200 Subject: [PATCH 08/16] Fix precipitation calculation for hourly forecast (#52676) It seems that hourly forecast have precipitation in 3h blocks. --- .../components/openweathermap/weather_update_coordinator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index 98f39290d22..73edc9fae75 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -191,6 +191,8 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): """Get rain data from weather data.""" if "all" in rain: return round(rain["all"], 2) + if "3h" in rain: + return round(rain["3h"], 2) if "1h" in rain: return round(rain["1h"], 2) return 0 @@ -201,6 +203,8 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): if snow: if "all" in snow: return round(snow["all"], 2) + if "3h" in snow: + return round(snow["3h"], 2) if "1h" in snow: return round(snow["1h"], 2) return 0 From 286c068f6fefe8a6a814835beaa7f2ec1a6eaa6b Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Thu, 8 Jul 2021 00:20:27 -0700 Subject: [PATCH 09/16] Move recorder.py import to runtime (#52682) --- homeassistant/components/stream/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index d8e4cb2cdb2..c7ca853c20c 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -43,7 +43,6 @@ from .const import ( ) from .core import PROVIDERS, IdleTimer, StreamOutput from .hls import async_setup_hls -from .recorder import RecorderOutput _LOGGER = logging.getLogger(__name__) @@ -265,6 +264,10 @@ class Stream: ) -> None: """Make a .mp4 recording from a provided stream.""" + # Keep import here so that we can import stream integration without installing reqs + # pylint: disable=import-outside-toplevel + from .recorder import RecorderOutput + # Check for file access if not self.hass.config.is_allowed_path(video_path): raise HomeAssistantError(f"Can't write {video_path}, no access to path!") From 5666e8b15510d4a53c624998785c21f065a8414a Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 7 Jul 2021 18:45:39 -0500 Subject: [PATCH 10/16] Bump simplisafe-python to 11.0.1 (#52684) --- homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index eff37bf1548..02713b106bd 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,7 +3,7 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==11.0.0"], + "requirements": ["simplisafe-python==11.0.1"], "codeowners": ["@bachya"], "iot_class": "cloud_polling" } diff --git a/requirements_all.txt b/requirements_all.txt index 5efcc2d3d3a..e0c259a6c3e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2102,7 +2102,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==11.0.0 +simplisafe-python==11.0.1 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f084564b39e..f69139d0148 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1148,7 +1148,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==11.0.0 +simplisafe-python==11.0.1 # homeassistant.components.slack slackclient==2.5.0 From 578ba6b065ce99551211f39e7919ea7f553a2d6e Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Thu, 8 Jul 2021 00:11:56 -0700 Subject: [PATCH 11/16] pyWeMo version bump (0.6.5) (#52701) --- homeassistant/components/wemo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json index bd153294282..3d051fcc6dc 100644 --- a/homeassistant/components/wemo/manifest.json +++ b/homeassistant/components/wemo/manifest.json @@ -3,7 +3,7 @@ "name": "Belkin WeMo", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wemo", - "requirements": ["pywemo==0.6.3"], + "requirements": ["pywemo==0.6.5"], "ssdp": [ { "manufacturer": "Belkin International Inc." diff --git a/requirements_all.txt b/requirements_all.txt index e0c259a6c3e..0c4bc904edf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1966,7 +1966,7 @@ pyvolumio==0.1.3 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==0.6.3 +pywemo==0.6.5 # homeassistant.components.wilight pywilight==0.0.70 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f69139d0148..b1bda645531 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1084,7 +1084,7 @@ pyvolumio==0.1.3 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==0.6.3 +pywemo==0.6.5 # homeassistant.components.wilight pywilight==0.0.70 From a4c563106deff7d1f7fb1040d42318789fc12d2a Mon Sep 17 00:00:00 2001 From: Jon Gilmore <7232986+JonGilmore@users.noreply.github.com> Date: Thu, 8 Jul 2021 08:18:08 -0500 Subject: [PATCH 12/16] Bump pylutron to 0.2.8 fixing python 3.9 incompatibility (#52702) --- homeassistant/components/lutron/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lutron/manifest.json b/homeassistant/components/lutron/manifest.json index db1c9090ce8..83c4ee72345 100644 --- a/homeassistant/components/lutron/manifest.json +++ b/homeassistant/components/lutron/manifest.json @@ -2,7 +2,7 @@ "domain": "lutron", "name": "Lutron", "documentation": "https://www.home-assistant.io/integrations/lutron", - "requirements": ["pylutron==0.2.7"], + "requirements": ["pylutron==0.2.8"], "codeowners": ["@JonGilmore"], "iot_class": "local_polling" } diff --git a/requirements_all.txt b/requirements_all.txt index 0c4bc904edf..89110d15ea2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1562,7 +1562,7 @@ pyloopenergy==0.2.1 pylutron-caseta==0.10.0 # homeassistant.components.lutron -pylutron==0.2.7 +pylutron==0.2.8 # homeassistant.components.mailgun pymailgunner==1.4 From 3b5c6039bb452b0abd60f98c3d1304090cd3acb8 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 8 Jul 2021 11:58:51 +0200 Subject: [PATCH 13/16] Add check for _client existence in modbus (#52719) --- .coveragerc | 1 + homeassistant/components/modbus/modbus.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.coveragerc b/.coveragerc index 7c741dc26cd..e05d97b66fb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -634,6 +634,7 @@ omit = homeassistant/components/mjpeg/camera.py homeassistant/components/mochad/* homeassistant/components/modbus/climate.py + homeassistant/components/modbus/modbus.py homeassistant/components/modem_callerid/sensor.py homeassistant/components/motion_blinds/__init__.py homeassistant/components/motion_blinds/const.py diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py index 2e5892dbf1d..0826f4d5794 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -310,6 +310,8 @@ class ModbusHub: """Convert async to sync pymodbus call.""" if self._config_delay: return None + if not self._client: + return None if not self._client.is_socket_open(): return None async with self._lock: From 15976555eb549d153a1ef338ef993eb7e4dd2ced Mon Sep 17 00:00:00 2001 From: Tom Matheussen Date: Thu, 8 Jul 2021 15:05:43 +0200 Subject: [PATCH 14/16] Fix KNX Fan features (#52732) * Fan entity should return support features * Revert "Fan entity should return support features" This reverts commit 3ad0e87708fbf1847aaa26e3bc76fcac365a1640. * Restore supported_features for KNX fan --- homeassistant/components/knx/fan.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py index f787795e1e8..21ede700fdd 100644 --- a/homeassistant/components/knx/fan.py +++ b/homeassistant/components/knx/fan.py @@ -66,9 +66,6 @@ class KNXFan(KnxEntity, FanEntity): # FanSpeedMode.STEP if max_step is set self._step_range: tuple[int, int] | None = (1, max_step) if max_step else None - self._attr_supported_features = SUPPORT_SET_SPEED - if self._device.supports_oscillation: - self._attr_supported_features |= SUPPORT_OSCILLATE self._attr_unique_id = str(self._device.speed.group_address) async def async_set_percentage(self, percentage: int) -> None: @@ -79,6 +76,16 @@ class KNXFan(KnxEntity, FanEntity): else: await self._device.set_speed(percentage) + @property + def supported_features(self) -> int: + """Flag supported features.""" + flags = SUPPORT_SET_SPEED + + if self._device.supports_oscillation: + flags |= SUPPORT_OSCILLATE + + return flags + @property def percentage(self) -> int | None: """Return the current speed as a percentage.""" From 594584645d5a4e60e8ccf3f29fcbabe96b1d5c24 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 9 Jul 2021 00:44:49 +1200 Subject: [PATCH 15/16] Esphome fix camera image (#52738) --- homeassistant/components/esphome/camera.py | 4 ++-- homeassistant/components/esphome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/esphome/camera.py b/homeassistant/components/esphome/camera.py index 7afd89bf9be..f047d5c1bdd 100644 --- a/homeassistant/components/esphome/camera.py +++ b/homeassistant/components/esphome/camera.py @@ -77,7 +77,7 @@ class EsphomeCamera(Camera, EsphomeBaseEntity): await self._image_cond.wait() if not self.available: return None - return self._state.image[:] + return self._state.data[:] async def _async_camera_stream_image(self) -> bytes | None: """Return a single camera image in a stream.""" @@ -88,7 +88,7 @@ class EsphomeCamera(Camera, EsphomeBaseEntity): await self._image_cond.wait() if not self.available: return None - return self._state.image[:] + return self._state.data[:] async def handle_async_mjpeg_stream(self, request): """Serve an HTTP MJPEG stream from the camera.""" diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index e69299a4a43..e48cd4847c8 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -3,7 +3,7 @@ "name": "ESPHome", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/esphome", - "requirements": ["aioesphomeapi==4.0.1"], + "requirements": ["aioesphomeapi==5.0.0"], "zeroconf": ["_esphomelib._tcp.local."], "codeowners": ["@OttoWinter", "@jesserockz"], "after_dependencies": ["zeroconf", "tag"], diff --git a/requirements_all.txt b/requirements_all.txt index 89110d15ea2..9d2ad7b154b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -160,7 +160,7 @@ aioeafm==0.1.2 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==4.0.1 +aioesphomeapi==5.0.0 # homeassistant.components.flo aioflo==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b1bda645531..3bad09bcaf5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -100,7 +100,7 @@ aioeafm==0.1.2 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==4.0.1 +aioesphomeapi==5.0.0 # homeassistant.components.flo aioflo==0.4.1 From 5b2164b3a2e04993fd507c7e85269eb7ab0fd93b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 8 Jul 2021 15:25:03 +0200 Subject: [PATCH 16/16] Bumped version to 2021.7.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 27eafd0287e..373f382a06e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -5,7 +5,7 @@ from typing import Final MAJOR_VERSION: Final = 2021 MINOR_VERSION: Final = 7 -PATCH_VERSION: Final = "0" +PATCH_VERSION: Final = "1" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)