From 903e2243e7502e12af8516973683873e0e523fbc Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Sun, 8 Aug 2021 09:43:08 -0400 Subject: [PATCH 01/22] Fix camera state and attributes for agent_dvr (#54049) * Fix camera state and attributes for agent_dvr * tweak * tweak --- homeassistant/components/agent_dvr/camera.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/agent_dvr/camera.py b/homeassistant/components/agent_dvr/camera.py index 30c27eb047a..8a29428a833 100644 --- a/homeassistant/components/agent_dvr/camera.py +++ b/homeassistant/components/agent_dvr/camera.py @@ -67,8 +67,6 @@ async def async_setup_entry( class AgentCamera(MjpegCamera): """Representation of an Agent Device Stream.""" - _attr_supported_features = SUPPORT_ON_OFF - def __init__(self, device): """Initialize as a subclass of MjpegCamera.""" device_info = { @@ -80,7 +78,6 @@ class AgentCamera(MjpegCamera): self._removed = False self._attr_name = f"{device.client.name} {device.name}" self._attr_unique_id = f"{device._client.unique}_{device.typeID}_{device.id}" - self._attr_should_poll = True super().__init__(device_info) self._attr_device_info = { "identifiers": {(AGENT_DOMAIN, self.unique_id)}, @@ -102,10 +99,10 @@ class AgentCamera(MjpegCamera): if self.device.client.is_available and not self._removed: _LOGGER.error("%s lost", self.name) self._removed = True - self._attr_available = self.device.client.is_available self._attr_icon = "mdi:camcorder-off" if self.is_on: self._attr_icon = "mdi:camcorder" + self._attr_available = self.device.client.is_available self._attr_extra_state_attributes = { ATTR_ATTRIBUTION: ATTRIBUTION, "editable": False, @@ -117,6 +114,11 @@ class AgentCamera(MjpegCamera): "alerts_enabled": self.device.alerts_active, } + @property + def should_poll(self) -> bool: + """Update the state periodically.""" + return True + @property def is_recording(self) -> bool: """Return whether the monitor is recording.""" @@ -137,6 +139,11 @@ class AgentCamera(MjpegCamera): """Return True if entity is connected.""" return self.device.connected + @property + def supported_features(self) -> int: + """Return supported features.""" + return SUPPORT_ON_OFF + @property def is_on(self) -> bool: """Return true if on.""" From 1809b7a98ba038384d0d0d23cdfc4c5718e70764 Mon Sep 17 00:00:00 2001 From: Reuben Gow Date: Mon, 9 Aug 2021 19:47:38 +0100 Subject: [PATCH 02/22] Force an attempted subscribe on speaker reboot (#54100) * Force an attempted subscribe on speaker reboot * Recreate subscriptions and timers explicitly on speaker reboot * only create poll timer if there is not one already Co-authored-by: jjlawren * Black Co-authored-by: jjlawren --- homeassistant/components/sonos/speaker.py | 29 ++++++++++++++++------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index 434717f7a85..919e03cf39b 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -496,9 +496,7 @@ class SonosSpeaker: self.async_write_entity_states() - async def async_unseen( - self, now: datetime.datetime | None = None, will_reconnect: bool = False - ) -> None: + async def async_unseen(self, now: datetime.datetime | None = None) -> None: """Make this player unavailable when it was not seen recently.""" if self._seen_timer: self._seen_timer() @@ -527,9 +525,8 @@ class SonosSpeaker: await self.async_unsubscribe() - if not will_reconnect: - self.hass.data[DATA_SONOS].discovery_known.discard(self.soco.uid) - self.async_write_entity_states() + self.hass.data[DATA_SONOS].discovery_known.discard(self.soco.uid) + self.async_write_entity_states() async def async_rebooted(self, soco: SoCo) -> None: """Handle a detected speaker reboot.""" @@ -538,8 +535,24 @@ class SonosSpeaker: self.zone_name, soco, ) - await self.async_unseen(will_reconnect=True) - await self.async_seen(soco) + await self.async_unsubscribe() + self.soco = soco + await self.async_subscribe() + if self._seen_timer: + self._seen_timer() + self._seen_timer = self.hass.helpers.event.async_call_later( + SEEN_EXPIRE_TIME.total_seconds(), self.async_unseen + ) + if not self._poll_timer: + self._poll_timer = self.hass.helpers.event.async_track_time_interval( + partial( + async_dispatcher_send, + self.hass, + f"{SONOS_POLL_UPDATE}-{self.soco.uid}", + ), + SCAN_INTERVAL, + ) + self.async_write_entity_states() # # Battery management From 746bb2997e0b941fd3469a0618e7fbf3cb3444d5 Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Mon, 9 Aug 2021 21:11:53 +0200 Subject: [PATCH 03/22] Fix login to BMW services for rest_of_world and north_america (#54261) 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 17aaa166942..8131ac1415c 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.7.16"], + "requirements": ["bimmer_connected==0.7.18"], "codeowners": ["@gerard33", "@rikroe"], "config_flow": true, "iot_class": "cloud_polling" diff --git a/requirements_all.txt b/requirements_all.txt index 8c936677ba2..a5cadd9b259 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -365,7 +365,7 @@ beautifulsoup4==4.9.3 bellows==0.26.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.16 +bimmer_connected==0.7.18 # homeassistant.components.bizkaibus bizkaibus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f9fbc43d789..ea3472684ce 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -220,7 +220,7 @@ base36==0.1.1 bellows==0.26.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.16 +bimmer_connected==0.7.18 # homeassistant.components.blebox blebox_uniapi==1.3.3 From 3edd505468cb1734bffcde7a96438bf867ce4416 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Aug 2021 14:13:55 -0500 Subject: [PATCH 04/22] Always set interfaces explicitly when IPv6 is present (#54268) --- homeassistant/components/zeroconf/__init__.py | 17 +++---- tests/components/zeroconf/test_init.py | 47 +++++++++++++++++-- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index cdb46318578..e7132f56b55 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -142,7 +142,15 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool: zc_args: dict = {} adapters = await network.async_get_adapters(hass) - if _async_use_default_interface(adapters): + + ipv6 = True + if not any(adapter["enabled"] and adapter["ipv6"] for adapter in adapters): + ipv6 = False + zc_args["ip_version"] = IPVersion.V4Only + else: + zc_args["ip_version"] = IPVersion.All + + if not ipv6 and _async_use_default_interface(adapters): zc_args["interfaces"] = InterfaceChoice.Default else: interfaces = zc_args["interfaces"] = [] @@ -158,13 +166,6 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool: if adapter["ipv6"] and adapter["index"] not in interfaces: interfaces.append(adapter["index"]) - ipv6 = True - if not any(adapter["enabled"] and adapter["ipv6"] for adapter in adapters): - ipv6 = False - zc_args["ip_version"] = IPVersion.V4Only - else: - zc_args["ip_version"] = IPVersion.All - aio_zc = await _async_get_instance(hass, **zc_args) zeroconf = cast(HaZeroconf, aio_zc.zeroconf) zeroconf_types, homekit_models = await asyncio.gather( diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index e1e346621fe..0db8f0f5227 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -794,11 +794,6 @@ async def test_async_detect_interfaces_setting_empty_route(hass, mock_async_zero ), patch( "homeassistant.components.zeroconf.AsyncServiceInfo", side_effect=get_service_info_mock, - ), patch( - "socket.if_nametoindex", - side_effect=lambda iface: {"eth0": 1, "eth1": 2, "eth2": 3, "vtun0": 4}.get( - iface, 0 - ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -827,3 +822,45 @@ async def test_get_announced_addresses(hass, mock_async_zeroconf): first_ip = ip_address("192.168.1.5").packed actual = _get_announced_addresses(_ADAPTERS_WITH_MANUAL_CONFIG, first_ip) assert actual[0] == first_ip and set(actual) == expected + + +_ADAPTER_WITH_DEFAULT_ENABLED_AND_IPV6 = [ + { + "auto": True, + "default": True, + "enabled": True, + "index": 1, + "ipv4": [{"address": "192.168.1.5", "network_prefix": 23}], + "ipv6": [ + { + "address": "fe80::dead:beef:dead:beef", + "network_prefix": 64, + "flowinfo": 1, + "scope_id": 3, + } + ], + "name": "eth1", + } +] + + +async def test_async_detect_interfaces_explicitly_set_ipv6(hass, mock_async_zeroconf): + """Test interfaces are explicitly set when IPv6 is present.""" + with patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc, patch.object( + hass.config_entries.flow, "async_init" + ), patch.object( + zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock + ), patch( + "homeassistant.components.zeroconf.network.async_get_adapters", + return_value=_ADAPTER_WITH_DEFAULT_ENABLED_AND_IPV6, + ), patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_service_info_mock, + ): + assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + assert mock_zc.mock_calls[0] == call( + interfaces=["192.168.1.5", 1], ip_version=IPVersion.All + ) From 955a72080f7d92599bf550dd563fcda0f44ae2a3 Mon Sep 17 00:00:00 2001 From: ZeGuigui Date: Mon, 9 Aug 2021 11:38:16 +0200 Subject: [PATCH 05/22] Fix atom integration for long term statistics (#54285) * Fix atom integration for long term statistics * Remove commented code * Fix last_reset syntax * last_reset not an extra attribute * last_reset as utc * black formatting * isort fix --- homeassistant/components/atome/sensor.py | 40 +++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/atome/sensor.py b/homeassistant/components/atome/sensor.py index bcb7b4f1ece..7295a9cee41 100644 --- a/homeassistant/components/atome/sensor.py +++ b/homeassistant/components/atome/sensor.py @@ -14,12 +14,13 @@ from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, CONF_USERNAME, + DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR, POWER_WATT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.util import Throttle +from homeassistant.util import Throttle, dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -87,12 +88,16 @@ class AtomeData: self._is_connected = None self._day_usage = None self._day_price = None + self._day_last_reset = None self._week_usage = None self._week_price = None + self._week_last_reset = None self._month_usage = None self._month_price = None + self._month_last_reset = None self._year_usage = None self._year_price = None + self._year_last_reset = None @property def live_power(self): @@ -137,6 +142,11 @@ class AtomeData: """Return latest daily usage value.""" return self._day_price + @property + def day_last_reset(self): + """Return latest daily last reset.""" + return self._day_last_reset + @Throttle(DAILY_SCAN_INTERVAL) def update_day_usage(self): """Return current daily power usage.""" @@ -144,6 +154,7 @@ class AtomeData: values = self.atome_client.get_consumption(DAILY_TYPE) self._day_usage = values["total"] / 1000 self._day_price = values["price"] + self._day_last_reset = dt_util.parse_datetime(values["startPeriod"]) _LOGGER.debug("Updating Atome daily data. Got: %d", self._day_usage) except KeyError as error: @@ -159,6 +170,11 @@ class AtomeData: """Return latest weekly usage value.""" return self._week_price + @property + def week_last_reset(self): + """Return latest weekly last reset value.""" + return self._week_last_reset + @Throttle(WEEKLY_SCAN_INTERVAL) def update_week_usage(self): """Return current weekly power usage.""" @@ -166,6 +182,7 @@ class AtomeData: values = self.atome_client.get_consumption(WEEKLY_TYPE) self._week_usage = values["total"] / 1000 self._week_price = values["price"] + self._week_last_reset = dt_util.parse_datetime(values["startPeriod"]) _LOGGER.debug("Updating Atome weekly data. Got: %d", self._week_usage) except KeyError as error: @@ -181,6 +198,11 @@ class AtomeData: """Return latest monthly usage value.""" return self._month_price + @property + def month_last_reset(self): + """Return latest monthly last reset value.""" + return self._month_last_reset + @Throttle(MONTHLY_SCAN_INTERVAL) def update_month_usage(self): """Return current monthly power usage.""" @@ -188,6 +210,7 @@ class AtomeData: values = self.atome_client.get_consumption(MONTHLY_TYPE) self._month_usage = values["total"] / 1000 self._month_price = values["price"] + self._month_last_reset = dt_util.parse_datetime(values["startPeriod"]) _LOGGER.debug("Updating Atome monthly data. Got: %d", self._month_usage) except KeyError as error: @@ -203,6 +226,11 @@ class AtomeData: """Return latest yearly usage value.""" return self._year_price + @property + def year_last_reset(self): + """Return latest yearly last reset value.""" + return self._year_last_reset + @Throttle(YEARLY_SCAN_INTERVAL) def update_year_usage(self): """Return current yearly power usage.""" @@ -210,6 +238,7 @@ class AtomeData: values = self.atome_client.get_consumption(YEARLY_TYPE) self._year_usage = values["total"] / 1000 self._year_price = values["price"] + self._year_last_reset = dt_util.parse_datetime(values["startPeriod"]) _LOGGER.debug("Updating Atome yearly data. Got: %d", self._year_usage) except KeyError as error: @@ -219,19 +248,19 @@ class AtomeData: class AtomeSensor(SensorEntity): """Representation of a sensor entity for Atome.""" - _attr_device_class = DEVICE_CLASS_POWER - def __init__(self, data, name, sensor_type): """Initialize the sensor.""" self._attr_name = name self._data = data self._sensor_type = sensor_type + self._attr_state_class = STATE_CLASS_MEASUREMENT if sensor_type == LIVE_TYPE: + self._attr_device_class = DEVICE_CLASS_POWER self._attr_unit_of_measurement = POWER_WATT - self._attr_state_class = STATE_CLASS_MEASUREMENT else: + self._attr_device_class = DEVICE_CLASS_ENERGY self._attr_unit_of_measurement = ENERGY_KILO_WATT_HOUR def update(self): @@ -247,6 +276,9 @@ class AtomeSensor(SensorEntity): } else: self._attr_state = getattr(self._data, f"{self._sensor_type}_usage") + self._attr_last_reset = dt_util.as_utc( + getattr(self._data, f"{self._sensor_type}_last_reset") + ) self._attr_extra_state_attributes = { "price": getattr(self._data, f"{self._sensor_type}_price") } From cf92d45f072ce3c997528d7bac62ca5bca0ad0f0 Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Mon, 9 Aug 2021 14:55:58 -0400 Subject: [PATCH 06/22] Use correct state attribute for alarmdecoder binary sensor (#54286) Co-authored-by: Martin Hjelmare --- homeassistant/components/alarmdecoder/binary_sensor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/alarmdecoder/binary_sensor.py b/homeassistant/components/alarmdecoder/binary_sensor.py index 397394e256b..430a4f73262 100644 --- a/homeassistant/components/alarmdecoder/binary_sensor.py +++ b/homeassistant/components/alarmdecoder/binary_sensor.py @@ -111,13 +111,13 @@ class AlarmDecoderBinarySensor(BinarySensorEntity): def _fault_callback(self, zone): """Update the zone's state, if needed.""" if zone is None or int(zone) == self._zone_number: - self._attr_state = 1 + self._attr_is_on = True self.schedule_update_ha_state() def _restore_callback(self, zone): """Update the zone's state, if needed.""" if zone is None or (int(zone) == self._zone_number and not self._loop): - self._attr_state = 0 + self._attr_is_on = False self.schedule_update_ha_state() def _rfx_message_callback(self, message): @@ -125,7 +125,7 @@ class AlarmDecoderBinarySensor(BinarySensorEntity): if self._rfid and message and message.serial_number == self._rfid: rfstate = message.value if self._loop: - self._attr_state = 1 if message.loop[self._loop - 1] else 0 + self._attr_is_on = bool(message.loop[self._loop - 1]) attr = {CONF_ZONE_NUMBER: self._zone_number} if self._rfid and rfstate is not None: attr[ATTR_RF_BIT0] = bool(rfstate & 0x01) @@ -150,5 +150,5 @@ class AlarmDecoderBinarySensor(BinarySensorEntity): message.channel, message.value, ) - self._attr_state = message.value + self._attr_is_on = bool(message.value) self.schedule_update_ha_state() From 985dab6bdf6f56ca9730bcd7760c25ae60c39a4a Mon Sep 17 00:00:00 2001 From: jjlawren Date: Mon, 9 Aug 2021 02:21:07 -0500 Subject: [PATCH 07/22] Bump soco to 0.23.3 (#54288) --- homeassistant/components/sonos/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index 4ce5623ac38..d9c2a2cc6c9 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -3,7 +3,7 @@ "name": "Sonos", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sonos", - "requirements": ["soco==0.23.2"], + "requirements": ["soco==0.23.3"], "dependencies": ["ssdp"], "after_dependencies": ["plex", "zeroconf"], "zeroconf": ["_sonos._tcp.local."], diff --git a/requirements_all.txt b/requirements_all.txt index a5cadd9b259..812bd5f3949 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2152,7 +2152,7 @@ smhi-pkg==1.0.15 snapcast==2.1.3 # homeassistant.components.sonos -soco==0.23.2 +soco==0.23.3 # homeassistant.components.solaredge_local solaredge-local==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ea3472684ce..55fda7e459d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1177,7 +1177,7 @@ smarthab==0.21 smhi-pkg==1.0.15 # homeassistant.components.sonos -soco==0.23.2 +soco==0.23.3 # homeassistant.components.solaredge solaredge==0.0.2 From 5402173e98417825f4ee04e6eb9f7a73950c970d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 9 Aug 2021 20:53:30 +0200 Subject: [PATCH 08/22] Fix ondilo_ico name attribute (#54290) --- homeassistant/components/ondilo_ico/sensor.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/ondilo_ico/sensor.py b/homeassistant/components/ondilo_ico/sensor.py index 26a61ddfe4c..7449524d9e5 100644 --- a/homeassistant/components/ondilo_ico/sensor.py +++ b/homeassistant/components/ondilo_ico/sensor.py @@ -141,9 +141,9 @@ class OndiloICO(CoordinatorEntity, SensorEntity): self._poolid = self.coordinator.data[poolidx]["id"] pooldata = self._pooldata() - self._unique_id = f"{pooldata['ICO']['serial_number']}-{description.key}" + self._attr_unique_id = f"{pooldata['ICO']['serial_number']}-{description.key}" self._device_name = pooldata["name"] - self._name = f"{self._device_name} {description.name}" + self._attr_name = f"{self._device_name} {description.name}" def _pooldata(self): """Get pool data dict.""" @@ -168,11 +168,6 @@ class OndiloICO(CoordinatorEntity, SensorEntity): """Last value of the sensor.""" return self._devdata()["value"] - @property - def unique_id(self): - """Return the unique ID of this entity.""" - return self._unique_id - @property def device_info(self): """Return the device info for the sensor.""" From 9caad5b2c7fc99a693346c3c891da86acefb418a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Aug 2021 13:58:27 -0500 Subject: [PATCH 09/22] Bump zeroconf to 0.34.3 (#54294) --- homeassistant/components/zeroconf/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/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index 1847a1c806b..83db312601c 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -2,7 +2,7 @@ "domain": "zeroconf", "name": "Zero-configuration networking (zeroconf)", "documentation": "https://www.home-assistant.io/integrations/zeroconf", - "requirements": ["zeroconf==0.33.4"], + "requirements": ["zeroconf==0.34.3"], "dependencies": ["network", "api"], "codeowners": ["@bdraco"], "quality_scale": "internal", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 6d22aa51b24..963cccf9ad2 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -33,7 +33,7 @@ sqlalchemy==1.4.17 voluptuous-serialize==2.4.0 voluptuous==0.12.1 yarl==1.6.3 -zeroconf==0.33.4 +zeroconf==0.34.3 pycryptodome>=3.6.6 diff --git a/requirements_all.txt b/requirements_all.txt index 812bd5f3949..2fb05e5348c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2439,7 +2439,7 @@ zeep[async]==4.0.0 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.33.4 +zeroconf==0.34.3 # homeassistant.components.zha zha-quirks==0.0.59 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 55fda7e459d..26f69c82727 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1341,7 +1341,7 @@ youless-api==0.10 zeep[async]==4.0.0 # homeassistant.components.zeroconf -zeroconf==0.33.4 +zeroconf==0.34.3 # homeassistant.components.zha zha-quirks==0.0.59 From 2a1d2b77a120cc098f276cd38d526cf6a370b84a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Aug 2021 14:03:55 -0500 Subject: [PATCH 10/22] Ensure hunterdouglas_powerview model type is a string (#54299) --- homeassistant/components/hunterdouglas_powerview/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/hunterdouglas_powerview/entity.py b/homeassistant/components/hunterdouglas_powerview/entity.py index bf0d5d564ff..db4b984703c 100644 --- a/homeassistant/components/hunterdouglas_powerview/entity.py +++ b/homeassistant/components/hunterdouglas_powerview/entity.py @@ -71,7 +71,7 @@ class ShadeEntity(HDEntity): "name": self._shade_name, "suggested_area": self._room_name, "manufacturer": MANUFACTURER, - "model": self._shade.raw_data[ATTR_TYPE], + "model": str(self._shade.raw_data[ATTR_TYPE]), "via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]), } From b21f319b0a0c832fb4ca7c0f8b7adc04acdcdf91 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 9 Aug 2021 04:21:41 -0700 Subject: [PATCH 11/22] Remove zwave_js transition on individual color channels (#54303) --- homeassistant/components/zwave_js/light.py | 2 +- tests/components/zwave_js/test_services.py | 9 +++++---- .../zwave_js/bulb_6_multi_color_state.json | 15 +++++---------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index f3cabe8b6a7..4f1de6c686d 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -301,7 +301,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): # fallback to setting the color(s) one by one if multicolor fails # not sure this is needed at all, but just in case for color, value in colors.items(): - await self._async_set_color(color, value, zwave_transition) + await self._async_set_color(color, value) async def _async_set_color( self, diff --git a/tests/components/zwave_js/test_services.py b/tests/components/zwave_js/test_services.py index 3ee656e40c0..4cc5b599f19 100644 --- a/tests/components/zwave_js/test_services.py +++ b/tests/components/zwave_js/test_services.py @@ -1021,8 +1021,7 @@ async def test_multicast_set_value_options( ], ATTR_COMMAND_CLASS: 51, ATTR_PROPERTY: "targetColor", - ATTR_PROPERTY_KEY: 2, - ATTR_VALUE: 2, + ATTR_VALUE: '{ "warmWhite": 0, "coldWhite": 0, "red": 255, "green": 0, "blue": 0 }', ATTR_OPTIONS: {"transitionDuration": 1}, }, blocking=True, @@ -1038,9 +1037,11 @@ async def test_multicast_set_value_options( assert args["valueId"] == { "commandClass": 51, "property": "targetColor", - "propertyKey": 2, } - assert args["value"] == 2 + assert ( + args["value"] + == '{ "warmWhite": 0, "coldWhite": 0, "red": 255, "green": 0, "blue": 0 }' + ) assert args["options"] == {"transitionDuration": 1} client.async_send_command.reset_mock() diff --git a/tests/fixtures/zwave_js/bulb_6_multi_color_state.json b/tests/fixtures/zwave_js/bulb_6_multi_color_state.json index dfa72af6aa4..58608131e90 100644 --- a/tests/fixtures/zwave_js/bulb_6_multi_color_state.json +++ b/tests/fixtures/zwave_js/bulb_6_multi_color_state.json @@ -267,8 +267,7 @@ "min": 0, "max": 255, "label": "Target value (Warm White)", - "description": "The target value of the Warm White color.", - "valueChangeOptions": ["transitionDuration"] + "description": "The target value of the Warm White color." } }, { @@ -286,8 +285,7 @@ "min": 0, "max": 255, "label": "Target value (Cold White)", - "description": "The target value of the Cold White color.", - "valueChangeOptions": ["transitionDuration"] + "description": "The target value of the Cold White color." } }, { @@ -305,8 +303,7 @@ "min": 0, "max": 255, "label": "Target value (Red)", - "description": "The target value of the Red color.", - "valueChangeOptions": ["transitionDuration"] + "description": "The target value of the Red color." } }, { @@ -324,8 +321,7 @@ "min": 0, "max": 255, "label": "Target value (Green)", - "description": "The target value of the Green color.", - "valueChangeOptions": ["transitionDuration"] + "description": "The target value of the Green color." } }, { @@ -343,8 +339,7 @@ "min": 0, "max": 255, "label": "Target value (Blue)", - "description": "The target value of the Blue color.", - "valueChangeOptions": ["transitionDuration"] + "description": "The target value of the Blue color." } }, { From 59c882a0f58a8a925c2857c8e53ee3258f838ec2 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 9 Aug 2021 18:48:01 +0100 Subject: [PATCH 12/22] Restores unit_of_measurement (#54335) --- homeassistant/components/integration/sensor.py | 4 ++++ tests/components/integration/test_sensor.py | 1 + 2 files changed, 5 insertions(+) diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index dea8970f4f7..b9fd1da4e42 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -145,6 +145,10 @@ class IntegrationSensor(RestoreEntity, SensorEntity): ) self._attr_device_class = state.attributes.get(ATTR_DEVICE_CLASS) + self._unit_of_measurement = state.attributes.get( + ATTR_UNIT_OF_MEASUREMENT + ) + @callback def calc_integration(event): """Handle the sensor state changes.""" diff --git a/tests/components/integration/test_sensor.py b/tests/components/integration/test_sensor.py index dd6bf980d0f..36d3d4b3b30 100644 --- a/tests/components/integration/test_sensor.py +++ b/tests/components/integration/test_sensor.py @@ -73,6 +73,7 @@ async def test_restore_state(hass: HomeAssistant) -> None: { "last_reset": "2019-10-06T21:00:00", "device_class": DEVICE_CLASS_ENERGY, + "unit_of_measurement": ENERGY_KILO_WATT_HOUR, }, ), ), From cb14acd6066baac076e5b185684c296339902b67 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 9 Aug 2021 20:57:36 +0200 Subject: [PATCH 13/22] Fix xiaomi air fresh fan preset modes (#54342) --- homeassistant/components/xiaomi_miio/fan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index c58d9ad0c66..05e32507b20 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -1138,6 +1138,7 @@ class XiaomiAirFresh(XiaomiGenericDevice): self._speed_list = OPERATION_MODES_AIRFRESH self._speed_count = 4 self._preset_modes = PRESET_MODES_AIRFRESH + self._supported_features = SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE self._state_attrs.update( {attribute: None for attribute in self._available_attributes} ) From 84f65860581e6eb6d699225a68d5a692b191af60 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 9 Aug 2021 22:27:09 +0200 Subject: [PATCH 14/22] Update frontend to 20210809.0 (#54350) --- homeassistant/components/frontend/manifest.json | 10 +++++++--- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index b9a84cbec02..135c0ec0244 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,9 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20210804.0"], + "requirements": [ + "home-assistant-frontend==20210809.0" + ], "dependencies": [ "api", "auth", @@ -15,6 +17,8 @@ "system_log", "websocket_api" ], - "codeowners": ["@home-assistant/frontend"], + "codeowners": [ + "@home-assistant/frontend" + ], "quality_scale": "internal" -} +} \ No newline at end of file diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 963cccf9ad2..2e07d0adc18 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -17,7 +17,7 @@ defusedxml==0.7.1 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.44.0 -home-assistant-frontend==20210804.0 +home-assistant-frontend==20210809.0 httpx==0.18.2 ifaddr==0.1.7 jinja2==3.0.1 diff --git a/requirements_all.txt b/requirements_all.txt index 2fb05e5348c..fa23e5c3d53 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -783,7 +783,7 @@ hole==0.5.1 holidays==0.11.2 # homeassistant.components.frontend -home-assistant-frontend==20210804.0 +home-assistant-frontend==20210809.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 26f69c82727..adf4c23fd03 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -449,7 +449,7 @@ hole==0.5.1 holidays==0.11.2 # homeassistant.components.frontend -home-assistant-frontend==20210804.0 +home-assistant-frontend==20210809.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 3fcbde3b9cd829cdc710994f49d090588508352d Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 9 Aug 2021 23:43:59 +0200 Subject: [PATCH 15/22] Fix Xiaomi-miio turn fan on with speed, percentage or preset (#54353) --- homeassistant/components/xiaomi_miio/fan.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 05e32507b20..feeadf2bccc 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -615,6 +615,10 @@ class XiaomiGenericDevice(XiaomiMiioEntity, FanEntity): **kwargs, ) -> None: """Turn the device on.""" + result = await self._try_command( + "Turning the miio device on failed.", self._device.on + ) + # Remove the async_set_speed call is async_set_percentage and async_set_preset_modes have been implemented if speed: await self.async_set_speed(speed) @@ -623,10 +627,6 @@ class XiaomiGenericDevice(XiaomiMiioEntity, FanEntity): await self.async_set_percentage(percentage) if preset_mode: await self.async_set_preset_mode(preset_mode) - else: - result = await self._try_command( - "Turning the miio device on failed.", self._device.on - ) if result: self._state = True From cfa6040d55c8068f585078d46d8b60eb617a45e1 Mon Sep 17 00:00:00 2001 From: dailow Date: Mon, 9 Aug 2021 14:50:09 -0700 Subject: [PATCH 16/22] Fix aqualogic state attribute update (#54354) --- homeassistant/components/aqualogic/sensor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/aqualogic/sensor.py b/homeassistant/components/aqualogic/sensor.py index 01f31757c9d..fff73cf00fa 100644 --- a/homeassistant/components/aqualogic/sensor.py +++ b/homeassistant/components/aqualogic/sensor.py @@ -93,9 +93,10 @@ class AquaLogicSensor(SensorEntity): if panel is not None: if panel.is_metric: self._attr_unit_of_measurement = SENSOR_TYPES[self._type][1][0] - self._attr_state = getattr(panel, self._type) - self.async_write_ha_state() else: self._attr_unit_of_measurement = SENSOR_TYPES[self._type][1][1] + + self._attr_state = getattr(panel, self._type) + self.async_write_ha_state() else: self._attr_unit_of_measurement = None From 4f3cf5a61c2cde0438db9894193710d01809d2ed Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 9 Aug 2021 14:50:39 -0700 Subject: [PATCH 17/22] Cast SimpliSafe version number as a string in device info (#54356) --- homeassistant/components/simplisafe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 0853aa3974c..924cf398f64 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -431,7 +431,7 @@ class SimpliSafeEntity(CoordinatorEntity): self._attr_device_info = { "identifiers": {(DOMAIN, system.system_id)}, "manufacturer": "SimpliSafe", - "model": system.version, + "model": str(system.version), "name": name, "via_device": (DOMAIN, system.serial), } From b4d466f87c1b9d9571c886dc87e84d8b33ee1d54 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 9 Aug 2021 16:45:56 -0700 Subject: [PATCH 18/22] Do not process forwarded for headers for cloud requests (#54364) --- homeassistant/components/cloud/manifest.json | 2 +- homeassistant/components/http/forwarded.py | 11 ++++++++++- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/http/test_forwarded.py | 20 ++++++++++++++++++++ 6 files changed, 34 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 7516f32c3e1..abf73c1d54b 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.44.0"], + "requirements": ["hass-nabucasa==0.45.1"], "dependencies": ["http", "webhook"], "after_dependencies": ["google_assistant", "alexa"], "codeowners": ["@home-assistant/cloud"], diff --git a/homeassistant/components/http/forwarded.py b/homeassistant/components/http/forwarded.py index 18bc51af1d1..9a76866ba21 100644 --- a/homeassistant/components/http/forwarded.py +++ b/homeassistant/components/http/forwarded.py @@ -63,12 +63,19 @@ def async_setup_forwarded( an HTTP 400 status code is thrown. """ + try: + from hass_nabucasa import remote # pylint: disable=import-outside-toplevel + except ImportError: + remote = None + @middleware async def forwarded_middleware( request: Request, handler: Callable[[Request], Awaitable[StreamResponse]] ) -> StreamResponse: """Process forwarded data by a reverse proxy.""" - overrides: dict[str, str] = {} + # Skip requests from Remote UI + if remote is not None and remote.is_cloud_request.get(): + return await handler(request) # Handle X-Forwarded-For forwarded_for_headers: list[str] = request.headers.getall(X_FORWARDED_FOR, []) @@ -120,6 +127,8 @@ def async_setup_forwarded( ) raise HTTPBadRequest from err + overrides: dict[str, str] = {} + # Find the last trusted index in the X-Forwarded-For list forwarded_for_index = 0 for forwarded_ip in forwarded_for: diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 2e07d0adc18..d03c4b7c4f7 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -16,7 +16,7 @@ cryptography==3.3.2 defusedxml==0.7.1 distro==1.5.0 emoji==1.2.0 -hass-nabucasa==0.44.0 +hass-nabucasa==0.45.1 home-assistant-frontend==20210809.0 httpx==0.18.2 ifaddr==0.1.7 diff --git a/requirements_all.txt b/requirements_all.txt index fa23e5c3d53..15858eee270 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -750,7 +750,7 @@ habitipy==0.2.0 hangups==0.4.14 # homeassistant.components.cloud -hass-nabucasa==0.44.0 +hass-nabucasa==0.45.1 # homeassistant.components.splunk hass_splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index adf4c23fd03..67b7b53458a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -428,7 +428,7 @@ habitipy==0.2.0 hangups==0.4.14 # homeassistant.components.cloud -hass-nabucasa==0.44.0 +hass-nabucasa==0.45.1 # homeassistant.components.tasmota hatasmota==0.2.20 diff --git a/tests/components/http/test_forwarded.py b/tests/components/http/test_forwarded.py index 400a1f32729..42e67416044 100644 --- a/tests/components/http/test_forwarded.py +++ b/tests/components/http/test_forwarded.py @@ -1,5 +1,6 @@ """Test real forwarded middleware.""" from ipaddress import ip_network +from unittest.mock import Mock, patch from aiohttp import web from aiohttp.hdrs import X_FORWARDED_FOR, X_FORWARDED_HOST, X_FORWARDED_PROTO @@ -441,3 +442,22 @@ async def test_x_forwarded_host_with_empty_header(aiohttp_client, caplog): assert resp.status == 400 assert "Empty value received in X-Forward-Host header" in caplog.text + + +async def test_x_forwarded_cloud(aiohttp_client, caplog): + """Test that cloud requests are not processed.""" + app = web.Application() + app.router.add_get("/", mock_handler) + async_setup_forwarded(app, True, [ip_network("127.0.0.1")]) + + mock_api_client = await aiohttp_client(app) + + with patch( + "hass_nabucasa.remote.is_cloud_request", Mock(get=Mock(return_value=True)) + ): + resp = await mock_api_client.get( + "/", headers={X_FORWARDED_FOR: "222.222.222.222", X_FORWARDED_HOST: ""} + ) + + # This request would normally fail because it's invalid, now it works. + assert resp.status == 200 From d4290d1e031e67ebfc9761b5de7250cc226e1231 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 9 Aug 2021 16:45:39 -0700 Subject: [PATCH 19/22] Revert "Use entity class attributes for Bluesound (#53033)" (#54365) --- .../components/bluesound/media_player.py | 185 +++++++++++------- 1 file changed, 115 insertions(+), 70 deletions(-) diff --git a/homeassistant/components/bluesound/media_player.py b/homeassistant/components/bluesound/media_player.py index a565a0f560c..86d0be72bdc 100644 --- a/homeassistant/components/bluesound/media_player.py +++ b/homeassistant/components/bluesound/media_player.py @@ -203,29 +203,33 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class BluesoundPlayer(MediaPlayerEntity): """Representation of a Bluesound Player.""" - _attr_media_content_type = MEDIA_TYPE_MUSIC - - def __init__(self, hass, host, port=DEFAULT_PORT, name=None, init_callback=None): + def __init__(self, hass, host, port=None, name=None, init_callback=None): """Initialize the media player.""" self.host = host self._hass = hass self.port = port self._polling_session = async_get_clientsession(hass) self._polling_task = None # The actual polling task. - self._attr_name = name + self._name = name + self._icon = None self._capture_items = [] self._services_items = [] self._preset_items = [] self._sync_status = {} self._status = None - self._is_online = None + self._last_status_update = None + self._is_online = False self._retry_remove = None + self._muted = False self._master = None - self._group_name = None - self._bluesound_device_name = None self._is_master = False + self._group_name = None self._group_list = [] + self._bluesound_device_name = None + self._init_callback = init_callback + if self.port is None: + self.port = DEFAULT_PORT class _TimeoutException(Exception): pass @@ -248,12 +252,12 @@ class BluesoundPlayer(MediaPlayerEntity): return None self._sync_status = resp["SyncStatus"].copy() - if not self.name: - self._attr_name = self._sync_status.get("@name", self.host) + if not self._name: + self._name = self._sync_status.get("@name", self.host) if not self._bluesound_device_name: self._bluesound_device_name = self._sync_status.get("@name", self.host) - if not self.icon: - self._attr_icon = self._sync_status.get("@icon", self.host) + if not self._icon: + self._icon = self._sync_status.get("@icon", self.host) master = self._sync_status.get("master") if master is not None: @@ -287,14 +291,14 @@ class BluesoundPlayer(MediaPlayerEntity): await self.async_update_status() except (asyncio.TimeoutError, ClientError, BluesoundPlayer._TimeoutException): - _LOGGER.info("Node %s is offline, retrying later", self.name) + _LOGGER.info("Node %s is offline, retrying later", self._name) await asyncio.sleep(NODE_OFFLINE_CHECK_TIMEOUT) self.start_polling() except CancelledError: - _LOGGER.debug("Stopping the polling of node %s", self.name) + _LOGGER.debug("Stopping the polling of node %s", self._name) except Exception: - _LOGGER.exception("Unexpected error in %s", self.name) + _LOGGER.exception("Unexpected error in %s", self._name) raise def start_polling(self): @@ -398,7 +402,7 @@ class BluesoundPlayer(MediaPlayerEntity): if response.status == HTTP_OK: result = await response.text() self._is_online = True - self._attr_media_position_updated_at = dt_util.utcnow() + self._last_status_update = dt_util.utcnow() self._status = xmltodict.parse(result)["status"].copy() group_name = self._status.get("groupName") @@ -434,58 +438,11 @@ class BluesoundPlayer(MediaPlayerEntity): except (asyncio.TimeoutError, ClientError): self._is_online = False - self._attr_media_position_updated_at = None + self._last_status_update = None self._status = None self.async_write_ha_state() - _LOGGER.info("Client connection error, marking %s as offline", self.name) + _LOGGER.info("Client connection error, marking %s as offline", self._name) raise - self.update_state_attr() - - def update_state_attr(self): - """Update state attributes.""" - if self._status is None: - self._attr_state = STATE_OFF - self._attr_supported_features = 0 - elif self.is_grouped and not self.is_master: - self._attr_state = STATE_GROUPED - self._attr_supported_features = ( - SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE - ) - else: - status = self._status.get("state") - self._attr_state = STATE_IDLE - if status in ("pause", "stop"): - self._attr_state = STATE_PAUSED - elif status in ("stream", "play"): - self._attr_state = STATE_PLAYING - supported = SUPPORT_CLEAR_PLAYLIST - if self._status.get("indexing", "0") == "0": - supported = ( - supported - | SUPPORT_PAUSE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_PLAY_MEDIA - | SUPPORT_STOP - | SUPPORT_PLAY - | SUPPORT_SELECT_SOURCE - | SUPPORT_SHUFFLE_SET - ) - if self.volume_level is not None and self.volume_level >= 0: - supported = ( - supported - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - ) - if self._status.get("canSeek", "") == "1": - supported = supported | SUPPORT_SEEK - self._attr_supported_features = supported - self._attr_extra_state_attributes = {} - if self._group_list: - self._attr_extra_state_attributes = {ATTR_BLUESOUND_GROUP: self._group_list} - self._attr_extra_state_attributes[ATTR_MASTER] = self._is_master - self._attr_shuffle = self._status.get("shuffle", "0") == "1" async def async_trigger_sync_on_all(self): """Trigger sync status update on all devices.""" @@ -585,6 +542,27 @@ class BluesoundPlayer(MediaPlayerEntity): return self._services_items + @property + def media_content_type(self): + """Content type of current playing media.""" + return MEDIA_TYPE_MUSIC + + @property + def state(self): + """Return the state of the device.""" + if self._status is None: + return STATE_OFF + + if self.is_grouped and not self.is_master: + return STATE_GROUPED + + status = self._status.get("state") + if status in ("pause", "stop"): + return STATE_PAUSED + if status in ("stream", "play"): + return STATE_PLAYING + return STATE_IDLE + @property def media_title(self): """Title of current playing media.""" @@ -639,7 +617,7 @@ class BluesoundPlayer(MediaPlayerEntity): return None mediastate = self.state - if self.media_position_updated_at is None or mediastate == STATE_IDLE: + if self._last_status_update is None or mediastate == STATE_IDLE: return None position = self._status.get("secs") @@ -648,9 +626,7 @@ class BluesoundPlayer(MediaPlayerEntity): position = float(position) if mediastate == STATE_PLAYING: - position += ( - dt_util.utcnow() - self.media_position_updated_at - ).total_seconds() + position += (dt_util.utcnow() - self._last_status_update).total_seconds() return position @@ -665,6 +641,11 @@ class BluesoundPlayer(MediaPlayerEntity): return None return float(duration) + @property + def media_position_updated_at(self): + """Last time status was updated.""" + return self._last_status_update + @property def volume_level(self): """Volume level of the media player (0..1).""" @@ -687,11 +668,21 @@ class BluesoundPlayer(MediaPlayerEntity): mute = bool(int(mute)) return mute + @property + def name(self): + """Return the name of the device.""" + return self._name + @property def bluesound_device_name(self): """Return the device name as returned by the device.""" return self._bluesound_device_name + @property + def icon(self): + """Return the icon of the device.""" + return self._icon + @property def source_list(self): """List of available input sources.""" @@ -787,15 +778,58 @@ class BluesoundPlayer(MediaPlayerEntity): return None @property - def is_master(self) -> bool: + def supported_features(self): + """Flag of media commands that are supported.""" + if self._status is None: + return 0 + + if self.is_grouped and not self.is_master: + return SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE + + supported = SUPPORT_CLEAR_PLAYLIST + + if self._status.get("indexing", "0") == "0": + supported = ( + supported + | SUPPORT_PAUSE + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_PLAY_MEDIA + | SUPPORT_STOP + | SUPPORT_PLAY + | SUPPORT_SELECT_SOURCE + | SUPPORT_SHUFFLE_SET + ) + + current_vol = self.volume_level + if current_vol is not None and current_vol >= 0: + supported = ( + supported + | SUPPORT_VOLUME_STEP + | SUPPORT_VOLUME_SET + | SUPPORT_VOLUME_MUTE + ) + + if self._status.get("canSeek", "") == "1": + supported = supported | SUPPORT_SEEK + + return supported + + @property + def is_master(self): """Return true if player is a coordinator.""" return self._is_master @property - def is_grouped(self) -> bool: + def is_grouped(self): """Return true if player is a coordinator.""" return self._master is not None or self._is_master + @property + def shuffle(self): + """Return true if shuffle is active.""" + return self._status.get("shuffle", "0") == "1" + async def async_join(self, master): """Join the player to a group.""" master_device = [ @@ -815,6 +849,17 @@ class BluesoundPlayer(MediaPlayerEntity): else: _LOGGER.error("Master not found %s", master_device) + @property + def extra_state_attributes(self): + """List members in group.""" + attributes = {} + if self._group_list: + attributes = {ATTR_BLUESOUND_GROUP: self._group_list} + + attributes[ATTR_MASTER] = self._is_master + + return attributes + def rebuild_bluesound_group(self): """Rebuild the list of entities in speaker group.""" if self._group_name is None: From 0b532c139c70cbb9c0c92f932b19ab1968fb0aa2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 9 Aug 2021 17:14:06 -0700 Subject: [PATCH 20/22] Bumped version to 2021.8.5 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 4c7ab9742b0..803ab0751e6 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -5,7 +5,7 @@ from typing import Final MAJOR_VERSION: Final = 2021 MINOR_VERSION: Final = 8 -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, 8, 0) From 5ac5b41a11517b2fee3ecdd564d26137c328a1a2 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 9 Aug 2021 23:16:18 -0400 Subject: [PATCH 21/22] Update Climacell rate limit (#54373) --- homeassistant/components/climacell/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/climacell/const.py b/homeassistant/components/climacell/const.py index 9e80c769abf..162fbb01545 100644 --- a/homeassistant/components/climacell/const.py +++ b/homeassistant/components/climacell/const.py @@ -66,7 +66,7 @@ DEFAULT_FORECAST_TYPE = DAILY DOMAIN = "climacell" ATTRIBUTION = "Powered by ClimaCell" -MAX_REQUESTS_PER_DAY = 500 +MAX_REQUESTS_PER_DAY = 100 CLEAR_CONDITIONS = {"night": ATTR_CONDITION_CLEAR_NIGHT, "day": ATTR_CONDITION_SUNNY} From 747eb92a4a3b1ab32285d227c9da8484c3f4d135 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 10 Aug 2021 13:21:24 +1000 Subject: [PATCH 22/22] Fix race condition in Advantage Air (#53439) --- .../components/advantage_air/climate.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/advantage_air/climate.py b/homeassistant/components/advantage_air/climate.py index 1d377abc065..1e6027b8db6 100644 --- a/homeassistant/components/advantage_air/climate.py +++ b/homeassistant/components/advantage_air/climate.py @@ -15,7 +15,6 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS -from homeassistant.core import callback from homeassistant.helpers import entity_platform from .const import ( @@ -166,19 +165,22 @@ class AdvantageAirZone(AdvantageAirClimateEntity): f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}' ) - async def async_added_to_hass(self): - """When entity is added to hass.""" - self.async_on_remove(self.coordinator.async_add_listener(self._update_callback)) - - @callback - def _update_callback(self) -> None: - """Load data from integration.""" - self._attr_current_temperature = self._zone["measuredTemp"] - self._attr_target_temperature = self._zone["setTemp"] - self._attr_hvac_mode = HVAC_MODE_OFF + @property + def hvac_mode(self): + """Return the current state as HVAC mode.""" if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN: - self._attr_hvac_mode = HVAC_MODE_FAN_ONLY - self.async_write_ha_state() + return HVAC_MODE_FAN_ONLY + return HVAC_MODE_OFF + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._zone["measuredTemp"] + + @property + def target_temperature(self): + """Return the target temperature.""" + return self._zone["setTemp"] async def async_set_hvac_mode(self, hvac_mode): """Set the HVAC Mode and State."""