From bf5633fa433041ff4ba1dfdf02e8bb6890c1e028 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 5 Jul 2022 18:54:03 +0200 Subject: [PATCH 001/104] Bump deCONZ dependency to v96 (#74460) --- homeassistant/components/deconz/light.py | 6 +----- homeassistant/components/deconz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 669800e2662..7f3a47d719d 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -113,11 +113,7 @@ async def async_setup_entry( first = True for light_id in group.lights: - if ( - (light := gateway.api.lights.lights.get(light_id)) - and light.ZHATYPE == Light.ZHATYPE - and light.reachable - ): + if (light := gateway.api.lights.lights.get(light_id)) and light.reachable: group.update_color_state(light, update_all_attributes=first) first = False diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index 09dcc190a4f..c19d75ec054 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -3,7 +3,7 @@ "name": "deCONZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", - "requirements": ["pydeconz==95"], + "requirements": ["pydeconz==96"], "ssdp": [ { "manufacturer": "Royal Philips Electronics", diff --git a/requirements_all.txt b/requirements_all.txt index 99be85cc418..c561730dda7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1444,7 +1444,7 @@ pydaikin==2.7.0 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==95 +pydeconz==96 # homeassistant.components.delijn pydelijn==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b6a493d37f9..7ae59c713b3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -974,7 +974,7 @@ pycoolmasternet-async==0.1.2 pydaikin==2.7.0 # homeassistant.components.deconz -pydeconz==95 +pydeconz==96 # homeassistant.components.dexcom pydexcom==0.2.3 From fa220c5c25341de0c110c51489a91ce8e84a8871 Mon Sep 17 00:00:00 2001 From: c-soft Date: Thu, 7 Jul 2022 02:02:08 +0200 Subject: [PATCH 002/104] Bump satel_integra to 0.3.7 to fix compat with python 3.10 (#74543) --- homeassistant/components/satel_integra/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/satel_integra/manifest.json b/homeassistant/components/satel_integra/manifest.json index 6c4a391698b..262507be6bb 100644 --- a/homeassistant/components/satel_integra/manifest.json +++ b/homeassistant/components/satel_integra/manifest.json @@ -2,7 +2,7 @@ "domain": "satel_integra", "name": "Satel Integra", "documentation": "https://www.home-assistant.io/integrations/satel_integra", - "requirements": ["satel_integra==0.3.4"], + "requirements": ["satel_integra==0.3.7"], "codeowners": [], "iot_class": "local_push", "loggers": ["satel_integra"] diff --git a/requirements_all.txt b/requirements_all.txt index c561730dda7..f5d2fec04a1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2125,7 +2125,7 @@ samsungctl[websocket]==0.7.1 samsungtvws[async,encrypted]==2.5.0 # homeassistant.components.satel_integra -satel_integra==0.3.4 +satel_integra==0.3.7 # homeassistant.components.dhcp scapy==2.4.5 From 414ea6fd8cbd06b6b2a2d18edaa8976780a3952c Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Thu, 7 Jul 2022 08:40:10 +0200 Subject: [PATCH 003/104] fjaraskupan: Make sure we stop bleak on home assistant stop (#74545) * Make sure we stop bleak on home assistant stop * Fix typing Co-authored-by: Martin Hjelmare --- homeassistant/components/fjaraskupan/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/fjaraskupan/__init__.py b/homeassistant/components/fjaraskupan/__init__.py index 4c4f19403a6..85e95db5513 100644 --- a/homeassistant/components/fjaraskupan/__init__.py +++ b/homeassistant/components/fjaraskupan/__init__.py @@ -12,8 +12,8 @@ from bleak.backends.scanner import AdvertisementData from fjaraskupan import Device, State, device_filter from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant, callback +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -131,6 +131,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: scanner.register_detection_callback(detection_callback) await scanner.start() + async def on_hass_stop(event: Event) -> None: + await scanner.stop() + + entry.async_on_unload( + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop) + ) + hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True From f6a23492fa9c276af15def2bb0ef35549c7c7c90 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 7 Jul 2022 02:43:10 -0500 Subject: [PATCH 004/104] Minimize Sonos `media_player.unjoin` timeout (#74549) --- homeassistant/components/sonos/media_player.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index c68110d9763..470386c341d 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -69,6 +69,7 @@ from .speaker import SonosMedia, SonosSpeaker _LOGGER = logging.getLogger(__name__) LONG_SERVICE_TIMEOUT = 30.0 +UNJOIN_SERVICE_TIMEOUT = 0.1 VOLUME_INCREMENT = 2 REPEAT_TO_SONOS = { @@ -775,7 +776,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): async def async_unjoin_player(self): """Remove this player from any group. - Coalesces all calls within 0.5s to allow use of SonosSpeaker.unjoin_multi() + Coalesces all calls within UNJOIN_SERVICE_TIMEOUT to allow use of SonosSpeaker.unjoin_multi() which optimizes the order in which speakers are removed from their groups. Removing coordinators last better preserves playqueues on the speakers. """ @@ -785,6 +786,9 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): async def async_process_unjoin(now: datetime.datetime) -> None: """Process the unjoin with all remove requests within the coalescing period.""" unjoin_data = sonos_data.unjoin_data.pop(household_id) + _LOGGER.debug( + "Processing unjoins for %s", [x.zone_name for x in unjoin_data.speakers] + ) await SonosSpeaker.unjoin_multi(self.hass, unjoin_data.speakers) unjoin_data.event.set() @@ -794,6 +798,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): unjoin_data = sonos_data.unjoin_data[household_id] = UnjoinData( speakers=[self.speaker] ) - async_call_later(self.hass, 0.5, async_process_unjoin) + async_call_later(self.hass, UNJOIN_SERVICE_TIMEOUT, async_process_unjoin) + _LOGGER.debug("Requesting unjoin for %s", self.speaker.zone_name) await unjoin_data.event.wait() From 606a1b57e6b73430437170a816e4024db74da7e6 Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Thu, 7 Jul 2022 03:39:37 -0400 Subject: [PATCH 005/104] Bump aioskybell to 22.7.0 (#74559) --- homeassistant/components/skybell/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/skybell/manifest.json b/homeassistant/components/skybell/manifest.json index c0e66aa5462..bfef4bc3422 100644 --- a/homeassistant/components/skybell/manifest.json +++ b/homeassistant/components/skybell/manifest.json @@ -3,7 +3,7 @@ "name": "SkyBell", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/skybell", - "requirements": ["aioskybell==22.6.1"], + "requirements": ["aioskybell==22.7.0"], "dependencies": ["ffmpeg"], "codeowners": ["@tkdrob"], "iot_class": "cloud_polling", diff --git a/requirements_all.txt b/requirements_all.txt index f5d2fec04a1..df719620b2a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -247,7 +247,7 @@ aiosenz==1.0.0 aioshelly==2.0.0 # homeassistant.components.skybell -aioskybell==22.6.1 +aioskybell==22.7.0 # homeassistant.components.slimproto aioslimproto==2.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7ae59c713b3..8d25e383315 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -216,7 +216,7 @@ aiosenz==1.0.0 aioshelly==2.0.0 # homeassistant.components.skybell -aioskybell==22.6.1 +aioskybell==22.7.0 # homeassistant.components.slimproto aioslimproto==2.1.1 From c9a31aab153c0d274ae1c2f47e45f447069b1068 Mon Sep 17 00:00:00 2001 From: ufodone <35497351+ufodone@users.noreply.github.com> Date: Thu, 7 Jul 2022 00:33:32 -0700 Subject: [PATCH 006/104] Bump pyenvisalink version to 4.6 (#74561) --- homeassistant/components/envisalink/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/envisalink/manifest.json b/homeassistant/components/envisalink/manifest.json index 44a40991a37..8d0fc735b90 100644 --- a/homeassistant/components/envisalink/manifest.json +++ b/homeassistant/components/envisalink/manifest.json @@ -2,7 +2,7 @@ "domain": "envisalink", "name": "Envisalink", "documentation": "https://www.home-assistant.io/integrations/envisalink", - "requirements": ["pyenvisalink==4.5"], + "requirements": ["pyenvisalink==4.6"], "codeowners": ["@ufodone"], "iot_class": "local_push", "loggers": ["pyenvisalink"] diff --git a/requirements_all.txt b/requirements_all.txt index df719620b2a..3ec631efb67 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1477,7 +1477,7 @@ pyeight==0.3.0 pyemby==1.8 # homeassistant.components.envisalink -pyenvisalink==4.5 +pyenvisalink==4.6 # homeassistant.components.ephember pyephember==0.3.1 From 174837dddfc3c8700f441e95241233a60351092e Mon Sep 17 00:00:00 2001 From: Glenn Waters Date: Thu, 7 Jul 2022 13:07:05 -0400 Subject: [PATCH 007/104] ElkM1 bump lib to support Python 3.10 SSL (#74569) Co-authored-by: J. Nick Koston --- homeassistant/components/elkm1/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/elkm1/manifest.json b/homeassistant/components/elkm1/manifest.json index 13f8ba8401f..7043fdfcb9d 100644 --- a/homeassistant/components/elkm1/manifest.json +++ b/homeassistant/components/elkm1/manifest.json @@ -2,7 +2,7 @@ "domain": "elkm1", "name": "Elk-M1 Control", "documentation": "https://www.home-assistant.io/integrations/elkm1", - "requirements": ["elkm1-lib==2.0.0"], + "requirements": ["elkm1-lib==2.0.2"], "dhcp": [{ "registered_devices": true }, { "macaddress": "00409D*" }], "codeowners": ["@gwww", "@bdraco"], "dependencies": ["network"], diff --git a/requirements_all.txt b/requirements_all.txt index 3ec631efb67..8844b7e2464 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -586,7 +586,7 @@ elgato==3.0.0 eliqonline==1.2.2 # homeassistant.components.elkm1 -elkm1-lib==2.0.0 +elkm1-lib==2.0.2 # homeassistant.components.elmax elmax_api==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8d25e383315..737550785d9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -426,7 +426,7 @@ eagle100==0.1.1 elgato==3.0.0 # homeassistant.components.elkm1 -elkm1-lib==2.0.0 +elkm1-lib==2.0.2 # homeassistant.components.elmax elmax_api==0.0.2 From fdc1b6ea9e8bd7decb6b23b8ed1813a23509f7ec Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 7 Jul 2022 09:03:43 +0200 Subject: [PATCH 008/104] Fix openweathermap hourly forecast (#74578) --- homeassistant/components/openweathermap/weather.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/openweathermap/weather.py b/homeassistant/components/openweathermap/weather.py index ea439a35586..9948ecc0428 100644 --- a/homeassistant/components/openweathermap/weather.py +++ b/homeassistant/components/openweathermap/weather.py @@ -143,7 +143,11 @@ class OpenWeatherMapWeather(WeatherEntity): """Return the forecast array.""" api_forecasts = self._weather_coordinator.data[ATTR_API_FORECAST] forecasts = [ - {ha_key: forecast[api_key] for api_key, ha_key in FORECAST_MAP.items()} + { + ha_key: forecast[api_key] + for api_key, ha_key in FORECAST_MAP.items() + if api_key in forecast + } for forecast in api_forecasts ] return cast(list[Forecast], forecasts) From 9514b0f1000079c4ea2181b14324d77c3455b4ea Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 7 Jul 2022 12:27:25 +0200 Subject: [PATCH 009/104] Fix mix of aiohttp and requests in Bloomsky (#74598) --- homeassistant/components/bloomsky/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/bloomsky/__init__.py b/homeassistant/components/bloomsky/__init__.py index 8311f6cbd96..ed2ce1ebc70 100644 --- a/homeassistant/components/bloomsky/__init__.py +++ b/homeassistant/components/bloomsky/__init__.py @@ -3,7 +3,6 @@ from datetime import timedelta from http import HTTPStatus import logging -from aiohttp.hdrs import AUTHORIZATION import requests import voluptuous as vol @@ -67,7 +66,7 @@ class BloomSky: _LOGGER.debug("Fetching BloomSky update") response = requests.get( f"{self.API_URL}?{self._endpoint_argument}", - headers={AUTHORIZATION: self._api_key}, + headers={"Authorization": self._api_key}, timeout=10, ) if response.status_code == HTTPStatus.UNAUTHORIZED: From 5a7e506c3844774e7fbaaf8b6e60be404c75243f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 7 Jul 2022 16:02:36 +0200 Subject: [PATCH 010/104] Update aiokafka to 0.7.2 (#74601) --- homeassistant/components/apache_kafka/__init__.py | 1 - homeassistant/components/apache_kafka/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/apache_kafka/__init__.py b/homeassistant/components/apache_kafka/__init__.py index 1b293bd2c04..38a70b450ab 100644 --- a/homeassistant/components/apache_kafka/__init__.py +++ b/homeassistant/components/apache_kafka/__init__.py @@ -102,7 +102,6 @@ class KafkaManager: self._hass = hass ssl_context = ssl_util.client_context() self._producer = AIOKafkaProducer( - loop=hass.loop, bootstrap_servers=f"{ip_address}:{port}", compression_type="gzip", security_protocol=security_protocol, diff --git a/homeassistant/components/apache_kafka/manifest.json b/homeassistant/components/apache_kafka/manifest.json index 3b290146a09..3fa1f70c57b 100644 --- a/homeassistant/components/apache_kafka/manifest.json +++ b/homeassistant/components/apache_kafka/manifest.json @@ -2,7 +2,7 @@ "domain": "apache_kafka", "name": "Apache Kafka", "documentation": "https://www.home-assistant.io/integrations/apache_kafka", - "requirements": ["aiokafka==0.6.0"], + "requirements": ["aiokafka==0.7.2"], "codeowners": ["@bachya"], "iot_class": "local_push", "loggers": ["aiokafka", "kafka_python"] diff --git a/requirements_all.txt b/requirements_all.txt index 8844b7e2464..ea5bd8cca1e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -181,7 +181,7 @@ aiohue==4.4.2 aioimaplib==1.0.0 # homeassistant.components.apache_kafka -aiokafka==0.6.0 +aiokafka==0.7.2 # homeassistant.components.kef aiokef==0.2.16 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 737550785d9..3cbc10480bb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -162,7 +162,7 @@ aiohttp_cors==0.7.0 aiohue==4.4.2 # homeassistant.components.apache_kafka -aiokafka==0.6.0 +aiokafka==0.7.2 # homeassistant.components.lookin aiolookin==0.1.1 From ed6a65156c50a195bb0b7caceab3f7b227f0f116 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 7 Jul 2022 14:51:16 +0200 Subject: [PATCH 011/104] Poll cast groups when media player is added or reconnected (#74610) --- homeassistant/components/cast/helpers.py | 2 +- homeassistant/components/cast/media_player.py | 13 ++++ tests/components/cast/test_media_player.py | 75 +++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index d7419f69563..48f57c39bd5 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -37,7 +37,7 @@ class ChromecastInfo: @property def friendly_name(self) -> str: - """Return the UUID.""" + """Return the Friendly Name.""" return self.cast_info.friendly_name @property diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 958c53ae394..c8a6a82571e 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -441,6 +441,19 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): connection_status.status, ) self._attr_available = new_available + if new_available and not self._cast_info.is_audio_group: + # Poll current group status + for group_uuid in self.mz_mgr.get_multizone_memberships( + self._cast_info.uuid + ): + group_media_controller = self.mz_mgr.get_multizone_mediacontroller( + group_uuid + ) + if not group_media_controller: + continue + self.multizone_new_media_status( + group_uuid, group_media_controller.status + ) self.schedule_update_ha_state() def multizone_new_media_status(self, group_uuid, media_status): diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 00626cc8c16..ed575c0fa22 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -731,6 +731,20 @@ async def test_entity_availability(hass: HomeAssistant): state = hass.states.get(entity_id) assert state.state == "off" + connection_status = MagicMock() + connection_status.status = "LOST" + conn_status_cb(connection_status) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == "unavailable" + + connection_status = MagicMock() + connection_status.status = "CONNECTED" + conn_status_cb(connection_status) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == "off" + connection_status = MagicMock() connection_status.status = "DISCONNECTED" conn_status_cb(connection_status) @@ -738,6 +752,14 @@ async def test_entity_availability(hass: HomeAssistant): state = hass.states.get(entity_id) assert state.state == "unavailable" + # Can't reconnect after receiving DISCONNECTED + connection_status = MagicMock() + connection_status.status = "CONNECTED" + conn_status_cb(connection_status) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == "unavailable" + @pytest.mark.parametrize("port,entry_type", ((8009, None), (12345, None))) async def test_device_registry(hass: HomeAssistant, hass_ws_client, port, entry_type): @@ -1675,6 +1697,59 @@ async def test_group_media_states(hass, mz_mock): assert state.state == "playing" +async def test_group_media_states_early(hass, mz_mock): + """Test media states are read from group if entity has no state. + + This tests case asserts group state is polled when the player is created. + """ + entity_id = "media_player.speaker" + reg = er.async_get(hass) + + info = get_fake_chromecast_info() + + mz_mock.get_multizone_memberships = MagicMock(return_value=[str(FakeGroupUUID)]) + mz_mock.get_multizone_mediacontroller = MagicMock( + return_value=MagicMock(status=MagicMock(images=None, player_state="BUFFERING")) + ) + + chromecast, _ = await async_setup_media_player_cast(hass, info) + _, conn_status_cb, _, _ = get_status_callbacks(chromecast, mz_mock) + + state = hass.states.get(entity_id) + assert state is not None + assert state.name == "Speaker" + assert state.state == "unavailable" + assert entity_id == reg.async_get_entity_id("media_player", "cast", str(info.uuid)) + + # Check group state is polled when player is first created + connection_status = MagicMock() + connection_status.status = "CONNECTED" + conn_status_cb(connection_status) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == "buffering" + + connection_status = MagicMock() + connection_status.status = "LOST" + conn_status_cb(connection_status) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == "unavailable" + + # Check group state is polled when player reconnects + mz_mock.get_multizone_mediacontroller = MagicMock( + return_value=MagicMock(status=MagicMock(images=None, player_state="PLAYING")) + ) + + connection_status = MagicMock() + connection_status.status = "CONNECTED" + conn_status_cb(connection_status) + await hass.async_block_till_done() + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == "playing" + + async def test_group_media_control(hass, mz_mock, quick_play_mock): """Test media controls are handled by group if entity has no state.""" entity_id = "media_player.speaker" From 8b01c132c1c441c615ad8ddec11d5e15bab74720 Mon Sep 17 00:00:00 2001 From: Arne Mauer Date: Thu, 7 Jul 2022 20:28:33 +0200 Subject: [PATCH 012/104] Ikea Starkvind support all models (#74615) * Add Particulate Matter 2.5 of ZCL concentration clusters to ZHA component * Fixed black and flake8 test * New sensors and manufacturer cluster to support IKEA STARKVIND (with quirk) * Fix multi_match for FilterLifeTime, device_run_time, filter_run_time sensors for Ikea starkvind * Remove model match because sensors are matched with manufacturer channel * Update manufacturerspecific.py * Update number.py --- homeassistant/components/zha/binary_sensor.py | 2 +- homeassistant/components/zha/number.py | 4 +--- homeassistant/components/zha/sensor.py | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 709515d7ca2..23d26e4b13f 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -188,7 +188,7 @@ class FrostLock(BinarySensor, id_suffix="frost_lock"): _attr_device_class: BinarySensorDeviceClass = BinarySensorDeviceClass.LOCK -@MULTI_MATCH(channel_names="ikea_airpurifier", models={"STARKVIND Air purifier"}) +@MULTI_MATCH(channel_names="ikea_airpurifier") class ReplaceFilter(BinarySensor, id_suffix="replace_filter"): """ZHA BinarySensor.""" diff --git a/homeassistant/components/zha/number.py b/homeassistant/components/zha/number.py index e1268e29190..36fc5267bd9 100644 --- a/homeassistant/components/zha/number.py +++ b/homeassistant/components/zha/number.py @@ -525,9 +525,7 @@ class TimerDurationMinutes(ZHANumberConfigurationEntity, id_suffix="timer_durati _zcl_attribute: str = "timer_duration" -@CONFIG_DIAGNOSTIC_MATCH( - channel_names="ikea_airpurifier", models={"STARKVIND Air purifier"} -) +@CONFIG_DIAGNOSTIC_MATCH(channel_names="ikea_airpurifier") class FilterLifeTime(ZHANumberConfigurationEntity, id_suffix="filter_life_time"): """Representation of a ZHA timer duration configuration entity.""" diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 4a4700b3c4c..c08c3f21201 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -810,7 +810,7 @@ class TimeLeft(Sensor, id_suffix="time_left"): _unit = TIME_MINUTES -@MULTI_MATCH(channel_names="ikea_airpurifier", models={"STARKVIND Air purifier"}) +@MULTI_MATCH(channel_names="ikea_airpurifier") class IkeaDeviceRunTime(Sensor, id_suffix="device_run_time"): """Sensor that displays device run time (in minutes).""" @@ -820,7 +820,7 @@ class IkeaDeviceRunTime(Sensor, id_suffix="device_run_time"): _unit = TIME_MINUTES -@MULTI_MATCH(channel_names="ikea_airpurifier", models={"STARKVIND Air purifier"}) +@MULTI_MATCH(channel_names="ikea_airpurifier") class IkeaFilterRunTime(Sensor, id_suffix="filter_run_time"): """Sensor that displays run time of the current filter (in minutes).""" From cd7f2eb73e97dd69c160bd28ae47629c3c2f706b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 7 Jul 2022 16:19:56 +0200 Subject: [PATCH 013/104] Update frontend to 20220707.0 (#74625) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 85ead380485..5c7fd1a20be 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20220706.0"], + "requirements": ["home-assistant-frontend==20220707.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c291d969219..de9df889eba 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ ciso8601==2.2.0 cryptography==36.0.2 fnvhash==0.1.0 hass-nabucasa==0.54.0 -home-assistant-frontend==20220706.0 +home-assistant-frontend==20220707.0 httpx==0.23.0 ifaddr==0.1.7 jinja2==3.1.2 diff --git a/requirements_all.txt b/requirements_all.txt index ea5bd8cca1e..8578c571fbb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -828,7 +828,7 @@ hole==0.7.0 holidays==0.14.2 # homeassistant.components.frontend -home-assistant-frontend==20220706.0 +home-assistant-frontend==20220707.0 # homeassistant.components.home_connect homeconnect==0.7.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3cbc10480bb..89b53d2b583 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -595,7 +595,7 @@ hole==0.7.0 holidays==0.14.2 # homeassistant.components.frontend -home-assistant-frontend==20220706.0 +home-assistant-frontend==20220707.0 # homeassistant.components.home_connect homeconnect==0.7.1 From 0da09ba47ec59afc814d5853a2c3c140f17ddec5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 7 Jul 2022 16:59:49 +0200 Subject: [PATCH 014/104] Fix mix of aiohttp and requests in ZAMG (#74628) --- homeassistant/components/zamg/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/zamg/sensor.py b/homeassistant/components/zamg/sensor.py index 87a1175b7cd..c32aa942625 100644 --- a/homeassistant/components/zamg/sensor.py +++ b/homeassistant/components/zamg/sensor.py @@ -10,7 +10,6 @@ import logging import os from typing import Union -from aiohttp.hdrs import USER_AGENT import requests import voluptuous as vol @@ -275,7 +274,7 @@ class ZamgData: """The class for handling the data retrieval.""" API_URL = "http://www.zamg.ac.at/ogd/" - API_HEADERS = {USER_AGENT: f"home-assistant.zamg/ {__version__}"} + API_HEADERS = {"User-Agent": f"home-assistant.zamg/ {__version__}"} def __init__(self, station_id): """Initialize the probe.""" From bac9af50df9004cc7bb6c16802722cb697889068 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Thu, 7 Jul 2022 20:27:48 +0200 Subject: [PATCH 015/104] Fix smart energy polling for Tuya plugs (#74640) * Add PolledSmartEnergySummation to poll summation_delivered for some ZHA plugs * Remove PolledSmartEnergyMetering, add stop_on_match_group to summation sensors --- homeassistant/components/zha/sensor.py | 37 ++++++++++++++------------ 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index c08c3f21201..513ba5510b5 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -472,25 +472,8 @@ class SmartEnergyMetering(Sensor): @MULTI_MATCH( channel_names=CHANNEL_SMARTENERGY_METERING, - models={"TS011F"}, stop_on_match_group=CHANNEL_SMARTENERGY_METERING, ) -class PolledSmartEnergyMetering(SmartEnergyMetering): - """Polled metering sensor.""" - - @property - def should_poll(self) -> bool: - """Poll the entity for current state.""" - return True - - async def async_update(self) -> None: - """Retrieve latest state.""" - if not self.available: - return - await self._channel.async_force_update() - - -@MULTI_MATCH(channel_names=CHANNEL_SMARTENERGY_METERING) class SmartEnergySummation(SmartEnergyMetering, id_suffix="summation_delivered"): """Smart Energy Metering summation sensor.""" @@ -523,6 +506,26 @@ class SmartEnergySummation(SmartEnergyMetering, id_suffix="summation_delivered") return round(cooked, 3) +@MULTI_MATCH( + channel_names=CHANNEL_SMARTENERGY_METERING, + models={"TS011F"}, + stop_on_match_group=CHANNEL_SMARTENERGY_METERING, +) +class PolledSmartEnergySummation(SmartEnergySummation): + """Polled Smart Energy Metering summation sensor.""" + + @property + def should_poll(self) -> bool: + """Poll the entity for current state.""" + return True + + async def async_update(self) -> None: + """Retrieve latest state.""" + if not self.available: + return + await self._channel.async_force_update() + + @MULTI_MATCH(channel_names=CHANNEL_PRESSURE) class Pressure(Sensor): """Pressure sensor.""" From 937d0d731d03348696046a9783524ad5959dcb31 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Jul 2022 12:46:19 -0500 Subject: [PATCH 016/104] Fix exception in doorbird logbook during startup (#74649) * Fix exception in doorbird logbook during startup Fixes ``` 2022-07-07 16:50:33.203 ERROR (MainThread) [homeassistant.helpers.integration_platform] Error processing platform doorbird.logbook Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/helpers/integration_platform.py", line 51, in _async_process_single_integration_platform_component await integration_platform.process_platform(hass, component_name, platform) File "/usr/src/homeassistant/homeassistant/components/logbook/__init__.py", line 159, in _process_logbook_platform platform.async_describe_events(hass, _async_describe_event) File "/usr/src/homeassistant/homeassistant/components/doorbird/logbook.py", line 43, in async_describe_events door_station = data[DOOR_STATION] KeyError: door_station ``` * py39 --- homeassistant/components/doorbird/logbook.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/doorbird/logbook.py b/homeassistant/components/doorbird/logbook.py index 110d3f22fbf..3b1563c2880 100644 --- a/homeassistant/components/doorbird/logbook.py +++ b/homeassistant/components/doorbird/logbook.py @@ -1,4 +1,7 @@ """Describe logbook events.""" +from __future__ import annotations + +from typing import Any from homeassistant.components.logbook.const import ( LOGBOOK_ENTRY_ENTITY_ID, @@ -28,12 +31,13 @@ def async_describe_events(hass, async_describe_event): ].get(doorbird_event, event.data.get(ATTR_ENTITY_ID)), } - domain_data = hass.data[DOMAIN] + domain_data: dict[str, Any] = hass.data[DOMAIN] - for config_entry_id in domain_data: - door_station = domain_data[config_entry_id][DOOR_STATION] - - for event in door_station.doorstation_events: + for data in domain_data.values(): + if DOOR_STATION not in data: + # We need to skip door_station_event_entity_ids + continue + for event in data[DOOR_STATION].doorstation_events: async_describe_event( DOMAIN, f"{DOMAIN}_{event}", async_describe_logbook_event ) From b73cc32ffbf6f572947b4221a3b66773c3efe81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Mrozek?= Date: Fri, 8 Jul 2022 00:46:23 +0200 Subject: [PATCH 017/104] Update kaiterra-async-client to 1.0.0 (#74677) --- homeassistant/components/kaiterra/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/kaiterra/manifest.json b/homeassistant/components/kaiterra/manifest.json index 9f2a4c0013f..94b861524eb 100644 --- a/homeassistant/components/kaiterra/manifest.json +++ b/homeassistant/components/kaiterra/manifest.json @@ -2,7 +2,7 @@ "domain": "kaiterra", "name": "Kaiterra", "documentation": "https://www.home-assistant.io/integrations/kaiterra", - "requirements": ["kaiterra-async-client==0.0.2"], + "requirements": ["kaiterra-async-client==1.0.0"], "codeowners": ["@Michsior14"], "iot_class": "cloud_polling", "loggers": ["kaiterra_async_client"] diff --git a/requirements_all.txt b/requirements_all.txt index 8578c571fbb..4a35ee93e08 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -912,7 +912,7 @@ jellyfin-apiclient-python==1.8.1 jsonpath==0.82 # homeassistant.components.kaiterra -kaiterra-async-client==0.0.2 +kaiterra-async-client==1.0.0 # homeassistant.components.keba keba-kecontact==1.1.0 From 5018b91f6ce09455f5fd0a071c6cf45ea4a1bf37 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 7 Jul 2022 21:59:59 -0700 Subject: [PATCH 018/104] Bumped version to 2022.7.1 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index c2ee7de691f..6902a728ea7 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 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, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index b4994d55edb..48e3ea452ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.7.0" +version = "2022.7.1" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 7ffc60fb2c40b4c125f246bc8c74bcb223e8fd4b Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Fri, 8 Jul 2022 15:16:13 +0200 Subject: [PATCH 019/104] Add missing strings for here_travel_time (#74641) * Add missing strings for here_travel_time * script.translations develop * Correct origin_menu option --- homeassistant/components/here_travel_time/strings.json | 7 +++++++ .../components/here_travel_time/translations/en.json | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/homeassistant/components/here_travel_time/strings.json b/homeassistant/components/here_travel_time/strings.json index e4a20a38d6b..dab135efc82 100644 --- a/homeassistant/components/here_travel_time/strings.json +++ b/homeassistant/components/here_travel_time/strings.json @@ -8,6 +8,13 @@ "mode": "Travel Mode" } }, + "origin_menu": { + "title": "Choose Origin", + "menu_options": { + "origin_coordinates": "Using a map location", + "origin_entity": "Using an entity" + } + }, "origin_coordinates": { "title": "Choose Origin", "data": { diff --git a/homeassistant/components/here_travel_time/translations/en.json b/homeassistant/components/here_travel_time/translations/en.json index d4f9984d945..f31d5a3783d 100644 --- a/homeassistant/components/here_travel_time/translations/en.json +++ b/homeassistant/components/here_travel_time/translations/en.json @@ -39,6 +39,13 @@ }, "title": "Choose Origin" }, + "origin_menu": { + "menu_options": { + "origin_coordinates": "Using a map location", + "origin_entity": "Using an entity" + }, + "title": "Choose Origin" + }, "user": { "data": { "api_key": "API Key", From dc33d5db827a9c42b594c136c04a5253231e8065 Mon Sep 17 00:00:00 2001 From: siyuan-nz <91467287+siyuan-nz@users.noreply.github.com> Date: Fri, 8 Jul 2022 19:51:10 +1200 Subject: [PATCH 020/104] Add ssh-rsa as acceptable an host key algorithm (#74684) --- homeassistant/components/unifi_direct/device_tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/unifi_direct/device_tracker.py b/homeassistant/components/unifi_direct/device_tracker.py index f71498faa11..7a81975c0ba 100644 --- a/homeassistant/components/unifi_direct/device_tracker.py +++ b/homeassistant/components/unifi_direct/device_tracker.py @@ -80,7 +80,7 @@ class UnifiDeviceScanner(DeviceScanner): def _connect(self): """Connect to the Unifi AP SSH server.""" - self.ssh = pxssh.pxssh() + self.ssh = pxssh.pxssh(options={"HostKeyAlgorithms": "ssh-rsa"}) try: self.ssh.login( self.host, self.username, password=self.password, port=self.port From 88d723736f3a7430b7d76d0339f36dba0458270b Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Fri, 8 Jul 2022 18:47:59 +0200 Subject: [PATCH 021/104] Fix ZHA group not setting the correct color mode (#74687) * Fix ZHA group not setting the correct color mode * Changed to use _attr_color_mode --- homeassistant/components/zha/light.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 309fdf2699b..e1c85b39d8e 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -141,7 +141,7 @@ class BaseLight(LogMixin, light.LightEntity): self._color_channel = None self._identify_channel = None self._default_transition = None - self._color_mode = ColorMode.UNKNOWN # Set by sub classes + self._attr_color_mode = ColorMode.UNKNOWN # Set by sub classes @property def extra_state_attributes(self) -> dict[str, Any]: @@ -159,11 +159,6 @@ class BaseLight(LogMixin, light.LightEntity): return False return self._state - @property - def color_mode(self): - """Return the color mode of this light.""" - return self._color_mode - @property def brightness(self): """Return the brightness of this light.""" @@ -309,7 +304,7 @@ class BaseLight(LogMixin, light.LightEntity): if isinstance(result, Exception) or result[1] is not Status.SUCCESS: self.debug("turned on: %s", t_log) return - self._color_mode = ColorMode.COLOR_TEMP + self._attr_color_mode = ColorMode.COLOR_TEMP self._color_temp = temperature self._hs_color = None @@ -323,7 +318,7 @@ class BaseLight(LogMixin, light.LightEntity): if isinstance(result, Exception) or result[1] is not Status.SUCCESS: self.debug("turned on: %s", t_log) return - self._color_mode = ColorMode.HS + self._attr_color_mode = ColorMode.HS self._hs_color = hs_color self._color_temp = None @@ -451,13 +446,13 @@ class Light(BaseLight, ZhaEntity): self._attr_supported_color_modes ) if len(self._attr_supported_color_modes) == 1: - self._color_mode = next(iter(self._attr_supported_color_modes)) + self._attr_color_mode = next(iter(self._attr_supported_color_modes)) else: # Light supports color_temp + hs, determine which mode the light is in assert self._color_channel if self._color_channel.color_mode == Color.ColorMode.Color_temperature: - self._color_mode = ColorMode.COLOR_TEMP + self._attr_color_mode = ColorMode.COLOR_TEMP else: - self._color_mode = ColorMode.HS + self._attr_color_mode = ColorMode.HS if self._identify_channel: self._supported_features |= light.LightEntityFeature.FLASH @@ -518,7 +513,7 @@ class Light(BaseLight, ZhaEntity): if "off_brightness" in last_state.attributes: self._off_brightness = last_state.attributes["off_brightness"] if "color_mode" in last_state.attributes: - self._color_mode = ColorMode(last_state.attributes["color_mode"]) + self._attr_color_mode = ColorMode(last_state.attributes["color_mode"]) if "color_temp" in last_state.attributes: self._color_temp = last_state.attributes["color_temp"] if "hs_color" in last_state.attributes: @@ -558,13 +553,13 @@ class Light(BaseLight, ZhaEntity): if (color_mode := results.get("color_mode")) is not None: if color_mode == Color.ColorMode.Color_temperature: - self._color_mode = ColorMode.COLOR_TEMP + self._attr_color_mode = ColorMode.COLOR_TEMP color_temp = results.get("color_temperature") if color_temp is not None and color_mode: self._color_temp = color_temp self._hs_color = None else: - self._color_mode = ColorMode.HS + self._attr_color_mode = ColorMode.HS color_x = results.get("current_x") color_y = results.get("current_y") if color_x is not None and color_y is not None: @@ -650,7 +645,7 @@ class LightGroup(BaseLight, ZhaGroupEntity): CONF_DEFAULT_LIGHT_TRANSITION, 0, ) - self._color_mode = None + self._attr_color_mode = None async def async_added_to_hass(self): """Run when about to be added to hass.""" From e80fd4fc783e7928337b007849fe26d6c59d1e84 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 8 Jul 2022 12:54:09 +0200 Subject: [PATCH 022/104] Bump deconz dependency to fix #74523 (#74710) --- homeassistant/components/deconz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/deconz/test_sensor.py | 31 +++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index c19d75ec054..06f8b6c0376 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -3,7 +3,7 @@ "name": "deCONZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", - "requirements": ["pydeconz==96"], + "requirements": ["pydeconz==97"], "ssdp": [ { "manufacturer": "Royal Philips Electronics", diff --git a/requirements_all.txt b/requirements_all.txt index 4a35ee93e08..48d08ed0b89 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1444,7 +1444,7 @@ pydaikin==2.7.0 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==96 +pydeconz==97 # homeassistant.components.delijn pydelijn==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 89b53d2b583..31a657d9888 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -974,7 +974,7 @@ pycoolmasternet-async==0.1.2 pydaikin==2.7.0 # homeassistant.components.deconz -pydeconz==96 +pydeconz==97 # homeassistant.components.dexcom pydexcom==0.2.3 diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index 658e11da906..5f11a4d7b0b 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -816,6 +816,37 @@ async def test_dont_add_sensor_if_state_is_none( assert len(hass.states.async_all()) == 0 +async def test_air_quality_sensor_without_ppb(hass, aioclient_mock): + """Test sensor with scaled data is not created if state is None.""" + data = { + "sensors": { + "1": { + "config": { + "on": True, + "reachable": True, + }, + "ep": 2, + "etag": "c2d2e42396f7c78e11e46c66e2ec0200", + "lastseen": "2020-11-20T22:48Z", + "manufacturername": "BOSCH", + "modelid": "AIR", + "name": "BOSCH Air quality sensor", + "state": { + "airquality": "poor", + "lastupdated": "2020-11-20T22:48:00.209", + }, + "swversion": "20200402", + "type": "ZHAAirQuality", + "uniqueid": "00:00:00:00:00:00:00:00-02-fdef", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) + + assert len(hass.states.async_all()) == 1 + + async def test_add_battery_later(hass, aioclient_mock, mock_deconz_websocket): """Test that a sensor without an initial battery state creates a battery sensor once state exist.""" data = { From 7b1cad223d7d05c87b507d93cef652bd91800103 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 8 Jul 2022 14:17:56 -0700 Subject: [PATCH 023/104] Bump atomicwrites (#74758) --- homeassistant/components/cloud/manifest.json | 2 +- homeassistant/package_constraints.txt | 4 ++-- pyproject.toml | 2 +- requirements.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index d5d0c2c0370..4987169d280 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "cloud", "name": "Home Assistant Cloud", "documentation": "https://www.home-assistant.io/integrations/cloud", - "requirements": ["hass-nabucasa==0.54.0"], + "requirements": ["hass-nabucasa==0.54.1"], "dependencies": ["http", "webhook"], "after_dependencies": ["google_assistant", "alexa"], "codeowners": ["@home-assistant/cloud"], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index de9df889eba..ad03978c6ef 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -6,7 +6,7 @@ aiohttp_cors==0.7.0 astral==2.2 async-upnp-client==0.31.2 async_timeout==4.0.2 -atomicwrites==1.4.0 +atomicwrites-homeassistant==1.4.1 attrs==21.2.0 awesomeversion==22.6.0 bcrypt==3.1.7 @@ -14,7 +14,7 @@ certifi>=2021.5.30 ciso8601==2.2.0 cryptography==36.0.2 fnvhash==0.1.0 -hass-nabucasa==0.54.0 +hass-nabucasa==0.54.1 home-assistant-frontend==20220707.0 httpx==0.23.0 ifaddr==0.1.7 diff --git a/pyproject.toml b/pyproject.toml index 48e3ea452ff..3ed9ca223d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ "astral==2.2", "async_timeout==4.0.2", "attrs==21.2.0", - "atomicwrites==1.4.0", + "atomicwrites-homeassistant==1.4.1", "awesomeversion==22.6.0", "bcrypt==3.1.7", "certifi>=2021.5.30", diff --git a/requirements.txt b/requirements.txt index 98b148fa923..c345cac25c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ aiohttp==3.8.1 astral==2.2 async_timeout==4.0.2 attrs==21.2.0 -atomicwrites==1.4.0 +atomicwrites-homeassistant==1.4.1 awesomeversion==22.6.0 bcrypt==3.1.7 certifi>=2021.5.30 diff --git a/requirements_all.txt b/requirements_all.txt index 48d08ed0b89..daa4ca8ce3d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -795,7 +795,7 @@ habitipy==0.2.0 hangups==0.4.18 # homeassistant.components.cloud -hass-nabucasa==0.54.0 +hass-nabucasa==0.54.1 # homeassistant.components.splunk hass_splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 31a657d9888..cf5ae0519b9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -574,7 +574,7 @@ habitipy==0.2.0 hangups==0.4.18 # homeassistant.components.cloud -hass-nabucasa==0.54.0 +hass-nabucasa==0.54.1 # homeassistant.components.tasmota hatasmota==0.5.1 From cb5658d7dc7ac9368b506aa15fd2a5bcd8c9c59b Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 8 Jul 2022 15:18:40 -0600 Subject: [PATCH 024/104] Bump regenmaschine to 2022.07.0 (#74680) --- homeassistant/components/rainmachine/__init__.py | 2 +- homeassistant/components/rainmachine/config_flow.py | 2 +- homeassistant/components/rainmachine/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index 6d51be9d921..c285bf89e57 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -183,7 +183,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD], port=entry.data[CONF_PORT], - ssl=entry.data.get(CONF_SSL, DEFAULT_SSL), + use_ssl=entry.data.get(CONF_SSL, DEFAULT_SSL), ) except RainMachineError as err: raise ConfigEntryNotReady from err diff --git a/homeassistant/components/rainmachine/config_flow.py b/homeassistant/components/rainmachine/config_flow.py index d24dae46c2b..c12362591e7 100644 --- a/homeassistant/components/rainmachine/config_flow.py +++ b/homeassistant/components/rainmachine/config_flow.py @@ -32,7 +32,7 @@ async def async_get_controller( websession = aiohttp_client.async_get_clientsession(hass) client = Client(session=websession) try: - await client.load_local(ip_address, password, port=port, ssl=ssl) + await client.load_local(ip_address, password, port=port, use_ssl=ssl) except RainMachineError: return None else: diff --git a/homeassistant/components/rainmachine/manifest.json b/homeassistant/components/rainmachine/manifest.json index e9df60e4697..4f06ed0d71b 100644 --- a/homeassistant/components/rainmachine/manifest.json +++ b/homeassistant/components/rainmachine/manifest.json @@ -3,7 +3,7 @@ "name": "RainMachine", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/rainmachine", - "requirements": ["regenmaschine==2022.06.1"], + "requirements": ["regenmaschine==2022.07.0"], "codeowners": ["@bachya"], "iot_class": "local_polling", "homekit": { diff --git a/requirements_all.txt b/requirements_all.txt index daa4ca8ce3d..d7c0b8124f0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2065,7 +2065,7 @@ raincloudy==0.0.7 raspyrfm-client==1.2.8 # homeassistant.components.rainmachine -regenmaschine==2022.06.1 +regenmaschine==2022.07.0 # homeassistant.components.renault renault-api==0.1.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cf5ae0519b9..83301dc1c4c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1376,7 +1376,7 @@ radios==0.1.1 radiotherm==2.1.0 # homeassistant.components.rainmachine -regenmaschine==2022.06.1 +regenmaschine==2022.07.0 # homeassistant.components.renault renault-api==0.1.11 From ea709912d493309a3b87baed9749b591a83ee3bd Mon Sep 17 00:00:00 2001 From: Benoit Anastay <45088785+BenoitAnastay@users.noreply.github.com> Date: Fri, 8 Jul 2022 23:22:31 +0200 Subject: [PATCH 025/104] Fix error with HDD temperature report in Freebox integration (#74718) * Fix error whith HDD temperature report There was a non handled error case, documented in issue https://github.com/home-assistant/core/issues/43812 back in 2020 and the fix wasn't applied * Use get method instead of ignoring the sensor * Update test values Add idle state drive with unkown temp * update Tests for system sensors api * Fix booleans values * Fix disk unique_id There was a typo in the code --- homeassistant/components/freebox/router.py | 2 +- homeassistant/components/freebox/sensor.py | 2 +- tests/components/freebox/const.py | 38 +++++++++++++++++++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index 70fc7b86a40..ce4d03aae7a 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -113,7 +113,7 @@ class FreeboxRouter: # According to the doc `syst_datas["sensors"]` is temperature sensors in celsius degree. # Name and id of sensors may vary under Freebox devices. for sensor in syst_datas["sensors"]: - self.sensors_temperature[sensor["name"]] = sensor["value"] + self.sensors_temperature[sensor["name"]] = sensor.get("value") # Connection sensors connection_datas: dict[str, Any] = await self._api.connection.get_status() diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index 46aa9ee8aa0..450456b9146 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -159,7 +159,7 @@ class FreeboxDiskSensor(FreeboxSensor): self._disk = disk self._partition = partition self._attr_name = f"{partition['label']} {description.name}" - self._unique_id = f"{self._router.mac} {description.key} {self._disk['id']} {self._partition['id']}" + self._attr_unique_id = f"{self._router.mac} {description.key} {self._disk['id']} {self._partition['id']}" @property def device_info(self) -> DeviceInfo: diff --git a/tests/components/freebox/const.py b/tests/components/freebox/const.py index cc3d720d7ef..25402cbcdef 100644 --- a/tests/components/freebox/const.py +++ b/tests/components/freebox/const.py @@ -22,6 +22,7 @@ DATA_SYSTEM_GET_CONFIG = { "fans": [{"id": "fan0_speed", "name": "Ventilateur 1", "value": 2130}], "sensors": [ {"id": "temp_hdd", "name": "Disque dur", "value": 40}, + {"id": "temp_hdd2", "name": "Disque dur 2"}, {"id": "temp_sw", "name": "Température Switch", "value": 50}, {"id": "temp_cpum", "name": "Température CPU M", "value": 60}, {"id": "temp_cpub", "name": "Température CPU B", "value": 56}, @@ -123,7 +124,42 @@ DATA_STORAGE_GET_DISKS = [ "path": "L0Rpc3F1ZSBkdXI=", } ], - } + }, + { + "idle_duration": 8290, + "read_error_requests": 0, + "read_requests": 2326826, + "spinning": False, + "table_type": "gpt", + "firmware": "0001", + "type": "sata", + "idle": True, + "connector": 0, + "id": 2000, + "write_error_requests": 0, + "state": "enabled", + "write_requests": 122733632, + "total_bytes": 2000000000000, + "model": "ST2000LM015-2E8174", + "active_duration": 0, + "temp": 0, + "serial": "WDZYJ27Q", + "partitions": [ + { + "fstype": "ext4", + "total_bytes": 1960000000000, + "label": "Disque 2", + "id": 2001, + "internal": False, + "fsck_result": "no_run_yet", + "state": "mounted", + "disk_id": 2000, + "free_bytes": 1880000000000, + "used_bytes": 85410000000, + "path": "L0Rpc3F1ZSAy", + } + ], + }, ] # switch From 14c6b8d41f651d53d9df14c7ccaa8f3d81d8d314 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 8 Jul 2022 14:22:45 -0700 Subject: [PATCH 026/104] Bumped version to 2022.7.2 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 6902a728ea7..796ae18f58d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 7 -PATCH_VERSION: Final = "1" +PATCH_VERSION: Final = "2" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 3ed9ca223d4..68b4758a688 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.7.1" +version = "2022.7.2" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From a3abe7456e3399d0e903fd03869bc6f68c27a8e0 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 2 Jul 2022 21:20:40 +0300 Subject: [PATCH 027/104] Fix CI failure due to integrations leaving dirty known_devices.yaml (#74329) --- tests/components/demo/test_init.py | 13 +++++-------- tests/components/device_tracker/test_init.py | 13 ++++++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/components/demo/test_init.py b/tests/components/demo/test_init.py index b00028e34d9..fa0aff8223b 100644 --- a/tests/components/demo/test_init.py +++ b/tests/components/demo/test_init.py @@ -1,12 +1,10 @@ """The tests for the Demo component.""" -from contextlib import suppress import json -import os +from unittest.mock import patch import pytest from homeassistant.components.demo import DOMAIN -from homeassistant.components.device_tracker.legacy import YAML_DEVICES from homeassistant.components.recorder import get_instance from homeassistant.components.recorder.statistics import list_statistic_ids from homeassistant.helpers.json import JSONEncoder @@ -22,11 +20,10 @@ def mock_history(hass): @pytest.fixture(autouse=True) -def demo_cleanup(hass): - """Clean up device tracker demo file.""" - yield - with suppress(FileNotFoundError): - os.remove(hass.config.path(YAML_DEVICES)) +def mock_device_tracker_update_config(hass): + """Prevent device tracker from creating known devices file.""" + with patch("homeassistant.components.device_tracker.legacy.update_config"): + yield async def test_setting_up_demo(hass): diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index 0953fc67b0a..d8914032f36 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -28,6 +28,8 @@ from homeassistant.helpers.json import JSONEncoder from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from . import common + from tests.common import ( assert_setup_component, async_fire_time_changed, @@ -35,7 +37,6 @@ from tests.common import ( mock_restore_cache, patch_yaml_files, ) -from tests.components.device_tracker import common TEST_PLATFORM = {device_tracker.DOMAIN: {CONF_PLATFORM: "test"}} @@ -165,6 +166,7 @@ async def test_setup_without_yaml_file(hass, enable_custom_integrations): """Test with no YAML file.""" with assert_setup_component(1, device_tracker.DOMAIN): assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) + await hass.async_block_till_done() async def test_gravatar(hass): @@ -210,10 +212,11 @@ async def test_gravatar_and_picture(hass): @patch("homeassistant.components.demo.device_tracker.setup_scanner", autospec=True) async def test_discover_platform(mock_demo_setup_scanner, mock_see, hass): """Test discovery of device_tracker demo platform.""" - await discovery.async_load_platform( - hass, device_tracker.DOMAIN, "demo", {"test_key": "test_val"}, {"bla": {}} - ) - await hass.async_block_till_done() + with patch("homeassistant.components.device_tracker.legacy.update_config"): + await discovery.async_load_platform( + hass, device_tracker.DOMAIN, "demo", {"test_key": "test_val"}, {"bla": {}} + ) + await hass.async_block_till_done() assert device_tracker.DOMAIN in hass.config.components assert mock_demo_setup_scanner.called assert mock_demo_setup_scanner.call_args[0] == ( From 267057c989f3b741ab60c5dfab831710feac6105 Mon Sep 17 00:00:00 2001 From: Hans Oischinger Date: Sun, 10 Jul 2022 22:15:43 +0200 Subject: [PATCH 028/104] Fix Vicare One Time Charge (#74872) --- homeassistant/components/vicare/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vicare/manifest.json b/homeassistant/components/vicare/manifest.json index 575ca35729b..db0ce9cddaa 100644 --- a/homeassistant/components/vicare/manifest.json +++ b/homeassistant/components/vicare/manifest.json @@ -3,7 +3,7 @@ "name": "Viessmann ViCare", "documentation": "https://www.home-assistant.io/integrations/vicare", "codeowners": ["@oischinger"], - "requirements": ["PyViCare==2.16.2"], + "requirements": ["PyViCare==2.16.4"], "iot_class": "cloud_polling", "config_flow": true, "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index d7c0b8124f0..7bb2b07636c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -47,7 +47,7 @@ PyTransportNSW==0.1.1 PyTurboJPEG==1.6.6 # homeassistant.components.vicare -PyViCare==2.16.2 +PyViCare==2.16.4 # homeassistant.components.xiaomi_aqara PyXiaomiGateway==0.13.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 83301dc1c4c..bd56b01d799 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -43,7 +43,7 @@ PyTransportNSW==0.1.1 PyTurboJPEG==1.6.6 # homeassistant.components.vicare -PyViCare==2.16.2 +PyViCare==2.16.4 # homeassistant.components.xiaomi_aqara PyXiaomiGateway==0.13.4 From f5d18108d001ffec7147c4f93932581dc73898ca Mon Sep 17 00:00:00 2001 From: kpine Date: Fri, 8 Jul 2022 15:20:44 -0700 Subject: [PATCH 029/104] Fix KeyError from zwave_js diagnostics (#74579) --- .../components/zwave_js/diagnostics.py | 29 ++++--- tests/components/zwave_js/test_diagnostics.py | 80 ++++++++++++++++++- 2 files changed, 92 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/zwave_js/diagnostics.py b/homeassistant/components/zwave_js/diagnostics.py index 3372b0eeec0..078bd761b71 100644 --- a/homeassistant/components/zwave_js/diagnostics.py +++ b/homeassistant/components/zwave_js/diagnostics.py @@ -94,20 +94,23 @@ def get_device_entities( # If the value ID returns as None, we don't need to include this entity if (value_id := get_value_id_from_unique_id(entry.unique_id)) is None: continue - state_key = get_state_key_from_unique_id(entry.unique_id) - zwave_value = node.values[value_id] - primary_value_data = { - "command_class": zwave_value.command_class, - "command_class_name": zwave_value.command_class_name, - "endpoint": zwave_value.endpoint, - "property": zwave_value.property_, - "property_name": zwave_value.property_name, - "property_key": zwave_value.property_key, - "property_key_name": zwave_value.property_key_name, - } - if state_key is not None: - primary_value_data["state_key"] = state_key + primary_value_data = None + if (zwave_value := node.values.get(value_id)) is not None: + primary_value_data = { + "command_class": zwave_value.command_class, + "command_class_name": zwave_value.command_class_name, + "endpoint": zwave_value.endpoint, + "property": zwave_value.property_, + "property_name": zwave_value.property_name, + "property_key": zwave_value.property_key, + "property_key_name": zwave_value.property_key_name, + } + + state_key = get_state_key_from_unique_id(entry.unique_id) + if state_key is not None: + primary_value_data["state_key"] = state_key + entity = { "domain": entry.domain, "entity_id": entry.entity_id, diff --git a/tests/components/zwave_js/test_diagnostics.py b/tests/components/zwave_js/test_diagnostics.py index 3ac3f32b45a..9f3a7b0884c 100644 --- a/tests/components/zwave_js/test_diagnostics.py +++ b/tests/components/zwave_js/test_diagnostics.py @@ -10,8 +10,12 @@ from homeassistant.components.zwave_js.diagnostics import ( async_get_device_diagnostics, ) from homeassistant.components.zwave_js.discovery import async_discover_node_values -from homeassistant.components.zwave_js.helpers import get_device_id -from homeassistant.helpers.device_registry import async_get +from homeassistant.components.zwave_js.helpers import ( + get_device_id, + get_value_id_from_unique_id, +) +from homeassistant.helpers.device_registry import async_get as async_get_dev_reg +from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg from .common import PROPERTY_ULTRAVIOLET @@ -53,7 +57,7 @@ async def test_device_diagnostics( version_state, ): """Test the device level diagnostics data dump.""" - dev_reg = async_get(hass) + dev_reg = async_get_dev_reg(hass) device = dev_reg.async_get_device({get_device_id(client.driver, multisensor_6)}) assert device @@ -106,7 +110,7 @@ async def test_device_diagnostics( async def test_device_diagnostics_error(hass, integration): """Test the device diagnostics raises exception when an invalid device is used.""" - dev_reg = async_get(hass) + dev_reg = async_get_dev_reg(hass) device = dev_reg.async_get_or_create( config_entry_id=integration.entry_id, identifiers={("test", "test")} ) @@ -118,3 +122,71 @@ async def test_empty_zwave_value_matcher(): """Test empty ZwaveValueMatcher is invalid.""" with pytest.raises(ValueError): ZwaveValueMatcher() + + +async def test_device_diagnostics_missing_primary_value( + hass, + client, + multisensor_6, + integration, + hass_client, +): + """Test that the device diagnostics handles an entity with a missing primary value.""" + dev_reg = async_get_dev_reg(hass) + device = dev_reg.async_get_device({get_device_id(client.driver, multisensor_6)}) + assert device + + entity_id = "sensor.multisensor_6_air_temperature" + ent_reg = async_get_ent_reg(hass) + entry = ent_reg.async_get(entity_id) + + # check that the primary value for the entity exists in the diagnostics + diagnostics_data = await get_diagnostics_for_device( + hass, hass_client, integration, device + ) + + value = multisensor_6.values.get(get_value_id_from_unique_id(entry.unique_id)) + assert value + + air_entity = next( + x for x in diagnostics_data["entities"] if x["entity_id"] == entity_id + ) + + assert air_entity["primary_value"] == { + "command_class": value.command_class, + "command_class_name": value.command_class_name, + "endpoint": value.endpoint, + "property": value.property_, + "property_name": value.property_name, + "property_key": value.property_key, + "property_key_name": value.property_key_name, + } + + # make the entity's primary value go missing + event = Event( + type="value removed", + data={ + "source": "node", + "event": "value removed", + "nodeId": multisensor_6.node_id, + "args": { + "commandClassName": value.command_class_name, + "commandClass": value.command_class, + "endpoint": value.endpoint, + "property": value.property_, + "prevValue": 0, + "propertyName": value.property_name, + }, + }, + ) + multisensor_6.receive_event(event) + + diagnostics_data = await get_diagnostics_for_device( + hass, hass_client, integration, device + ) + + air_entity = next( + x for x in diagnostics_data["entities"] if x["entity_id"] == entity_id + ) + + assert air_entity["primary_value"] is None From 59471a6fbd039133af2b7cc31df0f9046b3c3c51 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sat, 9 Jul 2022 18:55:33 +0100 Subject: [PATCH 030/104] Update systembridgeconnector to 3.3.2 (#74701) --- homeassistant/components/system_bridge/coordinator.py | 7 ++++--- homeassistant/components/system_bridge/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/system_bridge/coordinator.py b/homeassistant/components/system_bridge/coordinator.py index 6088967aa33..1719d951cf0 100644 --- a/homeassistant/components/system_bridge/coordinator.py +++ b/homeassistant/components/system_bridge/coordinator.py @@ -93,7 +93,7 @@ class SystemBridgeDataUpdateCoordinator( if not self.websocket_client.connected: await self._setup_websocket() - await self.websocket_client.get_data(modules) + self.hass.async_create_task(self.websocket_client.get_data(modules)) async def async_handle_module( self, @@ -107,9 +107,7 @@ class SystemBridgeDataUpdateCoordinator( async def _listen_for_data(self) -> None: """Listen for events from the WebSocket.""" - try: - await self.websocket_client.register_data_listener(MODULES) await self.websocket_client.listen(callback=self.async_handle_module) except AuthenticationException as exception: self.last_update_success = False @@ -175,6 +173,9 @@ class SystemBridgeDataUpdateCoordinator( self.async_update_listeners() self.hass.async_create_task(self._listen_for_data()) + + await self.websocket_client.register_data_listener(MODULES) + self.last_update_success = True self.async_update_listeners() diff --git a/homeassistant/components/system_bridge/manifest.json b/homeassistant/components/system_bridge/manifest.json index 087613413d8..4fb2201e2c7 100644 --- a/homeassistant/components/system_bridge/manifest.json +++ b/homeassistant/components/system_bridge/manifest.json @@ -3,7 +3,7 @@ "name": "System Bridge", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/system_bridge", - "requirements": ["systembridgeconnector==3.1.5"], + "requirements": ["systembridgeconnector==3.3.2"], "codeowners": ["@timmo001"], "zeroconf": ["_system-bridge._tcp.local."], "after_dependencies": ["zeroconf"], diff --git a/requirements_all.txt b/requirements_all.txt index d7c0b8124f0..ce409af3b0a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2268,7 +2268,7 @@ swisshydrodata==0.1.0 synology-srm==0.2.0 # homeassistant.components.system_bridge -systembridgeconnector==3.1.5 +systembridgeconnector==3.3.2 # homeassistant.components.tailscale tailscale==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 83301dc1c4c..387e646e83d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1513,7 +1513,7 @@ sunwatcher==0.2.1 surepy==0.7.2 # homeassistant.components.system_bridge -systembridgeconnector==3.1.5 +systembridgeconnector==3.3.2 # homeassistant.components.tailscale tailscale==0.2.0 From 43527d8d19bdbf8b5f4973fe1e62ee0aad76ec46 Mon Sep 17 00:00:00 2001 From: Ethan Madden Date: Sat, 9 Jul 2022 10:51:47 -0700 Subject: [PATCH 031/104] `air_quality` and `filter_life` fixes for Pur131S (#74740) --- homeassistant/components/vesync/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/vesync/sensor.py b/homeassistant/components/vesync/sensor.py index 2da6d8ea6b7..45018c79ca0 100644 --- a/homeassistant/components/vesync/sensor.py +++ b/homeassistant/components/vesync/sensor.py @@ -83,13 +83,12 @@ SENSORS: tuple[VeSyncSensorEntityDescription, ...] = ( native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, - value_fn=lambda device: device.details["filter_life"], + value_fn=lambda device: device.filter_life, exists_fn=lambda device: sku_supported(device, FILTER_LIFE_SUPPORTED), ), VeSyncSensorEntityDescription( key="air-quality", name="Air Quality", - state_class=SensorStateClass.MEASUREMENT, value_fn=lambda device: device.details["air_quality"], exists_fn=lambda device: sku_supported(device, AIR_QUALITY_SUPPORTED), ), From e233024533f142b26ad274766602038f9096f49f Mon Sep 17 00:00:00 2001 From: Pieter Mulder Date: Sat, 9 Jul 2022 19:13:46 +0200 Subject: [PATCH 032/104] Update pyCEC to version 0.5.2 (#74742) --- homeassistant/components/hdmi_cec/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hdmi_cec/manifest.json b/homeassistant/components/hdmi_cec/manifest.json index ff2411db35a..8ea56a51fa9 100644 --- a/homeassistant/components/hdmi_cec/manifest.json +++ b/homeassistant/components/hdmi_cec/manifest.json @@ -2,7 +2,7 @@ "domain": "hdmi_cec", "name": "HDMI-CEC", "documentation": "https://www.home-assistant.io/integrations/hdmi_cec", - "requirements": ["pyCEC==0.5.1"], + "requirements": ["pyCEC==0.5.2"], "codeowners": [], "iot_class": "local_push", "loggers": ["pycec"] diff --git a/requirements_all.txt b/requirements_all.txt index ce409af3b0a..42858b8f445 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1323,7 +1323,7 @@ py-zabbix==1.1.7 py17track==2021.12.2 # homeassistant.components.hdmi_cec -pyCEC==0.5.1 +pyCEC==0.5.2 # homeassistant.components.control4 pyControl4==0.0.6 From 79b4f8ce0c64c97c1380e40616e3b7de1e63295d Mon Sep 17 00:00:00 2001 From: Regev Brody Date: Sat, 9 Jul 2022 14:22:29 +0300 Subject: [PATCH 033/104] Bump pyezviz to 0.2.0.9 (#74755) * Bump ezviz dependency to fix #74618 * Bump ezviz dependency to fix #74618 Co-authored-by: J. Nick Koston --- homeassistant/components/ezviz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ezviz/manifest.json b/homeassistant/components/ezviz/manifest.json index cfdfd6e441d..47e2ec44e8a 100644 --- a/homeassistant/components/ezviz/manifest.json +++ b/homeassistant/components/ezviz/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/ezviz", "dependencies": ["ffmpeg"], "codeowners": ["@RenierM26", "@baqs"], - "requirements": ["pyezviz==0.2.0.8"], + "requirements": ["pyezviz==0.2.0.9"], "config_flow": true, "iot_class": "cloud_polling", "loggers": ["paho_mqtt", "pyezviz"] diff --git a/requirements_all.txt b/requirements_all.txt index 42858b8f445..2edf124752a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1489,7 +1489,7 @@ pyeverlights==0.1.0 pyevilgenius==2.0.0 # homeassistant.components.ezviz -pyezviz==0.2.0.8 +pyezviz==0.2.0.9 # homeassistant.components.fido pyfido==2.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 387e646e83d..4a5fc9ec897 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -995,7 +995,7 @@ pyeverlights==0.1.0 pyevilgenius==2.0.0 # homeassistant.components.ezviz -pyezviz==0.2.0.8 +pyezviz==0.2.0.9 # homeassistant.components.fido pyfido==2.1.1 From 2accc4c07da9f245a7d033530c00da9c0533cd5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Sat, 9 Jul 2022 17:13:27 +0200 Subject: [PATCH 034/104] Update aioqsw to v0.1.1 (#74784) --- homeassistant/components/qnap_qsw/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/qnap_qsw/manifest.json b/homeassistant/components/qnap_qsw/manifest.json index be565f2a07e..83c9423f0f4 100644 --- a/homeassistant/components/qnap_qsw/manifest.json +++ b/homeassistant/components/qnap_qsw/manifest.json @@ -3,7 +3,7 @@ "name": "QNAP QSW", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/qnap_qsw", - "requirements": ["aioqsw==0.1.0"], + "requirements": ["aioqsw==0.1.1"], "codeowners": ["@Noltari"], "iot_class": "local_polling", "loggers": ["aioqsw"], diff --git a/requirements_all.txt b/requirements_all.txt index 2edf124752a..36a6f9de35e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -229,7 +229,7 @@ aiopvpc==3.0.0 aiopyarr==22.6.0 # homeassistant.components.qnap_qsw -aioqsw==0.1.0 +aioqsw==0.1.1 # homeassistant.components.recollect_waste aiorecollect==1.0.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4a5fc9ec897..dae7af38692 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -198,7 +198,7 @@ aiopvpc==3.0.0 aiopyarr==22.6.0 # homeassistant.components.qnap_qsw -aioqsw==0.1.0 +aioqsw==0.1.1 # homeassistant.components.recollect_waste aiorecollect==1.0.8 From 357fe2a722494da07ddf37490a6ecd20171dc4be Mon Sep 17 00:00:00 2001 From: Paul Annekov Date: Sat, 9 Jul 2022 23:53:47 +0300 Subject: [PATCH 035/104] Bump python-gammu to 3.2.4 with Python 3.10 support (#74797) --- homeassistant/components/sms/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sms/manifest.json b/homeassistant/components/sms/manifest.json index d98304ebf23..b3426c01422 100644 --- a/homeassistant/components/sms/manifest.json +++ b/homeassistant/components/sms/manifest.json @@ -3,7 +3,7 @@ "name": "SMS notifications via GSM-modem", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sms", - "requirements": ["python-gammu==3.2.3"], + "requirements": ["python-gammu==3.2.4"], "codeowners": ["@ocalvo"], "iot_class": "local_polling", "loggers": ["gammu"] diff --git a/requirements_all.txt b/requirements_all.txt index 36a6f9de35e..0e564ea62f8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1895,7 +1895,7 @@ python-family-hub-local==0.0.2 python-forecastio==1.4.0 # homeassistant.components.sms -# python-gammu==3.2.3 +# python-gammu==3.2.4 # homeassistant.components.gc100 python-gc100==1.0.3a0 From 07f4efcd835ae8200b2c35a509857b95f5f583dc Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 9 Jul 2022 23:40:15 +0200 Subject: [PATCH 036/104] Bump deCONZ dependency to fix #74791 (#74804) --- homeassistant/components/deconz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index 06f8b6c0376..2ae400bbe19 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -3,7 +3,7 @@ "name": "deCONZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", - "requirements": ["pydeconz==97"], + "requirements": ["pydeconz==98"], "ssdp": [ { "manufacturer": "Royal Philips Electronics", diff --git a/requirements_all.txt b/requirements_all.txt index 0e564ea62f8..a9f7a7cdf6d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1444,7 +1444,7 @@ pydaikin==2.7.0 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==97 +pydeconz==98 # homeassistant.components.delijn pydelijn==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index dae7af38692..3fdbdc7244e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -974,7 +974,7 @@ pycoolmasternet-async==0.1.2 pydaikin==2.7.0 # homeassistant.components.deconz -pydeconz==97 +pydeconz==98 # homeassistant.components.dexcom pydexcom==0.2.3 From 2ba285b8e501e14c48b2628cc81ecf4a9b930aad Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 9 Jul 2022 10:32:15 -0600 Subject: [PATCH 037/104] Bump regenmaschine to 2022.07.1 (#74815) --- homeassistant/components/rainmachine/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rainmachine/manifest.json b/homeassistant/components/rainmachine/manifest.json index 4f06ed0d71b..b318ef7f295 100644 --- a/homeassistant/components/rainmachine/manifest.json +++ b/homeassistant/components/rainmachine/manifest.json @@ -3,7 +3,7 @@ "name": "RainMachine", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/rainmachine", - "requirements": ["regenmaschine==2022.07.0"], + "requirements": ["regenmaschine==2022.07.1"], "codeowners": ["@bachya"], "iot_class": "local_polling", "homekit": { diff --git a/requirements_all.txt b/requirements_all.txt index a9f7a7cdf6d..4578574a921 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2065,7 +2065,7 @@ raincloudy==0.0.7 raspyrfm-client==1.2.8 # homeassistant.components.rainmachine -regenmaschine==2022.07.0 +regenmaschine==2022.07.1 # homeassistant.components.renault renault-api==0.1.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3fdbdc7244e..57c33617e0c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1376,7 +1376,7 @@ radios==0.1.1 radiotherm==2.1.0 # homeassistant.components.rainmachine -regenmaschine==2022.07.0 +regenmaschine==2022.07.1 # homeassistant.components.renault renault-api==0.1.11 From 2f570fa715b557e0150f2b44e50dde2da13b8435 Mon Sep 17 00:00:00 2001 From: Stephan Uhle Date: Sat, 9 Jul 2022 22:22:30 +0200 Subject: [PATCH 038/104] Fixed unit of measurement. #70121 (#74838) --- homeassistant/components/edl21/sensor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/edl21/sensor.py b/homeassistant/components/edl21/sensor.py index f7a79d727a0..65603b0c8c4 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -17,6 +17,7 @@ from homeassistant.components.sensor import ( ) from homeassistant.const import ( CONF_NAME, + DEGREE, ELECTRIC_CURRENT_AMPERE, ELECTRIC_POTENTIAL_VOLT, ENERGY_KILO_WATT_HOUR, @@ -250,6 +251,7 @@ SENSOR_UNIT_MAPPING = { "W": POWER_WATT, "A": ELECTRIC_CURRENT_AMPERE, "V": ELECTRIC_POTENTIAL_VOLT, + "°": DEGREE, } @@ -449,7 +451,7 @@ class EDL21Entity(SensorEntity): @property def native_unit_of_measurement(self): """Return the unit of measurement.""" - if (unit := self._telegram.get("unit")) is None: + if (unit := self._telegram.get("unit")) is None or unit == 0: return None return SENSOR_UNIT_MAPPING[unit] From 01eae3687aaa1aecab780fb8a8aff4f4302062de Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Sun, 10 Jul 2022 05:51:40 -0500 Subject: [PATCH 039/104] Bump rokuecp to 0.17.0 (#74862) --- homeassistant/components/roku/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index 05fe0e1b260..910516b93e8 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -2,7 +2,7 @@ "domain": "roku", "name": "Roku", "documentation": "https://www.home-assistant.io/integrations/roku", - "requirements": ["rokuecp==0.16.0"], + "requirements": ["rokuecp==0.17.0"], "homekit": { "models": ["3820X", "3810X", "4660X", "7820X", "C105X", "C135X"] }, diff --git a/requirements_all.txt b/requirements_all.txt index 4578574a921..67aa76635dd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2092,7 +2092,7 @@ rjpl==0.3.6 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.16.0 +rokuecp==0.17.0 # homeassistant.components.roomba roombapy==1.6.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 57c33617e0c..6654bf8b1bd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1391,7 +1391,7 @@ rflink==0.0.63 ring_doorbell==0.7.2 # homeassistant.components.roku -rokuecp==0.16.0 +rokuecp==0.17.0 # homeassistant.components.roomba roombapy==1.6.5 From 986a86ebed921e76073b9402aad12f04e347d8cb Mon Sep 17 00:00:00 2001 From: Brandon Rothweiler Date: Sun, 10 Jul 2022 06:33:54 -0400 Subject: [PATCH 040/104] Bump pymazda to 0.3.6 (#74863) --- homeassistant/components/mazda/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/mazda/manifest.json b/homeassistant/components/mazda/manifest.json index a18b4406355..acf5282689f 100644 --- a/homeassistant/components/mazda/manifest.json +++ b/homeassistant/components/mazda/manifest.json @@ -3,7 +3,7 @@ "name": "Mazda Connected Services", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/mazda", - "requirements": ["pymazda==0.3.3"], + "requirements": ["pymazda==0.3.6"], "codeowners": ["@bdr99"], "quality_scale": "platinum", "iot_class": "cloud_polling", diff --git a/requirements_all.txt b/requirements_all.txt index 67aa76635dd..e1e5ea7b65e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1640,7 +1640,7 @@ pymailgunner==1.4 pymata-express==1.19 # homeassistant.components.mazda -pymazda==0.3.3 +pymazda==0.3.6 # homeassistant.components.mediaroom pymediaroom==0.6.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6654bf8b1bd..d7471a9df92 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1113,7 +1113,7 @@ pymailgunner==1.4 pymata-express==1.19 # homeassistant.components.mazda -pymazda==0.3.3 +pymazda==0.3.6 # homeassistant.components.melcloud pymelcloud==2.5.6 From bd069966f21a4d87f6d6001579b64ad652d0eb1d Mon Sep 17 00:00:00 2001 From: Hans Oischinger Date: Sun, 10 Jul 2022 22:15:43 +0200 Subject: [PATCH 041/104] Fix Vicare One Time Charge (#74872) --- homeassistant/components/vicare/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vicare/manifest.json b/homeassistant/components/vicare/manifest.json index 575ca35729b..db0ce9cddaa 100644 --- a/homeassistant/components/vicare/manifest.json +++ b/homeassistant/components/vicare/manifest.json @@ -3,7 +3,7 @@ "name": "Viessmann ViCare", "documentation": "https://www.home-assistant.io/integrations/vicare", "codeowners": ["@oischinger"], - "requirements": ["PyViCare==2.16.2"], + "requirements": ["PyViCare==2.16.4"], "iot_class": "cloud_polling", "config_flow": true, "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index e1e5ea7b65e..0e66461ff7c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -47,7 +47,7 @@ PyTransportNSW==0.1.1 PyTurboJPEG==1.6.6 # homeassistant.components.vicare -PyViCare==2.16.2 +PyViCare==2.16.4 # homeassistant.components.xiaomi_aqara PyXiaomiGateway==0.13.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d7471a9df92..64087216db2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -43,7 +43,7 @@ PyTransportNSW==0.1.1 PyTurboJPEG==1.6.6 # homeassistant.components.vicare -PyViCare==2.16.2 +PyViCare==2.16.4 # homeassistant.components.xiaomi_aqara PyXiaomiGateway==0.13.4 From adbcd8adb495814d190bacaf23e50167d53794b2 Mon Sep 17 00:00:00 2001 From: David Straub Date: Sun, 10 Jul 2022 12:49:18 +0200 Subject: [PATCH 042/104] Bump pysml to 0.0.8 (fixes #74382) (#74875) --- homeassistant/components/edl21/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/edl21/manifest.json b/homeassistant/components/edl21/manifest.json index 4cffabe87fc..cac35a10152 100644 --- a/homeassistant/components/edl21/manifest.json +++ b/homeassistant/components/edl21/manifest.json @@ -2,7 +2,7 @@ "domain": "edl21", "name": "EDL21", "documentation": "https://www.home-assistant.io/integrations/edl21", - "requirements": ["pysml==0.0.7"], + "requirements": ["pysml==0.0.8"], "codeowners": ["@mtdcr"], "iot_class": "local_push", "loggers": ["sml"] diff --git a/requirements_all.txt b/requirements_all.txt index 0e66461ff7c..cf39a40868e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1835,7 +1835,7 @@ pysmartthings==0.7.6 pysmarty==0.8 # homeassistant.components.edl21 -pysml==0.0.7 +pysml==0.0.8 # homeassistant.components.snmp pysnmplib==5.0.15 From 20f77ef832369ad556a2870f0f182bdb014a800a Mon Sep 17 00:00:00 2001 From: Thijs W Date: Sun, 10 Jul 2022 21:52:49 +0200 Subject: [PATCH 043/104] Bump afsapi to 0.2.5 (#74907) --- homeassistant/components/frontier_silicon/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/frontier_silicon/manifest.json b/homeassistant/components/frontier_silicon/manifest.json index 20092b941a9..12fb5145aa0 100644 --- a/homeassistant/components/frontier_silicon/manifest.json +++ b/homeassistant/components/frontier_silicon/manifest.json @@ -2,7 +2,7 @@ "domain": "frontier_silicon", "name": "Frontier Silicon", "documentation": "https://www.home-assistant.io/integrations/frontier_silicon", - "requirements": ["afsapi==0.2.4"], + "requirements": ["afsapi==0.2.5"], "codeowners": ["@wlcrs"], "iot_class": "local_polling" } diff --git a/requirements_all.txt b/requirements_all.txt index cf39a40868e..deb6ce80558 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -89,7 +89,7 @@ adguardhome==0.5.1 advantage_air==0.3.1 # homeassistant.components.frontier_silicon -afsapi==0.2.4 +afsapi==0.2.5 # homeassistant.components.agent_dvr agent-py==0.0.23 From 02452c7632d68010d0ffa97dd64094982d4f1200 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 10 Jul 2022 13:25:47 -0700 Subject: [PATCH 044/104] Bumped version to 2022.7.3 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 796ae18f58d..7f5cf999b96 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 7 -PATCH_VERSION: Final = "2" +PATCH_VERSION: Final = "3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 68b4758a688..c86fc6b26f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.7.2" +version = "2022.7.3" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 8259ce986858d4135a1f024bbd63a5e1f45d6ad2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 11 Jul 2022 10:49:06 +0200 Subject: [PATCH 045/104] Migrate ecobee to native_* (#74043) --- homeassistant/components/ecobee/weather.py | 43 +++++++++++----------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/ecobee/weather.py b/homeassistant/components/ecobee/weather.py index 1c330ceb4e2..aca4dcdf2f5 100644 --- a/homeassistant/components/ecobee/weather.py +++ b/homeassistant/components/ecobee/weather.py @@ -7,20 +7,24 @@ from pyecobee.const import ECOBEE_STATE_UNKNOWN from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, - ATTR_FORECAST_TEMP, - ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_NATIVE_TEMP, + ATTR_FORECAST_NATIVE_TEMP_LOW, + ATTR_FORECAST_NATIVE_WIND_SPEED, ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING, - ATTR_FORECAST_WIND_SPEED, WeatherEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PRESSURE_HPA, PRESSURE_INHG, TEMP_FAHRENHEIT +from homeassistant.const import ( + LENGTH_METERS, + PRESSURE_HPA, + SPEED_METERS_PER_SECOND, + TEMP_FAHRENHEIT, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import dt as dt_util -from homeassistant.util.pressure import convert as pressure_convert from .const import ( DOMAIN, @@ -49,6 +53,11 @@ async def async_setup_entry( class EcobeeWeather(WeatherEntity): """Representation of Ecobee weather data.""" + _attr_native_pressure_unit = PRESSURE_HPA + _attr_native_temperature_unit = TEMP_FAHRENHEIT + _attr_native_visibility_unit = LENGTH_METERS + _attr_native_wind_speed_unit = SPEED_METERS_PER_SECOND + def __init__(self, data, name, index): """Initialize the Ecobee weather platform.""" self.data = data @@ -101,7 +110,7 @@ class EcobeeWeather(WeatherEntity): return None @property - def temperature(self): + def native_temperature(self): """Return the temperature.""" try: return float(self.get_forecast(0, "temperature")) / 10 @@ -109,18 +118,10 @@ class EcobeeWeather(WeatherEntity): return None @property - def temperature_unit(self): - """Return the unit of measurement.""" - return TEMP_FAHRENHEIT - - @property - def pressure(self): + def native_pressure(self): """Return the pressure.""" try: pressure = self.get_forecast(0, "pressure") - if not self.hass.config.units.is_metric: - pressure = pressure_convert(pressure, PRESSURE_HPA, PRESSURE_INHG) - return round(pressure, 2) return round(pressure) except ValueError: return None @@ -134,15 +135,15 @@ class EcobeeWeather(WeatherEntity): return None @property - def visibility(self): + def native_visibility(self): """Return the visibility.""" try: - return int(self.get_forecast(0, "visibility")) / 1000 + return int(self.get_forecast(0, "visibility")) except ValueError: return None @property - def wind_speed(self): + def native_wind_speed(self): """Return the wind speed.""" try: return int(self.get_forecast(0, "windSpeed")) @@ -202,13 +203,13 @@ def _process_forecast(json): json["weatherSymbol"] ] if json["tempHigh"] != ECOBEE_STATE_UNKNOWN: - forecast[ATTR_FORECAST_TEMP] = float(json["tempHigh"]) / 10 + forecast[ATTR_FORECAST_NATIVE_TEMP] = float(json["tempHigh"]) / 10 if json["tempLow"] != ECOBEE_STATE_UNKNOWN: - forecast[ATTR_FORECAST_TEMP_LOW] = float(json["tempLow"]) / 10 + forecast[ATTR_FORECAST_NATIVE_TEMP_LOW] = float(json["tempLow"]) / 10 if json["windBearing"] != ECOBEE_STATE_UNKNOWN: forecast[ATTR_FORECAST_WIND_BEARING] = int(json["windBearing"]) if json["windSpeed"] != ECOBEE_STATE_UNKNOWN: - forecast[ATTR_FORECAST_WIND_SPEED] = int(json["windSpeed"]) + forecast[ATTR_FORECAST_NATIVE_WIND_SPEED] = int(json["windSpeed"]) except (ValueError, IndexError, KeyError): return None From 5e7174a5f4949a8ca8fd7a76920cb56386930de3 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 11 Jul 2022 10:58:57 +0200 Subject: [PATCH 046/104] Migrate homematicip_cloud to native_* (#74385) --- .../components/homematicip_cloud/weather.py | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/weather.py b/homeassistant/components/homematicip_cloud/weather.py index 985754a8417..3acbae95441 100644 --- a/homeassistant/components/homematicip_cloud/weather.py +++ b/homeassistant/components/homematicip_cloud/weather.py @@ -22,7 +22,7 @@ from homeassistant.components.weather import ( WeatherEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import TEMP_CELSIUS +from homeassistant.const import SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -71,6 +71,9 @@ async def async_setup_entry( class HomematicipWeatherSensor(HomematicipGenericEntity, WeatherEntity): """Representation of the HomematicIP weather sensor plus & basic.""" + _attr_native_temperature_unit = TEMP_CELSIUS + _attr_native_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR + def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize the weather sensor.""" super().__init__(hap, device) @@ -81,22 +84,17 @@ class HomematicipWeatherSensor(HomematicipGenericEntity, WeatherEntity): return self._device.label @property - def temperature(self) -> float: + def native_temperature(self) -> float: """Return the platform temperature.""" return self._device.actualTemperature - @property - def temperature_unit(self) -> str: - """Return the unit of measurement.""" - return TEMP_CELSIUS - @property def humidity(self) -> int: """Return the humidity.""" return self._device.humidity @property - def wind_speed(self) -> float: + def native_wind_speed(self) -> float: """Return the wind speed.""" return self._device.windSpeed @@ -129,6 +127,9 @@ class HomematicipWeatherSensorPro(HomematicipWeatherSensor): class HomematicipHomeWeather(HomematicipGenericEntity, WeatherEntity): """Representation of the HomematicIP home weather.""" + _attr_native_temperature_unit = TEMP_CELSIUS + _attr_native_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR + def __init__(self, hap: HomematicipHAP) -> None: """Initialize the home weather.""" hap.home.modelType = "HmIP-Home-Weather" @@ -145,22 +146,17 @@ class HomematicipHomeWeather(HomematicipGenericEntity, WeatherEntity): return f"Weather {self._home.location.city}" @property - def temperature(self) -> float: + def native_temperature(self) -> float: """Return the temperature.""" return self._device.weather.temperature - @property - def temperature_unit(self) -> str: - """Return the unit of measurement.""" - return TEMP_CELSIUS - @property def humidity(self) -> int: """Return the humidity.""" return self._device.weather.humidity @property - def wind_speed(self) -> float: + def native_wind_speed(self) -> float: """Return the wind speed.""" return round(self._device.weather.windSpeed, 1) From cc79c3d6e126d07210c95bb48aaed64be5689a98 Mon Sep 17 00:00:00 2001 From: Ludovico de Nittis Date: Mon, 11 Jul 2022 16:51:10 +0200 Subject: [PATCH 047/104] Update pyialarm to 2.2.0 (#74874) --- homeassistant/components/ialarm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ialarm/manifest.json b/homeassistant/components/ialarm/manifest.json index 60ecb9da74a..00a0cba9a27 100644 --- a/homeassistant/components/ialarm/manifest.json +++ b/homeassistant/components/ialarm/manifest.json @@ -2,7 +2,7 @@ "domain": "ialarm", "name": "Antifurto365 iAlarm", "documentation": "https://www.home-assistant.io/integrations/ialarm", - "requirements": ["pyialarm==1.9.0"], + "requirements": ["pyialarm==2.2.0"], "codeowners": ["@RyuzakiKK"], "config_flow": true, "iot_class": "local_polling", diff --git a/requirements_all.txt b/requirements_all.txt index deb6ce80558..6bb80d139ae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1553,7 +1553,7 @@ pyhomematic==0.1.77 pyhomeworks==0.0.6 # homeassistant.components.ialarm -pyialarm==1.9.0 +pyialarm==2.2.0 # homeassistant.components.icloud pyicloud==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 64087216db2..819b03b2229 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1047,7 +1047,7 @@ pyhiveapi==0.5.13 pyhomematic==0.1.77 # homeassistant.components.ialarm -pyialarm==1.9.0 +pyialarm==2.2.0 # homeassistant.components.icloud pyicloud==1.0.0 From d31a0c8dcadde568e5518ced19e7cf51a568b0c5 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sun, 10 Jul 2022 13:35:45 -0400 Subject: [PATCH 048/104] Correctly handle device triggers for missing ZHA devices (#74894) --- homeassistant/components/zha/core/helpers.py | 2 +- tests/components/zha/test_device_trigger.py | 38 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index 390ef290dc2..b60f61b1e8e 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -170,7 +170,7 @@ def async_get_zha_device(hass: HomeAssistant, device_id: str) -> ZHADevice: device_registry = dr.async_get(hass) registry_device = device_registry.async_get(device_id) if not registry_device: - raise ValueError(f"Device id `{device_id}` not found in registry.") + raise KeyError(f"Device id `{device_id}` not found in registry.") zha_gateway: ZHAGateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] ieee_address = list(list(registry_device.identifiers)[0])[1] ieee = zigpy.types.EUI64.convert(ieee_address) diff --git a/tests/components/zha/test_device_trigger.py b/tests/components/zha/test_device_trigger.py index 1f5fa467a93..8e19fe5b637 100644 --- a/tests/components/zha/test_device_trigger.py +++ b/tests/components/zha/test_device_trigger.py @@ -370,3 +370,41 @@ async def test_exception_bad_trigger(hass, mock_devices, calls, caplog): ) await hass.async_block_till_done() assert "Invalid config for [automation]" in caplog.text + + +async def test_exception_no_device(hass, mock_devices, calls, caplog): + """Test for exception on event triggers firing.""" + + zigpy_device, zha_device = mock_devices + + zigpy_device.device_automation_triggers = { + (SHAKEN, SHAKEN): {COMMAND: COMMAND_SHAKE}, + (DOUBLE_PRESS, DOUBLE_PRESS): {COMMAND: COMMAND_DOUBLE}, + (SHORT_PRESS, SHORT_PRESS): {COMMAND: COMMAND_SINGLE}, + (LONG_PRESS, LONG_PRESS): {COMMAND: COMMAND_HOLD}, + (LONG_RELEASE, LONG_RELEASE): {COMMAND: COMMAND_HOLD}, + } + + await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "device_id": "no_such_device_id", + "domain": "zha", + "platform": "device", + "type": "junk", + "subtype": "junk", + }, + "action": { + "service": "test.automation", + "data": {"message": "service called"}, + }, + } + ] + }, + ) + await hass.async_block_till_done() + assert "Invalid config for [automation]" in caplog.text From 3f53022b50db4171fbfca3f97f49ee393f2dbadd Mon Sep 17 00:00:00 2001 From: henryptung Date: Mon, 11 Jul 2022 02:33:28 -0700 Subject: [PATCH 049/104] Remove pip --prefix workaround (#74922) Remove --prefix workaround See discussion in https://github.com/home-assistant/core/issues/74405. This workaround is no longer needed on pip >= 21.0 and actively causes problems for pip >= 21.3. --- homeassistant/util/package.py | 3 --- tests/util/test_package.py | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index aad93e37542..18ab43967ec 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -89,9 +89,6 @@ def install_package( # This only works if not running in venv args += ["--user"] env["PYTHONUSERBASE"] = os.path.abspath(target) - # Workaround for incompatible prefix setting - # See http://stackoverflow.com/a/4495175 - args += ["--prefix="] _LOGGER.debug("Running pip command: args=%s", args) with Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env) as process: _, stderr = process.communicate() diff --git a/tests/util/test_package.py b/tests/util/test_package.py index d6b7402a5b6..7ab087f1463 100644 --- a/tests/util/test_package.py +++ b/tests/util/test_package.py @@ -137,7 +137,6 @@ def test_install_target(mock_sys, mock_popen, mock_env_copy, mock_venv): "--quiet", TEST_NEW_REQ, "--user", - "--prefix=", ] assert package.install_package(TEST_NEW_REQ, False, target=target) @@ -156,7 +155,7 @@ def test_install_target_venv(mock_sys, mock_popen, mock_env_copy, mock_venv): def test_install_error(caplog, mock_sys, mock_popen, mock_venv): - """Test an install with a target.""" + """Test an install that errors out.""" caplog.set_level(logging.WARNING) mock_popen.return_value.returncode = 1 assert not package.install_package(TEST_NEW_REQ) From 4d81d056da889247407c896b8bc220f187e12a8d Mon Sep 17 00:00:00 2001 From: Markus Date: Mon, 11 Jul 2022 10:49:31 +0200 Subject: [PATCH 050/104] Fix Pyload request content type headers (#74957) --- homeassistant/components/pyload/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/pyload/sensor.py b/homeassistant/components/pyload/sensor.py index e4bbf7df3d2..81e1a02408a 100644 --- a/homeassistant/components/pyload/sensor.py +++ b/homeassistant/components/pyload/sensor.py @@ -4,7 +4,6 @@ from __future__ import annotations from datetime import timedelta import logging -from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol @@ -144,7 +143,7 @@ class PyLoadAPI: """Initialize pyLoad API and set headers needed later.""" self.api_url = api_url self.status = None - self.headers = {CONTENT_TYPE: CONTENT_TYPE_JSON} + self.headers = {"Content-Type": CONTENT_TYPE_JSON} if username is not None and password is not None: self.payload = {"username": username, "password": password} From c45313e9dec151f951f2d2f5aec9bb49518a4cf7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Jul 2022 23:46:55 +0200 Subject: [PATCH 051/104] JSON serialize NamedTuple subclasses with aiohttp (#74971) --- homeassistant/helpers/aiohttp_client.py | 4 ++-- tests/helpers/test_aiohttp_client.py | 11 +++++++++++ tests/helpers/test_json.py | 22 ++++++++++++++++++++++ tests/test_util/aiohttp.py | 2 +- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 2e56698db41..2ef96091d15 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -14,7 +14,6 @@ from aiohttp import web from aiohttp.hdrs import CONTENT_TYPE, USER_AGENT from aiohttp.web_exceptions import HTTPBadGateway, HTTPGatewayTimeout import async_timeout -import orjson from homeassistant import config_entries from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, __version__ @@ -23,6 +22,7 @@ from homeassistant.loader import bind_hass from homeassistant.util import ssl as ssl_util from .frame import warn_use +from .json import json_dumps DATA_CONNECTOR = "aiohttp_connector" DATA_CONNECTOR_NOTVERIFY = "aiohttp_connector_notverify" @@ -98,7 +98,7 @@ def _async_create_clientsession( """Create a new ClientSession with kwargs, i.e. for cookies.""" clientsession = aiohttp.ClientSession( connector=_async_get_connector(hass, verify_ssl), - json_serialize=lambda x: orjson.dumps(x).decode("utf-8"), + json_serialize=json_dumps, **kwargs, ) # Prevent packages accidentally overriding our default headers diff --git a/tests/helpers/test_aiohttp_client.py b/tests/helpers/test_aiohttp_client.py index 599b2c6984e..1ffb4267167 100644 --- a/tests/helpers/test_aiohttp_client.py +++ b/tests/helpers/test_aiohttp_client.py @@ -19,6 +19,7 @@ from homeassistant.const import ( ) from homeassistant.core import EVENT_HOMEASSISTANT_CLOSE import homeassistant.helpers.aiohttp_client as client +from homeassistant.util.color import RGBColor from tests.common import MockConfigEntry @@ -215,6 +216,16 @@ async def test_async_aiohttp_proxy_stream_client_err(aioclient_mock, camera_clie assert resp.status == 502 +async def test_sending_named_tuple(hass, aioclient_mock): + """Test sending a named tuple in json.""" + resp = aioclient_mock.post("http://127.0.0.1/rgb", json={"rgb": RGBColor(4, 3, 2)}) + session = client.async_create_clientsession(hass) + resp = await session.post("http://127.0.0.1/rgb", json={"rgb": RGBColor(4, 3, 2)}) + assert resp.status == 200 + await resp.json() == {"rgb": RGBColor(4, 3, 2)} + aioclient_mock.mock_calls[0][2]["rgb"] == RGBColor(4, 3, 2) + + async def test_client_session_immutable_headers(hass): """Test we can't mutate headers.""" session = client.async_get_clientsession(hass) diff --git a/tests/helpers/test_json.py b/tests/helpers/test_json.py index 54c488690fa..1e85338f152 100644 --- a/tests/helpers/test_json.py +++ b/tests/helpers/test_json.py @@ -2,6 +2,7 @@ import datetime import json import time +from typing import NamedTuple import pytest @@ -13,6 +14,7 @@ from homeassistant.helpers.json import ( json_dumps_sorted, ) from homeassistant.util import dt as dt_util +from homeassistant.util.color import RGBColor @pytest.mark.parametrize("encoder", (JSONEncoder, ExtendedJSONEncoder)) @@ -96,3 +98,23 @@ def test_json_dumps_tuple_subclass(): tt = time.struct_time((1999, 3, 17, 32, 44, 55, 2, 76, 0)) assert json_dumps(tt) == "[1999,3,17,32,44,55,2,76,0]" + + +def test_json_dumps_named_tuple_subclass(): + """Test the json dumps a tuple subclass.""" + + class NamedTupleSubclass(NamedTuple): + """A NamedTuple subclass.""" + + name: str + + nts = NamedTupleSubclass("a") + + assert json_dumps(nts) == '["a"]' + + +def test_json_dumps_rgb_color_subclass(): + """Test the json dumps of RGBColor.""" + rgb = RGBColor(4, 2, 1) + + assert json_dumps(rgb) == "[4,2,1]" diff --git a/tests/test_util/aiohttp.py b/tests/test_util/aiohttp.py index 9ed47109210..4ed81a3a577 100644 --- a/tests/test_util/aiohttp.py +++ b/tests/test_util/aiohttp.py @@ -111,7 +111,7 @@ class AiohttpClientMocker: def create_session(self, loop): """Create a ClientSession that is bound to this mocker.""" - session = ClientSession(loop=loop) + session = ClientSession(loop=loop, json_serialize=json_dumps) # Setting directly on `session` will raise deprecation warning object.__setattr__(session, "_request", self.match_request) return session From a34e72f3a185e27a2af31da75106cdd374aaad15 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 11 Jul 2022 14:38:08 +0200 Subject: [PATCH 052/104] Fix mix of aiohttp and requests in ClickSend TTS (#74985) --- homeassistant/components/clicksend_tts/notify.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/clicksend_tts/notify.py b/homeassistant/components/clicksend_tts/notify.py index 712787c34e6..8026c8e150b 100644 --- a/homeassistant/components/clicksend_tts/notify.py +++ b/homeassistant/components/clicksend_tts/notify.py @@ -3,7 +3,6 @@ from http import HTTPStatus import json import logging -from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol @@ -20,7 +19,7 @@ _LOGGER = logging.getLogger(__name__) BASE_API_URL = "https://rest.clicksend.com/v3" -HEADERS = {CONTENT_TYPE: CONTENT_TYPE_JSON} +HEADERS = {"Content-Type": CONTENT_TYPE_JSON} CONF_LANGUAGE = "language" CONF_VOICE = "voice" From f738d39ad9f491581d8116f7d0f6fce49bdac8dc Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Tue, 12 Jul 2022 01:42:33 -0500 Subject: [PATCH 053/104] Do not spam log when Life360 member location is missing (#75029) --- homeassistant/components/life360/__init__.py | 6 +- .../components/life360/coordinator.py | 62 +++++++++++++++---- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/life360/__init__.py b/homeassistant/components/life360/__init__.py index 4527f6ac298..d05a9ec400b 100644 --- a/homeassistant/components/life360/__init__.py +++ b/homeassistant/components/life360/__init__.py @@ -37,7 +37,7 @@ from .const import ( SHOW_DRIVING, SHOW_MOVING, ) -from .coordinator import Life360DataUpdateCoordinator +from .coordinator import Life360DataUpdateCoordinator, MissingLocReason PLATFORMS = [Platform.DEVICE_TRACKER] @@ -128,6 +128,10 @@ class IntegData: coordinators: dict[str, Life360DataUpdateCoordinator] = field( init=False, default_factory=dict ) + # member_id: missing location reason + missing_loc_reason: dict[str, MissingLocReason] = field( + init=False, default_factory=dict + ) # member_id: ConfigEntry.entry_id tracked_members: dict[str, str] = field(init=False, default_factory=dict) logged_circles: list[str] = field(init=False, default_factory=list) diff --git a/homeassistant/components/life360/coordinator.py b/homeassistant/components/life360/coordinator.py index dc7fdb73a8c..05eecd43cdc 100644 --- a/homeassistant/components/life360/coordinator.py +++ b/homeassistant/components/life360/coordinator.py @@ -2,8 +2,10 @@ from __future__ import annotations +from contextlib import suppress from dataclasses import dataclass, field from datetime import datetime +from enum import Enum from typing import Any from life360 import Life360, Life360Error, LoginError @@ -33,6 +35,13 @@ from .const import ( ) +class MissingLocReason(Enum): + """Reason member location information is missing.""" + + VAGUE_ERROR_REASON = "vague error reason" + EXPLICIT_ERROR_REASON = "explicit error reason" + + @dataclass class Life360Place: """Life360 Place data.""" @@ -99,6 +108,7 @@ class Life360DataUpdateCoordinator(DataUpdateCoordinator): max_retries=COMM_MAX_RETRIES, authorization=entry.data[CONF_AUTHORIZATION], ) + self._missing_loc_reason = hass.data[DOMAIN].missing_loc_reason async def _retrieve_data(self, func: str, *args: Any) -> list[dict[str, Any]]: """Get data from Life360.""" @@ -141,10 +151,7 @@ class Life360DataUpdateCoordinator(DataUpdateCoordinator): if not int(member["features"]["shareLocation"]): continue - # Note that member may be in more than one circle. If that's the case just - # go ahead and process the newly retrieved data (overwriting the older - # data), since it might be slightly newer than what was retrieved while - # processing another circle. + member_id = member["id"] first = member["firstName"] last = member["lastName"] @@ -153,16 +160,45 @@ class Life360DataUpdateCoordinator(DataUpdateCoordinator): else: name = first or last - loc = member["location"] - if not loc: - if err_msg := member["issues"]["title"]: - if member["issues"]["dialog"]: - err_msg += f": {member['issues']['dialog']}" - else: - err_msg = "Location information missing" - LOGGER.error("%s: %s", name, err_msg) + cur_missing_reason = self._missing_loc_reason.get(member_id) + + # Check if location information is missing. This can happen if server + # has not heard from member's device in a long time (e.g., has been off + # for a long time, or has lost service, etc.) + if loc := member["location"]: + with suppress(KeyError): + del self._missing_loc_reason[member_id] + else: + if explicit_reason := member["issues"]["title"]: + if extended_reason := member["issues"]["dialog"]: + explicit_reason += f": {extended_reason}" + # Note that different Circles can report missing location in + # different ways. E.g., one might report an explicit reason and + # another does not. If a vague reason has already been logged but a + # more explicit reason is now available, log that, too. + if ( + cur_missing_reason is None + or cur_missing_reason == MissingLocReason.VAGUE_ERROR_REASON + and explicit_reason + ): + if explicit_reason: + self._missing_loc_reason[ + member_id + ] = MissingLocReason.EXPLICIT_ERROR_REASON + err_msg = explicit_reason + else: + self._missing_loc_reason[ + member_id + ] = MissingLocReason.VAGUE_ERROR_REASON + err_msg = "Location information missing" + LOGGER.error("%s: %s", name, err_msg) continue + # Note that member may be in more than one circle. If that's the case + # just go ahead and process the newly retrieved data (overwriting the + # older data), since it might be slightly newer than what was retrieved + # while processing another circle. + place = loc["name"] or None if place: @@ -179,7 +215,7 @@ class Life360DataUpdateCoordinator(DataUpdateCoordinator): if self._hass.config.units.is_metric: speed = convert(speed, LENGTH_MILES, LENGTH_KILOMETERS) - data.members[member["id"]] = Life360Member( + data.members[member_id] = Life360Member( address, dt_util.utc_from_timestamp(int(loc["since"])), bool(int(loc["charge"])), From bdc4171e3795244f89501b6243a357f89422c3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 12 Jul 2022 01:35:45 +0300 Subject: [PATCH 054/104] Upgrade huawei-lte-api to 1.6.1 (#75030) --- homeassistant/components/huawei_lte/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/huawei_lte/manifest.json b/homeassistant/components/huawei_lte/manifest.json index dd0382d5f55..c1b15d57620 100644 --- a/homeassistant/components/huawei_lte/manifest.json +++ b/homeassistant/components/huawei_lte/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/huawei_lte", "requirements": [ - "huawei-lte-api==1.6.0", + "huawei-lte-api==1.6.1", "stringcase==1.2.0", "url-normalize==1.4.1" ], diff --git a/requirements_all.txt b/requirements_all.txt index 6bb80d139ae..e20a7fa830e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -846,7 +846,7 @@ horimote==0.4.1 httplib2==0.20.4 # homeassistant.components.huawei_lte -huawei-lte-api==1.6.0 +huawei-lte-api==1.6.1 # homeassistant.components.huisbaasje huisbaasje-client==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 819b03b2229..8eebc524bef 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -610,7 +610,7 @@ homepluscontrol==0.0.5 httplib2==0.20.4 # homeassistant.components.huawei_lte -huawei-lte-api==1.6.0 +huawei-lte-api==1.6.1 # homeassistant.components.huisbaasje huisbaasje-client==0.1.0 From 620d2ed8fd19f1baf2db09571ad731f7790e1d73 Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Tue, 12 Jul 2022 09:06:38 -0500 Subject: [PATCH 055/104] Fix Ruckus Unleashed SSH connection failures (#75032) --- .../components/ruckus_unleashed/__init__.py | 7 +++---- .../components/ruckus_unleashed/config_flow.py | 14 +++++++------- .../components/ruckus_unleashed/coordinator.py | 4 +--- .../components/ruckus_unleashed/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/ruckus_unleashed/test_init.py | 4 ++-- 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/ruckus_unleashed/__init__.py b/homeassistant/components/ruckus_unleashed/__init__.py index 2c8c8bb108d..62ed4949e05 100644 --- a/homeassistant/components/ruckus_unleashed/__init__.py +++ b/homeassistant/components/ruckus_unleashed/__init__.py @@ -29,8 +29,7 @@ from .coordinator import RuckusUnleashedDataUpdateCoordinator async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Ruckus Unleashed from a config entry.""" try: - ruckus = await hass.async_add_executor_job( - Ruckus, + ruckus = await Ruckus.create( entry.data[CONF_HOST], entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], @@ -42,10 +41,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await coordinator.async_config_entry_first_refresh() - system_info = await hass.async_add_executor_job(ruckus.system_info) + system_info = await ruckus.system_info() registry = device_registry.async_get(hass) - ap_info = await hass.async_add_executor_job(ruckus.ap_info) + ap_info = await ruckus.ap_info() for device in ap_info[API_AP][API_ID].values(): registry.async_get_or_create( config_entry_id=entry.entry_id, diff --git a/homeassistant/components/ruckus_unleashed/config_flow.py b/homeassistant/components/ruckus_unleashed/config_flow.py index 7d34e620a13..4adf245c3de 100644 --- a/homeassistant/components/ruckus_unleashed/config_flow.py +++ b/homeassistant/components/ruckus_unleashed/config_flow.py @@ -21,22 +21,24 @@ DATA_SCHEMA = vol.Schema( ) -def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: core.HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. """ try: - ruckus = Ruckus(data[CONF_HOST], data[CONF_USERNAME], data[CONF_PASSWORD]) + ruckus = await Ruckus.create( + data[CONF_HOST], data[CONF_USERNAME], data[CONF_PASSWORD] + ) except AuthenticationError as error: raise InvalidAuth from error except ConnectionError as error: raise CannotConnect from error - mesh_name = ruckus.mesh_name() + mesh_name = await ruckus.mesh_name() - system_info = ruckus.system_info() + system_info = await ruckus.system_info() try: host_serial = system_info[API_SYSTEM_OVERVIEW][API_SERIAL] except KeyError as error: @@ -58,9 +60,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors = {} if user_input is not None: try: - info = await self.hass.async_add_executor_job( - validate_input, self.hass, user_input - ) + info = await validate_input(self.hass, user_input) except CannotConnect: errors["base"] = "cannot_connect" except InvalidAuth: diff --git a/homeassistant/components/ruckus_unleashed/coordinator.py b/homeassistant/components/ruckus_unleashed/coordinator.py index 8b80eaae0da..e84b79ef843 100644 --- a/homeassistant/components/ruckus_unleashed/coordinator.py +++ b/homeassistant/components/ruckus_unleashed/coordinator.py @@ -37,9 +37,7 @@ class RuckusUnleashedDataUpdateCoordinator(DataUpdateCoordinator): async def _fetch_clients(self) -> dict: """Fetch clients from the API and format them.""" - clients = await self.hass.async_add_executor_job( - self.ruckus.current_active_clients - ) + clients = await self.ruckus.current_active_clients() return {e[API_MAC]: e for e in clients[API_CURRENT_ACTIVE_CLIENTS][API_CLIENTS]} async def _async_update_data(self) -> dict: diff --git a/homeassistant/components/ruckus_unleashed/manifest.json b/homeassistant/components/ruckus_unleashed/manifest.json index f010d340147..a6b2ad0271c 100644 --- a/homeassistant/components/ruckus_unleashed/manifest.json +++ b/homeassistant/components/ruckus_unleashed/manifest.json @@ -3,7 +3,7 @@ "name": "Ruckus Unleashed", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ruckus_unleashed", - "requirements": ["pyruckus==0.12"], + "requirements": ["pyruckus==0.16"], "codeowners": ["@gabe565"], "iot_class": "local_polling", "loggers": ["pexpect", "pyruckus"] diff --git a/requirements_all.txt b/requirements_all.txt index e20a7fa830e..df753662c8a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1780,7 +1780,7 @@ pyrisco==0.3.1 pyrituals==0.0.6 # homeassistant.components.ruckus_unleashed -pyruckus==0.12 +pyruckus==0.16 # homeassistant.components.sabnzbd pysabnzbd==1.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8eebc524bef..7c0eaa25b14 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1211,7 +1211,7 @@ pyrisco==0.3.1 pyrituals==0.0.6 # homeassistant.components.ruckus_unleashed -pyruckus==0.12 +pyruckus==0.16 # homeassistant.components.sabnzbd pysabnzbd==1.1.1 diff --git a/tests/components/ruckus_unleashed/test_init.py b/tests/components/ruckus_unleashed/test_init.py index e9ac9ec7cd8..d72856aa542 100644 --- a/tests/components/ruckus_unleashed/test_init.py +++ b/tests/components/ruckus_unleashed/test_init.py @@ -31,7 +31,7 @@ async def test_setup_entry_login_error(hass): """Test entry setup failed due to login error.""" entry = mock_config_entry() with patch( - "homeassistant.components.ruckus_unleashed.Ruckus", + "homeassistant.components.ruckus_unleashed.Ruckus.connect", side_effect=AuthenticationError, ): entry.add_to_hass(hass) @@ -45,7 +45,7 @@ async def test_setup_entry_connection_error(hass): """Test entry setup failed due to connection error.""" entry = mock_config_entry() with patch( - "homeassistant.components.ruckus_unleashed.Ruckus", + "homeassistant.components.ruckus_unleashed.Ruckus.connect", side_effect=ConnectionError, ): entry.add_to_hass(hass) From 51a4c9856219746bf6257dd9675a115d1f80410e Mon Sep 17 00:00:00 2001 From: Thijs W Date: Tue, 12 Jul 2022 12:03:26 +0200 Subject: [PATCH 056/104] Bump afsapi to 0.2.6 (#75041) --- homeassistant/components/frontier_silicon/manifest.json | 2 +- homeassistant/components/frontier_silicon/media_player.py | 7 +++++-- requirements_all.txt | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontier_silicon/manifest.json b/homeassistant/components/frontier_silicon/manifest.json index 12fb5145aa0..b04d68e672d 100644 --- a/homeassistant/components/frontier_silicon/manifest.json +++ b/homeassistant/components/frontier_silicon/manifest.json @@ -2,7 +2,7 @@ "domain": "frontier_silicon", "name": "Frontier Silicon", "documentation": "https://www.home-assistant.io/integrations/frontier_silicon", - "requirements": ["afsapi==0.2.5"], + "requirements": ["afsapi==0.2.6"], "codeowners": ["@wlcrs"], "iot_class": "local_polling" } diff --git a/homeassistant/components/frontier_silicon/media_player.py b/homeassistant/components/frontier_silicon/media_player.py index 61dc6e69726..b16374c0bc0 100644 --- a/homeassistant/components/frontier_silicon/media_player.py +++ b/homeassistant/components/frontier_silicon/media_player.py @@ -179,11 +179,14 @@ class AFSAPIDevice(MediaPlayerEntity): self._attr_media_artist = await afsapi.get_play_artist() self._attr_media_album_name = await afsapi.get_play_album() - self._attr_source = (await afsapi.get_mode()).label + radio_mode = await afsapi.get_mode() + self._attr_source = radio_mode.label if radio_mode is not None else None self._attr_is_volume_muted = await afsapi.get_mute() self._attr_media_image_url = await afsapi.get_play_graphic() - self._attr_sound_mode = (await afsapi.get_eq_preset()).label + + eq_preset = await afsapi.get_eq_preset() + self._attr_sound_mode = eq_preset.label if eq_preset is not None else None volume = await self.fs_device.get_volume() diff --git a/requirements_all.txt b/requirements_all.txt index df753662c8a..bc734b22445 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -89,7 +89,7 @@ adguardhome==0.5.1 advantage_air==0.3.1 # homeassistant.components.frontier_silicon -afsapi==0.2.5 +afsapi==0.2.6 # homeassistant.components.agent_dvr agent-py==0.0.23 From 5c882429d48874f0e24a0f1670cb286e16ce7257 Mon Sep 17 00:00:00 2001 From: hahn-th Date: Tue, 12 Jul 2022 15:31:04 +0200 Subject: [PATCH 057/104] Bump homematicip to 1.0.4 (#75053) --- homeassistant/components/homematicip_cloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/manifest.json b/homeassistant/components/homematicip_cloud/manifest.json index 40f7e67fd07..f6fb9f3e739 100644 --- a/homeassistant/components/homematicip_cloud/manifest.json +++ b/homeassistant/components/homematicip_cloud/manifest.json @@ -3,7 +3,7 @@ "name": "HomematicIP Cloud", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homematicip_cloud", - "requirements": ["homematicip==1.0.3"], + "requirements": ["homematicip==1.0.4"], "codeowners": [], "quality_scale": "platinum", "iot_class": "cloud_push", diff --git a/requirements_all.txt b/requirements_all.txt index bc734b22445..5d99a060bea 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -834,7 +834,7 @@ home-assistant-frontend==20220707.0 homeconnect==0.7.1 # homeassistant.components.homematicip_cloud -homematicip==1.0.3 +homematicip==1.0.4 # homeassistant.components.home_plus_control homepluscontrol==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7c0eaa25b14..790814dfe0f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -601,7 +601,7 @@ home-assistant-frontend==20220707.0 homeconnect==0.7.1 # homeassistant.components.homematicip_cloud -homematicip==1.0.3 +homematicip==1.0.4 # homeassistant.components.home_plus_control homepluscontrol==0.0.5 From 55ae0228a93be4e5e3c1ee803988aea78947c5f8 Mon Sep 17 00:00:00 2001 From: mkmer Date: Wed, 13 Jul 2022 02:46:32 -0400 Subject: [PATCH 058/104] Bump AIOAladdinConnect to 0.1.23 (#75065) --- homeassistant/components/aladdin_connect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/aladdin_connect/manifest.json b/homeassistant/components/aladdin_connect/manifest.json index 3a9e295a08f..bdf906ad200 100644 --- a/homeassistant/components/aladdin_connect/manifest.json +++ b/homeassistant/components/aladdin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "aladdin_connect", "name": "Aladdin Connect", "documentation": "https://www.home-assistant.io/integrations/aladdin_connect", - "requirements": ["AIOAladdinConnect==0.1.21"], + "requirements": ["AIOAladdinConnect==0.1.23"], "codeowners": ["@mkmer"], "iot_class": "cloud_polling", "loggers": ["aladdin_connect"], diff --git a/requirements_all.txt b/requirements_all.txt index 5d99a060bea..89a25bd31d8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,7 +5,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.21 +AIOAladdinConnect==0.1.23 # homeassistant.components.adax Adax-local==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 790814dfe0f..ba4e19393dd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.21 +AIOAladdinConnect==0.1.23 # homeassistant.components.adax Adax-local==0.1.4 From 06e7f718913b483d012257435038b3f6819ea082 Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Wed, 13 Jul 2022 02:56:08 -0400 Subject: [PATCH 059/104] Fix Insteon thermostat issues (#75079) --- homeassistant/components/insteon/climate.py | 2 +- homeassistant/components/insteon/manifest.json | 4 ++-- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/insteon/climate.py b/homeassistant/components/insteon/climate.py index 97e21a02f6f..29c127ba0c7 100644 --- a/homeassistant/components/insteon/climate.py +++ b/homeassistant/components/insteon/climate.py @@ -86,7 +86,7 @@ class InsteonClimateEntity(InsteonEntity, ClimateEntity): @property def temperature_unit(self) -> str: """Return the unit of measurement.""" - if self._insteon_device.properties[CELSIUS].value: + if self._insteon_device.configuration[CELSIUS].value: return TEMP_CELSIUS return TEMP_FAHRENHEIT diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index 1be077a6b38..c48d502c16e 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -4,8 +4,8 @@ "documentation": "https://www.home-assistant.io/integrations/insteon", "dependencies": ["http", "websocket_api"], "requirements": [ - "pyinsteon==1.1.1", - "insteon-frontend-home-assistant==0.1.1" + "pyinsteon==1.1.3", + "insteon-frontend-home-assistant==0.2.0" ], "codeowners": ["@teharris1"], "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 89a25bd31d8..c980c6ca7db 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -891,7 +891,7 @@ influxdb-client==1.24.0 influxdb==5.3.1 # homeassistant.components.insteon -insteon-frontend-home-assistant==0.1.1 +insteon-frontend-home-assistant==0.2.0 # homeassistant.components.intellifire intellifire4py==2.0.1 @@ -1559,7 +1559,7 @@ pyialarm==2.2.0 pyicloud==1.0.0 # homeassistant.components.insteon -pyinsteon==1.1.1 +pyinsteon==1.1.3 # homeassistant.components.intesishome pyintesishome==1.8.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ba4e19393dd..04de268790f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -634,7 +634,7 @@ influxdb-client==1.24.0 influxdb==5.3.1 # homeassistant.components.insteon -insteon-frontend-home-assistant==0.1.1 +insteon-frontend-home-assistant==0.2.0 # homeassistant.components.intellifire intellifire4py==2.0.1 @@ -1053,7 +1053,7 @@ pyialarm==2.2.0 pyicloud==1.0.0 # homeassistant.components.insteon -pyinsteon==1.1.1 +pyinsteon==1.1.3 # homeassistant.components.ipma pyipma==2.0.5 From 98c3bc56b50fea9e30bae5e7955785d51a819fff Mon Sep 17 00:00:00 2001 From: Artem Draft Date: Wed, 13 Jul 2022 10:12:50 +0300 Subject: [PATCH 060/104] Fix missing ordered states in universal media player (#75099) --- homeassistant/components/universal/media_player.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index f33db3827af..352703f8f9b 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -69,11 +69,13 @@ from homeassistant.const import ( SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, + STATE_BUFFERING, STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING, + STATE_STANDBY, STATE_UNAVAILABLE, STATE_UNKNOWN, ) @@ -101,8 +103,10 @@ STATES_ORDER = [ STATE_UNAVAILABLE, STATE_OFF, STATE_IDLE, + STATE_STANDBY, STATE_ON, STATE_PAUSED, + STATE_BUFFERING, STATE_PLAYING, ] ATTRS_SCHEMA = cv.schema_with_slug_keys(cv.string) From b105b0fbcbe9f5ac32fbcfa4c70570b7812e128e Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Wed, 13 Jul 2022 22:05:43 +0200 Subject: [PATCH 061/104] Make sure device tuple is a list on save (#75103) --- homeassistant/components/rfxtrx/config_flow.py | 2 +- tests/components/rfxtrx/test_config_flow.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/rfxtrx/config_flow.py b/homeassistant/components/rfxtrx/config_flow.py index 61d01b8d533..508ca9a7037 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -199,7 +199,7 @@ class OptionsFlow(config_entries.OptionsFlow): if not errors: devices = {} device = { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: list(device_id), } devices[self._selected_device_event_code] = device diff --git a/tests/components/rfxtrx/test_config_flow.py b/tests/components/rfxtrx/test_config_flow.py index 2c695d71d2e..eaa7f1c79a1 100644 --- a/tests/components/rfxtrx/test_config_flow.py +++ b/tests/components/rfxtrx/test_config_flow.py @@ -867,6 +867,9 @@ async def test_options_configure_rfy_cover_device(hass): entry.data["devices"]["0C1a0000010203010000000000"]["venetian_blind_mode"] == "EU" ) + assert isinstance( + entry.data["devices"]["0C1a0000010203010000000000"]["device_id"], list + ) device_registry = dr.async_get(hass) device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) @@ -904,6 +907,9 @@ async def test_options_configure_rfy_cover_device(hass): entry.data["devices"]["0C1a0000010203010000000000"]["venetian_blind_mode"] == "EU" ) + assert isinstance( + entry.data["devices"]["0C1a0000010203010000000000"]["device_id"], list + ) def test_get_serial_by_id_no_dir(): From 9e99ea68fbf9eb7bc4dd5cafc51e72eb6fc8a57b Mon Sep 17 00:00:00 2001 From: kingy444 Date: Thu, 14 Jul 2022 06:06:32 +1000 Subject: [PATCH 062/104] Fix Powerview top shade open position (#75110) --- .../hunterdouglas_powerview/cover.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 3f7d04f5b87..7c4c90a2132 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -455,14 +455,6 @@ class PowerViewShadeTDBUTop(PowerViewShadeTDBU): super().__init__(coordinator, device_info, room_name, shade, name) self._attr_unique_id = f"{self._shade.id}_top" self._attr_name = f"{self._shade_name} Top" - # these shades share a class in parent API - # override open position for top shade - self._shade.open_position = { - ATTR_POSITION1: MIN_POSITION, - ATTR_POSITION2: MAX_POSITION, - ATTR_POSKIND1: POS_KIND_PRIMARY, - ATTR_POSKIND2: POS_KIND_SECONDARY, - } @property def should_poll(self) -> bool: @@ -485,6 +477,21 @@ class PowerViewShadeTDBUTop(PowerViewShadeTDBU): # these need to be inverted to report state correctly in HA return hd_position_to_hass(self.positions.secondary, MAX_POSITION) + @property + def open_position(self) -> PowerviewShadeMove: + """Return the open position and required additional positions.""" + # these shades share a class in parent API + # override open position for top shade + return PowerviewShadeMove( + { + ATTR_POSITION1: MIN_POSITION, + ATTR_POSITION2: MAX_POSITION, + ATTR_POSKIND1: POS_KIND_PRIMARY, + ATTR_POSKIND2: POS_KIND_SECONDARY, + }, + {}, + ) + @callback def _clamp_cover_limit(self, target_hass_position: int) -> int: """Dont allow a cover to go into an impossbile position.""" From 89e87119f27f9175801584011de733ce2a57cdf7 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Wed, 13 Jul 2022 17:13:09 -0400 Subject: [PATCH 063/104] Bump ZHA dependencies (#75133) --- homeassistant/components/zha/manifest.json | 4 ++-- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 78aea7c9fb6..769254511a7 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.31.0", + "bellows==0.31.1", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.77", @@ -12,7 +12,7 @@ "zigpy==0.47.2", "zigpy-xbee==0.15.0", "zigpy-zigate==0.9.0", - "zigpy-znp==0.8.0" + "zigpy-znp==0.8.1" ], "usb": [ { diff --git a/requirements_all.txt b/requirements_all.txt index c980c6ca7db..01e309e7b6a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -393,7 +393,7 @@ beautifulsoup4==4.11.1 # beewi_smartclim==0.0.10 # homeassistant.components.zha -bellows==0.31.0 +bellows==0.31.1 # homeassistant.components.bmw_connected_drive bimmer_connected==0.9.6 @@ -2516,7 +2516,7 @@ zigpy-xbee==0.15.0 zigpy-zigate==0.9.0 # homeassistant.components.zha -zigpy-znp==0.8.0 +zigpy-znp==0.8.1 # homeassistant.components.zha zigpy==0.47.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 04de268790f..9900ef878b4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -308,7 +308,7 @@ base36==0.1.1 beautifulsoup4==4.11.1 # homeassistant.components.zha -bellows==0.31.0 +bellows==0.31.1 # homeassistant.components.bmw_connected_drive bimmer_connected==0.9.6 @@ -1677,7 +1677,7 @@ zigpy-xbee==0.15.0 zigpy-zigate==0.9.0 # homeassistant.components.zha -zigpy-znp==0.8.0 +zigpy-znp==0.8.1 # homeassistant.components.zha zigpy==0.47.2 From f052c3ca7469d94e21881b96aac75300fa4bc915 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 13 Jul 2022 12:52:13 -0600 Subject: [PATCH 064/104] Ensure SimpliSafe diagnostics redact the `code` option (#75137) --- homeassistant/components/simplisafe/diagnostics.py | 3 ++- tests/components/simplisafe/conftest.py | 4 +++- tests/components/simplisafe/test_diagnostics.py | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/diagnostics.py b/homeassistant/components/simplisafe/diagnostics.py index dac89715c10..cd6e4ca52be 100644 --- a/homeassistant/components/simplisafe/diagnostics.py +++ b/homeassistant/components/simplisafe/diagnostics.py @@ -5,7 +5,7 @@ from typing import Any from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_ADDRESS, CONF_LOCATION +from homeassistant.const import CONF_ADDRESS, CONF_CODE, CONF_LOCATION from homeassistant.core import HomeAssistant from . import SimpliSafe @@ -23,6 +23,7 @@ CONF_WIFI_SSID = "wifi_ssid" TO_REDACT = { CONF_ADDRESS, + CONF_CODE, CONF_CREDIT_CARD, CONF_EXPIRES, CONF_LOCATION, diff --git a/tests/components/simplisafe/conftest.py b/tests/components/simplisafe/conftest.py index 56967ac24c5..82bd04a7349 100644 --- a/tests/components/simplisafe/conftest.py +++ b/tests/components/simplisafe/conftest.py @@ -43,7 +43,9 @@ def api_fixture(api_auth_state, data_subscription, system_v3, websocket): @pytest.fixture(name="config_entry") def config_entry_fixture(hass, config, unique_id): """Define a config entry.""" - entry = MockConfigEntry(domain=DOMAIN, unique_id=unique_id, data=config) + entry = MockConfigEntry( + domain=DOMAIN, unique_id=unique_id, data=config, options={CONF_CODE: "1234"} + ) entry.add_to_hass(hass) return entry diff --git a/tests/components/simplisafe/test_diagnostics.py b/tests/components/simplisafe/test_diagnostics.py index 13d5c778e89..446d9d5e9e3 100644 --- a/tests/components/simplisafe/test_diagnostics.py +++ b/tests/components/simplisafe/test_diagnostics.py @@ -8,7 +8,9 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_simplisa """Test config entry diagnostics.""" assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { "entry": { - "options": {}, + "options": { + "code": REDACTED, + }, }, "subscription_data": { "system_123": { From 1555f706e5a12e17c25c7a9dee637073af0e389e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 13 Jul 2022 14:12:53 -0700 Subject: [PATCH 065/104] Block bad pubnub version (#75138) --- homeassistant/package_constraints.txt | 4 ++++ script/gen_requirements_all.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index ad03978c6ef..167fe546b35 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -114,3 +114,7 @@ backoff<2.0 # Breaking change in version # https://github.com/samuelcolvin/pydantic/issues/4092 pydantic!=1.9.1 + +# Breaks asyncio +# https://github.com/pubnub/python/issues/130 +pubnub!=6.4.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 11e88976e83..7b000a30be2 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -132,6 +132,10 @@ backoff<2.0 # Breaking change in version # https://github.com/samuelcolvin/pydantic/issues/4092 pydantic!=1.9.1 + +# Breaks asyncio +# https://github.com/pubnub/python/issues/130 +pubnub!=6.4.0 """ IGNORE_PRE_COMMIT_HOOK_ID = ( From 533ae85a4984468cb66a0630ab227e141b91bb5c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 13 Jul 2022 14:14:13 -0700 Subject: [PATCH 066/104] Bumped version to 2022.7.4 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 7f5cf999b96..9db438feeb3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 7 -PATCH_VERSION: Final = "3" +PATCH_VERSION: Final = "4" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index c86fc6b26f0..b7f75f9a008 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.7.3" +version = "2022.7.4" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From a5693c083f63ba52250bbd973e20e8bd6c3442a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Huryn?= Date: Sun, 3 Jul 2022 17:06:38 +0200 Subject: [PATCH 067/104] Address Blebox uniapi review sidenotes (#74298) * Changes accordingly to sidenotes given by @MartinHjelmare in pull #73834. * Mini version bump according to notes in pull #73834. * Error message fix, test adjustment. --- CODEOWNERS | 4 ++-- homeassistant/components/blebox/const.py | 21 +------------------ homeassistant/components/blebox/cover.py | 18 ++++++++++++++-- homeassistant/components/blebox/light.py | 9 ++++---- homeassistant/components/blebox/manifest.json | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/blebox/test_light.py | 19 +++++++++-------- 8 files changed, 37 insertions(+), 42 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 5f5588c0b91..572ffc11081 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -129,8 +129,8 @@ build.json @home-assistant/supervisor /homeassistant/components/binary_sensor/ @home-assistant/core /tests/components/binary_sensor/ @home-assistant/core /homeassistant/components/bizkaibus/ @UgaitzEtxebarria -/homeassistant/components/blebox/ @bbx-a @bbx-jp @riokuu -/tests/components/blebox/ @bbx-a @bbx-jp @riokuu +/homeassistant/components/blebox/ @bbx-a @riokuu +/tests/components/blebox/ @bbx-a @riokuu /homeassistant/components/blink/ @fronzbot /tests/components/blink/ @fronzbot /homeassistant/components/blueprint/ @home-assistant/core diff --git a/homeassistant/components/blebox/const.py b/homeassistant/components/blebox/const.py index f0c6a2d7586..013a6501068 100644 --- a/homeassistant/components/blebox/const.py +++ b/homeassistant/components/blebox/const.py @@ -3,13 +3,7 @@ from homeassistant.components.cover import CoverDeviceClass from homeassistant.components.sensor import SensorDeviceClass from homeassistant.components.switch import SwitchDeviceClass -from homeassistant.const import ( - STATE_CLOSED, - STATE_CLOSING, - STATE_OPEN, - STATE_OPENING, - TEMP_CELSIUS, -) +from homeassistant.const import TEMP_CELSIUS DOMAIN = "blebox" PRODUCT = "product" @@ -30,19 +24,6 @@ BLEBOX_TO_HASS_DEVICE_CLASSES = { "temperature": SensorDeviceClass.TEMPERATURE, } -BLEBOX_TO_HASS_COVER_STATES = { - None: None, - 0: STATE_CLOSING, # moving down - 1: STATE_OPENING, # moving up - 2: STATE_OPEN, # manually stopped - 3: STATE_CLOSED, # lower limit - 4: STATE_OPEN, # upper limit / open - # gateController - 5: STATE_OPEN, # overload - 6: STATE_OPEN, # motor failure - # 7 is not used - 8: STATE_OPEN, # safety stop -} BLEBOX_TO_UNIT_MAP = {"celsius": TEMP_CELSIUS} diff --git a/homeassistant/components/blebox/cover.py b/homeassistant/components/blebox/cover.py index 368443988a4..8763ec34d34 100644 --- a/homeassistant/components/blebox/cover.py +++ b/homeassistant/components/blebox/cover.py @@ -9,12 +9,26 @@ from homeassistant.components.cover import ( CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPENING +from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import BleBoxEntity, create_blebox_entities -from .const import BLEBOX_TO_HASS_COVER_STATES, BLEBOX_TO_HASS_DEVICE_CLASSES +from .const import BLEBOX_TO_HASS_DEVICE_CLASSES + +BLEBOX_TO_HASS_COVER_STATES = { + None: None, + 0: STATE_CLOSING, # moving down + 1: STATE_OPENING, # moving up + 2: STATE_OPEN, # manually stopped + 3: STATE_CLOSED, # lower limit + 4: STATE_OPEN, # upper limit / open + # gateController + 5: STATE_OPEN, # overload + 6: STATE_OPEN, # motor failure + # 7 is not used + 8: STATE_OPEN, # safety stop +} async def async_setup_entry( diff --git a/homeassistant/components/blebox/light.py b/homeassistant/components/blebox/light.py index 32cc6360db1..2bb6bc91762 100644 --- a/homeassistant/components/blebox/light.py +++ b/homeassistant/components/blebox/light.py @@ -4,7 +4,6 @@ from __future__ import annotations from datetime import timedelta import logging -from blebox_uniapi.error import BadOnValueError import blebox_uniapi.light from blebox_uniapi.light import BleboxColorMode @@ -166,10 +165,10 @@ class BleBoxLightEntity(BleBoxEntity, LightEntity): else: try: await self._feature.async_on(value) - except BadOnValueError as ex: - _LOGGER.error( - "Turning on '%s' failed: Bad value %s (%s)", self.name, value, ex - ) + except ValueError as exc: + raise ValueError( + f"Turning on '{self.name}' failed: Bad value {value}" + ) from exc async def async_turn_off(self, **kwargs): """Turn the light off.""" diff --git a/homeassistant/components/blebox/manifest.json b/homeassistant/components/blebox/manifest.json index 5c57d5f6b9f..08554695316 100644 --- a/homeassistant/components/blebox/manifest.json +++ b/homeassistant/components/blebox/manifest.json @@ -3,8 +3,8 @@ "name": "BleBox devices", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/blebox", - "requirements": ["blebox_uniapi==2.0.0"], - "codeowners": ["@bbx-a", "@bbx-jp", "@riokuu"], + "requirements": ["blebox_uniapi==2.0.1"], + "codeowners": ["@bbx-a", "@riokuu"], "iot_class": "local_polling", "loggers": ["blebox_uniapi"] } diff --git a/requirements_all.txt b/requirements_all.txt index 01e309e7b6a..b2de6fd2584 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -402,7 +402,7 @@ bimmer_connected==0.9.6 bizkaibus==0.1.1 # homeassistant.components.blebox -blebox_uniapi==2.0.0 +blebox_uniapi==2.0.1 # homeassistant.components.blink blinkpy==0.19.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9900ef878b4..5442f40d743 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -314,7 +314,7 @@ bellows==0.31.1 bimmer_connected==0.9.6 # homeassistant.components.blebox -blebox_uniapi==2.0.0 +blebox_uniapi==2.0.1 # homeassistant.components.blink blinkpy==0.19.0 diff --git a/tests/components/blebox/test_light.py b/tests/components/blebox/test_light.py index 4663c216136..f61496714fb 100644 --- a/tests/components/blebox/test_light.py +++ b/tests/components/blebox/test_light.py @@ -509,17 +509,18 @@ async def test_turn_on_failure(feature, hass, config, caplog): caplog.set_level(logging.ERROR) feature_mock, entity_id = feature - feature_mock.async_on = AsyncMock(side_effect=blebox_uniapi.error.BadOnValueError) + feature_mock.async_on = AsyncMock(side_effect=ValueError) await async_setup_entity(hass, config, entity_id) feature_mock.sensible_on_value = 123 - await hass.services.async_call( - "light", - SERVICE_TURN_ON, - {"entity_id": entity_id}, - blocking=True, - ) + with pytest.raises(ValueError) as info: + await hass.services.async_call( + "light", + SERVICE_TURN_ON, + {"entity_id": entity_id}, + blocking=True, + ) - assert ( - f"Turning on '{feature_mock.full_name}' failed: Bad value 123 ()" in caplog.text + assert f"Turning on '{feature_mock.full_name}' failed: Bad value 123" in str( + info.value ) From b1c07ac17a80a3026e4ea57414c375b388f155ab Mon Sep 17 00:00:00 2001 From: Thomas Hollstegge Date: Thu, 14 Jul 2022 21:40:53 +0200 Subject: [PATCH 068/104] Fix Alexa: Only trigger doorbell event on actual state change to "ON" (#74924) * Alexa: Only trigger doorbell event on actual state change to "ON" * Remove unnecessary check for new_state Co-authored-by: Jan Bouwhuis * First check state is `on` before checking the old state Co-authored-by: Jan Bouwhuis --- .../components/alexa/state_report.py | 4 +- tests/components/alexa/test_state_report.py | 50 ++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index d3e476c2e8c..783397ca047 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -86,7 +86,9 @@ async def async_enable_proactive_mode(hass, smart_home_config): return if should_doorbell: - if new_state.state == STATE_ON: + if new_state.state == STATE_ON and ( + old_state is None or old_state.state != STATE_ON + ): await async_send_doorbell_event_message( hass, smart_home_config, alexa_changed_entity ) diff --git a/tests/components/alexa/test_state_report.py b/tests/components/alexa/test_state_report.py index b2d36b9d179..bb61cea2413 100644 --- a/tests/components/alexa/test_state_report.py +++ b/tests/components/alexa/test_state_report.py @@ -346,7 +346,11 @@ async def test_doorbell_event(hass, aioclient_mock): hass.states.async_set( "binary_sensor.test_doorbell", "off", - {"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"}, + { + "friendly_name": "Test Doorbell Sensor", + "device_class": "occupancy", + "linkquality": 42, + }, ) await state_report.async_enable_proactive_mode(hass, get_default_config(hass)) @@ -354,7 +358,21 @@ async def test_doorbell_event(hass, aioclient_mock): hass.states.async_set( "binary_sensor.test_doorbell", "on", - {"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"}, + { + "friendly_name": "Test Doorbell Sensor", + "device_class": "occupancy", + "linkquality": 42, + }, + ) + + hass.states.async_set( + "binary_sensor.test_doorbell", + "on", + { + "friendly_name": "Test Doorbell Sensor", + "device_class": "occupancy", + "linkquality": 99, + }, ) # To trigger event listener @@ -386,6 +404,34 @@ async def test_doorbell_event(hass, aioclient_mock): assert len(aioclient_mock.mock_calls) == 2 +async def test_doorbell_event_from_unknown(hass, aioclient_mock): + """Test doorbell press reports.""" + aioclient_mock.post(TEST_URL, text="", status=202) + + await state_report.async_enable_proactive_mode(hass, get_default_config(hass)) + + hass.states.async_set( + "binary_sensor.test_doorbell", + "on", + { + "friendly_name": "Test Doorbell Sensor", + "device_class": "occupancy", + }, + ) + + # To trigger event listener + await hass.async_block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + call = aioclient_mock.mock_calls + + call_json = call[0][2] + assert call_json["event"]["header"]["namespace"] == "Alexa.DoorbellEventSource" + assert call_json["event"]["header"]["name"] == "DoorbellPress" + assert call_json["event"]["payload"]["cause"]["type"] == "PHYSICAL_INTERACTION" + assert call_json["event"]["endpoint"]["endpointId"] == "binary_sensor#test_doorbell" + + async def test_doorbell_event_fail(hass, aioclient_mock, caplog): """Test proactive state retries once.""" aioclient_mock.post( From b5e24048dbf9ccc4720b19b36353bbccf2aa13a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Huryn?= Date: Thu, 14 Jul 2022 11:21:01 +0200 Subject: [PATCH 069/104] Fix Blebox light scenes (#75106) * Bug fix for light platform, when async_turn_on recieves multiple keys. * Changes according to @MartinHjelmare suggestion. * Moved effect set call in BleBoxLightEntity.async_turn_on method. * Added tests for effect in light platform. Added ValueError raise if effect not in effect list. * Removed duplicated line from test as @MartinHjelmare suggested. --- homeassistant/components/blebox/light.py | 15 +++++--- tests/components/blebox/test_light.py | 46 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/blebox/light.py b/homeassistant/components/blebox/light.py index 2bb6bc91762..a2ad51cfc9c 100644 --- a/homeassistant/components/blebox/light.py +++ b/homeassistant/components/blebox/light.py @@ -159,15 +159,20 @@ class BleBoxLightEntity(BleBoxEntity, LightEntity): else: value = feature.apply_brightness(value, brightness) + try: + await self._feature.async_on(value) + except ValueError as exc: + raise ValueError( + f"Turning on '{self.name}' failed: Bad value {value}" + ) from exc + if effect is not None: - effect_value = self.effect_list.index(effect) - await self._feature.async_api_command("effect", effect_value) - else: try: - await self._feature.async_on(value) + effect_value = self.effect_list.index(effect) + await self._feature.async_api_command("effect", effect_value) except ValueError as exc: raise ValueError( - f"Turning on '{self.name}' failed: Bad value {value}" + f"Turning on with effect '{self.name}' failed: {effect} not in effect list." ) from exc async def async_turn_off(self, **kwargs): diff --git a/tests/components/blebox/test_light.py b/tests/components/blebox/test_light.py index f61496714fb..7afb78e5b03 100644 --- a/tests/components/blebox/test_light.py +++ b/tests/components/blebox/test_light.py @@ -7,6 +7,7 @@ import pytest from homeassistant.components.light import ( ATTR_BRIGHTNESS, + ATTR_EFFECT, ATTR_RGBW_COLOR, ATTR_SUPPORTED_COLOR_MODES, ColorMode, @@ -524,3 +525,48 @@ async def test_turn_on_failure(feature, hass, config, caplog): assert f"Turning on '{feature_mock.full_name}' failed: Bad value 123" in str( info.value ) + + +async def test_wlightbox_on_effect(wlightbox, hass, config): + """Test light on.""" + + feature_mock, entity_id = wlightbox + + def initial_update(): + feature_mock.is_on = False + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + + def turn_on(value): + feature_mock.is_on = True + feature_mock.effect = "POLICE" + + feature_mock.async_on = AsyncMock(side_effect=turn_on) + + with pytest.raises(ValueError) as info: + await hass.services.async_call( + "light", + SERVICE_TURN_ON, + {"entity_id": entity_id, ATTR_EFFECT: "NOT IN LIST"}, + blocking=True, + ) + + assert ( + f"Turning on with effect '{feature_mock.full_name}' failed: NOT IN LIST not in effect list." + in str(info.value) + ) + + await hass.services.async_call( + "light", + SERVICE_TURN_ON, + {"entity_id": entity_id, ATTR_EFFECT: "POLICE"}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_EFFECT] == "POLICE" From 326ffdcd494cce725e1fd82f3125d028e2a21a93 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Fri, 15 Jul 2022 03:24:24 +0800 Subject: [PATCH 070/104] Fix playback of hls cameras in stream (#75166) --- homeassistant/components/stream/__init__.py | 19 +++-- homeassistant/components/stream/core.py | 39 ++++++++-- homeassistant/components/stream/fmp4utils.py | 12 +++ homeassistant/components/stream/worker.py | 78 ++++++++++++++------ tests/components/stream/test_worker.py | 4 +- 5 files changed, 111 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index b842eb7fb78..ef68ea7bcae 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -57,9 +57,15 @@ from .const import ( SOURCE_TIMEOUT, STREAM_RESTART_INCREMENT, STREAM_RESTART_RESET_TIME, - TARGET_SEGMENT_DURATION_NON_LL_HLS, ) -from .core import PROVIDERS, IdleTimer, KeyFrameConverter, StreamOutput, StreamSettings +from .core import ( + PROVIDERS, + STREAM_SETTINGS_NON_LL_HLS, + IdleTimer, + KeyFrameConverter, + StreamOutput, + StreamSettings, +) from .diagnostics import Diagnostics from .hls import HlsStreamOutput, async_setup_hls @@ -224,14 +230,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hls_part_timeout=2 * conf[CONF_PART_DURATION], ) else: - hass.data[DOMAIN][ATTR_SETTINGS] = StreamSettings( - ll_hls=False, - min_segment_duration=TARGET_SEGMENT_DURATION_NON_LL_HLS - - SEGMENT_DURATION_ADJUSTER, - part_target_duration=TARGET_SEGMENT_DURATION_NON_LL_HLS, - hls_advance_part_limit=3, - hls_part_timeout=TARGET_SEGMENT_DURATION_NON_LL_HLS, - ) + hass.data[DOMAIN][ATTR_SETTINGS] = STREAM_SETTINGS_NON_LL_HLS # Setup HLS hls_endpoint = async_setup_hls(hass) diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 09d9a9d5031..8c456af91aa 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -5,6 +5,7 @@ import asyncio from collections import deque from collections.abc import Callable, Coroutine, Iterable import datetime +import logging from typing import TYPE_CHECKING, Any from aiohttp import web @@ -16,13 +17,20 @@ from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers.event import async_call_later from homeassistant.util.decorator import Registry -from .const import ATTR_STREAMS, DOMAIN +from .const import ( + ATTR_STREAMS, + DOMAIN, + SEGMENT_DURATION_ADJUSTER, + TARGET_SEGMENT_DURATION_NON_LL_HLS, +) if TYPE_CHECKING: from av import CodecContext, Packet from . import Stream +_LOGGER = logging.getLogger(__name__) + PROVIDERS: Registry[str, type[StreamOutput]] = Registry() @@ -37,6 +45,15 @@ class StreamSettings: hls_part_timeout: float = attr.ib() +STREAM_SETTINGS_NON_LL_HLS = StreamSettings( + ll_hls=False, + min_segment_duration=TARGET_SEGMENT_DURATION_NON_LL_HLS - SEGMENT_DURATION_ADJUSTER, + part_target_duration=TARGET_SEGMENT_DURATION_NON_LL_HLS, + hls_advance_part_limit=3, + hls_part_timeout=TARGET_SEGMENT_DURATION_NON_LL_HLS, +) + + @attr.s(slots=True) class Part: """Represent a segment part.""" @@ -426,12 +443,22 @@ class KeyFrameConverter: return packet = self.packet self.packet = None - # decode packet (flush afterwards) - frames = self._codec_context.decode(packet) - for _i in range(2): - if frames: + for _ in range(2): # Retry once if codec context needs to be flushed + try: + # decode packet (flush afterwards) + frames = self._codec_context.decode(packet) + for _i in range(2): + if frames: + break + frames = self._codec_context.decode(None) break - frames = self._codec_context.decode(None) + except EOFError: + _LOGGER.debug("Codec context needs flushing, attempting to reopen") + self._codec_context.close() + self._codec_context.open() + else: + _LOGGER.debug("Unable to decode keyframe") + return if frames: frame = frames[0] if width and height: diff --git a/homeassistant/components/stream/fmp4utils.py b/homeassistant/components/stream/fmp4utils.py index f136784cf87..313f5632841 100644 --- a/homeassistant/components/stream/fmp4utils.py +++ b/homeassistant/components/stream/fmp4utils.py @@ -2,6 +2,10 @@ from __future__ import annotations from collections.abc import Generator +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from io import BytesIO def find_box( @@ -135,3 +139,11 @@ def get_codec_string(mp4_bytes: bytes) -> str: codecs.append(codec) return ",".join(codecs) + + +def read_init(bytes_io: BytesIO) -> bytes: + """Read the init from a mp4 file.""" + bytes_io.seek(24) + moov_len = int.from_bytes(bytes_io.read(4), byteorder="big") + bytes_io.seek(0) + return bytes_io.read(24 + moov_len) diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index e46d83542f7..aa49a6de7ae 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -5,11 +5,12 @@ from collections import defaultdict, deque from collections.abc import Callable, Generator, Iterator, Mapping import contextlib import datetime -from io import BytesIO +from io import SEEK_END, BytesIO import logging from threading import Event from typing import Any, cast +import attr import av from homeassistant.core import HomeAssistant @@ -24,8 +25,16 @@ from .const import ( SEGMENT_CONTAINER_FORMAT, SOURCE_TIMEOUT, ) -from .core import KeyFrameConverter, Part, Segment, StreamOutput, StreamSettings +from .core import ( + STREAM_SETTINGS_NON_LL_HLS, + KeyFrameConverter, + Part, + Segment, + StreamOutput, + StreamSettings, +) from .diagnostics import Diagnostics +from .fmp4utils import read_init from .hls import HlsStreamOutput _LOGGER = logging.getLogger(__name__) @@ -108,7 +117,7 @@ class StreamMuxer: hass: HomeAssistant, video_stream: av.video.VideoStream, audio_stream: av.audio.stream.AudioStream | None, - audio_bsf: av.BitStreamFilterContext | None, + audio_bsf: av.BitStreamFilter | None, stream_state: StreamState, stream_settings: StreamSettings, ) -> None: @@ -120,6 +129,7 @@ class StreamMuxer: self._input_video_stream: av.video.VideoStream = video_stream self._input_audio_stream: av.audio.stream.AudioStream | None = audio_stream self._audio_bsf = audio_bsf + self._audio_bsf_context: av.BitStreamFilterContext = None self._output_video_stream: av.video.VideoStream = None self._output_audio_stream: av.audio.stream.AudioStream | None = None self._segment: Segment | None = None @@ -151,7 +161,7 @@ class StreamMuxer: **{ # Removed skip_sidx - see https://github.com/home-assistant/core/pull/39970 # "cmaf" flag replaces several of the movflags used, but too recent to use for now - "movflags": "frag_custom+empty_moov+default_base_moof+frag_discont+negative_cts_offsets+skip_trailer", + "movflags": "frag_custom+empty_moov+default_base_moof+frag_discont+negative_cts_offsets+skip_trailer+delay_moov", # Sometimes the first segment begins with negative timestamps, and this setting just # adjusts the timestamps in the output from that segment to start from 0. Helps from # having to make some adjustments in test_durations @@ -164,7 +174,7 @@ class StreamMuxer: # Fragment durations may exceed the 15% allowed variance but it seems ok **( { - "movflags": "empty_moov+default_base_moof+frag_discont+negative_cts_offsets+skip_trailer", + "movflags": "empty_moov+default_base_moof+frag_discont+negative_cts_offsets+skip_trailer+delay_moov", # Create a fragment every TARGET_PART_DURATION. The data from each fragment is stored in # a "Part" that can be combined with the data from all the other "Part"s, plus an init # section, to reconstitute the data in a "Segment". @@ -194,8 +204,11 @@ class StreamMuxer: # Check if audio is requested output_astream = None if input_astream: + if self._audio_bsf: + self._audio_bsf_context = self._audio_bsf.create() + self._audio_bsf_context.set_input_stream(input_astream) output_astream = container.add_stream( - template=self._audio_bsf or input_astream + template=self._audio_bsf_context or input_astream ) return container, output_vstream, output_astream @@ -238,15 +251,29 @@ class StreamMuxer: self._part_has_keyframe |= packet.is_keyframe elif packet.stream == self._input_audio_stream: - if self._audio_bsf: - self._audio_bsf.send(packet) - while packet := self._audio_bsf.recv(): + if self._audio_bsf_context: + self._audio_bsf_context.send(packet) + while packet := self._audio_bsf_context.recv(): packet.stream = self._output_audio_stream self._av_output.mux(packet) return packet.stream = self._output_audio_stream self._av_output.mux(packet) + def create_segment(self) -> None: + """Create a segment when the moov is ready.""" + self._segment = Segment( + sequence=self._stream_state.sequence, + stream_id=self._stream_state.stream_id, + init=read_init(self._memory_file), + # Fetch the latest StreamOutputs, which may have changed since the + # worker started. + stream_outputs=self._stream_state.outputs, + start_time=self._start_time, + ) + self._memory_file_pos = self._memory_file.tell() + self._memory_file.seek(0, SEEK_END) + def check_flush_part(self, packet: av.Packet) -> None: """Check for and mark a part segment boundary and record its duration.""" if self._memory_file_pos == self._memory_file.tell(): @@ -254,16 +281,10 @@ class StreamMuxer: if self._segment is None: # We have our first non-zero byte position. This means the init has just # been written. Create a Segment and put it to the queue of each output. - self._segment = Segment( - sequence=self._stream_state.sequence, - stream_id=self._stream_state.stream_id, - init=self._memory_file.getvalue(), - # Fetch the latest StreamOutputs, which may have changed since the - # worker started. - stream_outputs=self._stream_state.outputs, - start_time=self._start_time, - ) - self._memory_file_pos = self._memory_file.tell() + self.create_segment() + # When using delay_moov, the moov is not written until a moof is also ready + # Flush the moof + self.flush(packet, last_part=False) else: # These are the ends of the part segments self.flush(packet, last_part=False) @@ -297,6 +318,10 @@ class StreamMuxer: # Closing the av_output will write the remaining buffered data to the # memory_file as a new moof/mdat. self._av_output.close() + # With delay_moov, this may be the first time the file pointer has + # moved, so the segment may not yet have been created + if not self._segment: + self.create_segment() elif not self._part_has_keyframe: # Parts which are not the last part or an independent part should # not have durations below 0.85 of the part target duration. @@ -305,6 +330,9 @@ class StreamMuxer: self._part_start_dts + 0.85 * self._stream_settings.part_target_duration / packet.time_base, ) + # Undo dts adjustments if we don't have ll_hls + if not self._stream_settings.ll_hls: + adjusted_dts = packet.dts assert self._segment self._memory_file.seek(self._memory_file_pos) self._hass.loop.call_soon_threadsafe( @@ -445,10 +473,7 @@ def get_audio_bitstream_filter( _LOGGER.debug( "ADTS AAC detected. Adding aac_adtstoaac bitstream filter" ) - bsf = av.BitStreamFilter("aac_adtstoasc") - bsf_context = bsf.create() - bsf_context.set_input_stream(audio_stream) - return bsf_context + return av.BitStreamFilter("aac_adtstoasc") break return None @@ -489,7 +514,12 @@ def stream_worker( audio_stream = None # Disable ll-hls for hls inputs if container.format.name == "hls": - stream_settings.ll_hls = False + for field in attr.fields(StreamSettings): + setattr( + stream_settings, + field.name, + getattr(STREAM_SETTINGS_NON_LL_HLS, field.name), + ) stream_state.diagnostics.set_value("container_format", container.format.name) stream_state.diagnostics.set_value("video_codec", video_stream.name) if audio_stream: diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index 8717e23a476..65a01051606 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -749,7 +749,9 @@ async def test_durations(hass, worker_finished_stream): }, ) - source = generate_h264_video(duration=SEGMENT_DURATION + 1) + source = generate_h264_video( + duration=round(SEGMENT_DURATION + target_part_duration + 1) + ) worker_finished, mock_stream = worker_finished_stream with patch("homeassistant.components.stream.Stream", wraps=mock_stream): From 674e02914bf035ae0b9053f6c1eabd992f615460 Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Thu, 14 Jul 2022 10:55:24 -0400 Subject: [PATCH 071/104] Bump version of pyunifiprotect to 4.0.10 (#75180) --- homeassistant/components/unifiprotect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 9aeb8b48050..35dafc96927 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -3,7 +3,7 @@ "name": "UniFi Protect", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/unifiprotect", - "requirements": ["pyunifiprotect==4.0.9", "unifi-discovery==1.1.4"], + "requirements": ["pyunifiprotect==4.0.10", "unifi-discovery==1.1.4"], "dependencies": ["http"], "codeowners": ["@briis", "@AngellusMortis", "@bdraco"], "quality_scale": "platinum", diff --git a/requirements_all.txt b/requirements_all.txt index b2de6fd2584..e4d2125bc36 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1993,7 +1993,7 @@ pytrafikverket==0.2.0.1 pyudev==0.22.0 # homeassistant.components.unifiprotect -pyunifiprotect==4.0.9 +pyunifiprotect==4.0.10 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5442f40d743..341409a28a7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1331,7 +1331,7 @@ pytrafikverket==0.2.0.1 pyudev==0.22.0 # homeassistant.components.unifiprotect -pyunifiprotect==4.0.9 +pyunifiprotect==4.0.10 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 From d401faac7c1e55067153fc8f74395a14c939671d Mon Sep 17 00:00:00 2001 From: mkmer Date: Thu, 14 Jul 2022 15:14:41 -0400 Subject: [PATCH 072/104] Bumped AIOAladdin Connect to 0.1.24 (#75182) --- homeassistant/components/aladdin_connect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/aladdin_connect/manifest.json b/homeassistant/components/aladdin_connect/manifest.json index bdf906ad200..8ef86a76bf8 100644 --- a/homeassistant/components/aladdin_connect/manifest.json +++ b/homeassistant/components/aladdin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "aladdin_connect", "name": "Aladdin Connect", "documentation": "https://www.home-assistant.io/integrations/aladdin_connect", - "requirements": ["AIOAladdinConnect==0.1.23"], + "requirements": ["AIOAladdinConnect==0.1.24"], "codeowners": ["@mkmer"], "iot_class": "cloud_polling", "loggers": ["aladdin_connect"], diff --git a/requirements_all.txt b/requirements_all.txt index e4d2125bc36..aec20c76564 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,7 +5,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.23 +AIOAladdinConnect==0.1.24 # homeassistant.components.adax Adax-local==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 341409a28a7..eb71ac5d06f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.23 +AIOAladdinConnect==0.1.24 # homeassistant.components.adax Adax-local==0.1.4 From 5eaa15138c75b950e1fb85415d5207026c0c5cce Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Thu, 14 Jul 2022 15:15:51 -0400 Subject: [PATCH 073/104] Bump zigpy from 0.47.2 to 0.47.3 (#75194) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 769254511a7..c43958f7e46 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -9,7 +9,7 @@ "pyserial-asyncio==0.6", "zha-quirks==0.0.77", "zigpy-deconz==0.18.0", - "zigpy==0.47.2", + "zigpy==0.47.3", "zigpy-xbee==0.15.0", "zigpy-zigate==0.9.0", "zigpy-znp==0.8.1" diff --git a/requirements_all.txt b/requirements_all.txt index aec20c76564..e7f719510a6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2519,7 +2519,7 @@ zigpy-zigate==0.9.0 zigpy-znp==0.8.1 # homeassistant.components.zha -zigpy==0.47.2 +zigpy==0.47.3 # homeassistant.components.zoneminder zm-py==0.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index eb71ac5d06f..1c26f68f499 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1680,7 +1680,7 @@ zigpy-zigate==0.9.0 zigpy-znp==0.8.1 # homeassistant.components.zha -zigpy==0.47.2 +zigpy==0.47.3 # homeassistant.components.zwave_js zwave-js-server-python==0.39.0 From b231eea0c6e97d96ba2dc1a26e702bdb5d705465 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Thu, 14 Jul 2022 13:38:22 -0400 Subject: [PATCH 074/104] Skip `iso4217` version 1.10, which includes a broken `__init__.pyi` file (#75200) --- homeassistant/package_constraints.txt | 4 ++++ script/gen_requirements_all.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 167fe546b35..207268404a5 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -118,3 +118,7 @@ pydantic!=1.9.1 # Breaks asyncio # https://github.com/pubnub/python/issues/130 pubnub!=6.4.0 + +# Package's __init__.pyi stub has invalid syntax and breaks mypy +# https://github.com/dahlia/iso4217/issues/16 +iso4217!=1.10.20220401 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 7b000a30be2..7f77f4ae106 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -136,6 +136,10 @@ pydantic!=1.9.1 # Breaks asyncio # https://github.com/pubnub/python/issues/130 pubnub!=6.4.0 + +# Package's __init__.pyi stub has invalid syntax and breaks mypy +# https://github.com/dahlia/iso4217/issues/16 +iso4217!=1.10.20220401 """ IGNORE_PRE_COMMIT_HOOK_ID = ( From 62d77a135b4f89caf59684fe57de4e57b66d454b Mon Sep 17 00:00:00 2001 From: Khole Date: Thu, 14 Jul 2022 19:50:35 +0100 Subject: [PATCH 075/104] Fix Hive power unit of measurement (#75210) --- homeassistant/components/hive/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index 5bac23fdb3d..d985f1a83bb 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -8,7 +8,7 @@ from homeassistant.components.sensor import ( SensorStateClass, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE, POWER_KILO_WATT +from homeassistant.const import PERCENTAGE, POWER_WATT from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -26,7 +26,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( ), SensorEntityDescription( key="Power", - native_unit_of_measurement=POWER_KILO_WATT, + native_unit_of_measurement=POWER_WATT, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.POWER, ), From c56caca1828c60943ff6936a47cc6deca5ca7cd5 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 14 Jul 2022 18:12:48 -0500 Subject: [PATCH 076/104] Bump frontend to 20220707.1 (#75232) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 5c7fd1a20be..288b4796e0e 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20220707.0"], + "requirements": ["home-assistant-frontend==20220707.1"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 207268404a5..d27d9382c37 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ ciso8601==2.2.0 cryptography==36.0.2 fnvhash==0.1.0 hass-nabucasa==0.54.1 -home-assistant-frontend==20220707.0 +home-assistant-frontend==20220707.1 httpx==0.23.0 ifaddr==0.1.7 jinja2==3.1.2 diff --git a/requirements_all.txt b/requirements_all.txt index e7f719510a6..965c6bfd1e2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -828,7 +828,7 @@ hole==0.7.0 holidays==0.14.2 # homeassistant.components.frontend -home-assistant-frontend==20220707.0 +home-assistant-frontend==20220707.1 # homeassistant.components.home_connect homeconnect==0.7.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1c26f68f499..a02857ab353 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -595,7 +595,7 @@ hole==0.7.0 holidays==0.14.2 # homeassistant.components.frontend -home-assistant-frontend==20220707.0 +home-assistant-frontend==20220707.1 # homeassistant.components.home_connect homeconnect==0.7.1 From e4f1fa48d315e2fce6516fbeccfd073e87e581a0 Mon Sep 17 00:00:00 2001 From: mkmer Date: Fri, 15 Jul 2022 00:28:00 -0400 Subject: [PATCH 077/104] Bump AIOAladdinConnect to 0.1.25 (#75235) Co-authored-by: Paulus Schoutsen --- homeassistant/components/aladdin_connect/__init__.py | 6 ++++-- homeassistant/components/aladdin_connect/config_flow.py | 7 +++++-- homeassistant/components/aladdin_connect/const.py | 1 + homeassistant/components/aladdin_connect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/aladdin_connect/__init__.py b/homeassistant/components/aladdin_connect/__init__.py index af996c9f5b2..82b0c8ec692 100644 --- a/homeassistant/components/aladdin_connect/__init__.py +++ b/homeassistant/components/aladdin_connect/__init__.py @@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import DOMAIN +from .const import CLIENT_ID, DOMAIN _LOGGER: Final = logging.getLogger(__name__) @@ -23,7 +23,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up platform from a ConfigEntry.""" username = entry.data[CONF_USERNAME] password = entry.data[CONF_PASSWORD] - acc = AladdinConnectClient(username, password, async_get_clientsession(hass)) + acc = AladdinConnectClient( + username, password, async_get_clientsession(hass), CLIENT_ID + ) try: if not await acc.login(): raise ConfigEntryAuthFailed("Incorrect Password") diff --git a/homeassistant/components/aladdin_connect/config_flow.py b/homeassistant/components/aladdin_connect/config_flow.py index 0d45ea9a8ef..44d3f01a9ec 100644 --- a/homeassistant/components/aladdin_connect/config_flow.py +++ b/homeassistant/components/aladdin_connect/config_flow.py @@ -18,7 +18,7 @@ from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import DOMAIN +from .const import CLIENT_ID, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -38,7 +38,10 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None: Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. """ acc = AladdinConnectClient( - data[CONF_USERNAME], data[CONF_PASSWORD], async_get_clientsession(hass) + data[CONF_USERNAME], + data[CONF_PASSWORD], + async_get_clientsession(hass), + CLIENT_ID, ) login = await acc.login() await acc.close() diff --git a/homeassistant/components/aladdin_connect/const.py b/homeassistant/components/aladdin_connect/const.py index 7a11cf63a9e..46d5d468f71 100644 --- a/homeassistant/components/aladdin_connect/const.py +++ b/homeassistant/components/aladdin_connect/const.py @@ -18,3 +18,4 @@ STATES_MAP: Final[dict[str, str]] = { DOMAIN = "aladdin_connect" SUPPORTED_FEATURES: Final = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE +CLIENT_ID = "1000" diff --git a/homeassistant/components/aladdin_connect/manifest.json b/homeassistant/components/aladdin_connect/manifest.json index 8ef86a76bf8..5baeba33971 100644 --- a/homeassistant/components/aladdin_connect/manifest.json +++ b/homeassistant/components/aladdin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "aladdin_connect", "name": "Aladdin Connect", "documentation": "https://www.home-assistant.io/integrations/aladdin_connect", - "requirements": ["AIOAladdinConnect==0.1.24"], + "requirements": ["AIOAladdinConnect==0.1.25"], "codeowners": ["@mkmer"], "iot_class": "cloud_polling", "loggers": ["aladdin_connect"], diff --git a/requirements_all.txt b/requirements_all.txt index 965c6bfd1e2..ea539b9339f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,7 +5,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.24 +AIOAladdinConnect==0.1.25 # homeassistant.components.adax Adax-local==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a02857ab353..02b6d9308ad 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.24 +AIOAladdinConnect==0.1.25 # homeassistant.components.adax Adax-local==0.1.4 From 99d39b203f831732d631d7baad0208dda1e4621c Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Thu, 14 Jul 2022 22:29:11 -0600 Subject: [PATCH 078/104] Bump pylitterbot to 2022.7.0 (#75241) --- homeassistant/components/litterrobot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/litterrobot/manifest.json b/homeassistant/components/litterrobot/manifest.json index a07f13a47b5..104a06afc8f 100644 --- a/homeassistant/components/litterrobot/manifest.json +++ b/homeassistant/components/litterrobot/manifest.json @@ -3,7 +3,7 @@ "name": "Litter-Robot", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/litterrobot", - "requirements": ["pylitterbot==2022.3.0"], + "requirements": ["pylitterbot==2022.7.0"], "codeowners": ["@natekspencer"], "iot_class": "cloud_polling", "loggers": ["pylitterbot"] diff --git a/requirements_all.txt b/requirements_all.txt index ea539b9339f..bb4bad5d959 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1625,7 +1625,7 @@ pylibrespot-java==0.1.0 pylitejet==0.3.0 # homeassistant.components.litterrobot -pylitterbot==2022.3.0 +pylitterbot==2022.7.0 # homeassistant.components.lutron_caseta pylutron-caseta==0.13.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 02b6d9308ad..79318dc1789 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1101,7 +1101,7 @@ pylibrespot-java==0.1.0 pylitejet==0.3.0 # homeassistant.components.litterrobot -pylitterbot==2022.3.0 +pylitterbot==2022.7.0 # homeassistant.components.lutron_caseta pylutron-caseta==0.13.1 From c185e636ed7ef3c8d47fbc02238489cc576a3793 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 14 Jul 2022 21:31:07 -0700 Subject: [PATCH 079/104] Bumped version to 2022.7.5 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 9db438feeb3..a2d71ea6d71 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 7 -PATCH_VERSION: Final = "4" +PATCH_VERSION: Final = "5" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index b7f75f9a008..c68cd921da2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.7.4" +version = "2022.7.5" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From a23b427025579a8be7d1cb5db13eb58ebdd0f963 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Thu, 14 Jul 2022 22:01:18 -0700 Subject: [PATCH 080/104] Remove nest mac prefix that matches cast devices (#75108) --- homeassistant/components/nest/manifest.json | 3 +-- homeassistant/generated/dhcp.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index d0588d46f06..72e0aed8420 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -11,8 +11,7 @@ "dhcp": [ { "macaddress": "18B430*" }, { "macaddress": "641666*" }, - { "macaddress": "D8EB46*" }, - { "macaddress": "1C53F9*" } + { "macaddress": "D8EB46*" } ], "iot_class": "cloud_push", "loggers": ["google_nest_sdm", "nest"] diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index e9cf6ca4c06..71c8dd530c2 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -69,7 +69,6 @@ DHCP: list[dict[str, str | bool]] = [ {'domain': 'nest', 'macaddress': '18B430*'}, {'domain': 'nest', 'macaddress': '641666*'}, {'domain': 'nest', 'macaddress': 'D8EB46*'}, - {'domain': 'nest', 'macaddress': '1C53F9*'}, {'domain': 'nexia', 'hostname': 'xl857-*', 'macaddress': '000231*'}, {'domain': 'nuheat', 'hostname': 'nuheat', 'macaddress': '002338*'}, {'domain': 'nuki', 'hostname': 'nuki_bridge_*'}, From fdaaed652384c43a0e8f17f630fa82fe20bc59cb Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 18 Jul 2022 10:20:49 -0400 Subject: [PATCH 081/104] Fix ZHA light turn on issues (#75220) * rename variable * default transition is for color commands not level * no extra command for groups * don't transition color change when light off -> on * clean up * update condition * fix condition again... * simplify * simplify * missed one * rename * simplify * rename * tests * color_provided_while_off with no changes * fix missing flag clear * more tests for transition scenarios * add to comment * fix comment * don't transition when force on is set * stale comment * dont transition when colors don't change * remove extra line * remove debug print :) * fix colors * restore color to 65535 until investigated --- homeassistant/components/zha/light.py | 98 ++-- tests/components/zha/test_light.py | 811 +++++++++++++++++++++++++- 2 files changed, 868 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index e1c85b39d8e..cc8dc475c43 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -73,7 +73,6 @@ CAPABILITIES_COLOR_LOOP = 0x4 CAPABILITIES_COLOR_XY = 0x08 CAPABILITIES_COLOR_TEMP = 0x10 -DEFAULT_TRANSITION = 1 DEFAULT_MIN_BRIGHTNESS = 2 UPDATE_COLORLOOP_ACTION = 0x1 @@ -119,7 +118,7 @@ class BaseLight(LogMixin, light.LightEntity): """Operations common to all light entities.""" _FORCE_ON = False - _DEFAULT_COLOR_FROM_OFF_TRANSITION = 0 + _DEFAULT_MIN_TRANSITION_TIME = 0 def __init__(self, *args, **kwargs): """Initialize the light.""" @@ -140,7 +139,7 @@ class BaseLight(LogMixin, light.LightEntity): self._level_channel = None self._color_channel = None self._identify_channel = None - self._default_transition = None + self._zha_config_transition = self._DEFAULT_MIN_TRANSITION_TIME self._attr_color_mode = ColorMode.UNKNOWN # Set by sub classes @property @@ -216,33 +215,49 @@ class BaseLight(LogMixin, light.LightEntity): transition = kwargs.get(light.ATTR_TRANSITION) duration = ( transition * 10 - if transition - else self._default_transition * 10 - if self._default_transition - else DEFAULT_TRANSITION - ) + if transition is not None + else self._zha_config_transition * 10 + ) or self._DEFAULT_MIN_TRANSITION_TIME # if 0 is passed in some devices still need the minimum default brightness = kwargs.get(light.ATTR_BRIGHTNESS) effect = kwargs.get(light.ATTR_EFFECT) flash = kwargs.get(light.ATTR_FLASH) + temperature = kwargs.get(light.ATTR_COLOR_TEMP) + hs_color = kwargs.get(light.ATTR_HS_COLOR) # If the light is currently off but a turn_on call with a color/temperature is sent, # the light needs to be turned on first at a low brightness level where the light is immediately transitioned # to the correct color. Afterwards, the transition is only from the low brightness to the new brightness. # Otherwise, the transition is from the color the light had before being turned on to the new color. - # This can look especially bad with transitions longer than a second. - color_provided_from_off = ( - not self._state + # This can look especially bad with transitions longer than a second. We do not want to do this for + # devices that need to be forced to use the on command because we would end up with 4 commands sent: + # move to level, on, color, move to level... We also will not set this if the bulb is already in the + # desired color mode with the desired color or color temperature. + new_color_provided_while_off = ( + not isinstance(self, LightGroup) + and not self._FORCE_ON + and not self._state + and ( + ( + temperature is not None + and ( + self._color_temp != temperature + or self._attr_color_mode != ColorMode.COLOR_TEMP + ) + ) + or ( + hs_color is not None + and ( + self.hs_color != hs_color + or self._attr_color_mode != ColorMode.HS + ) + ) + ) and brightness_supported(self._attr_supported_color_modes) - and (light.ATTR_COLOR_TEMP in kwargs or light.ATTR_HS_COLOR in kwargs) ) - final_duration = duration - if color_provided_from_off: - # Set the duration for the color changing commands to 0. - duration = 0 if ( brightness is None - and (self._off_with_transition or color_provided_from_off) + and (self._off_with_transition or new_color_provided_while_off) and self._off_brightness is not None ): brightness = self._off_brightness @@ -254,11 +269,11 @@ class BaseLight(LogMixin, light.LightEntity): t_log = {} - if color_provided_from_off: + if new_color_provided_while_off: # If the light is currently off, we first need to turn it on at a low brightness level with no transition. # After that, we set it to the desired color/temperature with no transition. result = await self._level_channel.move_to_level_with_on_off( - DEFAULT_MIN_BRIGHTNESS, self._DEFAULT_COLOR_FROM_OFF_TRANSITION + DEFAULT_MIN_BRIGHTNESS, self._DEFAULT_MIN_TRANSITION_TIME ) t_log["move_to_level_with_on_off"] = result if isinstance(result, Exception) or result[1] is not Status.SUCCESS: @@ -269,7 +284,7 @@ class BaseLight(LogMixin, light.LightEntity): if ( (brightness is not None or transition) - and not color_provided_from_off + and not new_color_provided_while_off and brightness_supported(self._attr_supported_color_modes) ): result = await self._level_channel.move_to_level_with_on_off( @@ -285,7 +300,7 @@ class BaseLight(LogMixin, light.LightEntity): if ( brightness is None - and not color_provided_from_off + and not new_color_provided_while_off or (self._FORCE_ON and brightness) ): # since some lights don't always turn on with move_to_level_with_on_off, @@ -297,9 +312,13 @@ class BaseLight(LogMixin, light.LightEntity): return self._state = True - if light.ATTR_COLOR_TEMP in kwargs: - temperature = kwargs[light.ATTR_COLOR_TEMP] - result = await self._color_channel.move_to_color_temp(temperature, duration) + if temperature is not None: + result = await self._color_channel.move_to_color_temp( + temperature, + self._DEFAULT_MIN_TRANSITION_TIME + if new_color_provided_while_off + else duration, + ) t_log["move_to_color_temp"] = result if isinstance(result, Exception) or result[1] is not Status.SUCCESS: self.debug("turned on: %s", t_log) @@ -308,11 +327,14 @@ class BaseLight(LogMixin, light.LightEntity): self._color_temp = temperature self._hs_color = None - if light.ATTR_HS_COLOR in kwargs: - hs_color = kwargs[light.ATTR_HS_COLOR] + if hs_color is not None: xy_color = color_util.color_hs_to_xy(*hs_color) result = await self._color_channel.move_to_color( - int(xy_color[0] * 65535), int(xy_color[1] * 65535), duration + int(xy_color[0] * 65535), + int(xy_color[1] * 65535), + self._DEFAULT_MIN_TRANSITION_TIME + if new_color_provided_while_off + else duration, ) t_log["move_to_color"] = result if isinstance(result, Exception) or result[1] is not Status.SUCCESS: @@ -322,9 +344,9 @@ class BaseLight(LogMixin, light.LightEntity): self._hs_color = hs_color self._color_temp = None - if color_provided_from_off: + if new_color_provided_while_off: # The light is has the correct color, so we can now transition it to the correct brightness level. - result = await self._level_channel.move_to_level(level, final_duration) + result = await self._level_channel.move_to_level(level, duration) t_log["move_to_level_if_color"] = result if isinstance(result, Exception) or result[1] is not Status.SUCCESS: self.debug("turned on: %s", t_log) @@ -371,12 +393,13 @@ class BaseLight(LogMixin, light.LightEntity): async def async_turn_off(self, **kwargs): """Turn the entity off.""" - duration = kwargs.get(light.ATTR_TRANSITION) + transition = kwargs.get(light.ATTR_TRANSITION) supports_level = brightness_supported(self._attr_supported_color_modes) - if duration and supports_level: + # is not none looks odd here but it will override built in bulb transition times if we pass 0 in here + if transition is not None and supports_level: result = await self._level_channel.move_to_level_with_on_off( - 0, duration * 10 + 0, transition * 10 ) else: result = await self._on_off_channel.off() @@ -387,7 +410,7 @@ class BaseLight(LogMixin, light.LightEntity): if supports_level: # store current brightness so that the next turn_on uses it. - self._off_with_transition = bool(duration) + self._off_with_transition = transition is not None self._off_brightness = self._brightness self.async_write_ha_state() @@ -460,7 +483,7 @@ class Light(BaseLight, ZhaEntity): if effect_list: self._effect_list = effect_list - self._default_transition = async_get_zha_config_value( + self._zha_config_transition = async_get_zha_config_value( zha_device.gateway.config_entry, ZHA_OPTIONS, CONF_DEFAULT_LIGHT_TRANSITION, @@ -472,6 +495,7 @@ class Light(BaseLight, ZhaEntity): """Set the state.""" self._state = bool(value) if value: + self._off_with_transition = False self._off_brightness = None self.async_write_ha_state() @@ -605,7 +629,7 @@ class HueLight(Light): @STRICT_MATCH( channel_names=CHANNEL_ON_OFF, aux_channels={CHANNEL_COLOR, CHANNEL_LEVEL}, - manufacturers={"Jasco", "Quotra-Vision"}, + manufacturers={"Jasco", "Quotra-Vision", "eWeLight", "eWeLink"}, ) class ForceOnLight(Light): """Representation of a light which does not respect move_to_level_with_on_off.""" @@ -621,7 +645,7 @@ class ForceOnLight(Light): class SengledLight(Light): """Representation of a Sengled light which does not react to move_to_color_temp with 0 as a transition.""" - _DEFAULT_COLOR_FROM_OFF_TRANSITION = 1 + _DEFAULT_MIN_TRANSITION_TIME = 1 @GROUP_MATCH() @@ -639,7 +663,7 @@ class LightGroup(BaseLight, ZhaGroupEntity): self._color_channel = group.endpoint[Color.cluster_id] self._identify_channel = group.endpoint[Identify.cluster_id] self._debounced_member_refresh = None - self._default_transition = async_get_zha_config_value( + self._zha_config_transition = async_get_zha_config_value( zha_device.gateway.config_entry, ZHA_OPTIONS, CONF_DEFAULT_LIGHT_TRANSITION, diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index dd6df0dff19..982ff622341 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -15,7 +15,11 @@ from homeassistant.components.light import ( ColorMode, ) from homeassistant.components.zha.core.group import GroupMember -from homeassistant.components.zha.light import FLASH_EFFECTS +from homeassistant.components.zha.light import ( + CAPABILITIES_COLOR_TEMP, + CAPABILITIES_COLOR_XY, + FLASH_EFFECTS, +) from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform import homeassistant.util.dt as dt_util @@ -142,6 +146,10 @@ async def device_light_1(hass, zigpy_device_mock, zha_device_joined): ieee=IEEE_GROUPABLE_DEVICE, nwk=0xB79D, ) + color_cluster = zigpy_device.endpoints[1].light_color + color_cluster.PLUGGED_ATTR_READS = { + "color_capabilities": CAPABILITIES_COLOR_TEMP | CAPABILITIES_COLOR_XY + } zha_device = await zha_device_joined(zigpy_device) zha_device.available = True return zha_device @@ -167,8 +175,13 @@ async def device_light_2(hass, zigpy_device_mock, zha_device_joined): } }, ieee=IEEE_GROUPABLE_DEVICE2, + manufacturer="Sengled", nwk=0xC79E, ) + color_cluster = zigpy_device.endpoints[1].light_color + color_cluster.PLUGGED_ATTR_READS = { + "color_capabilities": CAPABILITIES_COLOR_TEMP | CAPABILITIES_COLOR_XY + } zha_device = await zha_device_joined(zigpy_device) zha_device.available = True return zha_device @@ -201,6 +214,38 @@ async def device_light_3(hass, zigpy_device_mock, zha_device_joined): return zha_device +@pytest.fixture +async def eWeLink_light(hass, zigpy_device_mock, zha_device_joined): + """Mock eWeLink light.""" + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [ + general.OnOff.cluster_id, + general.LevelControl.cluster_id, + lighting.Color.cluster_id, + general.Groups.cluster_id, + general.Identify.cluster_id, + ], + SIG_EP_OUTPUT: [], + SIG_EP_TYPE: zha.DeviceType.COLOR_DIMMABLE_LIGHT, + SIG_EP_PROFILE: zha.PROFILE_ID, + } + }, + ieee="03:2d:6f:00:0a:90:69:e3", + manufacturer="eWeLink", + nwk=0xB79D, + ) + color_cluster = zigpy_device.endpoints[1].light_color + color_cluster.PLUGGED_ATTR_READS = { + "color_capabilities": CAPABILITIES_COLOR_TEMP | CAPABILITIES_COLOR_XY + } + zha_device = await zha_device_joined(zigpy_device) + zha_device.available = True + return zha_device + + async def test_light_refresh(hass, zigpy_device_mock, zha_device_joined_restored): """Test zha light platform refresh.""" @@ -323,6 +368,758 @@ async def test_light( await async_test_flash_from_hass(hass, cluster_identify, entity_id, FLASH_LONG) +@patch( + "zigpy.zcl.clusters.lighting.Color.request", + new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), +) +@patch( + "zigpy.zcl.clusters.general.Identify.request", + new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), +) +@patch( + "zigpy.zcl.clusters.general.LevelControl.request", + new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), +) +@patch( + "zigpy.zcl.clusters.general.OnOff.request", + new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), +) +async def test_transitions( + hass, device_light_1, device_light_2, eWeLink_light, coordinator +): + """Test ZHA light transition code.""" + zha_gateway = get_zha_gateway(hass) + assert zha_gateway is not None + zha_gateway.coordinator_zha_device = coordinator + coordinator._zha_gateway = zha_gateway + device_light_1._zha_gateway = zha_gateway + device_light_2._zha_gateway = zha_gateway + member_ieee_addresses = [device_light_1.ieee, device_light_2.ieee] + members = [GroupMember(device_light_1.ieee, 1), GroupMember(device_light_2.ieee, 1)] + + assert coordinator.is_coordinator + + # test creating a group with 2 members + zha_group = await zha_gateway.async_create_zigpy_group("Test Group", members) + await hass.async_block_till_done() + + assert zha_group is not None + assert len(zha_group.members) == 2 + for member in zha_group.members: + assert member.device.ieee in member_ieee_addresses + assert member.group == zha_group + assert member.endpoint is not None + + device_1_entity_id = await find_entity_id(Platform.LIGHT, device_light_1, hass) + device_2_entity_id = await find_entity_id(Platform.LIGHT, device_light_2, hass) + eWeLink_light_entity_id = await find_entity_id(Platform.LIGHT, eWeLink_light, hass) + assert device_1_entity_id != device_2_entity_id + + group_entity_id = async_find_group_entity_id(hass, Platform.LIGHT, zha_group) + assert hass.states.get(group_entity_id) is not None + + assert device_1_entity_id in zha_group.member_entity_ids + assert device_2_entity_id in zha_group.member_entity_ids + + dev1_cluster_on_off = device_light_1.device.endpoints[1].on_off + dev2_cluster_on_off = device_light_2.device.endpoints[1].on_off + eWeLink_cluster_on_off = eWeLink_light.device.endpoints[1].on_off + + dev1_cluster_level = device_light_1.device.endpoints[1].level + dev2_cluster_level = device_light_2.device.endpoints[1].level + eWeLink_cluster_level = eWeLink_light.device.endpoints[1].level + + dev1_cluster_color = device_light_1.device.endpoints[1].light_color + dev2_cluster_color = device_light_2.device.endpoints[1].light_color + eWeLink_cluster_color = eWeLink_light.device.endpoints[1].light_color + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, [device_light_1, device_light_2]) + await async_wait_for_updates(hass) + + # test that the lights were created and are off + group_state = hass.states.get(group_entity_id) + assert group_state.state == STATE_OFF + light1_state = hass.states.get(device_1_entity_id) + assert light1_state.state == STATE_OFF + light2_state = hass.states.get(device_2_entity_id) + assert light2_state.state == STATE_OFF + + # first test 0 length transition with no color provided + dev1_cluster_on_off.request.reset_mock() + dev1_cluster_level.request.reset_mock() + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {"entity_id": device_1_entity_id, "transition": 0, "brightness": 50}, + blocking=True, + ) + assert dev1_cluster_on_off.request.call_count == 0 + assert dev1_cluster_on_off.request.await_count == 0 + assert dev1_cluster_color.request.call_count == 0 + assert dev1_cluster_color.request.await_count == 0 + assert dev1_cluster_level.request.call_count == 1 + assert dev1_cluster_level.request.await_count == 1 + assert dev1_cluster_level.request.call_args == call( + False, + 4, + dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema, + 50, # brightness (level in ZCL) + 0, # transition time + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + light1_state = hass.states.get(device_1_entity_id) + assert light1_state.state == STATE_ON + assert light1_state.attributes["brightness"] == 50 + + dev1_cluster_level.request.reset_mock() + + # test non 0 length transition with color provided while light is on + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + { + "entity_id": device_1_entity_id, + "transition": 3, + "brightness": 18, + "color_temp": 432, + }, + blocking=True, + ) + assert dev1_cluster_on_off.request.call_count == 0 + assert dev1_cluster_on_off.request.await_count == 0 + assert dev1_cluster_color.request.call_count == 1 + assert dev1_cluster_color.request.await_count == 1 + assert dev1_cluster_level.request.call_count == 1 + assert dev1_cluster_level.request.await_count == 1 + assert dev1_cluster_level.request.call_args == call( + False, + 4, + dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema, + 18, # brightness (level in ZCL) + 30, # transition time (ZCL time in 10ths of a second) + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + assert dev1_cluster_color.request.call_args == call( + False, + 10, + dev1_cluster_color.commands_by_name["move_to_color_temp"].schema, + 432, # color temp mireds + 30.0, # transition time (ZCL time in 10ths of a second) + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + light1_state = hass.states.get(device_1_entity_id) + assert light1_state.state == STATE_ON + assert light1_state.attributes["brightness"] == 18 + assert light1_state.attributes["color_temp"] == 432 + assert light1_state.attributes["color_mode"] == ColorMode.COLOR_TEMP + + dev1_cluster_level.request.reset_mock() + dev1_cluster_color.request.reset_mock() + + # test 0 length transition to turn light off + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_off", + { + "entity_id": device_1_entity_id, + "transition": 0, + }, + blocking=True, + ) + assert dev1_cluster_on_off.request.call_count == 0 + assert dev1_cluster_on_off.request.await_count == 0 + assert dev1_cluster_color.request.call_count == 0 + assert dev1_cluster_color.request.await_count == 0 + assert dev1_cluster_level.request.call_count == 1 + assert dev1_cluster_level.request.await_count == 1 + assert dev1_cluster_level.request.call_args == call( + False, + 4, + dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema, + 0, # brightness (level in ZCL) + 0, # transition time (ZCL time in 10ths of a second) + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + light1_state = hass.states.get(device_1_entity_id) + assert light1_state.state == STATE_OFF + + dev1_cluster_level.request.reset_mock() + + # test non 0 length transition and color temp while turning light on (color_provided_while_off) + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + { + "entity_id": device_1_entity_id, + "transition": 1, + "brightness": 25, + "color_temp": 235, + }, + blocking=True, + ) + assert dev1_cluster_on_off.request.call_count == 0 + assert dev1_cluster_on_off.request.await_count == 0 + assert dev1_cluster_color.request.call_count == 1 + assert dev1_cluster_color.request.await_count == 1 + assert dev1_cluster_level.request.call_count == 2 + assert dev1_cluster_level.request.await_count == 2 + + # first it comes on with no transition at 2 brightness + assert dev1_cluster_level.request.call_args_list[0] == call( + False, + 4, + dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema, + 2, # brightness (level in ZCL) + 0, # transition time (ZCL time in 10ths of a second) + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + assert dev1_cluster_color.request.call_args == call( + False, + 10, + dev1_cluster_color.commands_by_name["move_to_color_temp"].schema, + 235, # color temp mireds + 0, # transition time (ZCL time in 10ths of a second) - no transition when color_provided_while_off + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + assert dev1_cluster_level.request.call_args_list[1] == call( + False, + 0, + dev1_cluster_level.commands_by_name["move_to_level"].schema, + 25, # brightness (level in ZCL) + 10.0, # transition time (ZCL time in 10ths of a second) + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + light1_state = hass.states.get(device_1_entity_id) + assert light1_state.state == STATE_ON + assert light1_state.attributes["brightness"] == 25 + assert light1_state.attributes["color_temp"] == 235 + assert light1_state.attributes["color_mode"] == ColorMode.COLOR_TEMP + + dev1_cluster_level.request.reset_mock() + dev1_cluster_color.request.reset_mock() + + # turn light 1 back off + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_off", + { + "entity_id": device_1_entity_id, + }, + blocking=True, + ) + assert dev1_cluster_on_off.request.call_count == 1 + assert dev1_cluster_on_off.request.await_count == 1 + assert dev1_cluster_color.request.call_count == 0 + assert dev1_cluster_color.request.await_count == 0 + assert dev1_cluster_level.request.call_count == 0 + assert dev1_cluster_level.request.await_count == 0 + group_state = hass.states.get(group_entity_id) + assert group_state.state == STATE_OFF + + dev1_cluster_on_off.request.reset_mock() + dev1_cluster_color.request.reset_mock() + dev1_cluster_level.request.reset_mock() + + # test no transition provided and color temp while turning light on (color_provided_while_off) + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + { + "entity_id": device_1_entity_id, + "brightness": 25, + "color_temp": 236, + }, + blocking=True, + ) + assert dev1_cluster_on_off.request.call_count == 0 + assert dev1_cluster_on_off.request.await_count == 0 + assert dev1_cluster_color.request.call_count == 1 + assert dev1_cluster_color.request.await_count == 1 + assert dev1_cluster_level.request.call_count == 2 + assert dev1_cluster_level.request.await_count == 2 + + # first it comes on with no transition at 2 brightness + assert dev1_cluster_level.request.call_args_list[0] == call( + False, + 4, + dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema, + 2, # brightness (level in ZCL) + 0, # transition time (ZCL time in 10ths of a second) + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + assert dev1_cluster_color.request.call_args == call( + False, + 10, + dev1_cluster_color.commands_by_name["move_to_color_temp"].schema, + 236, # color temp mireds + 0, # transition time (ZCL time in 10ths of a second) - no transition when color_provided_while_off + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + assert dev1_cluster_level.request.call_args_list[1] == call( + False, + 0, + dev1_cluster_level.commands_by_name["move_to_level"].schema, + 25, # brightness (level in ZCL) + 0, # transition time (ZCL time in 10ths of a second) + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + light1_state = hass.states.get(device_1_entity_id) + assert light1_state.state == STATE_ON + assert light1_state.attributes["brightness"] == 25 + assert light1_state.attributes["color_temp"] == 236 + assert light1_state.attributes["color_mode"] == ColorMode.COLOR_TEMP + + dev1_cluster_level.request.reset_mock() + dev1_cluster_color.request.reset_mock() + + # turn light 1 back off to setup group test + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_off", + { + "entity_id": device_1_entity_id, + }, + blocking=True, + ) + assert dev1_cluster_on_off.request.call_count == 1 + assert dev1_cluster_on_off.request.await_count == 1 + assert dev1_cluster_color.request.call_count == 0 + assert dev1_cluster_color.request.await_count == 0 + assert dev1_cluster_level.request.call_count == 0 + assert dev1_cluster_level.request.await_count == 0 + group_state = hass.states.get(group_entity_id) + assert group_state.state == STATE_OFF + + dev1_cluster_on_off.request.reset_mock() + dev1_cluster_color.request.reset_mock() + dev1_cluster_level.request.reset_mock() + + # test no transition when the same color temp is provided from off + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + { + "entity_id": device_1_entity_id, + "color_temp": 236, + }, + blocking=True, + ) + assert dev1_cluster_on_off.request.call_count == 1 + assert dev1_cluster_on_off.request.await_count == 1 + assert dev1_cluster_color.request.call_count == 1 + assert dev1_cluster_color.request.await_count == 1 + assert dev1_cluster_level.request.call_count == 0 + assert dev1_cluster_level.request.await_count == 0 + + assert dev1_cluster_on_off.request.call_args == call( + False, + 1, + dev1_cluster_on_off.commands_by_name["on"].schema, + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + assert dev1_cluster_color.request.call_args == call( + False, + 10, + dev1_cluster_color.commands_by_name["move_to_color_temp"].schema, + 236, # color temp mireds + 0, # transition time (ZCL time in 10ths of a second) - no transition when color_provided_while_off + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + light1_state = hass.states.get(device_1_entity_id) + assert light1_state.state == STATE_ON + assert light1_state.attributes["brightness"] == 25 + assert light1_state.attributes["color_temp"] == 236 + assert light1_state.attributes["color_mode"] == ColorMode.COLOR_TEMP + + dev1_cluster_on_off.request.reset_mock() + dev1_cluster_color.request.reset_mock() + + # turn light 1 back off to setup group test + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_off", + { + "entity_id": device_1_entity_id, + }, + blocking=True, + ) + assert dev1_cluster_on_off.request.call_count == 1 + assert dev1_cluster_on_off.request.await_count == 1 + assert dev1_cluster_color.request.call_count == 0 + assert dev1_cluster_color.request.await_count == 0 + assert dev1_cluster_level.request.call_count == 0 + assert dev1_cluster_level.request.await_count == 0 + group_state = hass.states.get(group_entity_id) + assert group_state.state == STATE_OFF + + dev1_cluster_on_off.request.reset_mock() + dev1_cluster_color.request.reset_mock() + dev1_cluster_level.request.reset_mock() + + # test sengled light uses default minimum transition time + dev2_cluster_on_off.request.reset_mock() + dev2_cluster_color.request.reset_mock() + dev2_cluster_level.request.reset_mock() + + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {"entity_id": device_2_entity_id, "transition": 0, "brightness": 100}, + blocking=True, + ) + assert dev2_cluster_on_off.request.call_count == 0 + assert dev2_cluster_on_off.request.await_count == 0 + assert dev2_cluster_color.request.call_count == 0 + assert dev2_cluster_color.request.await_count == 0 + assert dev2_cluster_level.request.call_count == 1 + assert dev2_cluster_level.request.await_count == 1 + assert dev2_cluster_level.request.call_args == call( + False, + 4, + dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema, + 100, # brightness (level in ZCL) + 1, # transition time - sengled light uses default minimum + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + light2_state = hass.states.get(device_2_entity_id) + assert light2_state.state == STATE_ON + assert light2_state.attributes["brightness"] == 100 + + dev2_cluster_level.request.reset_mock() + + # turn the sengled light back off + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_off", + { + "entity_id": device_2_entity_id, + }, + blocking=True, + ) + assert dev2_cluster_on_off.request.call_count == 1 + assert dev2_cluster_on_off.request.await_count == 1 + assert dev2_cluster_color.request.call_count == 0 + assert dev2_cluster_color.request.await_count == 0 + assert dev2_cluster_level.request.call_count == 0 + assert dev2_cluster_level.request.await_count == 0 + light2_state = hass.states.get(device_2_entity_id) + assert light2_state.state == STATE_OFF + + dev2_cluster_on_off.request.reset_mock() + + # test non 0 length transition and color temp while turning light on and sengled (color_provided_while_off) + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + { + "entity_id": device_2_entity_id, + "transition": 1, + "brightness": 25, + "color_temp": 235, + }, + blocking=True, + ) + assert dev2_cluster_on_off.request.call_count == 0 + assert dev2_cluster_on_off.request.await_count == 0 + assert dev2_cluster_color.request.call_count == 1 + assert dev2_cluster_color.request.await_count == 1 + assert dev2_cluster_level.request.call_count == 2 + assert dev2_cluster_level.request.await_count == 2 + + # first it comes on with no transition at 2 brightness + assert dev2_cluster_level.request.call_args_list[0] == call( + False, + 4, + dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema, + 2, # brightness (level in ZCL) + 1, # transition time (ZCL time in 10ths of a second) + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + assert dev2_cluster_color.request.call_args == call( + False, + 10, + dev2_cluster_color.commands_by_name["move_to_color_temp"].schema, + 235, # color temp mireds + 1, # transition time (ZCL time in 10ths of a second) - sengled transition == 1 when color_provided_while_off + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + assert dev2_cluster_level.request.call_args_list[1] == call( + False, + 0, + dev2_cluster_level.commands_by_name["move_to_level"].schema, + 25, # brightness (level in ZCL) + 10.0, # transition time (ZCL time in 10ths of a second) + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + light2_state = hass.states.get(device_2_entity_id) + assert light2_state.state == STATE_ON + assert light2_state.attributes["brightness"] == 25 + assert light2_state.attributes["color_temp"] == 235 + assert light2_state.attributes["color_mode"] == ColorMode.COLOR_TEMP + + dev2_cluster_level.request.reset_mock() + dev2_cluster_color.request.reset_mock() + + # turn the sengled light back off + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_off", + { + "entity_id": device_2_entity_id, + }, + blocking=True, + ) + assert dev2_cluster_on_off.request.call_count == 1 + assert dev2_cluster_on_off.request.await_count == 1 + assert dev2_cluster_color.request.call_count == 0 + assert dev2_cluster_color.request.await_count == 0 + assert dev2_cluster_level.request.call_count == 0 + assert dev2_cluster_level.request.await_count == 0 + light2_state = hass.states.get(device_2_entity_id) + assert light2_state.state == STATE_OFF + + dev2_cluster_on_off.request.reset_mock() + + # test non 0 length transition and color temp while turning group light on (color_provided_while_off) + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + { + "entity_id": group_entity_id, + "transition": 1, + "brightness": 25, + "color_temp": 235, + }, + blocking=True, + ) + + group_on_off_channel = zha_group.endpoint[general.OnOff.cluster_id] + group_level_channel = zha_group.endpoint[general.LevelControl.cluster_id] + group_color_channel = zha_group.endpoint[lighting.Color.cluster_id] + assert group_on_off_channel.request.call_count == 0 + assert group_on_off_channel.request.await_count == 0 + assert group_color_channel.request.call_count == 1 + assert group_color_channel.request.await_count == 1 + assert group_level_channel.request.call_count == 1 + assert group_level_channel.request.await_count == 1 + + # groups are omitted from the 3 call dance for color_provided_while_off + assert group_color_channel.request.call_args == call( + False, + 10, + dev2_cluster_color.commands_by_name["move_to_color_temp"].schema, + 235, # color temp mireds + 10.0, # transition time (ZCL time in 10ths of a second) - sengled transition == 1 when color_provided_while_off + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + assert group_level_channel.request.call_args == call( + False, + 4, + dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema, + 25, # brightness (level in ZCL) + 10.0, # transition time (ZCL time in 10ths of a second) + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + group_state = hass.states.get(group_entity_id) + assert group_state.state == STATE_ON + assert group_state.attributes["brightness"] == 25 + assert group_state.attributes["color_temp"] == 235 + assert group_state.attributes["color_mode"] == ColorMode.COLOR_TEMP + + group_on_off_channel.request.reset_mock() + group_color_channel.request.reset_mock() + group_level_channel.request.reset_mock() + + # turn the sengled light back on + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + { + "entity_id": device_2_entity_id, + }, + blocking=True, + ) + assert dev2_cluster_on_off.request.call_count == 1 + assert dev2_cluster_on_off.request.await_count == 1 + assert dev2_cluster_color.request.call_count == 0 + assert dev2_cluster_color.request.await_count == 0 + assert dev2_cluster_level.request.call_count == 0 + assert dev2_cluster_level.request.await_count == 0 + light2_state = hass.states.get(device_2_entity_id) + assert light2_state.state == STATE_ON + + dev2_cluster_on_off.request.reset_mock() + + # turn the light off with a transition + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_off", + {"entity_id": device_2_entity_id, "transition": 2}, + blocking=True, + ) + assert dev2_cluster_on_off.request.call_count == 0 + assert dev2_cluster_on_off.request.await_count == 0 + assert dev2_cluster_color.request.call_count == 0 + assert dev2_cluster_color.request.await_count == 0 + assert dev2_cluster_level.request.call_count == 1 + assert dev2_cluster_level.request.await_count == 1 + assert dev2_cluster_level.request.call_args == call( + False, + 4, + dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema, + 0, # brightness (level in ZCL) + 20, # transition time + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + light2_state = hass.states.get(device_2_entity_id) + assert light2_state.state == STATE_OFF + + dev2_cluster_level.request.reset_mock() + + # turn the light back on with no args should use a transition and last known brightness + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {"entity_id": device_2_entity_id}, + blocking=True, + ) + assert dev2_cluster_on_off.request.call_count == 0 + assert dev2_cluster_on_off.request.await_count == 0 + assert dev2_cluster_color.request.call_count == 0 + assert dev2_cluster_color.request.await_count == 0 + assert dev2_cluster_level.request.call_count == 1 + assert dev2_cluster_level.request.await_count == 1 + assert dev2_cluster_level.request.call_args == call( + False, + 4, + dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema, + 25, # brightness (level in ZCL) - this is the last brightness we set a few tests above + 1, # transition time - sengled light uses default minimum + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + light2_state = hass.states.get(device_2_entity_id) + assert light2_state.state == STATE_ON + + dev2_cluster_level.request.reset_mock() + + # test eWeLink color temp while turning light on from off (color_provided_while_off) + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + { + "entity_id": eWeLink_light_entity_id, + "color_temp": 235, + }, + blocking=True, + ) + assert eWeLink_cluster_on_off.request.call_count == 1 + assert eWeLink_cluster_on_off.request.await_count == 1 + assert eWeLink_cluster_color.request.call_count == 1 + assert eWeLink_cluster_color.request.await_count == 1 + assert eWeLink_cluster_level.request.call_count == 0 + assert eWeLink_cluster_level.request.await_count == 0 + + # first it comes on + assert eWeLink_cluster_on_off.request.call_args_list[0] == call( + False, + 1, + eWeLink_cluster_on_off.commands_by_name["on"].schema, + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + assert dev1_cluster_color.request.call_args == call( + False, + 10, + dev1_cluster_color.commands_by_name["move_to_color_temp"].schema, + 235, # color temp mireds + 0, # transition time (ZCL time in 10ths of a second) + expect_reply=True, + manufacturer=None, + tries=1, + tsn=None, + ) + + eWeLink_state = hass.states.get(eWeLink_light_entity_id) + assert eWeLink_state.state == STATE_ON + assert eWeLink_state.attributes["color_temp"] == 235 + assert eWeLink_state.attributes["color_mode"] == ColorMode.COLOR_TEMP + + async def async_test_on_off_from_light(hass, cluster, entity_id): """Test on off functionality from the light.""" # turn on at light @@ -463,7 +1260,7 @@ async def async_test_level_on_off_from_hass( 4, level_cluster.commands_by_name["move_to_level_with_on_off"].schema, 10, - 1, + 0, expect_reply=True, manufacturer=None, tries=1, @@ -601,7 +1398,10 @@ async def test_zha_group_light_entity( # test that the lights were created and are off group_state = hass.states.get(group_entity_id) assert group_state.state == STATE_OFF - assert group_state.attributes["supported_color_modes"] == [ColorMode.HS] + assert group_state.attributes["supported_color_modes"] == [ + ColorMode.COLOR_TEMP, + ColorMode.HS, + ] # Light which is off has no color mode assert "color_mode" not in group_state.attributes @@ -629,7 +1429,10 @@ async def test_zha_group_light_entity( # Check state group_state = hass.states.get(group_entity_id) assert group_state.state == STATE_ON - assert group_state.attributes["supported_color_modes"] == [ColorMode.HS] + assert group_state.attributes["supported_color_modes"] == [ + ColorMode.COLOR_TEMP, + ColorMode.HS, + ] assert group_state.attributes["color_mode"] == ColorMode.HS # test long flashing the lights from the HA From 1b61d72eaff9c8cfe85e87baa19b6d57cd273007 Mon Sep 17 00:00:00 2001 From: apaperclip <67401560+apaperclip@users.noreply.github.com> Date: Fri, 15 Jul 2022 04:57:23 -0400 Subject: [PATCH 082/104] Fix aruba ssh host key algorithm (#75224) --- homeassistant/components/aruba/device_tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/aruba/device_tracker.py b/homeassistant/components/aruba/device_tracker.py index ecdcc5f70f2..dc2d2fee8e9 100644 --- a/homeassistant/components/aruba/device_tracker.py +++ b/homeassistant/components/aruba/device_tracker.py @@ -87,7 +87,7 @@ class ArubaDeviceScanner(DeviceScanner): def get_aruba_data(self): """Retrieve data from Aruba Access Point and return parsed result.""" - connect = f"ssh {self.username}@{self.host}" + connect = f"ssh {self.username}@{self.host} -o HostKeyAlgorithms=ssh-rsa" ssh = pexpect.spawn(connect) query = ssh.expect( [ From 8b4cf288e341faa1e8c6457df1af9b8000e35e98 Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Sat, 16 Jul 2022 17:00:42 +0200 Subject: [PATCH 083/104] Force `_attr_native_value` to metric in bmw_connected_drive (#75225) Co-authored-by: rikroe --- .../components/bmw_connected_drive/coordinator.py | 3 ++- homeassistant/components/bmw_connected_drive/sensor.py | 7 ++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/coordinator.py b/homeassistant/components/bmw_connected_drive/coordinator.py index e5a968b47fd..08e90d3c4e0 100644 --- a/homeassistant/components/bmw_connected_drive/coordinator.py +++ b/homeassistant/components/bmw_connected_drive/coordinator.py @@ -33,7 +33,8 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator): entry.data[CONF_PASSWORD], get_region_from_name(entry.data[CONF_REGION]), observer_position=GPSPosition(hass.config.latitude, hass.config.longitude), - use_metric_units=hass.config.units.is_metric, + # Force metric system as BMW API apparently only returns metric values now + use_metric_units=True, ) self.read_only = entry.options[CONF_READ_ONLY] self._entry = entry diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index 9f19673c398..f1046881ed3 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -16,7 +16,6 @@ from homeassistant.components.sensor import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - CONF_UNIT_SYSTEM_IMPERIAL, LENGTH_KILOMETERS, LENGTH_MILES, PERCENTAGE, @@ -183,10 +182,8 @@ class BMWSensor(BMWBaseEntity, SensorEntity): self._attr_name = f"{vehicle.name} {description.key}" self._attr_unique_id = f"{vehicle.vin}-{description.key}" - if unit_system.name == CONF_UNIT_SYSTEM_IMPERIAL: - self._attr_native_unit_of_measurement = description.unit_imperial - else: - self._attr_native_unit_of_measurement = description.unit_metric + # Force metric system as BMW API apparently only returns metric values now + self._attr_native_unit_of_measurement = description.unit_metric @callback def _handle_coordinator_update(self) -> None: From bccdb29edc009845b99cc2e4ac6ca7447968818e Mon Sep 17 00:00:00 2001 From: clayton craft Date: Fri, 15 Jul 2022 15:38:06 -0700 Subject: [PATCH 084/104] Bump venstarcolortouch to 0.18 (#75237) venstarcolortouch: bump to 0.18 --- homeassistant/components/venstar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/venstar/manifest.json b/homeassistant/components/venstar/manifest.json index 2f3331af6e2..4a6eea28e24 100644 --- a/homeassistant/components/venstar/manifest.json +++ b/homeassistant/components/venstar/manifest.json @@ -3,7 +3,7 @@ "name": "Venstar", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/venstar", - "requirements": ["venstarcolortouch==0.17"], + "requirements": ["venstarcolortouch==0.18"], "codeowners": ["@garbled1"], "iot_class": "local_polling", "loggers": ["venstarcolortouch"] diff --git a/requirements_all.txt b/requirements_all.txt index bb4bad5d959..23b672581e8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2387,7 +2387,7 @@ vehicle==0.4.0 velbus-aio==2022.6.2 # homeassistant.components.venstar -venstarcolortouch==0.17 +venstarcolortouch==0.18 # homeassistant.components.vilfo vilfo-api-client==0.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 79318dc1789..8540cf41520 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1590,7 +1590,7 @@ vehicle==0.4.0 velbus-aio==2022.6.2 # homeassistant.components.venstar -venstarcolortouch==0.17 +venstarcolortouch==0.18 # homeassistant.components.vilfo vilfo-api-client==0.3.2 From 3a2beb2212f156ea14b00252cb1fafcaf65f4e1a Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Sat, 16 Jul 2022 18:28:17 -0400 Subject: [PATCH 085/104] Improve UniFi Protect unauth handling (#75269) --- homeassistant/components/unifiprotect/data.py | 12 ++++++--- tests/components/unifiprotect/test_init.py | 27 ++++++++++++++----- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/unifiprotect/data.py b/homeassistant/components/unifiprotect/data.py index 9e0783a99b1..d4140759a7b 100644 --- a/homeassistant/components/unifiprotect/data.py +++ b/homeassistant/components/unifiprotect/data.py @@ -72,6 +72,7 @@ class ProtectData: self._pending_camera_ids: set[str] = set() self._unsub_interval: CALLBACK_TYPE | None = None self._unsub_websocket: CALLBACK_TYPE | None = None + self._auth_failures = 0 self.last_update_success = False self.api = protect @@ -117,9 +118,13 @@ class ProtectData: try: updates = await self.api.update(force=force) except NotAuthorized: - await self.async_stop() - _LOGGER.exception("Reauthentication required") - self._entry.async_start_reauth(self._hass) + if self._auth_failures < 10: + _LOGGER.exception("Auth error while updating") + self._auth_failures += 1 + else: + await self.async_stop() + _LOGGER.exception("Reauthentication required") + self._entry.async_start_reauth(self._hass) self.last_update_success = False except ClientError: if self.last_update_success: @@ -129,6 +134,7 @@ class ProtectData: self._async_process_updates(self.api.bootstrap) else: self.last_update_success = True + self._auth_failures = 0 self._async_process_updates(updates) @callback diff --git a/tests/components/unifiprotect/test_init.py b/tests/components/unifiprotect/test_init.py index f6f0645df18..9392caa30ac 100644 --- a/tests/components/unifiprotect/test_init.py +++ b/tests/components/unifiprotect/test_init.py @@ -9,14 +9,18 @@ import aiohttp from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient from pyunifiprotect.data import NVR, Bootstrap, Light -from homeassistant.components.unifiprotect.const import CONF_DISABLE_RTSP, DOMAIN +from homeassistant.components.unifiprotect.const import ( + CONF_DISABLE_RTSP, + DEFAULT_SCAN_INTERVAL, + DOMAIN, +) from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from . import _patch_discovery -from .utils import MockUFPFixture, init_entry +from .utils import MockUFPFixture, init_entry, time_changed from tests.common import MockConfigEntry @@ -145,12 +149,23 @@ async def test_setup_failed_update(hass: HomeAssistant, ufp: MockUFPFixture): async def test_setup_failed_update_reauth(hass: HomeAssistant, ufp: MockUFPFixture): """Test setup of unifiprotect entry with update that gives unauthroized error.""" - ufp.api.update = AsyncMock(side_effect=NotAuthorized) - await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - assert ufp.entry.state == ConfigEntryState.SETUP_RETRY - assert ufp.api.update.called + assert ufp.entry.state == ConfigEntryState.LOADED + + # reauth should not be triggered until there are 10 auth failures in a row + # to verify it is not transient + ufp.api.update = AsyncMock(side_effect=NotAuthorized) + for _ in range(10): + await time_changed(hass, DEFAULT_SCAN_INTERVAL) + assert len(hass.config_entries.flow._progress) == 0 + + assert ufp.api.update.call_count == 10 + assert ufp.entry.state == ConfigEntryState.LOADED + + await time_changed(hass, DEFAULT_SCAN_INTERVAL) + assert ufp.api.update.call_count == 11 + assert len(hass.config_entries.flow._progress) == 1 async def test_setup_failed_error(hass: HomeAssistant, ufp: MockUFPFixture): From 75aea68b753db5d88821078cbd9ecef8156a02db Mon Sep 17 00:00:00 2001 From: mvn23 Date: Sat, 16 Jul 2022 22:26:22 +0200 Subject: [PATCH 086/104] Update pyotgw to 2.0.0 (#75285) * Update pyotgw to 2.0.0 * Include updated tests --- .../components/opentherm_gw/__init__.py | 4 +- .../components/opentherm_gw/config_flow.py | 4 +- .../components/opentherm_gw/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../opentherm_gw/test_config_flow.py | 42 +++++++++++++------ tests/components/opentherm_gw/test_init.py | 4 +- 7 files changed, 38 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index 5ec8c6420d5..0d01550df22 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -416,7 +416,7 @@ class OpenThermGatewayDevice: self.status = {} self.update_signal = f"{DATA_OPENTHERM_GW}_{self.gw_id}_update" self.options_update_signal = f"{DATA_OPENTHERM_GW}_{self.gw_id}_options_update" - self.gateway = pyotgw.pyotgw() + self.gateway = pyotgw.OpenThermGateway() self.gw_version = None async def cleanup(self, event=None): @@ -427,7 +427,7 @@ class OpenThermGatewayDevice: async def connect_and_subscribe(self): """Connect to serial device and subscribe report handler.""" - self.status = await self.gateway.connect(self.hass.loop, self.device_path) + self.status = await self.gateway.connect(self.device_path) version_string = self.status[gw_vars.OTGW].get(gw_vars.OTGW_ABOUT) self.gw_version = version_string[18:] if version_string else None _LOGGER.debug( diff --git a/homeassistant/components/opentherm_gw/config_flow.py b/homeassistant/components/opentherm_gw/config_flow.py index 1d66d6e2069..3f91496adab 100644 --- a/homeassistant/components/opentherm_gw/config_flow.py +++ b/homeassistant/components/opentherm_gw/config_flow.py @@ -59,8 +59,8 @@ class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def test_connection(): """Try to connect to the OpenTherm Gateway.""" - otgw = pyotgw.pyotgw() - status = await otgw.connect(self.hass.loop, device) + otgw = pyotgw.OpenThermGateway() + status = await otgw.connect(device) await otgw.disconnect() return status[gw_vars.OTGW].get(gw_vars.OTGW_ABOUT) diff --git a/homeassistant/components/opentherm_gw/manifest.json b/homeassistant/components/opentherm_gw/manifest.json index 7aa19224020..dfb60413721 100644 --- a/homeassistant/components/opentherm_gw/manifest.json +++ b/homeassistant/components/opentherm_gw/manifest.json @@ -2,7 +2,7 @@ "domain": "opentherm_gw", "name": "OpenTherm Gateway", "documentation": "https://www.home-assistant.io/integrations/opentherm_gw", - "requirements": ["pyotgw==1.1b1"], + "requirements": ["pyotgw==2.0.0"], "codeowners": ["@mvn23"], "config_flow": true, "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 23b672581e8..db0107a90cc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1715,7 +1715,7 @@ pyopnsense==0.2.0 pyoppleio==1.0.5 # homeassistant.components.opentherm_gw -pyotgw==1.1b1 +pyotgw==2.0.0 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8540cf41520..66dae65c33e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1167,7 +1167,7 @@ pyopenuv==2022.04.0 pyopnsense==0.2.0 # homeassistant.components.opentherm_gw -pyotgw==1.1b1 +pyotgw==2.0.0 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp diff --git a/tests/components/opentherm_gw/test_config_flow.py b/tests/components/opentherm_gw/test_config_flow.py index 99133cf17c3..344b21e4471 100644 --- a/tests/components/opentherm_gw/test_config_flow.py +++ b/tests/components/opentherm_gw/test_config_flow.py @@ -43,10 +43,12 @@ async def test_form_user(hass): "homeassistant.components.opentherm_gw.async_setup_entry", return_value=True, ) as mock_setup_entry, patch( - "pyotgw.pyotgw.connect", return_value=MINIMAL_STATUS + "pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS ) as mock_pyotgw_connect, patch( - "pyotgw.pyotgw.disconnect", return_value=None - ) as mock_pyotgw_disconnect: + "pyotgw.OpenThermGateway.disconnect", return_value=None + ) as mock_pyotgw_disconnect, patch( + "pyotgw.status.StatusManager._process_updates", return_value=None + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "/dev/ttyUSB0"} ) @@ -75,10 +77,12 @@ async def test_form_import(hass): "homeassistant.components.opentherm_gw.async_setup_entry", return_value=True, ) as mock_setup_entry, patch( - "pyotgw.pyotgw.connect", return_value=MINIMAL_STATUS + "pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS ) as mock_pyotgw_connect, patch( - "pyotgw.pyotgw.disconnect", return_value=None - ) as mock_pyotgw_disconnect: + "pyotgw.OpenThermGateway.disconnect", return_value=None + ) as mock_pyotgw_disconnect, patch( + "pyotgw.status.StatusManager._process_updates", return_value=None + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, @@ -117,10 +121,12 @@ async def test_form_duplicate_entries(hass): "homeassistant.components.opentherm_gw.async_setup_entry", return_value=True, ) as mock_setup_entry, patch( - "pyotgw.pyotgw.connect", return_value=MINIMAL_STATUS + "pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS ) as mock_pyotgw_connect, patch( - "pyotgw.pyotgw.disconnect", return_value=None - ) as mock_pyotgw_disconnect: + "pyotgw.OpenThermGateway.disconnect", return_value=None + ) as mock_pyotgw_disconnect, patch( + "pyotgw.status.StatusManager._process_updates", return_value=None + ): result1 = await hass.config_entries.flow.async_configure( flow1["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "/dev/ttyUSB0"} ) @@ -148,8 +154,10 @@ async def test_form_connection_timeout(hass): ) with patch( - "pyotgw.pyotgw.connect", side_effect=(asyncio.TimeoutError) - ) as mock_connect: + "pyotgw.OpenThermGateway.connect", side_effect=(asyncio.TimeoutError) + ) as mock_connect, patch( + "pyotgw.status.StatusManager._process_updates", return_value=None + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "socket://192.0.2.254:1234"}, @@ -166,7 +174,11 @@ async def test_form_connection_error(hass): DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("pyotgw.pyotgw.connect", side_effect=(SerialException)) as mock_connect: + with patch( + "pyotgw.OpenThermGateway.connect", side_effect=(SerialException) + ) as mock_connect, patch( + "pyotgw.status.StatusManager._process_updates", return_value=None + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "/dev/ttyUSB0"} ) @@ -196,7 +208,11 @@ async def test_options_migration(hass): with patch( "homeassistant.components.opentherm_gw.OpenThermGatewayDevice.connect_and_subscribe", return_value=True, - ), patch("homeassistant.components.opentherm_gw.async_setup", return_value=True): + ), patch( + "homeassistant.components.opentherm_gw.async_setup", return_value=True + ), patch( + "pyotgw.status.StatusManager._process_updates", return_value=None + ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/opentherm_gw/test_init.py b/tests/components/opentherm_gw/test_init.py index 554f58fd81b..7e16805c683 100644 --- a/tests/components/opentherm_gw/test_init.py +++ b/tests/components/opentherm_gw/test_init.py @@ -34,7 +34,7 @@ async def test_device_registry_insert(hass): with patch( "homeassistant.components.opentherm_gw.OpenThermGatewayDevice.cleanup", return_value=None, - ), patch("pyotgw.pyotgw.connect", return_value=MINIMAL_STATUS): + ), patch("pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS): await setup.async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() @@ -62,7 +62,7 @@ async def test_device_registry_update(hass): with patch( "homeassistant.components.opentherm_gw.OpenThermGatewayDevice.cleanup", return_value=None, - ), patch("pyotgw.pyotgw.connect", return_value=MINIMAL_STATUS_UPD): + ), patch("pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS_UPD): await setup.async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() From 34c30f5ab954726a0b71161ec36a9ab5eb74d7f2 Mon Sep 17 00:00:00 2001 From: Khole Date: Fri, 15 Jul 2022 22:03:22 +0100 Subject: [PATCH 087/104] Add fixes for hive light (#75286) --- homeassistant/components/hive/light.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index c06237f3709..69345c430c7 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -44,13 +44,15 @@ class HiveDeviceLight(HiveEntity, LightEntity): super().__init__(hive, hive_device) if self.device["hiveType"] == "warmwhitelight": self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} + self._attr_color_mode = ColorMode.BRIGHTNESS elif self.device["hiveType"] == "tuneablelight": self._attr_supported_color_modes = {ColorMode.COLOR_TEMP} + self._attr_color_mode = ColorMode.COLOR_TEMP elif self.device["hiveType"] == "colourtuneablelight": self._attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} - self._attr_min_mireds = self.device.get("min_mireds") - self._attr_max_mireds = self.device.get("max_mireds") + self._attr_min_mireds = 153 + self._attr_max_mireds = 370 @refresh_system async def async_turn_on(self, **kwargs): @@ -94,6 +96,13 @@ class HiveDeviceLight(HiveEntity, LightEntity): if self._attr_available: self._attr_is_on = self.device["status"]["state"] self._attr_brightness = self.device["status"]["brightness"] + if self.device["hiveType"] == "tuneablelight": + self._attr_color_temp = self.device["status"].get("color_temp") if self.device["hiveType"] == "colourtuneablelight": - rgb = self.device["status"]["hs_color"] - self._attr_hs_color = color_util.color_RGB_to_hs(*rgb) + if self.device["status"]["mode"] == "COLOUR": + rgb = self.device["status"]["hs_color"] + self._attr_hs_color = color_util.color_RGB_to_hs(*rgb) + self._attr_color_mode = ColorMode.HS + else: + self._attr_color_temp = self.device["status"].get("color_temp") + self._attr_color_mode = ColorMode.COLOR_TEMP From 8232a780eb5832e109f580add01f10e66c2a0e60 Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Sat, 16 Jul 2022 16:57:17 +0200 Subject: [PATCH 088/104] Bump bimmer_connected to 0.10.1 (#75287) Co-authored-by: rikroe --- homeassistant/components/bmw_connected_drive/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index b10d4842163..0381035a63e 100644 --- a/homeassistant/components/bmw_connected_drive/manifest.json +++ b/homeassistant/components/bmw_connected_drive/manifest.json @@ -2,7 +2,7 @@ "domain": "bmw_connected_drive", "name": "BMW Connected Drive", "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", - "requirements": ["bimmer_connected==0.9.6"], + "requirements": ["bimmer_connected==0.10.1"], "codeowners": ["@gerard33", "@rikroe"], "config_flow": true, "iot_class": "cloud_polling", diff --git a/requirements_all.txt b/requirements_all.txt index db0107a90cc..41bc6690907 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -396,7 +396,7 @@ beautifulsoup4==4.11.1 bellows==0.31.1 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.9.6 +bimmer_connected==0.10.1 # homeassistant.components.bizkaibus bizkaibus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 66dae65c33e..ad85fd22e89 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -311,7 +311,7 @@ beautifulsoup4==4.11.1 bellows==0.31.1 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.9.6 +bimmer_connected==0.10.1 # homeassistant.components.blebox blebox_uniapi==2.0.1 From 8b270cb487f10bd4c9d513d6b0aa6d2079a4e451 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 16 Jul 2022 08:45:09 -0600 Subject: [PATCH 089/104] Bump simplisafe-python to 2022.07.0 (#75294) --- 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 a09c273076c..b6a139fba80 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==2022.06.1"], + "requirements": ["simplisafe-python==2022.07.0"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 41bc6690907..f77331bde78 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2168,7 +2168,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==2022.06.1 +simplisafe-python==2022.07.0 # homeassistant.components.sisyphus sisyphus-control==3.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ad85fd22e89..1f3d1ce89dc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1440,7 +1440,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==2022.06.1 +simplisafe-python==2022.07.0 # homeassistant.components.slack slackclient==2.5.0 From 97b6912856e67b1acf16ad63e040a71826499ceb Mon Sep 17 00:00:00 2001 From: Nick Whyte Date: Mon, 18 Jul 2022 11:17:25 +0100 Subject: [PATCH 090/104] Upgrade ness_alarm dependencies (#75298) * Upgrade ness alarm dependencies to fix #74571 * Update requirements --- homeassistant/components/ness_alarm/__init__.py | 1 - homeassistant/components/ness_alarm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/ness_alarm/__init__.py b/homeassistant/components/ness_alarm/__init__.py index d082f77d837..c81c3e0c7f7 100644 --- a/homeassistant/components/ness_alarm/__init__.py +++ b/homeassistant/components/ness_alarm/__init__.py @@ -99,7 +99,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: client = Client( host=host, port=port, - loop=hass.loop, update_interval=scan_interval.total_seconds(), infer_arming_state=infer_arming_state, ) diff --git a/homeassistant/components/ness_alarm/manifest.json b/homeassistant/components/ness_alarm/manifest.json index 4aa01428d27..0b20ad7e6a9 100644 --- a/homeassistant/components/ness_alarm/manifest.json +++ b/homeassistant/components/ness_alarm/manifest.json @@ -2,7 +2,7 @@ "domain": "ness_alarm", "name": "Ness Alarm", "documentation": "https://www.home-assistant.io/integrations/ness_alarm", - "requirements": ["nessclient==0.9.15"], + "requirements": ["nessclient==0.10.0"], "codeowners": ["@nickw444"], "iot_class": "local_push", "loggers": ["nessclient"] diff --git a/requirements_all.txt b/requirements_all.txt index f77331bde78..6c2be7ec5ab 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1068,7 +1068,7 @@ nad_receiver==0.3.0 ndms2_client==0.1.1 # homeassistant.components.ness_alarm -nessclient==0.9.15 +nessclient==0.10.0 # homeassistant.components.netdata netdata==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1f3d1ce89dc..87db37d0510 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -742,7 +742,7 @@ mutesync==0.0.1 ndms2_client==0.1.1 # homeassistant.components.ness_alarm -nessclient==0.9.15 +nessclient==0.10.0 # homeassistant.components.discovery netdisco==3.0.0 From 7f43064f36410725414b326dee0ae4d86406a760 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 4 Jul 2022 08:41:23 -0500 Subject: [PATCH 091/104] Use the orjson equivalent default encoder when save_json is passed the default encoder (#74377) --- homeassistant/util/json.py | 27 +++++++++++++++++++++++++-- tests/util/test_json.py | 18 +++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index d69a4106728..68273c89743 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -11,6 +11,10 @@ import orjson from homeassistant.core import Event, State from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.json import ( + JSONEncoder as DefaultHASSJSONEncoder, + json_encoder_default as default_hass_orjson_encoder, +) from .file import write_utf8_file, write_utf8_file_atomic @@ -52,6 +56,15 @@ def _orjson_encoder(data: Any) -> str: ).decode("utf-8") +def _orjson_default_encoder(data: Any) -> str: + """JSON encoder that uses orjson with hass defaults.""" + return orjson.dumps( + data, + option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS, + default=default_hass_orjson_encoder, + ).decode("utf-8") + + def save_json( filename: str, data: list | dict, @@ -64,10 +77,20 @@ def save_json( Returns True on success. """ - dump: Callable[[Any], Any] = json.dumps + dump: Callable[[Any], Any] try: if encoder: - json_data = json.dumps(data, indent=2, cls=encoder) + # For backwards compatibility, if they pass in the + # default json encoder we use _orjson_default_encoder + # which is the orjson equivalent to the default encoder. + if encoder is DefaultHASSJSONEncoder: + dump = _orjson_default_encoder + json_data = _orjson_default_encoder(data) + # If they pass a custom encoder that is not the + # DefaultHASSJSONEncoder, we use the slow path of json.dumps + else: + dump = json.dumps + json_data = json.dumps(data, indent=2, cls=encoder) else: dump = _orjson_encoder json_data = _orjson_encoder(data) diff --git a/tests/util/test_json.py b/tests/util/test_json.py index 9974cbb9628..28d321036c5 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -5,12 +5,13 @@ from json import JSONEncoder, dumps import math import os from tempfile import mkdtemp -from unittest.mock import Mock +from unittest.mock import Mock, patch import pytest from homeassistant.core import Event, State from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.json import JSONEncoder as DefaultHASSJSONEncoder from homeassistant.helpers.template import TupleWrapper from homeassistant.util.json import ( SerializationError, @@ -127,6 +128,21 @@ def test_custom_encoder(): assert data == "9" +def test_default_encoder_is_passed(): + """Test we use orjson if they pass in the default encoder.""" + fname = _path_for("test6") + with patch( + "homeassistant.util.json.orjson.dumps", return_value=b"{}" + ) as mock_orjson_dumps: + save_json(fname, {"any": 1}, encoder=DefaultHASSJSONEncoder) + assert len(mock_orjson_dumps.mock_calls) == 1 + # Patch json.dumps to make sure we are using the orjson path + with patch("homeassistant.util.json.json.dumps", side_effect=Exception): + save_json(fname, {"any": {1}}, encoder=DefaultHASSJSONEncoder) + data = load_json(fname) + assert data == {"any": [1]} + + def test_find_unserializable_data(): """Find unserializeable data.""" assert find_paths_unserializable_data(1) == {} From 340da786afa1cecd401201565ecdaf9dab8a94a3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Jul 2022 07:25:19 -0500 Subject: [PATCH 092/104] Use default encoder when saving storage (#75319) --- homeassistant/util/json.py | 27 ++++++-------------- tests/helpers/test_storage.py | 48 ++++++++++++++++++++++++++++++++++- tests/util/test_json.py | 23 +++++------------ 3 files changed, 62 insertions(+), 36 deletions(-) diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index 68273c89743..1413f6d9b15 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -49,13 +49,6 @@ def load_json(filename: str, default: list | dict | None = None) -> list | dict: return {} if default is None else default -def _orjson_encoder(data: Any) -> str: - """JSON encoder that uses orjson.""" - return orjson.dumps( - data, option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS - ).decode("utf-8") - - def _orjson_default_encoder(data: Any) -> str: """JSON encoder that uses orjson with hass defaults.""" return orjson.dumps( @@ -79,21 +72,17 @@ def save_json( """ dump: Callable[[Any], Any] try: - if encoder: - # For backwards compatibility, if they pass in the - # default json encoder we use _orjson_default_encoder - # which is the orjson equivalent to the default encoder. - if encoder is DefaultHASSJSONEncoder: - dump = _orjson_default_encoder - json_data = _orjson_default_encoder(data) + # For backwards compatibility, if they pass in the + # default json encoder we use _orjson_default_encoder + # which is the orjson equivalent to the default encoder. + if encoder and encoder is not DefaultHASSJSONEncoder: # If they pass a custom encoder that is not the # DefaultHASSJSONEncoder, we use the slow path of json.dumps - else: - dump = json.dumps - json_data = json.dumps(data, indent=2, cls=encoder) + dump = json.dumps + json_data = json.dumps(data, indent=2, cls=encoder) else: - dump = _orjson_encoder - json_data = _orjson_encoder(data) + dump = _orjson_default_encoder + json_data = _orjson_default_encoder(data) except TypeError as error: msg = f"Failed to serialize to JSON: {filename}. Bad data at {format_unserializable_data(find_paths_unserializable_data(data, dump=dump))}" _LOGGER.error(msg) diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py index 53c1b8a4677..ca5cb92bfd5 100644 --- a/tests/helpers/test_storage.py +++ b/tests/helpers/test_storage.py @@ -2,6 +2,7 @@ import asyncio from datetime import timedelta import json +from typing import NamedTuple from unittest.mock import Mock, patch import pytest @@ -13,8 +14,9 @@ from homeassistant.const import ( from homeassistant.core import CoreState from homeassistant.helpers import storage from homeassistant.util import dt +from homeassistant.util.color import RGBColor -from tests.common import async_fire_time_changed +from tests.common import async_fire_time_changed, async_test_home_assistant MOCK_VERSION = 1 MOCK_VERSION_2 = 2 @@ -460,3 +462,47 @@ async def test_changing_delayed_written_data(hass, store, hass_storage): "key": MOCK_KEY, "data": {"hello": "world"}, } + + +async def test_saving_load_round_trip(tmpdir): + """Test saving and loading round trip.""" + loop = asyncio.get_running_loop() + hass = await async_test_home_assistant(loop) + + hass.config.config_dir = await hass.async_add_executor_job( + tmpdir.mkdir, "temp_storage" + ) + + class NamedTupleSubclass(NamedTuple): + """A NamedTuple subclass.""" + + name: str + + nts = NamedTupleSubclass("a") + + data = { + "named_tuple_subclass": nts, + "rgb_color": RGBColor(255, 255, 0), + "set": {1, 2, 3}, + "list": [1, 2, 3], + "tuple": (1, 2, 3), + "dict_with_int": {1: 1, 2: 2}, + "dict_with_named_tuple": {1: nts, 2: nts}, + } + + store = storage.Store( + hass, MOCK_VERSION_2, MOCK_KEY, minor_version=MOCK_MINOR_VERSION_1 + ) + await store.async_save(data) + load = await store.async_load() + assert load == { + "dict_with_int": {"1": 1, "2": 2}, + "dict_with_named_tuple": {"1": ["a"], "2": ["a"]}, + "list": [1, 2, 3], + "named_tuple_subclass": ["a"], + "rgb_color": [255, 255, 0], + "set": [1, 2, 3], + "tuple": [1, 2, 3], + } + + await hass.async_stop(force=True) diff --git a/tests/util/test_json.py b/tests/util/test_json.py index 28d321036c5..509c0376fae 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -12,7 +12,6 @@ import pytest from homeassistant.core import Event, State from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.json import JSONEncoder as DefaultHASSJSONEncoder -from homeassistant.helpers.template import TupleWrapper from homeassistant.util.json import ( SerializationError, find_paths_unserializable_data, @@ -83,23 +82,15 @@ def test_overwrite_and_reload(atomic_writes): def test_save_bad_data(): """Test error from trying to save unserializable data.""" + + class CannotSerializeMe: + """Cannot serialize this.""" + with pytest.raises(SerializationError) as excinfo: - save_json("test4", {"hello": set()}) + save_json("test4", {"hello": CannotSerializeMe()}) - assert ( - "Failed to serialize to JSON: test4. Bad data at $.hello=set()(" - in str(excinfo.value) - ) - - -def test_save_bad_data_tuple_wrapper(): - """Test error from trying to save unserializable data.""" - with pytest.raises(SerializationError) as excinfo: - save_json("test4", {"hello": TupleWrapper(("4", "5"))}) - - assert ( - "Failed to serialize to JSON: test4. Bad data at $.hello=('4', '5')(" - in str(excinfo.value) + assert "Failed to serialize to JSON: test4. Bad data at $.hello=" in str( + excinfo.value ) From 75641b6cd41b47215646d3cefee2f1abfd1a5f82 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Sun, 17 Jul 2022 05:07:47 +0800 Subject: [PATCH 093/104] Apply filter to libav.hls logging namespace (#75330) --- homeassistant/components/stream/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index ef68ea7bcae..f0b4ed99654 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -187,14 +187,15 @@ def filter_libav_logging() -> None: return logging.getLogger(__name__).isEnabledFor(logging.DEBUG) for logging_namespace in ( - "libav.mp4", + "libav.NULL", "libav.h264", "libav.hevc", + "libav.hls", + "libav.mp4", + "libav.mpegts", "libav.rtsp", "libav.tcp", "libav.tls", - "libav.mpegts", - "libav.NULL", ): logging.getLogger(logging_namespace).addFilter(libav_filter) From 219d1a8a1ebe71850f9fad55665c096ca6ba1e2f Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 16 Jul 2022 14:04:44 -0600 Subject: [PATCH 094/104] Handle (and better log) more AirVisual cloud API errors (#75332) --- .../components/airvisual/__init__.py | 11 ++++----- .../components/airvisual/config_flow.py | 4 +++- .../components/airvisual/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../components/airvisual/test_config_flow.py | 24 +++++++++++++++++++ 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index d0e1a741f2e..986c5306e27 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Mapping from datetime import timedelta from math import ceil -from typing import Any, cast +from typing import Any from pyairvisual import CloudAPI, NodeSamba from pyairvisual.errors import ( @@ -12,6 +12,7 @@ from pyairvisual.errors import ( InvalidKeyError, KeyExpiredError, NodeProError, + UnauthorizedError, ) from homeassistant.config_entries import ConfigEntry @@ -210,9 +211,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) try: - data = await api_coro - return cast(dict[str, Any], data) - except (InvalidKeyError, KeyExpiredError) as ex: + return await api_coro + except (InvalidKeyError, KeyExpiredError, UnauthorizedError) as ex: raise ConfigEntryAuthFailed from ex except AirVisualError as err: raise UpdateFailed(f"Error while retrieving data: {err}") from err @@ -253,8 +253,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async with NodeSamba( entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD] ) as node: - data = await node.async_get_latest_measurements() - return cast(dict[str, Any], data) + return await node.async_get_latest_measurements() except NodeProError as err: raise UpdateFailed(f"Error while retrieving data: {err}") from err diff --git a/homeassistant/components/airvisual/config_flow.py b/homeassistant/components/airvisual/config_flow.py index f97616c38fc..385c9f55753 100644 --- a/homeassistant/components/airvisual/config_flow.py +++ b/homeassistant/components/airvisual/config_flow.py @@ -9,8 +9,10 @@ from pyairvisual import CloudAPI, NodeSamba from pyairvisual.errors import ( AirVisualError, InvalidKeyError, + KeyExpiredError, NodeProError, NotFoundError, + UnauthorizedError, ) import voluptuous as vol @@ -119,7 +121,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if user_input[CONF_API_KEY] not in valid_keys: try: await coro - except InvalidKeyError: + except (InvalidKeyError, KeyExpiredError, UnauthorizedError): errors[CONF_API_KEY] = "invalid_api_key" except NotFoundError: errors[CONF_CITY] = "location_not_found" diff --git a/homeassistant/components/airvisual/manifest.json b/homeassistant/components/airvisual/manifest.json index ed803a3e6a1..9a6279f34a6 100644 --- a/homeassistant/components/airvisual/manifest.json +++ b/homeassistant/components/airvisual/manifest.json @@ -3,7 +3,7 @@ "name": "AirVisual", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/airvisual", - "requirements": ["pyairvisual==5.0.9"], + "requirements": ["pyairvisual==2022.07.0"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "loggers": ["pyairvisual", "pysmb"] diff --git a/requirements_all.txt b/requirements_all.txt index 6c2be7ec5ab..4de08b3ed71 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1366,7 +1366,7 @@ pyaftership==21.11.0 pyairnow==1.1.0 # homeassistant.components.airvisual -pyairvisual==5.0.9 +pyairvisual==2022.07.0 # homeassistant.components.almond pyalmond==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 87db37d0510..1a8aaf50166 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -929,7 +929,7 @@ pyaehw4a1==0.3.9 pyairnow==1.1.0 # homeassistant.components.airvisual -pyairvisual==5.0.9 +pyairvisual==2022.07.0 # homeassistant.components.almond pyalmond==0.0.2 diff --git a/tests/components/airvisual/test_config_flow.py b/tests/components/airvisual/test_config_flow.py index f0a75417487..9f9deca384c 100644 --- a/tests/components/airvisual/test_config_flow.py +++ b/tests/components/airvisual/test_config_flow.py @@ -4,8 +4,10 @@ from unittest.mock import patch from pyairvisual.errors import ( AirVisualError, InvalidKeyError, + KeyExpiredError, NodeProError, NotFoundError, + UnauthorizedError, ) import pytest @@ -84,6 +86,28 @@ async def test_duplicate_error(hass, config, config_entry, data): {CONF_API_KEY: "invalid_api_key"}, INTEGRATION_TYPE_GEOGRAPHY_NAME, ), + ( + { + CONF_API_KEY: "abcde12345", + CONF_CITY: "Beijing", + CONF_STATE: "Beijing", + CONF_COUNTRY: "China", + }, + KeyExpiredError, + {CONF_API_KEY: "invalid_api_key"}, + INTEGRATION_TYPE_GEOGRAPHY_NAME, + ), + ( + { + CONF_API_KEY: "abcde12345", + CONF_CITY: "Beijing", + CONF_STATE: "Beijing", + CONF_COUNTRY: "China", + }, + UnauthorizedError, + {CONF_API_KEY: "invalid_api_key"}, + INTEGRATION_TYPE_GEOGRAPHY_NAME, + ), ( { CONF_API_KEY: "abcde12345", From 630f731020d8bab7e20083a4cc0f29419660c943 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Jul 2022 12:56:07 -0500 Subject: [PATCH 095/104] Fix HKC device triggers (#75371) --- .../components/homekit_controller/device_trigger.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index dcac7238c8e..6337045a51f 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -16,7 +16,7 @@ from homeassistant.components.automation import ( ) from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE -from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, KNOWN_DEVICES, TRIGGERS @@ -86,13 +86,13 @@ class TriggerSource: ) -> CALLBACK_TYPE: """Attach a trigger.""" trigger_data = automation_info["trigger_data"] + job = HassJob(action) + @callback def event_handler(char): if config[CONF_SUBTYPE] != HK_TO_HA_INPUT_EVENT_VALUES[char["value"]]: return - self._hass.async_create_task( - action({"trigger": {**trigger_data, **config}}) - ) + self._hass.async_run_hass_job(job, {"trigger": {**trigger_data, **config}}) trigger = self._triggers[config[CONF_TYPE], config[CONF_SUBTYPE]] iid = trigger["characteristic"] @@ -231,11 +231,11 @@ async def async_setup_triggers_for_entry(hass: HomeAssistant, config_entry): def async_fire_triggers(conn: HKDevice, events: dict[tuple[int, int], Any]): """Process events generated by a HomeKit accessory into automation triggers.""" + trigger_sources: dict[str, TriggerSource] = conn.hass.data[TRIGGERS] for (aid, iid), ev in events.items(): if aid in conn.devices: device_id = conn.devices[aid] - if device_id in conn.hass.data[TRIGGERS]: - source = conn.hass.data[TRIGGERS][device_id] + if source := trigger_sources.get(device_id): source.fire(iid, ev) From 5c2ef50fca48d03e786b0953eefa2329b65a50fb Mon Sep 17 00:00:00 2001 From: mkmer Date: Mon, 18 Jul 2022 17:41:06 -0400 Subject: [PATCH 096/104] Bump AIOAladdinConnect to 0.1.27 (#75400) --- homeassistant/components/aladdin_connect/cover.py | 7 ++++--- homeassistant/components/aladdin_connect/manifest.json | 2 +- homeassistant/components/aladdin_connect/model.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/aladdin_connect/conftest.py | 1 + tests/components/aladdin_connect/test_cover.py | 5 +++++ 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/aladdin_connect/cover.py b/homeassistant/components/aladdin_connect/cover.py index 9c03cd322b6..5c28d649936 100644 --- a/homeassistant/components/aladdin_connect/cover.py +++ b/homeassistant/components/aladdin_connect/cover.py @@ -88,6 +88,7 @@ class AladdinDevice(CoverEntity): self._device_id = device["device_id"] self._number = device["door_number"] self._attr_name = device["name"] + self._serial = device["serial"] self._attr_unique_id = f"{self._device_id}-{self._number}" async def async_added_to_hass(self) -> None: @@ -97,8 +98,8 @@ class AladdinDevice(CoverEntity): """Schedule a state update.""" self.async_write_ha_state() - self._acc.register_callback(update_callback, self._number) - await self._acc.get_doors(self._number) + self._acc.register_callback(update_callback, self._serial) + await self._acc.get_doors(self._serial) async def async_will_remove_from_hass(self) -> None: """Close Aladdin Connect before removing.""" @@ -114,7 +115,7 @@ class AladdinDevice(CoverEntity): async def async_update(self) -> None: """Update status of cover.""" - await self._acc.get_doors(self._number) + await self._acc.get_doors(self._serial) @property def is_closed(self) -> bool | None: diff --git a/homeassistant/components/aladdin_connect/manifest.json b/homeassistant/components/aladdin_connect/manifest.json index 5baeba33971..4a04bf69aed 100644 --- a/homeassistant/components/aladdin_connect/manifest.json +++ b/homeassistant/components/aladdin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "aladdin_connect", "name": "Aladdin Connect", "documentation": "https://www.home-assistant.io/integrations/aladdin_connect", - "requirements": ["AIOAladdinConnect==0.1.25"], + "requirements": ["AIOAladdinConnect==0.1.27"], "codeowners": ["@mkmer"], "iot_class": "cloud_polling", "loggers": ["aladdin_connect"], diff --git a/homeassistant/components/aladdin_connect/model.py b/homeassistant/components/aladdin_connect/model.py index 4248f3504fe..63624b223a9 100644 --- a/homeassistant/components/aladdin_connect/model.py +++ b/homeassistant/components/aladdin_connect/model.py @@ -11,3 +11,4 @@ class DoorDevice(TypedDict): door_number: int name: str status: str + serial: str diff --git a/requirements_all.txt b/requirements_all.txt index 4de08b3ed71..0f20e80c459 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,7 +5,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.25 +AIOAladdinConnect==0.1.27 # homeassistant.components.adax Adax-local==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1a8aaf50166..1973add02b8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.25 +AIOAladdinConnect==0.1.27 # homeassistant.components.adax Adax-local==0.1.4 diff --git a/tests/components/aladdin_connect/conftest.py b/tests/components/aladdin_connect/conftest.py index ee68d207361..c8f7d240ba5 100644 --- a/tests/components/aladdin_connect/conftest.py +++ b/tests/components/aladdin_connect/conftest.py @@ -10,6 +10,7 @@ DEVICE_CONFIG_OPEN = { "name": "home", "status": "open", "link_status": "Connected", + "serial": "12345", } diff --git a/tests/components/aladdin_connect/test_cover.py b/tests/components/aladdin_connect/test_cover.py index 54ec4ee5de1..4e65607fa9d 100644 --- a/tests/components/aladdin_connect/test_cover.py +++ b/tests/components/aladdin_connect/test_cover.py @@ -33,6 +33,7 @@ DEVICE_CONFIG_OPEN = { "name": "home", "status": "open", "link_status": "Connected", + "serial": "12345", } DEVICE_CONFIG_OPENING = { @@ -41,6 +42,7 @@ DEVICE_CONFIG_OPENING = { "name": "home", "status": "opening", "link_status": "Connected", + "serial": "12345", } DEVICE_CONFIG_CLOSED = { @@ -49,6 +51,7 @@ DEVICE_CONFIG_CLOSED = { "name": "home", "status": "closed", "link_status": "Connected", + "serial": "12345", } DEVICE_CONFIG_CLOSING = { @@ -57,6 +60,7 @@ DEVICE_CONFIG_CLOSING = { "name": "home", "status": "closing", "link_status": "Connected", + "serial": "12345", } DEVICE_CONFIG_DISCONNECTED = { @@ -65,6 +69,7 @@ DEVICE_CONFIG_DISCONNECTED = { "name": "home", "status": "open", "link_status": "Disconnected", + "serial": "12345", } DEVICE_CONFIG_BAD = { From 55ef33af26b50e957c6eaebb75a1ab317b11a3ea Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 20 Jul 2022 03:15:36 -0400 Subject: [PATCH 097/104] Bump pytomorrowio to 0.3.4 (#75478) --- homeassistant/components/tomorrowio/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tomorrowio/manifest.json b/homeassistant/components/tomorrowio/manifest.json index 5447b90d1ce..0823b5ac185 100644 --- a/homeassistant/components/tomorrowio/manifest.json +++ b/homeassistant/components/tomorrowio/manifest.json @@ -3,7 +3,7 @@ "name": "Tomorrow.io", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tomorrowio", - "requirements": ["pytomorrowio==0.3.3"], + "requirements": ["pytomorrowio==0.3.4"], "codeowners": ["@raman325", "@lymanepp"], "iot_class": "cloud_polling" } diff --git a/requirements_all.txt b/requirements_all.txt index 0f20e80c459..a13c639eacd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1973,7 +1973,7 @@ pythonegardia==1.0.40 pytile==2022.02.0 # homeassistant.components.tomorrowio -pytomorrowio==0.3.3 +pytomorrowio==0.3.4 # homeassistant.components.touchline pytouchline==0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1973add02b8..5197e155d25 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1314,7 +1314,7 @@ python_awair==0.2.3 pytile==2022.02.0 # homeassistant.components.tomorrowio -pytomorrowio==0.3.3 +pytomorrowio==0.3.4 # homeassistant.components.traccar pytraccar==0.10.0 From 0a11a623a5de9f2c77aa72661958c19e66b521d2 Mon Sep 17 00:00:00 2001 From: Pascal Winters Date: Wed, 20 Jul 2022 12:57:00 +0200 Subject: [PATCH 098/104] Bump pySwitchbot to 0.14.1 (#75487) --- homeassistant/components/switchbot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index cb485ffd8a5..23fb36a3a41 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,7 +2,7 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.14.0"], + "requirements": ["PySwitchbot==0.14.1"], "config_flow": true, "codeowners": ["@danielhiversen", "@RenierM26"], "iot_class": "local_polling", diff --git a/requirements_all.txt b/requirements_all.txt index a13c639eacd..cb83971fe6e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -37,7 +37,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.14.0 +PySwitchbot==0.14.1 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5197e155d25..fcdd3fb2760 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -33,7 +33,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.14.0 +PySwitchbot==0.14.1 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 From 787f55e513338c61615fe251f0fc6a9368c85bd8 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 20 Jul 2022 15:51:54 +0200 Subject: [PATCH 099/104] Fix Netgear update entity (#75496) --- homeassistant/components/netgear/update.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/netgear/update.py b/homeassistant/components/netgear/update.py index 8d4a9b4912a..e913d488c8e 100644 --- a/homeassistant/components/netgear/update.py +++ b/homeassistant/components/netgear/update.py @@ -59,7 +59,9 @@ class NetgearUpdateEntity(NetgearRouterEntity, UpdateEntity): """Latest version available for install.""" if self.coordinator.data is not None: new_version = self.coordinator.data.get("NewVersion") - if new_version is not None: + if new_version is not None and not new_version.startswith( + self.installed_version + ): return new_version return self.installed_version From e53a072e8a6607e5115d417471fe81b62491e442 Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Wed, 20 Jul 2022 19:27:11 +0200 Subject: [PATCH 100/104] Fix - Forcast.solar issue on saving settings in options flow without api key (#75504) --- homeassistant/components/forecast_solar/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/forecast_solar/config_flow.py b/homeassistant/components/forecast_solar/config_flow.py index 86de17ef285..600ed363a8b 100644 --- a/homeassistant/components/forecast_solar/config_flow.py +++ b/homeassistant/components/forecast_solar/config_flow.py @@ -99,7 +99,7 @@ class ForecastSolarOptionFlowHandler(OptionsFlow): CONF_API_KEY, description={ "suggested_value": self.config_entry.options.get( - CONF_API_KEY + CONF_API_KEY, "" ) }, ): str, From 4ac7d68552582110f27958fdc2b1b8962c885546 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Jul 2022 12:25:17 -0500 Subject: [PATCH 101/104] Fix failure to raise on bad YAML syntax from include files (#75510) Co-authored-by: Franck Nijhof --- homeassistant/util/yaml/loader.py | 4 ++-- tests/util/yaml/fixtures/bad.yaml.txt | 26 ++++++++++++++++++++++++++ tests/util/yaml/test_init.py | 10 ++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 tests/util/yaml/fixtures/bad.yaml.txt diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index e3add3a7c44..09e19af6840 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections import OrderedDict from collections.abc import Iterator import fnmatch -from io import StringIO +from io import StringIO, TextIOWrapper import logging import os from pathlib import Path @@ -169,7 +169,7 @@ def parse_yaml( except yaml.YAMLError: # Loading failed, so we now load with the slow line loader # since the C one will not give us line numbers - if isinstance(content, (StringIO, TextIO)): + if isinstance(content, (StringIO, TextIO, TextIOWrapper)): # Rewind the stream so we can try again content.seek(0, 0) return _parse_yaml_pure_python(content, secrets) diff --git a/tests/util/yaml/fixtures/bad.yaml.txt b/tests/util/yaml/fixtures/bad.yaml.txt new file mode 100644 index 00000000000..8f6a62a6511 --- /dev/null +++ b/tests/util/yaml/fixtures/bad.yaml.txt @@ -0,0 +1,26 @@ +- id: '1658085239190' + alias: Config validation test + description: '' + trigger: + - platform: time + at: 00:02:03 + condition: [] + action: + - service: script.notify_admin + data: + title: 'Here's something that does not work...!' + message: failing + mode: single +- id: '165808523911590' + alias: Config validation test FIXED + description: '' + trigger: + - platform: time + at: 00:02:03 + condition: [] + action: + - service: script.notify_admin + data: + title: 'Here is something that should work...!' + message: fixed? + mode: single diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index 11dc40233dc..8d1b7c1adf1 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -2,6 +2,7 @@ import importlib import io import os +import pathlib import unittest from unittest.mock import patch @@ -490,3 +491,12 @@ def test_input(try_both_loaders, try_both_dumpers): def test_c_loader_is_available_in_ci(): """Verify we are testing the C loader in the CI.""" assert yaml.loader.HAS_C_LOADER is True + + +async def test_loading_actual_file_with_syntax(hass, try_both_loaders): + """Test loading a real file with syntax errors.""" + with pytest.raises(HomeAssistantError): + fixture_path = pathlib.Path(__file__).parent.joinpath( + "fixtures", "bad.yaml.txt" + ) + await hass.async_add_executor_job(load_yaml_config_file, fixture_path) From e692d2e28460709f357516e5a8f6c01bb04c851a Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 20 Jul 2022 11:29:23 -0600 Subject: [PATCH 102/104] Fix incorrect Ambient PWS lightning strike sensor state classes (#75520) --- homeassistant/components/ambient_station/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index c837ef6fdec..a103316f3ff 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -281,14 +281,14 @@ SENSOR_DESCRIPTIONS = ( name="Lightning Strikes Per Day", icon="mdi:lightning-bolt", native_unit_of_measurement="strikes", - state_class=SensorStateClass.TOTAL_INCREASING, + state_class=SensorStateClass.TOTAL, ), SensorEntityDescription( key=TYPE_LIGHTNING_PER_HOUR, name="Lightning Strikes Per Hour", icon="mdi:lightning-bolt", native_unit_of_measurement="strikes", - state_class=SensorStateClass.TOTAL_INCREASING, + state_class=SensorStateClass.TOTAL, ), SensorEntityDescription( key=TYPE_MAXDAILYGUST, From 67fc1ac40a6c57247887b7337ed39a46a1939ad6 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Wed, 20 Jul 2022 22:11:05 +0300 Subject: [PATCH 103/104] Bump aioshelly to 2.0.1 (#75523) --- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index a9aade00933..48e48b618e4 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==2.0.0"], + "requirements": ["aioshelly==2.0.1"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index cb83971fe6e..11b785c4fc5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -244,7 +244,7 @@ aiosenseme==0.6.1 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==2.0.0 +aioshelly==2.0.1 # homeassistant.components.skybell aioskybell==22.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fcdd3fb2760..63ef39eb63e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -213,7 +213,7 @@ aiosenseme==0.6.1 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==2.0.0 +aioshelly==2.0.1 # homeassistant.components.skybell aioskybell==22.7.0 From 7402dc824e81ffce80a8b50eb09605b0c5d2aeda Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 20 Jul 2022 21:49:42 +0200 Subject: [PATCH 104/104] Bumped version to 2022.7.6 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index a2d71ea6d71..08c92cee96e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 7 -PATCH_VERSION: Final = "5" +PATCH_VERSION: Final = "6" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index c68cd921da2..191b337eb65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.7.5" +version = "2022.7.6" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst"