Merge pull request #74689 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2022-07-07 23:11:02 -07:00 committed by GitHub
commit d539acf2ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 170 additions and 68 deletions

View File

@ -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,

View File

@ -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"]

View File

@ -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:

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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",

View File

@ -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
)

View File

@ -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"],

View File

@ -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"]

View File

@ -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

View File

@ -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",

View File

@ -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"]

View File

@ -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)

View File

@ -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"]

View File

@ -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",

View File

@ -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()

View File

@ -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."""

View File

@ -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."""

View File

@ -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."""

View File

@ -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."""
@ -810,7 +813,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 +823,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)."""

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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

View File

@ -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
@ -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
@ -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
@ -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
@ -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

View File

@ -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"